【Linux】进程创建、退出和等待(fork、exit和_exit、waitpid和wait、阻塞和非阻塞)

news2024/10/7 12:20:40

文章目录

    • 1、进程创建
      • 1.1 理解fork函数
      • 1.2 fork函数的细节
    • 2、进程退出
      • 2.1 退出码
      • 2.2 exit函数和_exit系统调用
    • 3、进程等待
      • 3.1 wait和waitpid
      • 3.2 阻塞和非阻塞

1、进程创建

进程的创建主要依靠系统接口fork函数

fork函数从已存在的一个进程中,创建一个子进程,原进程为父进程。

#include <unistd>
#include <sys/type.h>//用pid_t 需要包括这个文件
pid_t fork(void);

父进程返回子进程pid, 子进程返回0,出错返回-1。

1.1 理解fork函数

先从一个小程序看看fork函数的效果。

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/type.h> //用pid_t 需要包括这个文件
  4 int main()
  5 {
  6     pid_t id = fork();                                                                                                                                      
  7     if(id < 0)        
  8     {         
  9         printf("fork error!\n");
 10     }                           
 11                                 
 12     if(id > 0)
 13     {         
 14         printf("当前进程的PID为: %d, 父进程PID是: %d, id: %d\n", getpid(), getppid(), id);
 15     }                                                                                     
 16                                                                                           
 17     else
 18     {   
 19         printf("当前进程的PID为: %d, 父进程PID是: %d, id: %d\n", getpid(), getppid(), id);
 20     }                                                                                     
 21     sleep(2);                                                                             
 22              
 23     return 0;
 24 }

在这里插入图片描述

pid_t 是什么?
首先在/usr/include/sys/types.h中,通过通过/pid_t查询
这里是引用
再到/usr/include/bits/types.h中,通过/__pid_t查询

在这里插入图片描述
再到/usr/include/bits/typesizes.h中,通过/__PID_T_TYPE查询
在这里插入图片描述
回到/usr/include/bits/types.h中,通过/__S32_TYPE查询,发现其实就是int。
在这里插入图片描述

为什么要弄这么麻烦? 其实为了代码在不同平台上跑,可能其它平台是long,而不是int。(为了可移植性)

可能你会有疑惑,为什么会有两次打印?打印为什么是这个结果?代码是怎么走的?

我们的程序代码执行前
首先,我们所写的程序,在运行后加载到内存就成了Linux系统中的一个进程。
当我们运行编译好的程序后,程序加载到内存称为了一个Linux进程。
该进程(对应pid:31200)由命令行解释器bash(bash是一个系统进程,这里对应31107)创建,作为其子进程执行代码。

代码执行过程
进入main函数,执行pid_t id = fork(); 此时转到操作系统内核fork定义处,执行fork函数代码。
(下图的子进程,其实不是在fork中马上创了一个空间,这里为了更好理解,下面会解释)
在这里插入图片描述

所以其实很简单,就是fork之后,有了两个执行流,通过返回值的不同走不同的代码路径。

1.2 fork函数的细节

有几个细节,能让我们更好理解fork。
在前面我们解释了fork函数为什么有两个返回值的问题,就是通过fork创建子进程,有了两个执行流。

首先

  • 如何理解fork之后,父进程返回子进程id,子进程返回0?
    我们都知道,一个父亲可以对应着多个孩子,而多个孩子只能对应一个父亲。
    进程也一样,我们可以通过getpid和getppid得到唯一的自己和父亲,但对于孩子,如果我们需要找其中一个就需要有一个确定值。

其次

  • 为什么会有一个变量id,储存两个不同的值?

pid id = fork(); 首先对于一个进程,我们并不确定父子进程哪个先执行完。
返回的本质,其实就是写入值到id,所以谁先返回谁就先写入id。
后写入的进程,因为进程独立性,为了不影响前面的一个进程就会发生写时拷贝
在这里插入图片描述

  • 我们看到fork失败会返回-1,那么什么情况会发生呢?
    1、当系统中有太多进程,通常意味着某方面出了问题(比如 死循环调用fork)。
    2、当该用户ID的进程数超过了系统的限制数。(CHILD_MAX)

  • fork的通常用法
    1、通过创建子进程,继承父进程的代码,运行和父进程运行的不同代码路径。
    2、创建子进程,运行其它的进程(比如后续进程程序替换中的exec系列的函数)。

2、进程退出

2.1 退出码

从main函数开始。
我们之前写的程序很多在最后都会有一个return 0;
这个0其实就是退出码,它标识着程序运行成功。

int main()
{
	return 0;
}


通过echo $? 可以查看记录最近一个进程在命令行执行完毕时对应的退出码。

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

  • 进程退出的情况?
    1、代码跑完,结果正确 — return 0;
    2、代码跑完了,结果不正确 — return !0;
    3、代码没跑完,程序异常了,退出码无意义。

如果我们关心退出码,可以通过不同的数字,表述不同的错误。
如果我们并不知道退出码对应的退出信息是什么,可以通过strerror(errno)。

如果熟悉个别退出码对应的信息,可以通过strerror(num) 打印退出信息。

  1 #include <stdio.h>
  2 #include <string.h>
  3 #include <errno.h>
  4 
  5 int main()
  6 {
  7     int i = 0;
  8     for(i = 0; i < 200; ++i)
  9     {
 10         printf("[%d]: %s\n", i, strerror(i));                                                                                                               
 11     }
 12     printf("return infor: %s\n", strerror(errno));
 13     return 0;
 14 }

由于结果太长,只截了开头一段和结尾一段。(通过运行看结果 可以看到退出码只有0-133)
在这里插入图片描述
在这里插入图片描述

2.2 exit函数和_exit系统调用

  • exit()
    exit函数终止进程,返回对应退出码。

    #include <stdlib.h>
    void exit(int status);
    

    exit虽然并没有返回值,但是会将status传给父进程接收退出码。(这个后面进程等待会解释,先了解)
    通过man 3 exit
    在这里插入图片描述
    在C语言阶段,我们会在一些地方使用exit(num),里面的num其实就是退出码,退出码可以根据需要自己定义。

    #include <stdio.h>
    #include <stdlib.h>
    void fun()
    {
    	exit(10);//从这里程序退出。
    }
    int main()
    {
    	fun();
    	printf("hello world");
    }
    

  • _exit
    _exit作为一个系统接口,在操作系统层。以及exit其实就是调_exit实现的。

    #include <unistd.h>
    void _exit(int status);
    
  • exit和_exit的区别

    先通过一个小程序看exit

    	  1 #include <stdio.h>
    	  2 #include <unistd.h>
    	  3 #include <stdlib.h>                                                                                                                                         
    	  4 
    	  5 int main()
    	  6 {
    	  7     printf("process");
    	  8     sleep(2);
    	  9     exit(1);
    	 10 }
    

    通过运行
    在这里插入图片描述

    再经过小小修改

      1 #include <stdio.h>
      2 #include <unistd.h>
      3 #include <stdlib.h>
      4 
      5 int main()
      6 {
      7     printf("process");
      8     sleep(2);
      9     _exit(1);                                                                                                                                               
     10 }
    
    

    在这里插入图片描述

    其实exit和_exit的区别就是exit刷新缓冲区,但是_exit不刷新缓冲区。
    sleep后,进程放入等待队列,输出在缓冲区,等进程重新回到运行队列_exit直接退出了,exit会刷新缓冲区,所以有这两个结果。

    根据这个结果我们也可以推出:缓存区其实是一个用户级的缓冲区。
    在这里插入图片描述

3、进程等待

一个子进程在退出后,操作系统回收它的数据与代码,但是进程一定是为了什么目的才存在的,一个进程完成后可以不将结果汇报给创造它的父进程,但是不能没有结果。

其实,一个进程在退出后,操作系统依旧会保留其PCB,等待父进程或系统对该进程进行回收。
子进程在这个PCB被保留的状态就是一个僵尸进程,父进程通过进程等待的方式对子进程回收并且获得子进程退出信息

3.1 wait和waitpid

  • wait()
#include <sys/wait.h>
#include <sys/type.h>
pid_t wait(int* status);

当status值设为NULL时,只回收子进程,代表不在意回收的子进程的退出码。
当status不为NULL时,回收子进程,并且获得子进程的退出信息,存放在status中。

假设status不为NULL,status不是简单的存一个值,下面解释它如何保存信息。
在这里插入图片描述

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <assert.h>
  5 #include <sys/wait.h>
  6 #include <sys/type.h>
  7 
  8 int main()
  9 {
 10     pid_t id = fork();
 11     assert(id >= 0);
 12     if(id == 0)
 13     {
 14         //子进程
 15         printf("我是子进程: %d, 父进程: %d, id: %d\n", getpid(), getppid(), id);
 16         exit(10); //随意设置
 17     }
 18 
 19     //父进程
 20     sleep(2); //让子进程先运行完
 21     int status = 0;
 22     pid_t ret = wait(&status);
 23     printf("return code : %d, sig : %d\n", (status >> 8), (status & 0x7F));                                                                                 
 24     if(id > 0)
 25     {
 26         printf("wait success: %d\n", ret);
 27 
 28     }
 29 }

在这里插入图片描述

  • waitpid()
#include <sys/wait.h>
#include <sys/type.h>
pid_t waitpid(pid_t pid, int* status, int options);

pid:进行等待的进程pid。
status:记录回收进程的退出信息。
options:一般选择是阻塞还是非阻塞两个状态。(下面会说啥是阻塞)
返回值返回回收的子进程pid,如果子进程还没退出返回0,如果waitpid调用失败返回-1。


稍稍修改一下代码

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <assert.h>
  5 #include <sys/wait.h>
  6 #include <sys/types.h>                                                                                                                                      
  7 
  8 int main()
  9 {
 10     pid_t id = fork();
 11     assert(id >= 0);
 12     if(id == 0)
 13     {
 14         //子进程
 15         printf("我是子进程: %d, 父进程: %d, id: %d\n", getpid(), getppid(), id);
 16         exit(10);
 17     }
 18 
 19     //父进程
 20     sleep(2); //让子进程先运行完
 21     int status = 0;
 22     pid_t ret = waitpid(id, &status, 0);// 0 代表阻塞式等待 WNOHANG代表非阻塞式等待
 23     printf("return code : %d, sig : %d\n", (status >> 8), (status & 0x7F));
 24     if(id > 0)
 25     {
 26         printf("wait success: %d\n", ret);
 27 
 28     }
 29 }
  • 子进程退出的退出信息存放在哪?
    在这里插入图片描述

  • 补充:宏函数
    WIFEXITED(status)。W-wait,wait是否退出,若正常退出子进程,返回真。
    WEXITSTATUS(status)。查看进程退出码,若WIFEXITED非零,提取子进程退出码。

    //是否正常退出
    if(WIFEXITED(status))
    {
    	// 判断子进程运行结果是否ok
    	printf("exit code: %d\n", WEXITSTATUS(status);
    }
    

3.2 阻塞和非阻塞

前面wait相关的测试都是在子进程已经退出的前提下进行的。

阻塞和非阻塞很简单,将waitpid设置为阻塞后如果子进程没有退出,那么父进程就会一直等待,直到子进程退出。

父进程查看子进程状态,子进程没有退出,父进程立即返回去执行其它任务,这一次的过程叫做非阻塞。(而父进程多次来回确认子进程有没有退出的过程称为轮询)

一个测试非阻塞的程序

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/wait.h>
  4 #include <sys/types.h>
  5 #include <stdlib.h>
  6 #include <string.h>
  7 #define NUM 10
  8 
  9 typedef void (*func_t)();
 10 func_t handlerTask[NUM];
 11 
 12 void task1()
 13 {
 14     printf("do task1!\n");
 15 }
 16 
 17 void task2()
 18 {
 19     printf("do task2!\n");
 20 }
 21 
 22 void loadTask()
 23 {
 24     memset(handlerTask, 0, sizeof(handlerTask));
 25     handlerTask[0] = task1;
 26     handlerTask[1] = task2;
 27 }
 28 
 29 int main()
 30 {
 31     pid_t id = fork();
 32     if(id == 0)
 33     {
 34          while(1)
 35          {
 36              //child
 37              int cnt = 3;
 38              while(cnt)
 39              {
 40                  printf("child running, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt--);
 41                  sleep(1);
 42              }
 43 
 44              exit(10);
 45         }
 46     }
 47     //parent
 48     loadTask();                                                                                                                                                                                                                                                           
 49     int status = 0;
 50     while(1)
 51     {
 52         pid_t ret = waitpid(id, &status, WNOHANG); //WNOHANG: 非阻塞 -> 子进程没有退出, 父进程检测时候, 立即返回.
 53         if(ret == 0)
 54         {
 55             //waitpid调用成功 && 子进程没退出
 56             //子进程没有退出,我的waitpid没有等待失败,仅仅是检测到了子进程没退出
 57             printf("wait done, but child is runing , parent do :\n");
 58             int i = 0;
 59             for(i = 0; handlerTask[i]!=NULL; ++i)
 60             {
 61                 handlerTask[i]();
 62             }
 63         }
 64         else if(ret > 0)
 65         {
 66             //waitpid调用成功 && 子进程退出了
 67             printf("wait success, exit code: %d, sig: %d\n", (status >> 8), (status & 0x7F));
 68             break;
 69         }
 70         else
 71         {
 72             //waitpid调用失败
 73             printf("waitpid call failed\n");
 74             break;
 75         }
 76         sleep(1);
 77     }
 78 
 79     return 0;
 80 }

在这里插入图片描述
非阻塞不会占用父进程所有精力,可以在轮询期间干点别的!

本章完~

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

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

相关文章

刚接触测试如何编写测试用例?看看摸鱼5年的老点工是怎么写的

给你个需求&#xff0c;你要怎么转变成最终的用例&#xff1f; 直接把需求文档翻译一下就完事了。 老点工拿到需求后的标准操作&#xff1a; 第一步&#xff1a;解析需求 先解析需求-找出所有需求中的动词&#xff0c;再列出所有测试点。测试点过程不断发散&#xff0c;对于…

第十四章: ReentrantLock、ReentrantReadWriteLock、StampedLock

相关面试题锁的演变ReentrantReadWriteLock锁降级案例演示一案例演示二总结为什么要有锁降级邮戳锁 StampedLock邮戳锁的特点案例演示一案例演示二StampedLock 缺点相关面试题 你说你用过读写锁&#xff0c;锁饥饿问题是什么&#xff1f;有没有比读写锁更快的锁&#xff1f;St…

域名解析函数 gethostbyname

在实现TCP/UDP编程的时候&#xff0c;服务端绑定的IP地址是点分十进制的&#xff0c;如124.222.215.205&#xff0c;如果提供的是域名&#xff0c;此时我们需要将域名转换成点分十进制的IP地址&#xff0c;这个过程就是 “ 域名解析 ”&#xff0c;实现域名转换的函数就是 geth…

Python Aws Ubuntu20 配置多IP

1. 业务场景&#xff1a; 高频次交易需要多次访问接口&#xff0c;配置单服务器多IP绕过币安单个IP频率限制 2. AWS实例选择&#xff1a; t3系列&#xff0c;东京A区&#xff0c;ubuntu20 这是我测试的币安延迟最低的配置&#xff08;平均6ms&#xff09; 换一个区域、一个系…

【王道计算机网络笔记】数据链路层-数据链路层设备

文章目录物理层扩展以太网链路层扩展以太网网桥透明网桥源路由网桥以太网交换机直通式交换机存储转发式交换机冲突域和广播域物理层扩展以太网 主机和集线器不能超过100m&#xff0c;超过100m会失真严重&#xff0c;无法恢复&#xff0c;扩展以太网的方法&#xff1a; 通过光纤…

欢迎报名Rust China Hackathon 2022 达坦科技组

12月4日下午&#xff0c;DatenLord就2022Rust China Hackathon大赛活动企业组&#xff08;达坦科技组&#xff09;的赛题进行了空中宣讲会。不仅对赛事流程进行了全面的讲解&#xff0c;同时对赛题背景以及完赛标准和要点进行了深入的剖析。会后更是设置问答环节&#xff0c;细…

目标检测算法——图像分割数据集汇总(附下载链接)

>>>深度学习Tricks&#xff0c;第一时间送达<<< &#x1f384;&#x1f384;近期&#xff0c;小海带在空闲之余&#xff0c;收集整理了一批图像分割数据集供大家参考。 整理不易&#xff0c;小伙伴们记得一键三连喔&#xff01;&#xff01;&#xff01;&am…

给visCode编辑器添加背景图

看见很多大牛的visCode编辑器上有个背景图&#xff0c;感觉挺有逼格的&#xff0c;要想跟大牛一样有逼格&#xff0c;那先把编辑器设置的跟大牛的逼格一样高 话不多说&#xff0c;开始 第一步&#xff1a;先安装背景扩展 第二步&#xff1a;点击选择扩展设置 第三步&#xff1…

原码、反码、补码的互相转换

原码、反码、补码的互相转换 简介&#xff1a;本文是为了计算机组成原理复习&#xff0c;本文以具体题目的方式来对原码、反码、补码的互相转换进行讲解。 概述 原码 &#xff1a;最高位是符号位&#xff0c;0代表正数&#xff0c;1代表负数&#xff0c;非符号位为该数字绝对…

实战web漏洞挖掘小技巧

几个月前看到国外的bug bounty tips在传Host攻击技巧&#xff0c;前阵子又看到国内有不少文章总结&#xff0c;冷渗透就不再复述了&#xff0c;直接介绍实际业务场景中碰到的案例。 0x01 自定义Host—窃取Token 1. 找到重置密码处&#xff0c;填写任意一个受害者邮箱帐号&#…

记录--微信小程序获取用户信息(附代码、流程图)

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 本篇文章主要总结了微信小程序开发&#xff0c;获取用户信息的整个流程步骤。补充了网上很多碎片化的代码&#xff0c;本人梳理了思路写下了这篇文章。 思路 1、在js文件中&#xff0c;设置userinfo、…

华丰科技将于12月13日上会:业绩波动明显,海通证券等为股东

近日&#xff0c;四川华丰科技股份有限公司&#xff08;下称“华丰科技”&#xff09;在上海证券交易所科创板递交招股书&#xff08;上会稿&#xff09;。据贝多财经了解&#xff0c;华丰科技将于2022年12月13日上会&#xff0c;接受科创板上市委的现场审议。 根据公开信息&am…

【嵌入式硬件芯片开发笔记】LP87702/LP87524电源芯片配置流程

【嵌入式硬件芯片开发笔记】LP87702/LP87524电源芯片配置流程 LP8752x-Q1 10-A Buck Converter With Integrated Switche 具有诊断功能的 LP87702-Q1 双路降压转换器和 5V 升压转换器 LP87702/LP87524从机地址都是0x60 LP87702/LP87524中的设备ID等寄存器&#xff0c;是根据OT…

维护4年的组件化框架 Component 升级到 KComponent 啦

前言 KComponent 它来啦. 从 2018.8 开始, Component 项目正式开源. 到目前已经 4 年的时间了. 这几年中, Component 不断升级优化. 最后趋于稳定. 为了组件化框架更好的发展, KComponent 继承了 Component 的优点, 升级为一个纯 Kotlin 的项目. 不再支持 Java, 注解驱动器也…

Java招生报名咨询系统毕业设计,Java招生咨询问答系统设计与实现,毕业设计论文怎么写毕设源码开题报告需求分析怎么做

功能清单 【系统管理员功能】 关于我们设置&#xff1a;设置学校简介、联系我们、加入我们、法律声明、学校详情 广告管理&#xff1a;设置小程序首页轮播图广告和链接 留言列表&#xff1a;所有用户留言信息列表&#xff0c;支持删除 会员列表&#xff1a;查看所有注册会员信…

20.前端笔记CSS-精灵图

1、为啥要精灵图 一个网页中往往会应用很多小的背景图像作为修饰&#xff0c;当网页中的图像过多时&#xff0c;服务器就会频繁的接收和发生请求图片&#xff0c;造成服务器的请求压力过大&#xff0c;这会大大降低页面的加载速度 为了有效减少服务器接收和发生请求的次数&…

[附源码]JAVA毕业设计图书借阅系统演示录像(系统+LW)

[附源码]JAVA毕业设计图书借阅系统演示录像&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目…

你不知道的goland技巧

很多人使用 Goland 有很长时间的&#xff0c;却没有好好利用上 Goland 工具带给我们的遍历&#xff0c;今天咱们就来解锁一下新技巧 过去我们使用 Goland 就是简单的配置一下 go 的 proxy &#xff0c;以及配置一下 ssh &#xff0c;与服务器进行文件的上传和下载&#xff0c;其…

基于Hadoop的用户购买行为的商品推荐系统

目录 一 绪论 1 1.1 编写目的 1 1.2 背景及意义 1 1.3 开发及运行环境 2 二 需求分析 3 2.1 系统概述 3 2.3 系统功能需求 4 2.3.1 收集原始数据 4 2.3.2 计算物品相似度矩阵 4 2.3.3 计算用户购买向量 5 2.3.4 计算推荐向量并去重和排序 5 2.3.4 数据入库 5 2.3.5 作业控制 5 …

百数:用报表搭建生产进度看板,让生产更高效

现如今我国已经步入了全新的现代化科技时代&#xff0c;国内各个行业对于数字技术也在进行着不断地应用和研究。目前数字技术在企业生产管理系统的运用中具备较为显著的优势&#xff0c;其中生产进度看板就是数字化管理系统的重要应用之一。 生产进度看板是一种可视化管理系统…