【Linux】详解进程控制 ( fork函数 | 写时拷贝 | 进程退出 | 进程等待 )

news2024/11/17 21:42:18

  • fork函数
    • fork函数初识
    • fork返回值问题
    • fork常规用法
  • 写时拷贝
  • 进程退出
    • 进程退出码
    • 进程退出场景
    • 进程如何退出
  • 进程等待
    • 僵尸进程
    • 进程等待的方法

fork函数

fork函数初识

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

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

 当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程

fork返回值问题

  • 子进程返回0,
  • 父进程返回的是子进程的pid
  1. 如何理解fork函数有两个返回值问题?

在这里插入图片描述

在这里插入图片描述
  我们常说在fork函数之后就有两个执行流其实不太严谨,其实在fork函数内部就已经有两个执行流了。

  1. 如何理解fork返回之后,给父进程返回子进程的pid,给子进程返回0?
    我们可以这么理解,在生活中我们父亲与孩子的关系是 1 :n的关系(n>=1),任何一个人只有一个父亲,但是一个父亲可以有多个儿子,所以孩子找父亲具有唯一性。这也是我们要给父进程返回子进程的pid,是为了父进程更好的找到子进程

  2. 如何理解同一个id值,怎么可能会保存两个不同的值,让if 、else if同时执行?

	pid_t id = fork();

  我们返回的本质就是写入,所以说谁先返回,谁就写入id值,因为进程具有独立性,所以此时会发生写时拷贝。此时也就会出现同一个id,它的地址是一样的,但是内容不一样。

fork常规用法

 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子
进程来处理请求。
 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

fork调用失败的原因:

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

写时拷贝

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

在这里插入图片描述

进程退出

进程退出码

查看进程退出码:

echo $?
  • ./Test : 运行一个进程
  • echo $? :永远记录最近一个进程在命令行中执行完毕时对应的退出码
    在这里插入图片描述
     我们运行程序发现第一次退出码是1,然后重复命令echo $? 退出码变成0,是因为echo本身就是一个进程,进程正常运行完以后返回0.

如何设定main函数返回值呢?
 如果不关心进程退出码,return 0就行
 如果未来我们是要关心进程退出码的时候,要返回特定的数据表明特定的错误进程退出的时候,对应的退出码标定进程执行的结果是否正确, 退出码的意义:0: success,!0:标识失败,!0具体是几,标识不同的错误

这个退出码就跟考试成绩一样,你考了100分你爸不会问你为什么,而你考了30分你爸就会问你为什么只考了30分,如果是正确的返回0就行,但是如果你代码出错了,可能有多种错误情况,返回的也就不尽相同。

系统的退出码及各自对应的含义:

	strerror(i);

在这里插入图片描述
在这里插入图片描述

进程退出场景

  1. 代码运行完毕,结果正确 – return 0;
  2. 代码运行完毕,结果不正确 – return !0 (退出码这个时候起作用)
  3. 代码没跑完,程序异常了,退出码无意义
    比如你考试作弊被抓住了,那么你的考试成绩无论是多少分都是无意义的。

进程如何退出

  1. main函数return返回
  2. 任意地方调用exit(i)返回(不管在哪里只要调用了exit就直接终止该进程了)
    在这里插入图片描述
  3. _exit(i);
    在这里插入图片描述
     exit函数这边过2秒才会打印而不是打印了再过2秒,是因为我们printf函数没有写‘\n’数据还在缓冲区。
  • exit是库函数,终止进程,会主动刷新缓冲区
  • _exit是系统调用,终止进程,不会刷新缓存区
    在这里插入图片描述
    缓冲区在哪?在用户级的缓冲区

进程等待

僵尸进程

进程等待必要性

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

进程等待的方法

在这里插入图片描述

1. wait方法:
我们通过下面代码和循环打印pid的脚本来解释wait方法:

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
	成功返回被等待进程pid,失败返回-1。
参数:
	输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
  1 #include <stdio.h>  
  2 #include <stdlib.h>                                                                                
  3 #include <unistd.h>                     
  4 #include <sys/types.h>                  
  5 #include <sys/wait.h>                   
  6 int main()                              
  7 {                                       
  8                                         
  9     pid_t id = fork();                  
 10     if(id == 0)                         
 11     {                                   
 12         //子进程                        
 13         int cnt = 5;                    
 14         while(cnt)                      
 15         {                                                                                 
 16             printf("我是子进程: %d, 父进程: %d, cnt: %d\n", getpid(), getppid(), cnt--);  
 17             sleep(1);                                                                   
 18         }                               
 19         exit(0);//子进程退出            
 20     }                                   
 21     //父进程                            
 22     sleep(8);                           
 23     pid_t ret = wait(NULL);             
 24     if(id>0)                            
 25     {                                    
 26         printf("wait sucess:%d\n",ret);  
 27     }                                   
 28     sleep(5);
 29		return 0;	
 30}
//循环打印进程信息
while :; do ps axj | head -1 && ps axj | grep mytest | grep -v grep;sleep 1;done

在这里插入图片描述
 代码主要逻辑:创建子进程,然后子进程先自己打印5秒,然后直接exit退出,这时候子进程就进入了僵尸状态,而父进程从一开始就睡眠8秒,当父进程睡眠完以后通过wait回收子进程资源,成功就打印“wait sucess”,这个时候子进程就退出僵尸状态,最后父进程再睡眠5秒然后父进程终止。

2. waitpid方法:
我们通过下面代码来解释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。

 这样使用是错误的:

printf("wait success: %d, status:%d\n", ret,status);

 打印结果:
在这里插入图片描述
 原因是:
 我们的退出场景为以下三种情况:

  • 运行完
    1.代码完,结果对
    2.代码完,结果不对
  • 异常
    代码没跑完,出异常了

获取子进程status:

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特
    ):

在这里插入图片描述

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 #include <sys/wait.h>
  6 
  7 
  8 int main()
  9 {
 10     pid_t id = fork();
 11     if(id == 0)
 12     {
 13         //子进程
 14         int cnt=5;
 15         while(cnt)
 16         {
 17             printf("我是子进程: %d, 父进程: %d, cnt: %d\n", getpid(), getppid(), cnt--);
 18             sleep(1);
 19         }
 20         exit(15); //进程退出
 21     }
 22     // 父进程
 23     int status = 0; // 不是被整体使用的,有自己的位图结构
 24     pid_t ret = waitpid(id, &status, 0);
 25     if(id > 0)  
 26     {  
 27         printf("wait success: %d, sig number: %d, child exit code: %d\n", ret, (status & 0x7F), (status>>8) & 0xFF);
 28     }  
 29   
 30     sleep(5);
 31     return 0;
 32 }  

 (status & 0x7F) 前7位
 (status>>8) & 0xFF)第8-16位

 运行结果:
在这里插入图片描述
 我们写一个空指针解引用:
在这里插入图片描述

//查看异常信息
kill -l

在这里插入图片描述
 我们将循环调整大一点,使用 kill 命令对子进程:
在这里插入图片描述

僵尸进程:
进程进入僵尸状态后它的代码和数据因为程序结束了,所以都不需要了,但是它的退出码和退出信号都会保留在PCB中,PCB不会被删除
在这里插入图片描述
我们也可以不使用status,使用宏:

  • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
    if(ret > 0)
    {
        // 是否正常退出
        if(WIFEXITED(status))
        {
            // 判断子进程运行结果是否ok
            printf("exit code: %d\n", WEXITSTATUS(status));
        }
        else{
            //TODO异常
            printf("child exit not normal!\n");
        }

如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀

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

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

相关文章

第42步 深度学习图像识别:Mobilenet建模(Tensorflow)

基于WIN10的64位系统演示 一、写在前面 &#xff08;1&#xff09;Mobilenet MobileNet是谷歌研究团队于2017年发布的一种轻量级的深度学习网络架构。这种架构特别适用于移动设备和嵌入式设备上&#xff0c;因为它的模型体积小&#xff0c;计算量少&#xff0c;但又能保持相…

无人船实时路径规划与编队控制仿真研究

源自&#xff1a;系统仿真学报 作者&#xff1a;宋大雷 干文浩 许嘤枝 曲秀青 曹江丽 摘 要 安全和无碰撞导航是无人船正常航行的基础。通过Unity3D构建高保真的虚拟海洋环境&#xff0c;在无人船建模基础上&#xff0c;提出一种面向未知复杂环境的实时路径规划及编队控…

系统空间数据库设计

文章目录 1 .空间数据库2. 空间数据库实体结构属性与空间数据库关联设计 1 .空间数据库 系统空间数据库存储电力管线设备和管网各业务图的地理空间信息&#xff0c; GIS 服务器GeoServer 通过读取空间数据库的实体表中的空间信息确定地物类的位置和几何形状&#xff0c;然后将…

全志V3S嵌入式驱动开发(spi-nor image制作和烧入)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 其实&#xff0c;我们之前就讨论过怎么把image烧入到v3s的spi-nor当中去。当时使用的方法是借助于sunxi-fel工具&#xff0c;烧入的image也比计较小…

重磅:微前端与模块联邦项目落地

微前端概念及诞生背景 微前端的出现背景可以追溯到大型前端应用的开发和维护过程中所面临的挑战和问题。 大型应用的复杂性&#xff1a;随着前端应用规模的扩大&#xff0c;应用的复杂性也增加。大型应用通常由多个团队协同开发&#xff0c;每个团队负责一部分功能模块&#x…

干货 | 智能网联车个人数据流通安全要求

以下内容整理自清华大学《数智安全与标准化》课程大作业期末报告同学的汇报内容。 我们对相关已有的标准规范进行调研&#xff0c;在工业和信息化部办公厅印发的《车联网网络安全和数据安全标准体系建设指南》中规定了六个部分的安全要求&#xff0c;我们做的主要是个人信息保护…

MaskFormer:将语义分割和实例分割作为同一任务进行训练

目标检测和实例分割是计算机视觉的基本任务&#xff0c;在从自动驾驶到医学成像的无数应用中发挥着关键作用。目标检测的传统方法中通常利用边界框技术进行对象定位&#xff0c;然后利用逐像素分类为这些本地化实例分配类。但是当处理同一类的重叠对象时&#xff0c;或者在每个…

c++11 标准模板(STL)(std::basic_streambuf)(五)

定义于头文件 <streambuf> template< class CharT, class Traits std::char_traits<CharT> > class basic_streambuf; 类 basic_streambuf 控制字符序列的输入与输出。它包含下列内容并提供到它们的访问&#xff1a; 1) 受控制字符序列&#xff…

【探索 Kubernetes|作业管理篇 系列 13】StatefulSet 拓扑状态

前言 大家好&#xff0c;我是秋意零。 在上一篇中&#xff0c;我们讲解了 Deployment 这种无状态的控制器&#xff1b;而如果要部署有状态的应用&#xff0c;那么 Deployment 显然是达不到我们的需求的。 今天讲解的是 StatefulSet 有状态控制器。 最近搞了一个扣扣群&…

电气照明节能设计在智能控制系统中的应用

【摘要】&#xff1a;随着社会的不断发展&#xff0c;经济水平也随之稳步提升&#xff0c;人们的消防意识也正在逐步加强&#xff0c;这就促进了消防应急照明系统的发展。当今社会,智能照明系统已经普及到家家户户&#xff0c;并在建筑工程中被广泛应用&#xff0c;同时其自身具…

【计算机网络自顶向下】计算机网络期末自测题(一)

前言 “(学不懂一点) &#xff08;阴暗的爬行&#xff09;&#xff08;尖叫&#xff09;&#xff08;扭曲&#xff09;&#xff08;阴暗的爬行&#xff09;&#xff08;尖叫&#xff09;&#xff08;扭曲&#xff09;&#xff08;阴暗的爬行&#xff09;&#xff08;尖叫&#…

非公平锁实现原理+源码解读

目录 非公平锁实现原理 加锁解锁流程 加锁源码 解锁源码 非公平锁实现原理 加锁解锁流程 先从构造器开始看&#xff0c;默认为非公平锁实现 public ReentrantLock() {sync new NonfairSync(); } NonfairSync 继承自 AQS 没有竞争时 第一个竞争出现时 Thread-1 执行了…

解决不允许一个用户使用一个以上用户名与一个服务器或共享资源的多重连接的问题

问题概述&#xff1a; 用windows server 2012 r2 vl x64搭了个文件服务器&#xff0c;在使用时有个问题&#xff0c;老是用户登录有问题&#xff0c;提示“不允许一个用户使用一个以上用户名与一个服务器或共享资源的多重连接”。出现的原因不详&#xff0c;网上也没查到合理的…

操作系统3:CPU任务调度和进程调度

目录 1、处理机调度的层次 &#xff08;1&#xff09;高级调度(High Level Scheduling) &#xff08;2&#xff09;低级调度(Low Level Scheduling) &#xff08;3&#xff09;中级调度(Intermediate Scheduling) 2、处理机调度算法的目标 批处理系统的目标 3、作业与作…

MongoDB 基于角色的访问控制

一、概述 MongoDB采用基于角色的访问控制&#xff08;RBAC&#xff09;来管理对 MongoDB系统。向用户授予一个或多个角色那 确定用户对数据库资源和操作的访问权限。外面 在角色分配中&#xff0c;用户无权访问系统。 1、启用访问控制 MongoDB默认情况下不启用访问控制。您可…

Facebook Insights分析工具解读,掌握关键数据指标

什么是Facebook Insights&#xff1f; Facebook Insights是Facebook平台上的一项内置分析工具&#xff0c;旨在帮助企业和品牌了解其在Facebook上的表现和受众互动情况。该工具提供了丰富的数据和指标&#xff0c;可以帮助用户洞察粉丝群体、了解发布内容的表现&#xff0c;并…

闭门造轮(LVGL_2)

例程1_// 组件(widgets)&#xff1a; 标签(label)的用法 static void label_event_cb(lv_event_t * e) {lv_obj_t * obj lv_event_get_target(e); // 获取触发事件的部件(对象)lv_event_code_t code lv_event_get_code(e); // 获取当前部件(对象)触发的事件代码sw…

Golang每日一练(leetDay0105) 最小高度树、戳气球

目录 310. 最小高度树 Minimum Height Trees &#x1f31f;&#x1f31f; 312. 戳气球 Burst Balloons &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一…

用自定义域名访问Tailscale节点

需求 tailscale 是好东西&#xff0c;在任何地方都可以和在局域网访问一样&#xff0c;但是也有着 IP 访问的不便&#xff0c;一方面 IP 是 tailscale 分配的&#xff08;非子网路由模式&#xff09;&#xff0c;另一方面还要记住各种端口 tailscale 也考虑到了这些问题&…

SpringBoot 使用 TestRestTemplate 进行 RESTful API 集成测试

SpringBoot 使用 TestRestTemplate 进行 RESTful API 集成测试 RESTful API 集成测试是测试应用程序与其外部依赖项之间的集成。SpringBoot提供了TestRestTemplate来测试RESTful API&#xff0c;本文将介绍如何使用TestRestTemplate进行RESTful API集成测试。 1. 什么是 TestR…