当前位置: 首页 > java >正文

Linux环境下的进程创建、退出和进程等待

目录

一、进程创建

1.fork函数

1)进程调用fork函数是如何创建子进程的

2)代码示范

2.写时拷贝

二、进程退出

1.退出码

1)什么是退出码?

2)为什么要有退出码?

3)退出码是怎么做到的?

4)echo $?:查看最近一个进程的退出码

2.进程的三种退出状态

1)又可分为能运行完和没运行完

2)exit( )和_exit( )的区别

三、进程等待

1.进程等待是什么

2.为什么要进程等待

3.父进程是怎么等待的

1)wait和waitpid

2)写入型参数status的使用方式

①终止信号

②退出状态

③status相关宏定义

3)使用示范

4)阻塞等待与非阻塞等待的区别

总结



一、进程创建

1.fork函数

在linux种fork函数的作用是:从存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

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

这里的pid_t 可以看成是对int的封装

1)进程调用fork函数是如何创建子进程的

①通过分配新的内存块和内核数据结构给子进程

②将父进程部分数据结构内容拷贝至子进程

③添加子进程到系统进程列表当中 ,fork返回。

理解fork函数需要对进程的地址空间有着一定的理解,若对进程地址空间有疑惑的读者可以翻看笔者之前的“短”文看最后的“进程地址空间详解”就可以了,这里给出链接:进程优先级介绍,详解环境变量,详解进程地址空间-CSDN博客

2)代码示范

这里利用fork对父子进程返回的id值的不同,使得父子进程运行不同得条件语句。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>int main()
{pid_t id=fork();if(id<0){printf("创建子进程失败\n");exit(1);}else if(id>0){//父进程while(1){printf("我是父进程,我的id是:%d,我的父进程id是:%d\n",getpid(),getppid());sleep(1);}}else{//子进程while(2){printf("我是子进程,我的id是:%d,我的父进程id是:%d\n",getpid(),getppid());sleep(2);}}return 0;
}

运行结果试图:(可以通过CTRL+C终止进程)

2.写时拷贝

在通过fork函数创建子进程时,父进程将部分数据结构内容拷贝至子进程,此时在内存中,操作系统为节省内存,父子进程的页表指向的是其实是同一块物理地址,换句话说,此时父子代码共享,数据也是共享的

写时拷贝的概念是:若当父子进程中有一方要修改某个变量时,操作系统会临时将该变量拷贝一份放在另一块内存中,并修改该进程的页表指向临时拷贝出来的变量,以确保进程的独立性

二、进程退出

1.退出码

1)什么是退出码?

退出码就是main函数的而返回值,其中包括return返回或者exit返回

2)为什么要有退出码?

父进程会通过检查子进程的退出码从而确定子进程执行情况,然后再将子进程的资源释放——删除残留的task_struct(PCB),以结束子进程的僵尸状态

3)退出码是怎么做到的?

Linux环境下的退出码共有134种,可分为两种情况:

退出码方便计算机识别程序运行中出现的不同错误,但对人而言就不是很友好。

我们可以通过strerror()函数将退出码翻译为解释语言:

#include<stdio.h>
#include<string.h>int main()
{for(int i=0;i<150;++i)printf("%d:%s\n",i,strerror(i));return 0;
}

读者若感兴趣可自我尝试,这里截取一些常见的退出码:

如上,共134种错误。

4)echo $?:查看最近一个进程的退出码

正如上述,在Linux种有一个变量“?”记录着最近一个进程的退出码。

#include<stdio.h>
#include<stdlib.h>int main()
{exit(10);return 0;
}

值得注意的是如果连续两次使用echo $?指令,第二次打印出的总是0,有误echo也是个程序也有返回值。

2.进程的三种退出状态

1)又可分为能运行完和没运行完

2)exit( )和_exit( )的区别

除了通过return返回外,程序还可通过exit和_exit两个函数返回。

exit是“stdlib.h”中的函数,是基于_exit做的封装

_exit是系统调用,在“unistd.h”中。

他们的作用都是“终止调用进程”,但执行起来有些微区别:

exit在结束进程时会刷新缓冲区,且会执行自定义的清理函数而_exit不会。

#include<stdio.h>
#include<stdlib.h>
#inlcude<unistd.h>int main()
{printf("hello Linux");//注意没有换行符exit(0);//_exit(0);return 0;
}

exit执行情况:

_exit执行情况:

三、进程等待

1.进程等待是什么

每个子进程在运行结束后都会有一段僵尸进程状态,而进程等待是父进程通过系统调用等待子进程的一种方式。

僵尸进程是指 已终止但未被父进程回收(reap)的进程

2.为什么要进程等待

①释放子进程的僵尸状态;②获取子进程的运行状态

3.父进程是怎么等待的

1)wait和waitpid

waitpid参数解释:

pid——要等待(回收)的子进程;

*status写入型参数——将读取到的子进程状态写入status中;

options为等待方式,传0则是阻塞式等待,传入WNOHANG则是非阻塞等待(若有疑问的读者可直接跳转至“阻塞等待与非阻塞等待的区别”查看)

2)写入型参数status的使用方式

写入型参数,顾名思义就是系统会向这个形参中写入数据,通过写入的数据便可以判别进程的运行状态。

一般status创建为整型初始化为0,而系统写入的数据一般在status变量的低十六位

解释:4字节的整形status由32位二进制组成,而系统写入的数据则在低16位,也就是2^0-2^15之间,而就这16位二进制包含了两份数据——从0位到7位的二进制表示终止信号从8位到15的二进制表示退出状态

①终止信号

还记得上面讲述的进程的三种退出状态吗

二者三种状态又可分为两种情况:

①情况一:程序无异常,代码能跑完,无退出码

②情况二:程序出现异常,中途退出,无退出码

终止信号数据用于记录第二种情况——进程没跑完,中途出现异常的情况

若进程代码顺利跑完,则终止信号默认为0(status初始化值就是0)

获取终止信号的方式:

(status & 0X7F),0X7F是十六进制表示111 1111(其他全为0),&是位运算,(上下)同为1结果才是1

Linux环境下的终止信号有个64个,可以通过kill -l指令查看:

②退出状态

status二进制的次八位,也就是退出码

获取退出状态(退出码)的方式:

(status>>8)& 0XFF,>>是位运算符,作用是将status的二进制右移动八位,前边补0.

Linux环境下的退出码有134种。

③status相关宏定义

若是觉得status中的两个数据使用过于麻烦,系统为我们提供了两个宏以便使用:

一个是WIFEXITED(status),他的作用是检查进程是否运行完毕,有无异常。若进程正常退出则WIFEXITED(status)值为非零,否则为0.

另一个是WEXITSTATUS(status),他的值是进程的退出码。

3)使用示范

值得注意的是waitpid的返回值就是等待进程的id

#include<stdio.h>
#include<string.h>
#include<stdlib.h>//exit
#include<sys/types.h>//waitpid
#include<sys/wait.h>//waitpid
#include<unistd.h>//forkint main()
{pid_t id=fork();if(id==0){//子进程int cnt=5;while(cnt--){printf("我的id:%d,父进程id:%d\n",getpid(),getppid());sleep(1);}exit(10);}//父进程int _status=0;int s_id=waitpid(id,&_status,0);printf("我的id:%d,子进程id:%d,sig number:%d,child exit code:%d",getpid(),s_id,(_status & 0X7F),(_status>>8&0XFF));sleep(1);return 0;
}

4)阻塞等待与非阻塞等待的区别

阻塞等待与非阻塞等待各有优劣,使用哪种等待方式全看当时情况:

比如,父进程只需等待单个子进程,且无其他任务需并行处理时就适合用阻塞等待,原因是可以将cpu资源让出来供其他进程使用。


总结

本文从进程的创建开始,依次叙述了Linux环境下的进程的创建、进程的退出和进程的等待相关概念。笔者水平浅薄,难免有疏忽大意的地方,若有错误还请读者多多指出。

若是读者觉得有所收获,能否点一个免费的赞呢。
 

http://www.xdnf.cn/news/3879.html

相关文章:

  • 谷歌 NotebookLM 支持生成中文播客
  • n8n 条件节点详解:IF 与 Switch 的多分支工作流设计
  • 虚函数VS虚拟继承:C++多重继承二义性破解与性能调优
  • 论快乐的学习和学习的快乐
  • 万字详解ADC药物Payload
  • Debezium 架构详解与实战示例
  • 【操作系统】深入理解内存管理:从虚拟内存到OOM Killer
  • cloudfare+gmail 配置 smtp 邮箱
  • 【CISCO】Se2/0, Se3/0:串行口(Serial) 这里串口的2/0 和 3/0分别都是什么?
  • React hooks详解
  • 快速外网访问,证书自动续约 | 极空间IPv4IPv6 DDNS 配置详解
  • 数据结构与算法:回溯
  • Python:Seaborn 美化图表的技术指南
  • 【五一培训】Day 4
  • 常用命令集合
  • PCB叠层设计方案
  • 探秘DeepSeek模型参数:解锁AI潜能的密码
  • GenCLS++:通过联合优化SFT和RL,提升生成式大模型的分类效果
  • Python之学习笔记(六)
  • Prompt compress 技术探究-LLMLingua
  • SpringAi接入DeepSeek大模型
  • SurfSense开源程序是NotebookLM / Perplexity / Glean的开源替代品,连接到外部来源,如搜索引擎
  • ArrayList的扩容机制(源码解析)
  • 深度学习的简单介绍
  • PISI:眼图1:眼图相关基本概念
  • 使用synchronized关键字同步Java线程
  • AndroidLogger常用命令和搜索功能介绍
  • STM32Cube-FreeRTOS任务调度与任务管理-笔记
  • ruoyi-flowable框架关于启动时提示锁住问题
  • LLM论文笔记 27: Looped Transformers for Length Generalization