【Linux】5. 进程控制 (进程创建、进程终止、进程等待、进程程序替换)

1. 进程创建

写时拷贝
在这里插入图片描述
父子进程代码共享,数据独有;当任意一方试图写入,便以写时拷贝的方式拷贝一份副本

2. 进程终止

2.1 进程退出的场景

(1) 代码运行完毕,结果正确
(2) 代码运行完毕,结果不正确
(3) 代码异常终止

2.2 进程退出的方法

2.2.1 main函数return

2.2.2 _exit函数

void _exit(int status);
该函数为系统调用函数,谁调用谁退出

2.2.3 exit函数

void exit(int status);
头文件:#include <stdlib.h>
status:是一个整型的参数,可以利用这个参数传递进程结束时的状态。一般来说,0表示正常结束;其他的数值表示出现了错误,进程非正常结束。在实际编程时,父进程可以利用wait 系统调用接收子进程的返回值,从而针对不同的情况进行不同的处理。
该函数为库函数,谁调用谁退出
exit函数的内部封装了_exit函数

在这里插入图片描述
从图中可以看出,_exit 函数的作用是:直接使进程停止运行,清除其使用的内存空间,并清除其在内核的各种数据结构;
exit 函数则在这些基础上做了一些小动作,在执行退出之前还加了若干道工序。exit() 函数与 _exit() 函数的最大区别在于exit()函数在调用exit系统调用前要检查文件的打开情况,把文件缓冲区中的内容写回文件,也就是图中的“清理I/O缓冲”。

exit函数在退出进程的时候要比_exit函数多做两件事
执行用户自定义的清理函数
刷新缓冲区

在这里插入图片描述
在这里插入图片描述
父子进程是抢占式运行,可能是父进程先运行,也可能是子进程先运行。
子进程中运行_exit(0)并未将"This is the content in the buffer _exit"打印出来,而父进程中运行的exit(0)将"This is the content in the buffer exit"打印出来了。说明exit(0)会在终止进程前,将缓冲I/O内容清理掉,所以即使printf里面没有 \n 也会被打印出来,而_exit(0)是直接终止进程,并未将缓冲I/O内容清理掉,所以不会被打印出来。

2.2.4 ctrl+c

终止一个前台进程(前台进程:是当前正在使用的程序,后台进程:是在当前没有使用的但是也在运行的进程)

在这里插入图片描述

2.3 回调函数 atexit

atexit函数
int atexit (void (*function)(void));
头文件:#include<stdlib.h>
返回类型是整型,参数为函数指针类型,接收一个函数的地址,函数的返回值为void,参数也是void
在这里插入图片描述
在这里插入图片描述
atexit函数是注册了一个函数mycallback,注册并不是调用。
当main()函数结束之后,才会调用刚刚注册的mycallback函数。
回调函数:1.注册回调函数 (上面的mycallback就是回调函数)
                  2.调用回调函数

2.4 刷新缓冲区的办法

(1) main函数的return返回
(2) fflush:强制刷新
(3) exit函数
(4) 关于"\n"刷新缓冲区
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这里面有一个缓冲区的问题,带\n,会刷新缓冲区的内容。
在这里插入图片描述
在这里插入图片描述

3. 进程等待

3.1 进程等待的作用

父进程调用进程等待的方法,等待子进程退出,回收子进程的退出资源,防止子进程变成僵尸进程

3.2 进程等待的方法

3.2.1 wait函数

pid_t wait(int *status);
头文件:#include<sys/wait.h>
函数功能是:父进程一旦调用了 wait 就立即阻塞自己,由 wait 自动分析当前进程的某个子进程是否已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait 就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

参数: 输出型参数:将wait函数内部计算的结果通过status返回给调用者
            输入型参数:调用者给被调用函数的传参
参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉毫不在意,只想把这个僵尸进程消灭掉,我们就可以设定这个参数为NULL,就像下面这样:pid = wait(NULL);
例如:swap(int *a,int *b); 输入型输出型参数
           编码的时候,小小的代码规范:
                   输入型参数:给引用
                   输入输出型参数,输出型参数:给指针

返回值:如果执行成功则返回子进程的pid, 如果有错误发生则返回-1。失败原因存于errno 中。

注:
wait()要与fork()配套出现,如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.2.2 判断当前程序是正常退出或者异常退出?

判断是否有退出信号
int status ==》 wait(&status)
       status & 0x7F ==0:正常退出
       status & 0x7F > 0:异常退出
       0x7F:0111 1111
判断coredump标志位:
       (status >> 7) & 0x1
判断退出码:
       (status >> 8) & 0xFF

3.2.4 验证wait函数

(1) 创建子进程
(2) 模拟产生一个僵尸进程
在这里插入图片描述
目前子进程是一个僵尸进程
(3) 父进程调用wait函数,再看是否产生僵尸进程(wait函数要包含<sys/wait.h>头文件)
在这里插入图片描述
在这里插入图片描述
不会产生僵尸进程
(4) 阻塞
在这里插入图片描述
牵扯到一个阻塞的问题,如果父进程调用wait函数,当子进程没有退出之前,wait函数是不会返回的。调用wait函数后,wait函数会压在父进程的栈针上面。
第一种情况:先执行子进程,打印,再执行父进程,wait等到,返回
第二种情况:先执行父进程,调用wait函数,产生阻塞,会等待子进程,然后打印,返回。子进程并不会变成孤儿进程,因为父进程的wait函数等到了子进程。
在这里插入图片描述

在这里插入图片描述
子进程不退出在等待的这1秒钟,从父进程的堆栈可以看出,父进程一直在调用wait函数,并没有退出。

3.2.5 waitpid函数

pid_t waitpid(pid_t pid, int *status, int options)
如果在调用waitpid()函数时,当指定等待的子进程已经停止运行或结束了,则waitpid()会立即返回;但是如果子进程还没有停止运行或结束,则调用waitpid()函数的父进程则会被阻塞,暂停运行。
作用同于wait,但可指定pid进程清理,可以不阻塞。

参数pid:pid<-1,等待进程组识别码为pid 绝对值的任何子进程
               pid = -1,等待任意一个子进程。与wait等效
               pid = 0,等待和当前调用waitpid一个组的所有子进程
               pid > 0,等待一个进程ID与pid相等的子进程

status:同wait
options: 0:阻塞模式
                WNOHANG:非阻塞模式,非阻塞模式需要搭配循环使用
返回值:成功:返回清理掉的子进程ID
              失败:-1(无子进程)

注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程需要用到循环

4. 进程程序替换

替换正在运行的程序,让正在运行的进程执行其他程序
在这里插入图片描述

4.1 进程程序替换的原理

在这里插入图片描述
替换当前进程的代码段和数据段为新的程序,并且更新堆栈
注意:当前进程程序替换完毕之后,进程就在执行新的程序,但是进程的进程号不变

4.2 进程程序替换的函数:exec函数簇

头文件:#include <unistd.h>

4.2.1 execl函数

int execl(const char* path,const cahr* arg, …)
path:待要替换的可执行程序,需要指定该程序在哪一个路径下
arg:给可执行程序传递的命令行参数
           (1) 第一个参数必须是可执行程序本身
                  例如:ls -al (使用which ls命令,可以找到ls的路径)
                  execl(“/usr/bin/ls”, ”/usr/bin/ls”, “-al”,NULL);
           (2) 可变列表也是填充给可执行程序的参数,需要以NULL结尾
返回值:如果替换成功,则没有返回值,因为替换成功后,就执行其他的程序了。
               如果替换失败,则返回-1
在这里插入图片描述
在这里插入图片描述

4.2.2 execlp函数

int execlp(const char* file, const char* arg, …);
file:待要替换的可执行程序,可以不给路径(但是这个待要替换的可执行程序一定能在PATH环境变量中可以搜索到)

在这里插入图片描述
在这里插入图片描述

4.2.3 execle函数

int execle(const char* path, const char* arg, … , char* const envp[ ]);
envp[ ]:表示程序员需要自己组织环境变量

在这里插入图片描述
在这里插入图片描述

4.2.4 execv函数

int execv(const char* path, char* const argv[ ]);
argv[ ]:指针数组,保存的是给可执行程序传递的参数
            数组的第一个元素应为可执行程序本身
            数组的最后一个元素应为NULL
在这里插入图片描述

4.2.5 execvp函数

int execvp(const char* file, char* const argv[ ]);
在这里插入图片描述

4.2.6 execve函数

int execve(const char* file, char* const argv[], char* const envp[]);
file:带有路径的可执行程序(待要替换的可执行程序)
argv[]:指针数组,保存的是给可执行程序传递的参数
            数组的第一个元素应为可执行程序本身
            数组的最后一个元素应为NULL
envp[]:程序员自己组织环境变量,最后一个参数一定为NULL
在这里插入图片描述

4.2.7 总结

在这里插入图片描述
(1) 函数名称当中带有l和不带l的区别:
如果说函数名称当中带有l,则表示函数的参数为可变参数列表的形式
如果说函数名称当中不带有l,则表示函数没有可变参数列表
(2) 函数名称当中带p和不带p的区别:
如果函数名称当中有p,则表示会自动搜索环境变量
如果函数名称当中没有p,则表示不会自动搜索环境变量。这也是为什么execl函数在替换的时候,一定要给带替换的可执行程序带上路径的原因。
(3) 函数名称当中带有e和不带有e的区别:
如果说函数当中带有e,则表示程序员自己组织环境变量
如果说函数当中不带有e,则表示程序员不需要自己组织环境变量

5. 进程程序替换在工业当中常见的场景:

5.1 bash的应用场景

在这里插入图片描述

5.2 守护进程

7×24小时服务
作用:保护业务进程
(1) 启动业务进程的时候,不是直接启动业务进程,而是启动守护进程
(2) 让守护进程,创建一个子进程,让子进程进程程序替换业务程序进程
(3) 守护进程和业务进程进行进程通信,让守护进程得知业务进程的情况

业务进程正常,则守护进程不做处理
业务进程崩溃或者异常,则守护进程重新建立一个子进程,让子进程再进行进程程序替换
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值