frok
一个运行的进程,它调用了fork()函数,然后就产生了子进程,原来的进程叫父进程。这个子进程也是进程,但凡是进程,都有自己的虚拟地址空间(让每个进程自己看起来是独占内存,通过段页内存管理映射到不同的物理地址空间)。虚拟地址空间是从0到4G的大小,其中3-4G是属于内核的(32位系统)。创建完子进程后,父进程继续运行程序(即原来的进程)的代码,刚创建出来的子进程拥有和父进程完全一样的代码段,数据段,也就是说完完全全拷贝了一份父进程,和父进程完全一样。即clone父进程0-3G的内容,而3-4G的kernel只需要重新映射一下到物理地址的kernel即可。但是操作系统要如何区分这两个进程呢?答案就是进程ID即pid。pid是存储在PCB当中的类似身份证的东西. kernel会创建子进程自己的PCB,然后clone父进程的PCB(task_struct)的绝大部分信息,如内存映射(mm_struct,采用 COW 机制copy on write),文件描述符表,调度信息(优先级、CPU 时间等),但某些关键字段会被修改,如pid,ppid等.
kernel会设置 fork() 的返回值.在子进程的 task_struct 中,kernel会预先设置 eax/rax 寄存器(存储返回值)为 0,因此子进程看到的 fork() 返回 0。父进程的 fork() 返回子进程的 pid。
|
|
fork底层是调用了内核的函数来实现fork的功能的,即先create()先创建进程,此时进程内容为空,然后clone()复制父进程的内容到子进程中,此时子进程就诞生了,接着父进程就return返回了。而子进程诞生后,是直接运行return返回的,然后接着执行后面的程序,这里注意:子进程是不会执行前面父进程已经执行过的程序了得,因为PCB中记录了当前进程运行到哪里,而子进程又是完全拷贝过来的,所以PCB的程序计数器也是和父进程相同的,所以是从fork()后面的程序继续执行。
|
|
exec族
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支), 子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的 用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建 新进程,所以调用exec前后该进程的id并未改变
|
|
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错 则返回-1,所以exec函数只有出错的返回值而没有成功的返回值
wait/waitpid
僵尸进程: 子进程退出,父进程没有回收子进程资源(PCB),则子进程变成僵尸进程 孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为1号 进程init进程,称为init进程领养孤儿进程
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还 保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止 则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这 些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变 量$?查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状 态同时彻底清除掉这个进程。但是,如果父进程先于子进程结束,则子进程成为孤儿进程。孤儿进程将被 init 进程(进程号为1)领养,并由 init 进程对孤儿进程完成状态收集工作。而如果子进程先于父进程退出,同时父进程太忙了,无瑕回收子进程的资源,子进程残留资源(PCB)存放于内核中,变成僵尸。任何进程在刚终止时都是僵尸进程,正常情况下,僵 尸进程都立刻被父进程清理了.
|
|
在这个程序里,父进程创建子进程之后,就休眠 5 秒钟。而子进程只休眠 3 秒钟就退出,在它退出之后,父进程还未苏醒,因此没人给子进程「收尸」,所以它就变成了僵尸进程。
僵尸进程其实已经就是退出的进程,因此无法再利用kill命令杀死僵尸进程。僵尸进程的罪魁祸首是父进程没有回收它的资源,那我们可以想办法它其它进程去回收僵尸进程的资源,这个进程就是 init 进程。因此,我们可以直接杀死父进程,init 进程就会很善良地把那些僵尸进程领养过来,并合理的回收它们的资源,那些僵尸进程就得到了妥善的处理了