本篇文章的学习与总结来源于 https://www.bilibili.com/cheese/play/ep182659?csource=common_hp_history_null&t=3&spm_id_from=333.1007.top_right_bar_window_history.content.click
通常使用fork()函数产生新的子进程,需要包含两个头文件<sys/types.h>以及<unistd.h>,如果记不住也没关系,可以使用man命令进行查询
man fork
常用相关函数介绍:
pid_t getpid(void); //获取当前进程ID
pid_t getppid(void); //获取父进程ID
fork()函数的基本使用
1. 从fork()函数往后,所有的代码子进程与父进程都要执行
2. fork()函数调用一次,则返回两次;也就是说有两个返回值,如果返回值大于0则为父进程返回,返回值等于0则为子进程返回
两个返回值的作用:可以用于父进程与子进程执行不同的逻辑代码
3. fork() 出来的子进程拥有所有资源的副本,在子进程中改变变量值,并不会影响父进程中的该变量的值,但是该变量在父进程与子进程中的地址是一样的(实际上是不一样的,这里的地址是经过处理的,是个虚地址,而真实的物理地址是不一样的)
4. 如果父进程与子进程同时往同一个文件中写东西,则父进程与子进程共享同一个文件位置指针(文件偏移量)
僵尸进程的产生
1. 如果父进程比子进程先退出,则之后子进程由系统1号进程来托管(这也是让程序在后台运行的一种方式,其他方式有类似在命令行或程序后面加 '&' 符号来达成后台运行)
所以在任何程序main函数最开始的地方,加上 if(fork() > 0) return 0; 这行代码,都会让程序在后台运行
2. 如果子进程比父进程先退出,而父进程又没有处理子进程的退出信息,则子进程将会变成僵尸进程
(僵尸进程的危害:僵尸进程会一直占用系统分配的进程编号,如果有太多僵尸进程的话,则可能会导致系统因没有可用的进程编号而不能产生新的进程;因为系统所产生的进程编号是有限的,会采用延迟复用算法将进程编号进行重复使用)
(知识点:系统内核为每一个子进程保留了一个数据结构,记录了进程ID,进程状态,进程所用cpu时间等信息,如果父进程处理了子进程的退出信息,则系统会立即释放该数据结构;否则,将不会释放该数据结构,导致子进程的进程编号一直被占用而得不到释放)
僵尸进程的规避
1. 父进程添加 signal(SIGCHLD,SIG_IGN); 表示对子进程的退出并不感兴趣(因为子进程退出时,内核会向父进程发送 SIGCHLD 的信号,如果父进程忽略了该信号,则子进程退出后,内核会立即释放数据机构)
该方法简单易懂,但是不会得到子进程的退出相关信息
2. 父进程调用wait()函数等待子进程结束,在子进程退出前,父进程将一直处于阻塞状态
pid_t wait(int *stat_loc);
函数返回值为子进程编号;
stat_loc 保留子进程的终止信息:
如果正常退出(使用return,exit(),_exit()等都属于正常退出),宏 WIFEXITED(stat_loc)为真,宏WEXITSTATUS(stat_loc)可获取终止状态
如果异常退出(使用kill/kill -9 /程序访问非法内存都是异常退出),宏WTERMSIG(stat_loc)可获取终止进程的信号
使用 kill pid 命令杀死进程
使用 kill -9 pid 命令杀死进程
访问非法内存
以上总结了进程创建与僵尸进程如何规避的基本使用方法,共勉。