一、进程的创建
1.系统调用clone()与fork()的区别:
资源的继承方式 | 参数 | 区分父进程与子进程的方法 | |
---|---|---|---|
fork() | 全部复制,即父进程的所有资源全部通过数据结构的复制传给子进程 | 无参数 | 父进程与子进程,从fork()返回时的返回值不同,以此区分二者。子进程返回0.父进程返回子进程的pid |
clone() | 有选择地将资源复制给子进程,没有复制的资源通过指针复制的方式与子进程共享(共享与复制的关系见2) | 有参数,用于设置资源复制的方式 | clone()所产生的子线程的PID有可能是0,因此采用比较系统堆栈指针的方法来区分二者。 |
note:
(1)为什么返回值的方式对fork()适用?
因为fork()后子进程拥有独立的资源,是一个进程,有自己的独一无二的PID,不可能是0
(2)为什么返回的方式对clone()不适用?
因为clone()不一定复制资源,产生的有可能是一个线程。线程的PID与它所在的线程组的PID相同。如果父线程的PID是0,那么子线程的PID也是0。父与子的返回值是一样的,无法区分。
(3)为什么比较系统堆栈指针的方式对fork()不适用?
因为普通的进程都在用户空间,系统堆栈不知道在哪里
(4)为什么比较系统堆栈指针的方式对clone()适用?
每一个内核线程都有自己的系统空间堆栈,子线程的堆栈空间的指针必须与父线程不同
(5)它们最终都是调用do_fork()创建子进程的
note中的内容讲得不好理解,因为有几句话强调线程与进程的区别,有几句话里进程指的是进程或线程,如果结合Linux2.6进程-1中的表格,会有助于对note的理解
2.线程创建函数kernel_thread(),本质也是调用do_fork(),但是不复制进程的页表。因为新内核线程不会访问用户态地址空间
3.共享与复制的关系
(1)相同点:
复制完全之初,子进程有了一个副本,它的内容与父进程的正本内容基本相同。
(2)不同点-共享:
共享是指仅复制指针,是浅层复制。事实上,父与子都是对同一地址进程操作。
父与子并发地对同一堆栈区进行操作,这种行为是致命的。
通常的解决方法是:扣留父进程,只让子进程返回。当子进程有了自己的用户空间,或子进程确定不再使用父的用户空间(死亡)后,才能让父返回。
(3)不同点-复制:
互不干扰
4.所有用于创建子进程的系统调用,最终都是通过调用do_fork()创建子进程的
(1)获取一个空进程项
(2)获取一页内存
(3)自制当前进程的数据结构到内存中
(4)新进程设置为不可中断等待
(5)对新进程的数据结构修改
note:不分配物理内存,写时复制
5.写时复制
(1)将父进程的页面表项改为写保护
(2)把表项设置到子进程的页面表中
(3)当父进程或子进程企图写页面时,发生页面异常,给要写的进程另分配一个物理页面,此时才是真正的复制
(5)置两个页面都为可写
6.父进程创建子进程后,父进程剩余节拍数被划分为两等份,一份给父,一份给子。
一个进程不能通过创建多个后代来霸占资源
二、进程的撤消与删除
1.子进程被终止以后,不能立即删除,因为它可能还有父进程需要的信息。
只有父进程发出了与被终止进程相关的wait类系统调用后,才允许由父进程为子进程彻底删除
2.子进程在do_exit()的最后,会调用schedule()。这个函数的返回条件是再次被调度。但此进程再也不会被调度,因此schedule()不会返回,do_exit()也不会被返回
此时进程的状态是ZOMBIE,即调度被无限推迟,直到父进程收到信号后来处理后事,将其task_struct释放,这个进程就彻底消失了。
3.父进程要为子进程处理后事。
如果一个进程被终止前还有子进程,则要把子进程托付给某个进程。
如果父进程是一个线程组的成员,就把子进程托付给线程组的下一个成员。
否则就把子进程托付给0号进程
4.中断服务程序、软件中断服务程序、0号进程、1号进程不允许终止。