fork()
fork()函数模型
进程概念:
一个可执行程序,执行起来就是一个进程,再执行一次又是一个进程(多个进程可以共享同一个可执行文件)
文雅说法:进程定义为程序执行的一个实例
在一个进程(程序)中,可以用fork()创建一个子进程,当该子进程创建时,从fork()指令的的下一条(或者说fork()函数的返回处)开始执行与父进程相同的代码
说白了:fork()函数产生了一个和当前进程完全一样的新进程,并和当前进程一样从fork()函数里返回;原来一条执行通路(父进程),现在变成两条(父进程+子进程)
fork() 一分为二
对于以下代码,两个fork();
1 |
|
运行结果如下,有四个进程
再来看一个复杂例子,有7个进程。
fork()函数简单范例
1 |
|
给子进程12477号一个SIGKILL信号之后,父进程12476收到了一个SIGCHLD信号,如图所示
SIGCHLD:一个进程被终止或者停止的时候,这个信号会被发送给父进程。
继续查看进程状态发现子进程12477变为了僵尸进程并且仍然挂载在前台,如下图所示:
僵尸进程
僵尸进程的产生:在Unix系统中,一个子进程结束了,但是他的父进程还活着,但该父进程没有调用(wait/waitpid)函数对子进程进行额外处理,那么该子进程会变成一个僵尸进程。
僵尸进程:已经被终止,不干活了,但是依旧没有被内核丢弃掉,因为内核认为父亲进程可能还需要子进程的一些信息
如何杀死僵尸进程:
- 重启电脑
- 手工的把僵尸进程的父进程kill掉,僵尸进程会自动消失
waitpid()函数
头文件:#include <sys/types.h> #include <sys/wait.h>
定义函数:pid_t waitpid(pid_t pid, int * status, int options);
函数说明:waitpid()会暂时停止目前进程的执行, 直到有信号来到或子进程结束。 如果在调用wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值. 子进程的结束状态值会由参数status 返回, 而子进程的进程识别码也会一快返回. 如果不在意结束状态值, 则参数status 可以设成NULL. 参数pid 为欲等待的子进程识别码, 其他数值意义如下:
1、pid<-1 等待进程组识别码为pid 绝对值的任何子进程.
2、pid=-1 等待任何子进程, 相当于wait().
3、pid=0 等待进程组识别码与目前进程相同的任何子进程.
4、pid>0 等待任何子进程识别码为pid 的子进程.
参数option 可以为0 或下面的OR 组合:
WNOHANG:如果没有任何已经结束的子进程则马上返回, 不予以等待.
WUNTRACED:如果子进程进入暂停执行情况则马上返回, 但结束状态不予以理会. 子进程的结束状态返回后存于status, 底下有几个宏可判别结束情况
WIFEXITED(status):如果子进程正常结束则为非0 值.
WEXITSTATUS(status):取得子进程exit()返回的结束代码, 一般会先用WIFEXITED 来判断是否正常结束才能使用此宏.
WIFSIGNALED(status):如果子进程是因为信号而结束则此宏值为真
WTERMSIG(status):取得子进程因信号而中止的信号代码, 一般会先用WIFSIGNALED 来判断后才使用此宏.
WIFSTOPPED(status):如果子进程处于暂停执行情况则此宏值为真. 一般只有使用WUNTRACED时才会有此情况.
WSTOPSIG(status):取得引发子进程暂停的信号代码, 一般会先用WIFSTOPPED 来判断后才使用此宏.
返回值:
- 当正常返回的时候,waitpid返回收集到的子进程的进程ID;
- 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
- 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD;
函数加入SIGCHLD处理
在信号处理函数中添加对SIGCHLD信号的处理并且在main函数中加入signal(SIGCHLD,sig_usr)
1 | void sig_usr(int signo) |
再次编译运行之后,如下图所示,杀死子进程后没有再出现僵尸进程。
写时复制,读时共享
原进程(父进程)和子进程一起共享一个内存空间,但这个内存空间的特性是“写时复制”,也就是说:原来的进程和fork()出来的子进程可以同时、自由的读取内存,但如果子进程(父进程)对内存进行修改的话,那么这个内存就会复制一份给该进程单独使用,以免影响到共享这个内存空间的其他进程使用;
完善fork()代码
fork()会返回两次,子进程返回一次,父进程返回一次,而且fork在父进程中返回的值和子进程中返回的值不同,正好可以区分父子进程。
父进程的fork()返回值会 > 0(实际返回的是子进id程)而子进程的fork()返回值为0
1 |
|
fork()失败原因
- 系统中进程太多了,缺省情况,最大的pid:32767
- 每个用户有个允许开启的进程总数最大值:7788