对于进程的创建,系统调用fork()允许一个进程(父进程)创建一个新进程(子进程)。新的进程几乎就等于父进程的翻版,子进程获得了父进程的栈,数据段,堆和执行文本段的拷贝。
对于进程的终止,则是使用exit(status)函数,此函数会将进程所占用的所有资源(内存,文件描述符等)归还内核,交给内核进行再次分配。status这个参数则表明了进程退出的状态,父进程可以使用wait()来获取到该状态。
系统会调用wait(),其原因有二:
最后,系统调用execve(pathname,argv,envp)来加载一个新的程序到当前进程的内存之中,注意,在子进程的内存中现在还存在着父进程留下的程序文本段,但是此函数执行后,会丢弃掉现存的程序文本段,并为新程序重新创建栈,数据段,以及堆,把这一系列的行为称之为:execing。值得注意的是exec开头的还有多个函数,但是无一例外,它们都是由execve函数为基础的。
对以上的内容稍微做个总结可以得出如下图的内容,下图以一个shell执行命令为例子:
如上所述,创建新景程主要是使用了以下函数:
#include <unistd.h>
pid_t fork(void)
理解这个函数需要知道如下的信息:
就代码层面上而言,是通过fork的返回值来区别两个进程的。对于父进程,直接返回的是子进程的进程ID,这很好理解,因为父进程通常都会fork出不止一个子进程,所以知道子进程的进程号是很有必要的,相对地,子进程的返回值则是0,因为它只需要自己就可以了,因为其返回值是0,所以若是有必要的话,子进程可以使用getpid()来得到自身的pid,也可以调用getppid来获取到父进程的pid。
关于fork函数调用完 之后的函数执行顺序(即谁先被cpu调度),需要知道的就是,这个是无法确定的,这个是很好理解的,因为你无法确认cpu的指令执行进度,在这种时候,很容易出现 ”竞争条件“的错误,这是由内核根据系统当时的负载而做出的调度决定,因而,很难进行调试 。若是在这个时候,需要对执行顺序进行控制,有以下的方法:
可以在fork执行完成之后,让父进程执行sleep函数,以此来确保子进程可以获取到被先执行的机会,从而将执行顺序给固定下来,当然从理论上讲的话,这种方法不是百分之百有用的。
利用同步来进行同步,其中包含信号量,文件锁,进程间pipe等。
关于fork之后,是子进程先执行,还是父进程先执行,其实各有各的理由,若是父进程先执行,那么父进程依旧会为子进程复制那些修改后的数据,然而子进程一旦获得调度,执行exec,之前的复制工作就纯粹是浪费。反过来,若是子进程先执行,在这时,父进程这是处于cpu活跃中的状态,让其挂起则是低效率的。
虽然这么说,但是在应用程序来说,无乱哪一个进程先执行,影响都不大。
进程的终止,主要是有以下两种方式:
#include <unistd.h>
void _exit(int status);
该函数中的status值定义了终止状态值,父进程可以调用wait()来获取这个值。按照惯例,当返回值为0时,就表示success,而非0,这是失败,具体的失败值需要具体定义。
以上一直在说函数:_exit(),但是通常情况下,程序一般不会直接执行该函数,而是会执行库函数:exit()。
该函数主要是做了以下步骤的工作:
这里需要说明下,这个注册进去的退出程序是由程序员自己所定义的内容,执行程序员想要的回收相关工作。
还需要注意的是,这个退出程序只有在正常退出情况下才会执行,而在异常情况下,则不会执行,这个就很好理解了, 这个只调用库函数时才会调用。
有一种情况,那就是父进程需要知道子进程在何时改变了状态,在Linux主要靠以下技术来进行监控:
系统调用的函数声明如下;
#include <sys/wait.h>
pid_t wait(int *status);
这个函数的行为主要如下:
大家都知道,父进程与子进程的生命周期一般都是不同的,它们长短不一。这会有以下几个问题:
* 谁是孤儿进程的父进程?答案是所有进程之父,进程id为1的init进程会接管孤儿进程,换言之,某一进程的父进程终止之后,对其使用getppid会得到1.这也是一个判断某一进程直接父进程是否还存在的方法。
* 那么父进程在执行wait()之前,子进程就终止了,这是什么情况?这时候系统仍然允许父进程在之后某一时刻去执行wait,以此来确定子进程是如何结束的。在这种情况下,即父进程未执行wait,内核会将进程转化为僵尸进程,换句话说,系统会将子进程的大部分资源释放,仅仅在内核进程表中保存一条记录,里面包含了子进程id,终止状态,资源使用数等信息。僵尸进程之所以叫僵尸进程,原因在于它无法使用信号来杀死,这可以确保父进程总是可以执行wait。
但是有一个问题,若是父进程创建了子进程,然后并没有执行wait,那么内核进程表将会永远为该进程保留这么一条记录,若是大量的僵尸进程存在,势必会填满内核进程表,从来阻碍新进程的创建。这时候唯一的方法就是杀死它们的父进程,然后将子进程交接给init进程,init进程会自动调用wait来清理这些进程
当子进程终止时,其父进程会收到SIGCHLD的信号,可以利用该信号设置信号处理程序来进行处理。
本文发布于:2024-02-02 19:49:33,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170687457146048.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |