欢迎光临
我们一直在努力

进程分析2.0——进程退出、进程等待-Linux重要经典模块

目录

子进程创建中写时拷贝

子进程

写时拷贝:

进程退出

程序中途退出

执行结果:

退出码查询(strerror)

运行后退出码查询指令echon $?

自定义退出码

终结进程的两个exit()

void exit(int status); 

区别

非阻塞轮询与阻塞调用

进程等待

进程等待的必要性:

☆☆☆waitpid()

参数列表

id:

cdStatus:

option:

返回值

wait()

waitpid()使用示例展示

int* status参数深度解析

1)正常中止

2)被信号所杀/异常中止(初识)

​编辑

status使用的固定格式:

致谢


子进程创建中写时拷贝

页表:原父进程,虚拟内存表经过页表映射着物理内存表。

在虚拟内存表中,存在数据段(全局变量+静态变量)和“只读”限制代码段(可执行程序+常量)。

子进程

子进程浅拷贝父进程后,先公用同一张物理内存表,但是二者共同对应的数据段映射的页表相应数据权限收缩变为“只读”。为清晰展示图解使用两张页表“但实际上是一张表”。

但是子进程内的数据可能会发生“写”,既然已经“只读”修饰那么是怎么实现“写”的呢?

写时拷贝:

Ⅰ子进程最初创建下,限制页表数据为“只读”(因为代码执行与虚拟内存的对应必须经过页表传递,所以应限制页表数据)。 Ⅱ当发生“写”时,操作系统(OS)检测到“只读”权限,通过发生“报错”来触发子进程新物理内存页的创建,

进程退出

进程退出的场景:

  • 代码正常运行,后退出。
  • 代码运行到一半,退出。(像:return -1;)
  • 程序中途直接终止。(像:异常捕获)举例如下:

程序中途退出

异常捕获情景:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

void sigsegv_handler(int sig) {
printf("Caught signal %d (SIGSEGV): Invalid memory access!\\n", sig);
// 可以尝试恢复、记录日志、或优雅退出
exit(1);
}

int main() {
// 注册异常捕获函数
signal(SIGSEGV, sigsegv_handler);

int *p = NULL;
*p = 42; // 写入地址 0 → 触发页面错误 → 内核发送 SIGSEGV
// 程序不会崩溃,而是进入 sigsegv_handler

printf("This line will not be executed (unless handler returns)\\n");
return 0;
}

执行结果:

退出码查询(strerror)

退出码:程序退出时时的返回值,每个退出码对应一个退出描述。

char*  strterror(int index);    #返回退出描述共[0,133]

void test2()
{
for(int i = 0;i < 140;i++)
cout << i << "->" << strerror(i) << endl;
}

运行得到退出码表:

运行后退出码查询指令echon $?

查询最近一次程序运行后的退出码:

前言:在base下的每次命令执行后,就相当于一次子进程的运行自然有一次退出码。

已知程序返回1,使用echo $? 连续查询两次

自定义退出码

由:char*  strterror(int index);

观察strerror可知我们可以手动定义自己的退出码,仅需将返回值内容修改即可。

终结进程的两个exit()

void exit(int status); 

头文件:<stdlib.h>

参数:status     #作为返回参数,返回对象为父进程。

功能:终结调用此函数的进程。状态值(status)的低八位被返回给父进程。

区别

1.exit()退出后会刷新缓冲区而_exit()函数退出后不会刷新缓冲区。

2.明显exit()是语言层面,而_exit()是系统层面——其实exit()的实现内部封装_exit()。

非阻塞轮询与阻塞调用

非阻塞轮询与阻塞调用的重大区别在于:检测进程(等待进程),是否在等待时(执行到waitpid()/wait())可以依旧执行自己的任务

结合名字“阻塞调用”就是等待进程无法依旧执行自己的任务而“非阻塞轮询”下就可以执行自己的任务的同时监测待退出进程。 

因此“同时监测”就一定意味着等待进程在循环体内,执行逻辑,使得“一直”对目标进程“询问”是否执行完毕?

进程等待

进程等待的必要性:

  • 产生僵尸进程:子进程退出后,父进程不管不顾的子进程状态。——“资源泄露”
  • 僵尸进程无法被kill -9去除,因为kill -9杀死的是活着的进程。
  • 子进程完成的任务怎么样,我们必须要知道。

进程等待的处理原则:

父进程通过等待的方式(执行函数 wait() / waitpid() )来回收子进程的资源+获取子进程的信息

☆☆☆waitpid()

头文件:<sys/wait.h>

函数原型:

pid_t waitpid(pid_t id, int* cdStatus, int option);
// 接纳子进程退出值

参数列表
id:
id 含义
-1 等待所有子进程
>0 指定对象子进程id等待
cdStatus:

子进程退出值的接纳,便于后续位图操作。

option:

0:阻塞等待子进程退出。 WNOHANG:非阻塞,父进程检测是否有子进程退出,如果无立即返回0

返回值

>0 已经退出的子进程id。 =0 设置了WNOHANG,且没有子进程退出(非阻塞返回)。 -1 出错,表示调用失败。

wait()

pid_t wait(int *status);   // 等待所有的子进程

返回值:退出等待的中止进程id / -1 表示等待失败

*我们常用waitpid()

waitpid()使用示例展示

涉及进程分析此处推荐我的一篇呕心沥血文章 进程分析—从操作系统到Linux内核深入

父进程阻塞等待(option == 0),子进程运行四秒。

void test3()
{
//父进程等待4秒子进程退出 父进程回收
pid_t id = fork();
int count = 4;
if(id == 0)
{
while(count)
{
cout << "子进程退出倒计时: " << count << endl;
count–;
sleep(1);
}
}
else if(id > 0){
int status=0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0) cout << "子进程退出后成功回收, 回收id为: " << ret << endl;
else perror("失败\\n");
}
}

执行:

int* status参数深度解析

更深入功能:通过1*32位图分析,解析出"退出码"+"退出信号"。

其功能分作进程终结的三种情况来分析。

1)正常中止

32个比特位仅使用8+7位:

据其性质我们常使用位运算-> status>>8或者使用宏-WEXITSTATUS(status)来获取退出码,与strerror()参照。

子进程等待三秒,使用exit直接中止子进程,父进程捕获退出码与中止信号:

void test()
{
pid_t id = fork();
if(id == 0)
{
//子进程
int cut = 3;
while(cut)
{
printf("我是一个子进程, pid:%d,ppid:%d\\n", getpid(), getppid());
sleep(1);
cut–;
}
exit(10);//退出码被父进程捕获
}

//父进程
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid > 0)
{
printf("wait success, rid:%d,exit code: %d, exit signal: %d\\n",\\
rid, status>>8, status&0x7F);
// 退出码: status右移8位 退出信号: status低7位
}
else // ==0 WNOHANG下子进程未退出, 父进程未等待, 没阻塞
// -1 出错
{
perror("wait failed\\n");
}
}

运行结果:

2)被信号所杀/异常中止(初识)

总结:取退出码是对waitpid的第二个参数status右移8位取值,取中止信号是取status的低7位(status&0x7F)。

status使用的固定格式:

致谢

感谢支持 努力终有回报 欢迎关注 (✪ω✪)

赞(0)
未经允许不得转载:171主机测评 » 进程分析2.0——进程退出、进程等待-Linux重要经典模块
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址