进程的等待(非阻塞轮询+阻塞)和替换控制详解

news2024/11/14 14:52:12

引言

在Linux系统中,进程管理是核心功能之一。理解进程的创建、执行和终止是系统编程中的基础。本文将深入探讨Linux中的进程控制机制,包括进程的生命周期、父子进程的交互、以及进程状态的管理

1. 进程创建:fork()函数

在Linux操作系统中,fork()是用于创建新进程的主要系统调用。通过此函数,当前进程(父进程)创建一个新的进程(子进程),子进程是父进程的一个复制品。

关键点:

  • 父进程和子进程的关系:父进程通过fork()返回子进程的PID进行标识和管理,而子进程中fork()返回0,以区分两者的执行流。
  • 内存和资源的复制fork()实际上在内存层面采用了写时拷贝技术,优化了资源的使用。
  • 代码示例
    #include <unistd.h>
    #include <stdio.h>
    
    int main() {
        pid_t pid = fork();
        if (pid == 0) {
            printf("This is the child process.\n");
        } else {
            printf("This is the parent process. Child PID: %d\n", pid);
        }
        return 0;
    }
    

 

当一个进程调用fork之后,就有两个二进制代码相同的进程。那么我们知道:
fork函数父进程返回的是子进程的Pid,子进程返回0的原因是方便父进程对子进程进行标识,从而进行分流。
当进程结束后,我们就要谈到今天的重点话题:进程终止
首先我们说当一个进程结束之后就要终止了
进程退出场景有哪些呢?
  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止
  1. 首先,终止是在做什么?

: 先释放曾经的代码和数据占据的空间,然后释放内核数据结构,task_struct会被延期处理会处于Z僵尸状态

     2.进程终止的3种情况:  代码跑完,结果正确。代码跑完,结果不正确。可以通过进程的退出码决定!代码执行时,出现了异常导致提前退出。一旦出现异常,退出码就没有意义了。那么为什么出现异常,本质是因为进程收到了OS发给进程的信号

     3.所以我们衡量一个进程退出,只需要两个数字,退出码退出信号

 那么我们如何终止进程呢?

进程常见退出方法
正常终止(可以通过 echo $? 查看进程退出码):
1. main 返回
2. 调用 exit
3._exit

 异常退出

ctrl + c ,信号终止

 首先可以main直接return,表示进程终止(非main函数return叫做函数结束)其次,代码调用exit函数也可以终止进程。在代码的任意位置调用都表示进程退出。最后,我们还可以采用_exit,这是一个system call.区别是什么呢?Exit会在退出的时候刷新缓冲区,_exit不会。目前我们所说的缓冲区不是内核缓冲区,一定不在操作系统内部,因为exit本质是调用_exit。

 _exit函数

#include <unistd.h>
void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值
说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值
是255。

 exit函数

#include <unistd.h>
void exit(int status);
exit 最后也会调用 exit, 但在调用 exit 之前,还做了其他工作:
1. 执行用户通过 atexit on_exit 定义的清理函数。
2. 关闭所有打开的流,所有的缓存数据均被写入
3. 调用 _exit

 进程等待

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成僵尸进程的问题,进而造成内存泄漏。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,杀人不眨眼kill -9 也无能为力,因为谁也没有办法 杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对, 或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源

 

为防止僵尸进程的出现,父进程需要等待子进程结束,这可以通过wait()waitpid()函数实现。

  • wait():阻塞当前进程,直到一个子进程结束。
  • waitpid():提供更多控制,如非阻塞等待,允许父进程在子进程运行时执行其他任务。

 wait方法

 

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
 成功返回被等待进程pid,失败返回-1。
参数:
 输出型参数,
waitpid 方法
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
 当正常返回的时候waitpid返回收集到的子进程的进程ID;
 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
 pid:
 Pid=-1,等待任一个子进程。与wait等效。
 Pid>0.等待其进程ID与pid相等的子进程。
 status:
 WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
 WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
 options:
 WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。
获取子进程 status
wait waitpid ,都有一个 status 参数,该参数是一个输出型参数,由操作系统填充。
如果传递 NULL ,表示不关心子进程的退出状态信息。
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status 不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究 status 16 比特位

 

 

测试代码:
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main( void ){
 pid_t pid;
 if ( (pid=fork()) == -1 )
 perror("fork"),exit(1);
 if ( pid == 0 ){
 sleep(20);
 exit(10);
 } else {
 int st;
 int ret = wait(&st);
 
 if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出
 printf("child exit code:%d\n", (st>>8)&0XFF);
 } else if( ret > 0 ) { // 异常退出
 printf("sig code : %d\n", st&0X7F );
 }
 }
}测试结果:
 [root@localhost linux]# ./a.out #等20秒退出
 child exit code:10 
 [root@localhost linux]# ./a.out #在其他终端kill掉
 sig code : 9

 

具体代码实现

 非阻塞等待,等待成功,子进程退出了,父进程回收成pid_t >0,反之pid<0.pid_t==0:检测是成功的,只不过子进程没退出,需要你下一次重复等待。所以我们一般都是 通过 非阻塞等待的时候+循环来确定状态,这个就叫非阻塞轮询。在非阻塞轮询的过程中允许父进程做一些其他的事

 进程的阻塞等待方式:

int main()
{
 pid_t pid;
 pid = fork();
 if(pid < 0){
 printf("%s fork error\n",__FUNCTION__);
 return 1;
 } else if( pid == 0 ){ //child
 printf("child is run, pid is : %d\n",getpid());
 sleep(5);
 exit(257);
 } else{
 int status = 0;
 pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5S
 printf("this is test for wait\n");
 if( WIFEXITED(status) && ret == pid ){
 printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));
 }else{
 printf("wait child failed, return.\n");
 return 1;
 } }
 return 0;
}
运行结果:
[root@localhost linux]# ./a.out
child is run, pid is : 45110
this is test for wait
wait child 5s success, child return code is :1.
进程的非阻塞等待方式:

 

#include <stdio.h> 
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
 pid_t pid;
 
 pid = fork();
 if(pid < 0){
 printf("%s fork error\n",__FUNCTION__);
 return 1;
 }else if( pid == 0 ){ //child
 printf("child is run, pid is : %d\n",getpid());
 sleep(5);
 exit(1);
 } else{
 int status = 0;
 pid_t ret = 0;
 do
 {
 ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待
 if( ret == 0 ){
 printf("child is running\n");
 }
 sleep(1);
 }while(ret == 0);
 
 if( WIFEXITED(status) && ret == pid ){
 printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));
 }else{
 printf("wait child failed, return.\n");
 return 1;
 }
 }
 return 0;
}

进程程序替换

 

fork 创建子进程后执行的是和父进程相同的程序 ( 但有可能执行不同的代码分支 ), 子进程往往要调用一种 exec 函数以执行另一个程序。当进程调用一种exec 函数时 , 该进程的用户空间代码和数据完全被新程序替换 , 从新程序的启动例程开始执行。调用exec 并不创建新进程 , 所以调用 exec 前后该进程的 id 并未改变

 

 替换函数

其实有六种以 exec 开头的函数 , 统称 exec 函数:
#include <unistd.h>`
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
函数解释
这些函数如果调用成功则加载新的程序从启动代码开始执行 , 不再返回。
如果调用出错则返回 -1
所以 exec 函数只有出错的返回值而没有成功的返回值。

 命名理解

这些函数原型看起来很容易混 , 但只要掌握了规律就很好记
  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量

 

 

exec 调用举例如下 :
#include <unistd.h>
int main()
{
 char *const argv[] = {"ps", "-ef", NULL};
 char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
 execl("/bin/ps", "ps", "-ef", NULL);
 // 带p的,可以使用环境变量PATH,无需写全路径
 execlp("ps", "ps", "-ef", NULL);
 // 带e的,需要自己组装环境变量
 execle("ps", "ps", "-ef", NULL, envp);
 execv("/bin/ps", argv);
 
 // 带p的,可以使用环境变量PATH,无需写全路径
 execvp("ps", argv);
 // 带e的,需要自己组装环境变量
 execve("/bin/ps", argv, envp);
 exit(0);
}

理解Linux中的进程管理是高效系统编程的关键。通过掌握fork(), exit(), wait()exec()等系统调用,开发者可以有效地控制进程的生命周期和行为。这些概念的深入了解和实际应用对于任何系统程序员来说都是必不可少的技能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1990522.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Prompt万能框架与常用评估指标

引言 在人工智能的飞速发展中&#xff0c;大型语言模型&#xff08;LLM&#xff09;已成为研究和应用的热点。LLM以其强大的语言理解和生成能力&#xff0c;在诸如自然语言处理、文本生成、问答系统等多个领域展现出巨大潜力。然而&#xff0c;要充分发挥LLM的能力&#xff0c…

NSSCTF练习记录:[SWPUCTF 2021 新生赛]caidao

题目&#xff1a; 图片上给出了代码&#xff0c;是php的一句话木马 eval($_POST[wllm]);符号 符号表示后面的语句即使执行错误&#xff0c;也不报错。 eval()函数 eval()函数的作用是把括号内的字符串全部当作php代码来执行。 ** P O S T [ ′ w l l m ′ ] ∗ ∗ p o s t …

一起学习LeetCode热题100道(30/100)

30.两两交换链表中的节点(学习) 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;head …

【LVS】调度算法概念

fd相当于静态 ovf相当于动态

界面控件DevExpress WinForms,支持HTML CSS提升用户体验(二)

DevExpress WinForms现在可以利用HTML/CSS强大的功能&#xff0c;帮助受DevExpress驱动的WinForms应用程序引入现代的UI元素和用户体验&#xff01; P.S&#xff1a;DevExpress WinForms拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。Dev…

室内定位导航技术:蓝牙信号强度(RSSI)与三角定位算法应用

在数字化时代&#xff0c;位置服务已成为连接物理世界与数字世界的桥梁。在室内环境中&#xff0c;由于GPS信号受建筑物遮挡而失效&#xff0c;传统的室外定位技术难以满足需求&#xff0c;无法精准指引我们在商场、机场、医院等庞大而复杂的建筑内部寻路。室内定位导航技术不仅…

在 Manim 中,kwargs 传递的关键字参数

在 Manim 中&#xff0c;kwargs 用于传递关键字参数&#xff0c;常用于构造对象时。这里的关键参数有那些。我都要&#xff0c;给我整理一下 Sider Fusion 在 Manim 中&#xff0c;kwargs 允许您传递可选的关键字参数来控制动画、对象的外观和行为。尽管具体可用的参数会因对象…

【开端】通过Java 过滤器灵活配置URL访问权限,并返回403

一、绪论 在JAVA项目系统中&#xff0c;后端给前端提供接口。但是在某些场景我们需要临时控制接口是否能被访问。或关闭某一接口的访问权限。 比如某一接口被攻击了或者某一接口存在漏洞&#xff0c;在系统不关闭的情况下&#xff0c;如何控制系统的访问权限。 二、控制接口访…

CVE-2017-15715~Apache解析漏洞【春秋云境靶场渗透】

Apache解析漏洞 漏洞原理 # Apache HTTPD 支持一个文件拥有多个后缀&#xff0c;并为不同后缀执行不同的指令。比如如下配置文件&#xff1a; AddType text/html .html AddLanguage zh-CN .cn# 其给 .html 后缀增加了 media-type &#xff0c;值为 text/html &#xff1b;给 …

【案例35】销售订单公式问题导致系统宕机

问题现象 经过顾问反馈&#xff0c;发现系统现在出现卡顿&#xff0c;NCC一直在转圈。 问题分析 远程排查&#xff0c;发现在服务器从机上defalut-7发生了内存溢出&#xff0c;宕机。 生成了宕机日志。分析结果如下&#xff1a; 销售订单相关操作&#xff0c;vo太多了导致…

Java stream流支持多字段排序

背景 对于排序而言,比较常见的场景是前端传递所需的排序字段名和排序方向,然后通过stream流或者数据库来实现排序. 为动态接收参数,继承Map来支持多字段传入.另外stream流原生的sorted写起来相对比较繁琐,通过compartor方法封装构建多字段排序的逻辑.具体就是通过反射拿到对应…

怎么在 React Native 应用中处理深度链接?

深度链接是一种技术&#xff0c;其中给定的 URL 或资源用于在移动设备上打开特定页面或屏幕。因此&#xff0c;深度链接可以引导用户到应用程序内的特定屏幕&#xff0c;而不仅仅是启动移动设备上的应用程序&#xff0c;从而提供更好的用户体验。这个特定的屏幕可能位于一系列层…

docker 好用的加速器

cd /etc/docker vi daemon.json { "registry-mirrors":["https://docker.rainbond.cc"] }

SpringIOC整合dbUtil做的增删改查以及转账业务的实现

目录 一、xml方式实现 1.介绍lombok插件 2.功能 3.步骤 3.1 idea安装插件(只做一次) 3.2 添加坐标 3.3 编写注解 4.核心类 4.1 QueryRunner 4.2 query() 查询 4.3 update() 增删改 5.配置文件applicationContext.xml 6.junit测试 6.1使用步骤 6.1.1 坐标 6.1.2…

【Material-UI】Button Group 中的 Disabled Elevation 功能

文章目录 一、Button Group 组件概述二、什么是 Elevation&#xff1f;三、为什么需要禁用 Elevation&#xff1f;四、使用 disableElevation 属性五、属性解析1. disableElevation 属性2. variant 属性3. aria-label 属性 六、应用场景1. 表单操作2. 工具栏3. 导航按钮 七、样…

vue中v-html 后端返回html + script js中click事件不生效

效果图&#xff1a; 需求&#xff1a;点击加号执行后端返回的script中的代码 后端返回的html&#xff1a; <!DOCTYPE html> <html langzh> <head> <title>xxx</title> <style>body{font-size: 14px}p{text-indent: 30px;}textarea{width…

PythonStudio 控件使用常用方式(十三)TScrollBox

PythonStudio是一个极强的开发Python的IDE工具&#xff0c;它使用的是Delphi的控件&#xff0c;常用的内容是与Delphi一致的。但是相关文档并一定完整。现在我试试能否逐步把它的控件常用用法写一点点&#xff0c;也作为PythonStudio的参考。 从1.2.1版开始&#xff0c;Python…

(Qt) QThread 信号槽所在线程

文章目录 &#x1f481;&#x1f3fb;前言&#x1f481;&#x1f3fb;Code&#x1f481;&#x1f3fb;‍♂️Code&#x1f481;&#x1f3fb;‍♂️环境 &#x1f481;&#x1f3fb;当前线程信号&#x1f481;&#x1f3fb;‍♂️默认效果&#x1f481;&#x1f3fb;‍♂️Qt::…

RTOS(7)队列

1.队列的理论知识 下面的结构体里包含了&#xff1a;头部指针&#xff0c;写指针&#xff0c;读指针&#xff0c;长度&#xff0c;项目大小&#xff0c;两个链表&#xff1b; 写队列的时候&#xff0c;写指针指向头部&#xff0c;写进去之后&#xff0c;itemsize&#xff0c;移…

MySQL中的日志

错误日志 错误日志是MySQL中最重要的日志之一默认是开启的&#xff0c;它记录了MySQL启动和停止时&#xff0c;以及入伍再运行过程中发发生任何严重错误时的相关信息&#xff0c;当数据库出现任何故障无法正常运行时可以查看此日志。 二进制日志 二进制日志记录了所有的DDL语…