一.进程创建
进程调用fork,当控制转移到内核中的fork代码后,内核做:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
- fork返回,开始调度器调度
二.进程终止
1.为什么终止
释放曾经的代码和数据所占据的空间。释放内核数据结构。
2.终止的三种情况
- 代码跑完,结果正确
- 代码跑完,结果不正确
- 代码执行时,出现了异常,提前退出了。
echo $?指令:父进程bash获取到最近一个的子进程的退出码,要知道子进程的退出情况。
结果是否正确可以通过进程的退出码决定,****退出码为0表示退出成功,非0则表示失败。
非0的情况有多种,每个退出码都代表失败的不同原因,可以使用下述函数,通过退出码来打印退出信息:
**char *strerror(int errnum); **
父进程bash要通过退出码来获取子进程的退出情况(成功或失败,失败的原因),为用户负责。
进程的异常退出,本质是进程收到了OS发出的进程信号。
想知道进程如何异常退出,可以看进程退出时的退出信号是多少。
衡量一个进程退出的情况,只需要两个数字,退出码和退出信号。
3.如何终止
- main函数中直接return,表示进程终止(非main函数,return表示函数结束,而非进程退出)。
- 代码调用exit函数。在代码的任意位置调用exit,都代表进程终止。
- _exit() -- 系统指令。
exit和_exit的区别:exit会在进程退出时冲刷缓冲区,_exit不会。
三.进程等待
1.为什么等待
任何子进程,在退出情况下,一般必须要被父进程进行等待。等待的原因如下:
- 父进程通过等待,解决子进程退出的僵尸问题,回收系统资源(必须考虑)。
- 获取子进程的退出信息,知道子进程是因为什么原因退出的(非必须)。
2.怎么等待
函数
- *pid_t wait(int status):等待父进程中,任意一个子进程退出,等待成功返回子进程的pid。
- *pid_t waitpid(pid_t pid,int status,int options)
在子进程退出之前,父进程会一直进行阻塞等待,而不会执行自己的代码。
参数
pid:
- pid = -1 ,表示等待任一个子进程,与wait等效。
- pid>0,等待其进程ID与输入的pid相等的子进程。
status:
输出型参数,用于得到子进程的退出信息。退出信息包括:退出码和退出信号。
如果父进程不关心子进程的退出信息,则可以设为NULL。
int类型的status由32位组成,其中高16位不使用,对于低16位,高8位为进程的退出码,低七位为进程的退出信号。
- WIFEXITED(status)函数:若为正常终止子进程返回的状态,则为真。(查看进程是否正常退出)
- WEXITSTATUS(status)函数:若WIFEXITED函数返回值非零,提取子进程退出码。(查看进程退出码)
options:
子进程没有退出,而父进程在执行waitpid等待子进程,就会导致阻塞等待,即options默认为0。
将options改为WNOHANG,则为非阻塞等待。
在阻塞等待下,父进程调用waitpid函数会一直等待子进程的状态,直到其达到某种情况(如退出)才会停止等待。
在非阻塞等待下,父进程调用waitpid函数只会确认一次子进程的状态,而不进行等待,可以干其他事。此时,父进程需要循环调用waitpid函数来判断子进程是否退出,
**非阻塞等待 + 循环 = 非阻塞轮询 **
返回值
阻塞等待:
- 当正常返回的时候,waitpid返回收集到的子进程的进程ID;
- 如果调用中出错,则返回-1,这时erron会被设置成相应的值以示错误所在。
非阻塞等待:
- pid_t > 0:等待成功的,子进程退出了,并且父进程回收成功。
- pid_t < 0:等待失败了。
- pid_t == 0:检测是成功了,只不过子进程还没完全退出,需要你下一次进行重复等待。
四.进程替换
使用exec*系列的函数,在原本的程序中执行新的程序,称为程序替换。
其本质是exec*系列的函数类似于linux系统上的加载函数,将新的程序加载到内存中。
exec*系列的函数,执行完之后,后边的代码不会再被执行,因为被替换了。
这些函数的返回值不用关心,只要替换成功,就不会向后运行,如果向后运行,代表替换失败。
进程 = 内核数据结构 + 代码和数据,而进程替换即替换****掉代码和数据,没有创建新的进程,而是复用原本的虚拟地址空间和页表,重新建立页表映射关系。
当父进程创建子进程,通过进程替换让子进程去执行一个新的进程时,父进程的代码和数据都将进行写时拷贝,子进程将享有新的代码和数据,实现与父进程的完全独立。
1.替换函数
exec*是整个替换函数系列的开头,后边追加的字符则代表不同的含义。
(1)execl
**int execl(const char path,const char arg,...)
- “l”代表list,即列表,表示该函数可以按列表方式执行程序。
- path是程序所在的路径,执行程序必须要给出其所在的路径。
- arg表示程序名(指令名)。
- "..."表示该函数为变参函数,用于追加多个后缀指令。
- 参数必须以NULL结尾。
例子:
execl("/usr/bin/ls","ls","-l","-a",NULL);
ls -l -a即为一个列表
执行ls -l -a指令。
(2)execv
**int execv(const char path, char const argv[])
- "v"表示数组vector,说明该函数调用的指令需要从数组中获取。
- argv即为要存放指令的数组。
例子:
*char const argv[] = ("ls","-l","-a",NULL);
execv("/usr/bin/ls",argv);
execlp/execvp
**int execlp(const char flie,const char arg,...)
**int execvp(const char file, char const argv[])
"p"表示环境变量PATH。
这两个函数与上述两种函数功能完全相同,但是使用这两个函数,第一个参数可以不传要执行的程序所在的路径,而只需传入该程序的名字,而后函数会从系统的PATH环境变量下找到该程序并执行。
execle/execvpe
**int execle(const char *flie,const char arg,...,char const envp[])
**int execvpe(const char *file, char const argv[],char const envp[])
e表示环境变量,使用这两种函数,用户替换程序的环境变量。
本质是用新的环境变量替换程序原本的环境变量, 使其成为新环境变量下的程序。
版权归原作者 很楠不爱 所有, 如有侵权,请联系我们删除。