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
相关推荐
望获linux3 小时前
北京亦庄机器人马拉松:人机共跑背后的技术突破与产业启示
linux·人工智能·机器人·操作系统·开源软件·rtos·具身智能
张槊哲4 小时前
字符和编码(python)
linux·数据库·python
共享家95276 小时前
C++模板知识
c++
阿沁QWQ6 小时前
友元函数和友元类
开发语言·c++
herinspace7 小时前
管家婆易指开单如何设置零售开单
运维·服务器·数据库·软件工程·sass·零售
小镇敲码人7 小时前
【网络层】之IP协议
服务器·网络·tcp/ip
achene_ql7 小时前
缓存置换:用c++实现最近最少使用(LRU)算法
开发语言·c++·算法·缓存
IT阳晨。8 小时前
【嵌入式Linux】基于ARM-Linux的zero2平台的智慧楼宇管理系统项目
linux·arm开发
_清风来叙8 小时前
【Linux】Linux内核模块开发
linux·arm开发
于齐龙8 小时前
pip 常用命令及配置
linux·python·pip