进程控制相关

devtools/2024/9/24 4:22:09/

进程终止

        进程终止时,操作系统要释放对应进程申请的相关内核数据结构和对应的代码和数据。其不本质就是释放进程申请的系统资源。

进程终止的常见方式:

1、代码运行完毕且结果正确。

2、代码运行完毕但结果不正确。

3、代码没运行完,进程异常退出。

        main函数的返回值是进程的退出码,如果为0,表示运行结果是正确的,如果非零,标识的是运行结果不正确。进程退出码用来返回给上一级进程,用来评判该进程的执行结果,另外,如果进程退出码是非零的,不同的非零值可以标识不同的错误原因,便于定位错误原因。通常使用stderror(errno)以字符串形式来输出具体错误原因。

进程等待

        子进程退出时,如果父进程不管子进程,那么子进程就处于僵尸状态,会导致系统资源层面的内存泄漏问题。另外,父进程创建子进程,是为了完成某种任务,那么父进程就可能要关心子进程将该任务完成的怎么样,因此需要等待子进程退出。

具体系统接口:

wait函数和waitpid函数的返回值:

        wait函数等待子进程成功就会返回子进程的pid,如果失败,就会返回-1。 如果设置了status参数,那么对status进行设置,将进程的具体状态设置进去。

waitpid函数参数

        pid_t pid:可以有四种状态,当pid = -1时,代表等待任意一个子进程退出,当pid > 0时,会等待对应子进程识别码为pid的子进程退出,当pid < -1时,等待进程组识别码为pid绝对值的任何子进程,当pid = 0时,等待进程组识别码为pid的子进程。

        options: 0表示阻塞等待,如果没有等到子进程退出,就会一直阻塞等待,WNOHANG表示非阻塞等待。

        status:wait和waitpid函数会把被等待进程的退出状态信息记录到status中。另外,如果进程异常退出,那么此时的进程退出码是没有意义的。

status的构成

        status并不是按照整数来整体使用的,而是按照比特位的方式,将32个比特位进行划分,这里只关心低七位和次低八位。

        最低的7个比特位用来表示进程收到的信号,次低八位用来表示进程退出码。

具体例子:

#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>
#include<stdlib.h>
int code = 0;
int main()
{pid_t id = fork();if (id < 0){//创建子进程失败perror("fork");exit(1);//不正常退出}else if (id == 0){//child processint cnt = 5;while (cnt){printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt, getpid(), getppid());sleep(1);cnt--;                                                   }code = 15;exit(15);}else{//parent processprintf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());int status = 0;pid_t ret = waitpid(id, &status, 0); //阻塞式的等待!if (ret > 0){//0x7F -> 00000..1111111低七位表示退出信号//次低八位表示退出信号printf("等待子进程成功,ret: %d, 子进程信号编号:%d,子进程退出码%d\n", ret, status & 0x7F, (status >> 8) & 0xFF);}}
}

        因为进程异常退出时,所得到的进程退出码是无意义的,所以一般只有在进程正常退出时,才去关心进程的退出码。Linux中提供了对应的宏,不用每次手动提取。

WIFEXITED:若为正常终止的子进程返回的状态,则为真(查看进程是否正常退出)
WEXITSTATUS:若WIFEXITED非零,提取子进程退出码(查看进程退出码)

具体例子:

#include<sys/wait.h>
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int status = 0;
int main()
{pid_t id = fork();if(id < 0){perror("fork");exit(-1);}else if(id > 0){//child process}else{//parent processpid_t ret =  waitpid(id, &status, 0);if(WIFEXITED(status))//退出信号{//子进程正常退出printf("子进程执行完毕,退出码:%d\n", WEXITSTATUS(status));}else{printf("子进程异常退出:%d\n", WIFEXITED(status));} }return 0;
}

进程程序替换

        fork()之后,父子进程各自执行进程代码的一部分,但如果子进程想执行一个全新的程序,就要通过进程程序替换,通过特定的接口,加载磁盘上的一个全新的程序(代码+数据),加载到调用进程的地址空间中。其本质是将新的磁盘上的程序上的程序加载到内存,并和当前进程的页表重新建立映射的过程。

        进程替换并没有创建新的子进程,只是改变了对应的映射关系。

具体的exec接口系列:

 具体例子:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int status = 0;
int main()
{pid_t id = fork();if(id < 0){perror("fork");_exit(1);//系统接口不会刷新缓冲区}else if(id > 0){//parent processprintf("我是父进程\n");}else{//child processprintf("我是子进程\n");execlp("python", "python", "test.py", NULL);fflush(stdout);pid_t ret = waitpid(id, &status, 0);if(WIFEXITED(status))//退出信号{//子进程正常退出printf("子进程执行完毕,退出码:%d\n", WEXITSTATUS(status));                            }else{printf("子进程异常退出:%d\n", WIFEXITED(status));}printf("我是子进程");    }return 0;
}

        细节:execl系列的接口是程序替换,调用该函数成功后,会将当前进程的所有代码和数据都进行替换,包括已经执行的和没有执行的。所以一旦调用成功,后续所有代码都不会被执行。

为何需要创建子进程的原因:

        结合进程替换,如果不创建子进程,那么在进程替换时只能替换父进程。创建子进程之后,替换的就是子进程,而不影响父进程。因为父进程一般聚焦于读取数据,解析数据,指派执行代码的功能。


http://www.ppmy.cn/devtools/2637.html

相关文章

服务限流--一起学习吧之架构

一、主要算法&#xff1a; 计数器算法&#xff1a;该算法定义了一个单位时间&#xff08;如1秒&#xff09;的阈值&#xff0c;每收到一次请求&#xff0c;计数就增加一次。如果请求总数超过当前单位时间内的阈值&#xff0c;就触发限流处理。这种算法简单直观&#xff0c;但存…

LinkedList部分底层源码分析

JDK版本为1.8.0_271&#xff0c;以插入和删除元素为例&#xff0c;LinkedList部分源码如下&#xff1a; //属性&#xff0c;底层结构为双向链表 transient Node<E> first; //记录第一个结点的位置 transient Node<E> last; //记录最后一个结点的尾元素 transient …

maven 项目示例

maven 项目 <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><properti…

Apache Storm的详细配置

Apache Storm的详细配置主要涉及以下几个方面: Zookeeper配置:Apache Storm使用Zookeeper来进行协调和配置管理。你需要配置Zookeeper集群的连接信息,包括Zookeeper服务器的主机和端口。 Storm Nimbus配置:Nimbus是Storm的主节点,负责分配任务给各个工作节点。你需要配置N…

Rust常见陷阱 | 失效的可变性

Rust 作为一门系统编程语言,最大的卖点之一在于其内存安全的保证。这种保证很大程度上得益于其变量绑定(Binding)规则中的一项核心特性:不变性(Immutability)。然而,不当的使用不变性可能会导致一些编程陷阱。在本文中,我将详细探讨 Rust 编程中的不变性陷阱,并以丰富…

K8S哲学 - 常见的资源类型

资源类型 namespace kubectl apply 和 kubectl create kubectl apply是声明式的 和 kubectl create是命令式的对吗 deployment 和 job的区别 k8s 的 lable 的意义

js如何設置滾動到元素位置

在JavaScript中&#xff0c;你可以使用window.scrollTo()方法或元素的scrollIntoView()方法来设置滚动位置。这些方法允许你将页面滚动到特定的坐标或元素。 使用 window.scrollTo() window.scrollTo() 方法接受两个参数&#xff1a;x坐标和y坐标&#xff0c;分别代表水平滚动…

「探索C语言内存:动态内存管理解析」

&#x1f320;先赞后看&#xff0c;不足指正!&#x1f320; &#x1f388;这将对我有很大的帮助&#xff01;&#x1f388; &#x1f4dd;所属专栏&#xff1a;C语言知识 &#x1f4dd;阿哇旭的主页&#xff1a;Awas-Home page 目录 引言 1. 静态内存 2. 动态内存 2.1 动态内…