Linux——进程终止/等待/替换

前言

本章主要对进程终止,进程等待,进程替换的详细认识,根据实验去理解其中的原理,干货满满!

1.进程终止

概念:进程终止就是释放进程申请的内核数据结构和对应的代码和数据

进程退出的三种状态

  • 代码运行完毕 结果正确
  • 代码运行完毕 结果错误
  • 代码中止异常

我们在学习C语言的时候,写main函数,一般都会写return 0;main函数的返回值,通常就代表程序的执行情况,0代表成功,非0代表代码运行完毕,结果错误,不同的值代表不同的错误
当父进程创建子进程是为了让子进程完成某种任务,当子进程结束,肯定要将执行结果返回给父进程,让父进程知道是什么情况,我们知道子进程结束会保留task_struct,等待父进程获取结果信息,此时子进程就是僵尸进程,执行结果,也就是返回值就会存放到task_struct中,被父进程获取

1.1退出码

进程结束返回的一个状态 通常是一个整数值

echo $?

打印最近一个进程退出时的退出码

errno:当库函数调用失败或者系统调用失败 他们通常会将一个特定的错误代码赋值给errno
strerror是C语言的一个库函数,该函数接收一个errno的错误代码作为参数, strerror负责将这些错误代码转换为具体的错误描述信息,一共有134条

我们可以看到0表示成功 1表示操作不被允许 2表示没有这个目录或文件 3表示没有这个进程

相信后面的大家也可以读懂 这样我们就可以根据退出码知道我们的错误是什么了

此时在当前目录并没有c.txt文件,ls是C语言写的一个程序,当执行完程序,发现文件不存在,此时的退出码是2,不就是上面的2号找不到文件吗

1.2进程常见的退出方法

1.2.1从main返回

我们知道main函数是程序的入口,当main函数结束,也return了,进程也就结束了

其他的函数只表示调用函数完成了 返回退出码 并不代码进程结束

1.2.2exit

exit手册内容 exit是直接结束进程,引起进程终止 它需要一个参数,就是状态(进程退出码)

1.2.3_exit

_exit手册内容 用于终止进程的系统调用 通过实验我们发现和exit一样都可以终止进程

1.2.4exit和_exit区别

补充:我们知道只有OS才可以杀掉进程 库和系统调用是上下层的关系 库调用系统调用

相同点:都可以终止进程

不同点:

  • exit是标准C库函数 _exit是系统调用
  • exit会刷新缓冲区 _exit不会刷新缓冲区

实验:通过下面的实验我们也可以验证exit不会刷新缓冲区

2.进程等待

在理解进程等待前,我们先来想一下进程为什么要进行等待呢?

  • 回收子进程资源(处理僵尸进程)
  • 获取子进程退出信息

我们知道当子进程退出时,task_struct不会被释放,需要父进程回收资源,当父进程一直不管时,就可能会造成僵尸进程,可能会出现内存泄露

2.1wait

手册内容:wait的参数status是一个输出型参数 在后续的waitpid我们会详细了解

wait会等待任意一个子进程 回收成功会返回回收的pid

接下来我们来使用一下wait

创建一个子进程 让他跑五次 当子进程结束时,让程序暂停10s,这个时候父进程还没有回收资源,所以子进程此时就是僵尸进程,根据子进程状态我们可以看到是Z,父进程回收子进程资源,解决了僵尸进程,返回了子进程的pid,子进程资源被全部释放,剩余父进程,程序再休眠10s,最后父进程进程也结束

2.2waitpid

pid_t waitpid(pid_t pid, int *status, int options); waitpid共有三个参数

接下来会一个一个拆开分析:

2.2.1pid_t pid

pid_id的值有四种

等待指定pid或者等待任意子进程

如果等待失败 会出现什么样的情况呢?

2.2.2int *status

输出型参数 存储进程退出时的状态信息 不关心状态信息设置NULL

我们来进行测试一下:发现出现了一些问题

status不能当做整型看待,可以当做位图来看待,前16位不考虑,次8位是退出状态,那么此时应该前7位是0,然后是1,后面还有8个0,因为是2进制,所以应该是2的8次方,也就是256!

当除数为0时就会使程序出现异常

其实exit code 和 exit signal都存放在子进程的task_struct中,当子进程是僵尸进程,等待父进程通过操作系统调用回收子进程资源,获取子进程的退出信息

我们在上述的实验是通过位操作来提取信息的,真正的OS是使用宏,其实就是封装了一下位操作

  • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是

否是正常退出,程序是否异常,=0为真)

  • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的

退出码)

2.2.3int options

补充:

  • 阻塞调用:当一个程序发起操作时,程序会暂停操作,直到这个操作完成并返回结果,在执行操作时,程序不能做任何事,也就是子进程在执行任务时,父进程一直在wait阻塞

  • 非阻塞调用:当一个程序发起操作,不会等操作完成,而是立即返回,执行后续的操作,程序可以定期检查这个操作是否已经完成,这个操作叫做非阻塞轮询
    我们从手册里可以看到options是有几个选项的

  • 默认是0:阻塞调用,如果子进程还没有退出父进程就会一直阻塞在那里

  • WNOHANG:非阻塞调用
    waitpid的返回值可以是 -1 0 >0

-1表示失败

0表示 调用结束 但是子进程没有退出

>0 子进程结束
非阻塞轮询实验,将waitpid的第三个参数设置为WNOHANG,非阻塞调用,这样父进程就不会一直等待子进程完成任务,而是立即返回,然后定期去询问子进程完成任务了吗?

非阻塞调用实验:我们可以通过函数指针回调函数来操作

通过实验我们可以看到在子进程执行任务时,父进程也在执行其他任务

3.进程替换 exec

进程替换就是OS根据指定的程序文件路径和参数,将新程序的代码和数据加载到当前进程的地址空间中,覆盖原来的进程内容,从而实现进程替换

程序替换错误返回-1 没有成功返回值
当我们知道程序替换会将原先的程序进行覆盖,我们原先的程序就没有了,所以我们一般会创建子进程去执行程序替换,这样父进程的代码数据也不会丢失了!!

3.1进程替换的原理

我们先来使用一下execl,我们可以发现第一句printf执行了,然后执行程序替换,然后就没有输出了,我们来了解一下程序替换的原理!其实在上面就已近谈到了,就是将新程序的代码和数据加载到当前进程的地址空间中,覆盖替换原来的进程内容,从而实现进程替换

在程序替换的过程中,并没有创建新的进程

3.2程序替换接口函数

我们在上面的手册中可以看到6个接口函数,接下来我们学习四个 后续的大家肯定就都懂

还有一个是系统命令 execve 我们在上面看到的6个接口函数其实都是需要去调用execve

在上层进行封装 为了应对各种各样的场景 最后会统一转化 调用系统调用execve

3.2.1execl

cpp 复制代码
int execl(const char *path, const char *arg, ...);

execl的l可以看做是一个list,第一个参数就是路径+程序名,第二个参数就是命令,在命令行怎么写,这里我们就怎么写,...是可变参数列表的意思,因为我们不知道命令有几个,最后要以NULL结尾,表明参数已传完!!

3.2.2 execlp

cpp 复制代码
int execlp(const char *file, const char *arg, ...);

execlp的l看做list,p看做PATH,第一个参数就是要执行的文件名,execp会自动在环境变量PATH中查找命令,第二个参数就是命令,同上!

3.2.3execv

cpp 复制代码
int execv(const char *path, char *const argv[]);

execv的v可以看做是一个vector,第一个参数是要执行的路径+文件名,第二个参数是一个指针数组,也就是命令行参数表

3.2.4execvp

cpp 复制代码
int execvp(const char *file, char *const argv[]);

execvp的v可以理解为vector,p理解为PATH,第一个参数就是要执行的文件,第二个是指针数组,就是指命令行参数表,相信大家看到这里一看就看懂了!!

3.2.5 execvpe

cpp 复制代码
int execvpe(const char *file, char *const argv[], char *const envp[]);

execvpe的v可以看做vector,e看做environment,第一个参数就是要执行的文件,第二个就是命令行参数表,第三个也是一个指针数组,是环境变量表,这里的环境变量表会进行覆盖替换父进程的环境变量表,当然也有方法在原始的环境变量表的基础上进行添加!!

补充

putenv 添加环境变量的参数

envrion 访问当前环境的整个环境列表
解决方案 :

  • 不使用需要传带env的参数 直接进行putenv
  • 使用传env的参数 putenv envrion
相关推荐
MyY_DO11 分钟前
通讯录实现(Linux+Cpp)
linux·运维·服务器
独行soc13 分钟前
2025年渗透测试面试题总结-腾讯[实习]玄武实验室-安全工程师(题目+回答)
linux·安全·web安全·面试·职场和发展·渗透测试·区块链
Nightmare00421 分钟前
ubuntu22.04安装taskfile
运维·服务器·taskfile
YKPG1 小时前
C++学习-入门到精通【14】标准库算法
c++·学习·算法
zm1 小时前
极限复习c++
开发语言·c++
程序猿本员2 小时前
线程池精华
c++·后端
自动驾驶小卡2 小时前
ubuntu 常用操作指令(与域控制器交互相关)
linux·ubuntu·操作指令
靡樊2 小时前
Socket编程UDP\TCP
网络·c++·学习·tcp/ip·udp
意如流水任东西2 小时前
Linux开发工具(apt,vim,gcc)
linux·服务器
XMAIPC_Robot2 小时前
基于RK3568的多网多串电力能源1U机箱解决方案,支持B码,4G等
linux·fpga开发·能源·边缘计算