Linux 进程控制(进程创建,进程等待)

目录

进程创建

fork函数初识

fork函数返回值

写时拷贝

fork常规用法

fork调用失败的原因

进程终止

进程退出场景

进程退出码

进程常见退出方法

exit函数

_exit函数

return退出

return、exit和_exit之间的区别与联系

进程异常退出

进程等待

进程等待的必要性

获取子进程status

进程等待的方法

wait方法

waitpid方法

多进程创建以及等待的代码模型

非阻塞轮询


进程创建

fork函数初识

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

返回值:

子进程中返回0,父进程返回子进程id,出错返回-1

进程调用fork,当控制转移到内核中的fork代码后,内核做: 分配新的内存块和内核数据结构给子进程 将父进程部分数据结构内容拷贝至子进程 添加子进程到系统进程列表当中 fork返回,开始调度器调度

fork之后,父子进程代码共享

运行之后:

我们可以观察到fork之前的代码执行了一次,而fork之后的代码执行了两次,其中Before是由父进程打印的,而调用fork函数之后打印的两个After,则分别由父进程和子进程两个进程执行。也就是说,fork之前父进程独立执行,而fork之后父子进程两个执行流分别执行

注意:fork之后,父进程和子进程谁先执行完全是由调度器决定

fork函数返回值

fork函数为什么要给子进程返回0,给父进程返回子进程的pid?

一个父进程可以创建很多个子进程,而一个子进程只能有一个父进程。因此对于子进程来说,父进程是不需要被标识的;而对于父进程来说,子进程是需要被标识的,因为父进程创建子进程的目的是让其执行任务的,父进程只有知道了子进程的pid才能很好的对子进程指派任务

为什么fork有两个返回值?

父进程调用fork函数后,为了创建子进程,fork函数内部将会进行一系列操作,包括创建子进程的进程控制块(PCB - task_struct),创建子进程的进程地址空间,创建子进程对应的页表等等。子进程创建完毕之后,操作系统还需要将子进程的进程控制块添加到系统进程列表当中,此时子进程便创建完毕了

也就是说,在fork函数内部执行return语句之前,子进程就已经创建完毕了,那么之后的return语句不仅父进程需要执行,子进程也同样需要执行,这就是fork有两个返回值的原因

写时拷贝

通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:

1.为什么数据要进行写时拷贝?

进程具有独立性。多进程运行,需要独享各种资源,多进程运行期间互不干扰,不能让子进程的修改影响到父进程

2.为什么不创建子进程的时候就进行数据的拷贝?

子进程不一定会使用父进程的所有数据,并且在子进程不对数据进行写入的情况下,没有必要对数据进行拷贝,我们应该按需分配,在需要修改数据的时候再分配(延迟分配),这样可以高效的使用内存空间

3.代码会不会进行写时拷贝?

90%的情况下不会的,但着并不代表代码不能进行写时拷贝,例如再进行进程替换的时候,则需要进行代码的写时拷贝

fork常规用法

1.一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子 进程来处理请求。

2.一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

fork调用失败的原因

fork函数创建子进程也可能会失败,有以下两种情况:

1.系统中有太多的进程

2.实际用户的进程数超过了限制

进程终止

进程退出场景

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

进程退出码

进程退出码
我们都知道main函数是代码的入口,但实际上main函数只是用户级别代码的入口,main函数也是被其他函数调用的,例如在VS2022当中main函数就是被一个名为__tmainCRTStartup的函数所调用,而__tmainCRTStartup函数又是通过加载器被操作系统所调用的,也就是说main函数是间接性被操作系统所调用的。

既然main函数是间接性被操作系统所调用的,那么当main函数调用结束后就应该给操作系统返回相应的退出信息,而这个所谓的退出信息就是以退出码的形式作为main函数的返回值返回,我们一般以0表示代码成功执行完毕,以非0表示代码执行过程中出现错误,这就是为什么我们都在main函数的最后返回0的原因。

当我们的代码运行起来就变成了进程,当进程结束后main函数的返回值实际上就是该进程的进程退出码,我们可以使用echo $?命令查看最近一次进程退出的退出码信息。
例如,对于下面这个简单的代码:


该进程结束之后我们可以查看该进程的退出码:

使用echo $?指令

这时,我们就可以确定以上代码顺序执行

为什么以0表示代码执行成功,以非0表示代码执行错误?

因为成功只有一种情况,成功了就算成功了,而失败有很多种情况,例如:野指针问题,除0错误,栈溢出,越界访问,内存空间不足等原因

c语言中的strerror函数可以通过错误码来获取该错误码对应的错误信息:

运行之后,我们就可以得到错误码所对应的错误信息

实际上再Linux中的 pwd ,ls指令都是可执行程序,在其执行完毕之后也会有退出码

顺序运行之后退出码为0

但是,如果我们使用的是错误的指令,它会返回非0的错误码

进程常见退出方法

exit函数

1. 执行用户通过 atexit或on_exit定义的清理函数。

2. 关闭所有打开的流,所有的缓存数据均被写入(刷新缓冲区)

3. 调用_exit终止进程

exit在退出进程时,会先将缓冲区的数据输出,在终止进程

运行之后,我们可以看到,缓冲区的数据输入到了缓冲区上

_exit函数

尽量不要去使用这个接口,它可以在程序的任何地方使用,使用时会直接终止掉程序,并不会再终止程序前做任何收尾工作

以下代码在使用_exit终止程序,缓冲区的数据不会被刷新出来

代码运行之后:

通过观察可以发现缓冲区的数据并没有刷新出来

return退出

return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返 回值当做 exit的参数。

return、exit和_exit之间的区别与联系

return、exit和_exit之间的区别

只有在main函数中return 才能起到退出进程的作用,在其它子函数中只会退出该函数并不会退出进程,exit和_exit可以在代码中任何地方使用,都有退出进程的作用

使用exit函数退出进程前,exit函数会执行用户定义的清理函数、冲刷缓冲,关闭流等操作,然后再终止进程,而_exit函数会直接终止进程,不会做任何收尾工作。

return、exit和_exit之间的联系

执行return n等同于执行exit(n),因为在main函数运行结束之后,return的返回值会当作exit函数的参数,来调用exit函数

使用exit函数退出进程前,exit函数会先执行用户定义的清理函数、冲刷缓冲,关闭流等操作,然后再调用_exit函数终止进程。

进程异常退出

情况一:向进程发出信号导致进程异常退出

例如:对一个进程使用 kill -9 pid 或者 使用ctrl+C使进程异常退出 

情况二:代码错误导致进程运行时异常退出

例如:代码执行时遇到野指针或者遇到除0错误时,进程异常退出

进程等待

进程等待的必要性

1.子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。

2.进程一旦变成僵尸状态,连kill -9 也无法杀掉该进程,因为谁也没有办法杀死一个已经死去的进程。

3.父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对, 或者是否正常退出。

4.父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

获取子进程status

下面进程等待所使用的两个函数wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统进行填充

如果对status这个参数传入NULL,则表示不关心子进程的退出状态信息,否则操作系统会通过该参数将子进程的退出信息反馈给父进程

status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

在status低十六位中,高八位表示进程的退出状态(退出码),

进程若是被信号所杀,则低7位表示终止信号,而第八位比特位是core dump 标记

我们可以通过一系列位操作,就可以根据status得到进程的退出码和退出信息

exitCode = (status >> 8) & 0xff; //退出码
exitSignal = status & 0x7f; //退出信号

对于次系统还提供了两个宏来获取退出码和退出信号

WIFEXITED(status):用于查看进程是否正常退出,本质是检查是否收到信号

WEXITSTATUS(status):用于获取进程的退出码

exitNormal = WIFEXITED(status); //是否正常退出
exitCode = WEXITSTATUS(status); //获取退出码

注意:当一个进程非正常退出时,说明该进程是被信号所杀死,那么该进程的退出码也就没意义了

进程等待的方法

wait方法

函数原型: pid_t wait(int* status);

作用:等待任意子进程

返回值:等待成功返回被等待进程的pid,等待失败返回-1

参数:输出型参数,获取子进程的退出状态,不关心可设置为NULL

例如:创建子进程之后,父进程可使用wait函数一直等待子进程,直到子进程退出后读取子进程的退出信息

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 #include <sys/wait.h>
  6 
  7 int main()
  8 {
  9         pid_t id = fork();
 10         if(id == 0) //child
 11         {
 12                 int cnt = 10;
 13                 while(cnt)
 14                 {
 15                         printf("I am child!. pid: %d, ppid: %d\n", getpid(), getppid())    ;
 16                         sleep(1);
 17                         cnt--;
 18                 }
 19                 exit(0);
 20         }
 21 
 22         //parent
 23         int status = 0;
 24         pid_t ret = wait(&status);
 25 
 26         if(ret > 0)
 27         {
 28                 printf("wait child success...\n");
 29                 if(WIFEXITED(status)) //exit normal
 30                 {       
 31                         printf("exit code: %d", WEXITSTATUS(status));
 32                 }
 33         }
 34         
 35         sleep(3);
 36         return 0;
 37 }

我们可以使用监控脚本来对本进程进行实时监控:

while :; do ps ajx | head -1 && ps ajx | grep myproc | grep -v grep; echo "###############"; sleep 1; done
 

这时我们可以看到,当子进程退出之后,父进程读取了子进程的退出信息,子进程也就不会变成僵尸进程了

waitpid方法

函数原型: pid_ t waitpid(pid_t pid, int *status, int options);

作用:等待指定子进程或任意子进程

返回值:

1.等待成功返回被等待进程的pid

2.如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0

3.如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在

参数:

1.pid:待等待子进程的pid,若设置为-1,则等待任意子进程

2.status:输出型参数,获取子进程的退出状态,不关心可设置为NULL

3.option:当设置WNOHANG|时,若等待的子进程没有结束,则waitpid函数直接返回0,不予以等待,若正常结束,则返回该子进程的pid

例如:创建子进程之后,父进程可以使用waitpid函数一直等待子进程(此时将waitpid的第三个参数设置为0),直到子进程退出后读取子进程的退出信息

1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 #include <sys/wait.h>
  6 
  7 int main()
  8 {
  9         pid_t id = fork();
 10         if(id == 0) //child
 11         {
 12                 int count = 10;
 13                 while(count)
 14                 {
 15                         printf("I am child..., pid: %d, ppid: %d\n", getpid(), getppid());
 16                         sleep(1);
 17                         count--;
 18                 }
 19                 exit(0);
 20         }
 21 
 22         //parent
 23         int status = 0;
 24         pid_t ret = waitpid(id, &status, 0);
 25         if(ret > 0)
 26         {
 27                 printf("Wait success....\n");
 28                 //wait success
 29                 if(WIFEXITED(status))
 30                 {
 31                        //exit normal
 32                         printf("exit code: %d\n", WEXITSTATUS(status));
 33                 }
 34                 else
 35                 {
 36                         //signal kill
 37                         printf("signal kill: %d\n", status&0x7F);
 38                 }
 39         }
 40 
 41         sleep(3);
 42 
 43         return 0;
 44 }

在父进程运行的过程中,我们可以尝试使用kill -9命令将子进程杀死,这时父进程也能等待子进程成功

注意:被信号杀死而退出的进程,其退出码将没有意义

多进程创建以及等待的代码模型

实际上我们还可以同时创建多个子进程,然后让父进程依次等待子进程退出,这叫做多进程创建以及等待的代码模型

例如以下代码中同时创建了10个子进程,同时将子进程这些子进程的pid放入到数组中

#include <stdio.h>
#include <stdlib.h>  
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{ 

	int ids[10] = {0};
	for(int i = 0; i < 10; ++i)
	{
		pid_t id  = fork();
		if(id == 0) //child
		{
			printf("child create success...:%d pid: %d, ppid: %d\n", i, getpid(), getppid());
			//sleep(1);
			exit(i);
		}
		//parent
		sleep(1);
		ids[i] = id;
	}

	int status = 0;
	for(int i = 0; i < 10; ++i)
	{
		pid_t ret = waitpid(ids[i], &status, 0);
		if(ret >= 0)
		{
			printf("wait success...\n");
			if(WIFEXITED(status))
			{
				//exit normal
				printf("exit code: %d\n", WEXITSTATUS(status));
				sleep(1);
			}
			else
			{
				//signal kill
				printf("exit signal: %d\n", status&0x7F);
				sleep(1);
			}
		}
	}

	return 0;
}

运行如下:

非阻塞轮询

在上述的例子中,当子进程未退出时,父进程都在一直等待子进程的退出,在等待期间,在等待期间父进程没有做任何事情,这种父进程处于阻塞状态下的等待叫做阻塞等待

实际上我们可以让父进程不要一直等待子进程退出,而是当子进程未推出时父进程苦于做一些自己的事情,当子进程未退出时父进程可以做一些自己的事情,当子进程退出时再读取子进程的退出信息,即:非阻塞等待

向waitpid函数的第三个参数potions传入WNOHANG,这样一来,等待的子进程若是没结束,那么waitpid函数将直接返回0,不予以等待。而等待的子进程若是正常结束,则返回子进程的pid

例如,父进程可以隔一段时间调用一次waitpid函数,若是等待的子进程尚未退出,则父进程可以去做一些其他事,过一段时间在调用waitpid函数读取子进程的退出信息

int main()
{
	pid_t id = fork();
	//child
	if(id == 0)
	{
		int cnt = 3;
		while(cnt--)
		{
			printf("child do something...PID:%d, PPID:%d\n", getpid(), getppid());
			sleep(3);
		}
		exit(0);
	}
	
	//parent
	while(1)
	{
		int status = 0;
		pid_t ret = waitpid(id, &status, WNOHANG);
		if(ret > 0)
		{
			printf("wait child success...\n");
			printf("exit code:%d\n", WEXITSTATUS(status));
			break;
		}
		else if(ret == 0)
		{
			printf("parent do other things...\n");
			sleep(1);
		}
		else
		{
			printf("waitpid error...\n");
			break;
		}
	}
	return 0;
}

运行结果:父进程每隔一段时间进去查看子进程是否退出,若为退出则父进程先去忙自己的事情,过一段时间再来查看,直到子进程退出之后读取子进程的退出信息

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

七星棋牌全开源修复版源码解析:6端兼容,200种玩法全面支持

本篇文章将详细讲解 七星棋牌修复版源码 的 技术架构、功能实现、二次开发思路、搭建教程 等内容&#xff0c;助您快速掌握该棋牌系统的开发技巧。 1. 七星棋牌源码概述 七星棋牌修复版源码是一款高度自由的 开源棋牌项目&#xff0c;该版本修复了原版中的多个 系统漏洞&#…

SuperMap GIS基础产品FAQ集锦(20250217)

一、SuperMap iServer 问题1&#xff1a;GPA算子是否有相关文档? 11.1.1 【解决办法】该功能算子可参考帮助文档&#xff1a;https://help.supermap.com/iServer/Server_Service_Management/Geoprocessing/GPFun/FunctionDescription/FunctionMD/GeoprocessingFunctionMD.z…

Vgg 改进:添加EMA注意力机制高效提升跨空间学习

目录 1. EMAAttention 模块 2. vgg 改进 3. 完整代码 Tips:融入模块后的网络经过测试,可以直接使用,设置好输入和输出的图片维度即可 1. EMAAttention 模块 EMA(Exponential Moving Average,指数移动平均)注意力机制是一种结合了指数移动平均和注意力机制的模型,旨在…

EasyExcel的简单使用

EasyExcel使用 官方文档&#xff1a;关于EasyExcel 1.1EasyExcel相关依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.11</version></dependency> 1.2 写Excel 1.2.1 最…

亲测有效!使用Ollama本地部署DeepSeekR1模型,指定目录安装并实现可视化聊天与接口调用

文章目录 一、引言二、准备工作&#xff08;Ollama 工具介绍与下载&#xff09;2.1 Ollama介绍2.2 Ollama安装 三、指定目录安装 DeepSeek R1四、Chatbox 可视化聊天搭建4.1 Chatbox下载安装4.2 关联 DeepSeek R1 与 Chatbox 的步骤 五、使用 Ollama 调用 DeepSeek 接口5.1 请求…

反向代理模块kf

1 概念 1.1 反向代理概念 反向代理是指以代理服务器来接收客户端的请求&#xff0c;然后将请求转发给内部网络上的服务器&#xff0c;将从服务器上得到的结果返回给客户端&#xff0c;此时代理服务器对外表现为一个反向代理服务器。 对于客户端来说&#xff0c;反向代理就相当于…

代码随想录D50-51 图论 Python

理论基础 理论基础部分依然沿用代码随想录教程中的介绍&#xff1a; 图的种类 度 连通性 连通性用于表示图中节点的连通情况。 如果有节点不能到达其他节点&#xff0c;则为非连通图&#xff0c;想象将多个水分子表示为图&#xff0c;不考虑非键作用&#xff0c;这张图就不是…

VScode运行后出现黑窗口

原文链接&#xff1a;VScode运行出黑窗口 1.安装插件&#xff1a;C/C Compile Run 2.快捷键【CtrlShiftp】,点击【首选项&#xff1a;打开用户设置】

【时时三省】(C语言基础)怎样表示一个算法

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 用自然语言表示算法 自然语言就是人们日常使用的汉语、英语或其他语言。用自然语言表示通俗易懂&#xff0c;但文字冗长&#xff0c;容易出现歧义。自然语言表示的含义往往不大严格&#xf…

PHP图书借阅小程序

&#x1f4da; 图书借阅小程序&#xff1a;一键开启智慧阅读新篇章 &#x1f31f; 这是一款由ThinkPHP与UniApp两大技术巨擘强强联手精心打造的图书借阅微信小程序&#xff0c;它犹如一座随身携带的移动图书馆&#xff0c;让您无论身处何地都能轻松畅游知识的海洋。创新的多书…

GPT-4o悄然升级:能力与个性双突破,AI竞技场再掀波澜

在大模型竞技场中&#xff0c;GPT-4o悄悄发布了全新版本&#xff0c;凭借其卓越的多项能力&#xff0c;迅速超越了DeepSeek-R1&#xff0c;成功登上并列第一的位置。这次更新不仅在数学&#xff08;第6名&#xff09;上有所突破&#xff0c;还在创意写作、编程、指令遵循、长文…

【个人开发】deepspeed+Llama-factory 本地数据多卡Lora微调

文章目录 1.背景2.微调方式2.1 关键环境版本信息2.2 步骤2.2.1 下载llama-factory2.2.2 准备数据集2.2.3 微调模式2.2.3.1 zero-3微调2.2.3.2 zero-2微调2.2.3.3 单卡Lora微调 2.3 踩坑经验2.3.1 问题一&#xff1a;ValueError: Undefined dataset xxxx in dataset_info.json.2…

微信小程序之mobx-miniprogram状态管理

目前已经学习了6种小程序页面、组件间的数据通信方案,分别是: 1. 数据绑定: properties 2.获取组件实例: this.selectComponent() 3.事件绑定: this.triggerEvent() 4. 获取应用实例&#xff1a;getApp() 5. 页面间通信: EventChannel 6.事件总线:pubsub-js 在中小型项目…

express + vue 部署宝塔

域名备案 我这里是不同的账号&#xff0c;需要先登录服务器的账号生成授权码给到对应域名的账号。目前域名审核中。 进入域名账号&#xff0c;进行备案即可。 登录阿里云密码设置 未设置登录远程服务的密码&#xff0c;要先设置密码。 登录服务 设置安全组 根据宝塔的需要端…

250214-java类集框架

引言 类集框架本质上相当于是容器&#xff0c;容器装什么东西由程序员指定 1.单列集合 单列集合是list和set&#xff0c;list的实现类有ArrayList和LinkedList&#xff0c;前者是数组实现&#xff0c;后者是链表实现。list和set&#xff0c;前者有序、可重复&#xff0c;后者…

【华为OD机考】2024E+D卷真题【完全原创题解 详细考点分类 不断更新题目 六种主流语言Py+Java+Cpp+C+Js+Go】

可上 欧弟OJ系统 练习华子OD、大厂真题 绿色聊天软件戳 oj1441了解算法冲刺训练&#xff08;备注【CSDN】否则不通过&#xff09; 文章目录 相关推荐阅读模拟 数学排序字符串基础数组基础系统设计蒙特卡洛模拟其他 栈 常规栈单调栈 队列&#xff08;题目极少&#xff0c;几乎不…

【论文投稿】Python 网络爬虫:探秘网页数据抓取的奇妙世界

目录 前言 一、Python—— 网络爬虫的绝佳拍档 二、网络爬虫基础&#xff1a;揭开神秘面纱 &#xff08;一&#xff09;工作原理&#xff1a;步步为营的数据狩猎 &#xff08;二&#xff09;分类&#xff1a;各显神通的爬虫家族 三、Python 网络爬虫核心库深度剖析 &…

借3D视觉定位东风,汽车零部件生产线实现无人化的精准飞跃

在新能源汽车市场的推动下&#xff0c;汽车零部件制造业正迎来前所未有的发展机遇。然而&#xff0c;传统的生产方式已经无法满足现代制造业对高效、精准的要求。为了应对这一挑战&#xff0c;越来越多的企业开始探索智能化生产的道路。 在这个过程中&#xff0c;3D视觉定位系…

Linux 服务器部署deepseek

把手教你在linux服务器部署deepseek&#xff0c;打造专属自己的数据库知识库 正文开始 第一步&#xff1a;安装Ollama 打开官方网址&#xff1a;https://ollama.com/download/linux 下载Ollama linux版本 复制命令到linux操作系统执行 [rootpostgresql ~]# curl -fsSL http…

20250213编译飞凌的OK3588-C_Linux5.10.209+Qt5.15.10_用户资料_R1

20250213编译飞凌的OK3588-C_Linux5.10.209Qt5.15.10_用户资料_R1 2025/2/13 11:43 缘起&#xff1a;飞凌发布了高版本内核的适配OK3588-C的Buildroot的SDK&#xff1a;OK3588-C_Linux5.10.209Qt5.15.10_用户资料_R1。 但是编译异常了。 于是按照百度升级libc6&#xff0c;可以…