【linux】进程控制——进程创建,进程退出,进程等待

个人主页:东洛的克莱斯韦克-CSDN博客

祝福语:愿你拥抱自由的风

相关文章

【Linux】进程地址空间-CSDN博客

【linux】详解linux基本指令-CSDN博客

目录

进程控制概述

创建子进程

fork函数

父子进程执行流

原理刨析

常见用法

出错原因

进程退出

概述

场景分析

返回退出状态的函数

exit和_exit

进程等待

概述

进程等待方法

wait

waitpid

详谈status


进程控制概述

一个进程创建了另一个进程,创建者为父进程,被创建者为子进程。

父进程可以创建多个子进程。

操作系统是一号进程,所有进程构成一颗多叉树结构。

每个父进程只对直系的子进程负责。

父进程创建子进程的一整套流程为:创建子进程——>子进程完成任务——>父进程回收子进程

创建子进程

fork函数

功能:   创建一个子进程

头文件:   #include <unistd.h>
函数原型:   pid_t fork(void);
返回值:   子进程返回 0 ,父进程返回子进程 id ,出错返回-1
fork之后的父进程和子进程谁先被执行是由调度器决定
代码示例
int main( void )
{
 pid_t pid;

 printf("Before: pid is %d\n", getpid());

 if ( (pid=fork()) == -1 )perror("fork()"),exit(1);

 printf("After:pid is %d, fork return %d\n", getpid(), pid);

 sleep(1);

 return 0;
}

父子进程执行流

父进程和子进程共享代码,可以用判断语句让父进程和子进程执行不同代码块实现分流。

int main()
{
pid_t t = fork();

if (-1 == t)
{
//创建进程失败
}
else if (0 == t)
{
//子进程代码块
}
else
{
//父进程代码块
}
return 0;
}

上述代码中如果在父子进程分别写一个死循环就会有个神奇的现象——在一个main函数中能同时运行两个死循环。

系统层面理解

这在语言层面是无法理解的。在系统层面,是由两个进程运行了该代码。

fork()也是一个函数,在fork函数内部实现在创建一个进程的方法,在执行fork的main函数的return的时候就已经有了两个进程,子进程的fork返回 0 ,父进程的fork 返回子进程的pid(进程唯一标识符)。

出了fork函数,父进程和子进程分别执行自己的代码块,也就有了上述一个在main函数中运行了两个死循环的现象。

在fork函数中,linux内核做了如下工作

Linux内核为子进程创建内核资源,包括task_struct(管理进程),mm_struct(管理进程地址空间),页表等,并为该子进程分配唯一标识符PID。子进程的内核资源继承于父进程。

Linux内核复制父进程的上下文,包括堆栈指针、程序计数器(PC)以及通用寄存器的状态。

Linux内核为子进程分配独立的进程地址空间

处理返回值,子进程返回 0 ,父进程返回 子进程的PID。

原理刨析

原理剖析设计进程地址空间,可参考【Linux】进程地址空间-CSDN博客

子进程的页表也是继承于父进程,这意味着子进程的地址空间在物理内存的映射是重叠的。当子进程要对父进程的数据进行写入时,会发生写时拷贝。

通过写时拷贝实现代码共享,数据隔离。

上述策略可以有效节省物理内存

常见用法

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

出错原因

系统中进程太多

用户进程的数量超过了限制

进程退出

概述

父进程先退,子进程未退。此时子进程的父亲进程被改为1号进程(操作系统),那么该子进程被称为孤儿进程

父进程未退,子进程先退。但父进程没有读取到子进程的退出(进程等待),该子进程的状态被改为 Z 状态,Z 状态的进程被成为僵尸进程。

操作系统杀不掉僵尸进程,所以僵尸进程会造成内存泄漏

如下是维持30秒僵尸进程的代码示例

#include <stdio.h>
#include <stdlib.h>
int main()
{
 pid_t id = fork();

 if(id < 0){

 perror("fork");
 return 1;

}
 else if(id > 0){ //parent

 printf("parent[%d] is sleeping...\n", getpid());
 sleep(30);

 }else{

 printf("child[%d] is begin Z...\n", getpid());
 sleep(5);
 exit(EXIT_SUCCESS);

 }

 return 0;
}

场景分析

进程异常退出

异常退出的本质是因为进程出发了硬件级别的错误,操作系统给进程发送信号终止进程。

被信号杀掉的进程由于自身任务没有完成就挂掉了,所以是异常退出。

每一种信号都有自己的编号,可用下述命令查看

kill -l

用户可以输入如下指令,向进程发送信号来终止进程

kill     -9     进程PID

Ctrl    c 

进程正常退出,结果不正确

进程退出码:进程正常结束后给外部返回一个值,来代表自己任务完成情况。

如下指令可以查看最近一次进程的退出码

echo $?

? 中储存的是最近一次进程的退出码的值

退出码的值可以自己定,退出码的含义可以根据代码的需求给定

可用如下程序显示linux的退出码的含义

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

进程正常退出,结果正确

一般情况下无需关心

返回退出状态的函数

return 是一种常见的退出进程方法。执行 return n 等同于执行 exit(n), 因为调用 main 的运行时函数会将 main 的返回值当做 exit 的参数
但如果该函数不是main函数,就需要exit函数结束进程,并返回退出状态
#include <unistd.h>
void _exit(int status);
参数: status 定义了进程的终止状态,父进程通过 wait 来获取该值
#include <unistd.h>
void exit(int status);

参数: status 定义了进程的终止状态,父进程通过 wait 来获取该值

exit和_exit

exit  最后也会调用 _ exit , 但在调用 _ exit  之前,还做了其他工作:
1. 执行用户通过 atexit on_exit 定义的清理函数。
2. 关闭所有打开的流,所有的缓存数据均被写入
3. 调用 _exit
如下示意图

代码示例

int main()
{
 printf("hello");
 exit(0);
}

运行结果

[root@localhost linux]# ./a.out
hello[root@localhost linux]#
int main()
{
 printf("hello");
 _exit(0);
}

运行结果

[root@localhost linux]# ./a.out
[root@localhost linux]#

进程等待

概述

父进程只对直系的子进程负责,父进程创建子进程的目的是为了让子进程为自己完成一些事情。所以在子进程变成 X 状态(死亡状态)之前,有一个 Z 状态即僵尸状态。

僵尸状态是为了能让父进程读取子进程的退出信息。

退出信息:进程是否触发硬件级别的错误而被信号所杀(异常结束),进程如果没有异常结束退出码是否符合预期(结果运行的结果是否正确)

进程等待有如下场景

1.父进程等待子进程,但子进程并未退出,此时父进程可以阻塞等待,非阻塞等待,非阻塞轮询等待。

2.子进程变成僵尸进程,父进程等待子进程并读取子进程退出信息,子进程被设为 X 状态。

子进程的退出信息只能被父进程拿到——父进程只对直系子进程负责。

进程等待和 Z 状态本质是为了应对父子进程退出的各种场景,让父进程拿到子进程的退出信息

进程等待方法

wait

头文件:   #include<sys/types.h>
头文件:   #include<sys/wait.h>
函数原型:   pid_t wait(int*status)
功能:等待子进程
返回值:  成功返回被等待进程pid ,失败返回 -1
参数:  输出型参数,获取子进程退出状态, 不关心则可以设置成为 NULL
wait函数的特性是:如果子进程为僵尸状态,直接获取子进程的退出信息,如果子进程并未退出,父进程的状态被改为 S 状态(睡眠状态,阻塞状态)——父进程一直等待子进程
参数:我们可以定义一个int类型的变量,wait用指针接收,wait去子进程中拿到退出信息就可以通过 status传递给父进程
代码示例
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
       #include <unistd.h>
#include<stdlib.h>
#include <sys/wait.h>


int main()
{

  pid_t i = fork();

if (-1  == i)//创建子进程失败
{
printf("创建子进程失败\n");
return 2;
}
else if (0 == i)//子进程
{

int cat = 2;

while (cat)
{
//eep(1);
printf("我是一个子进程 %d  我的父亲是 %d\n", getpid(), getppid());  
cat--;
}

exit(21);

//return 12;      
}
else//父进程
{
int  status = 0;

  pid_t t = wait(&status);
sleep(1);
printf("我是一个父进程%d,子进程退出状态为%d,子进程的退出码是%d\n", getpid(), WIFEXITED(status),WEXITSTATUS(status));

//printf("我是一个父进程%d,子进程退出状态为%p\n", getpid(),status);
  return 99;

}

}

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:
  输出型参数,获取子进程退出状态, 不关心则可以设置成为 NULL
options:
WNOHANG: pid 指定的子进程没有结束,则 waitpid() 函数返回 0 ,不予以等待。若正常结束,则返回该子进程的ID
如果为0:进入阻塞等待,与wait等价
如下是不同场景的代码示例
父进程只等待一次,子进程变成孤儿进程
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
       #include <unistd.h>
#include<stdlib.h>
#include <sys/wait.h>


int main()
{

  pid_t i = fork();

if (-1  == i)//创建子进程失败
{
printf("创建子进程失败\n");
return 2;
}
else if (0 == i)//子进程
{

int cat = 2;

while (1)
{
sleep(1);
printf("我是一个子进程 %d  我的父亲是 %d\n", getpid(), getppid());  
cat--;
}

exit(21);

//return 12;      
}
else//父进程
{
int  status = 0;

  pid_t t = waitpid(-1, &status, WNOHANG);
sleep(1);
printf("我是一个父进程%d,子进程退出状态为%d,子进程的退出码是%d\n", getpid(), WIFEXITED(status),WEXITSTATUS(status));

//printf("我是一个父进程%d,子进程退出状态为%p\n", getpid(),status);
  return 99;

}

}

非阻塞轮询等待

父进程死循环等待,options的参数是WNOHANG,这种等待称为非阻塞轮询等待,等待期间父进程可以做自己的事情

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


int main()
{

  pid_t i = fork();

if (-1  == i)//创建子进程失败
{
printf("创建子进程失败\n");
return 2;
}
else if (0 == i)//子进程
{

int cat = 2;

while (1)
{
sleep(1);
printf("我是一个子进程 %d  我的父亲是 %d\n", getpid(), getppid());  
cat--;
}

exit(21);

//return 12;      
}
else//父进程
{
int  status = 0;
while(1)
{

  pid_t t = waitpid(-1, &status, WNOHANG);
sleep(1);
//printf("我是一个父进程%d,子进程退出状态为%d,子进程的退出码是%d\n", getpid(), WIFEXITED(status),WEXITSTATUS(status));
if (-1 == t)//等待失败
{
return -1;
}
else if (0 == t)
{
sleep(1);
  printf("子进程还没完事\n");
}
else{//等待成功
  break;
}
//printf("我是一个父进程%d,子进程退出状态为%p\n", getpid(),status);
}
return 99;

}

}

为什么要用上述两个函数来完成父进程对子进程退出信息的获取呢

wait和waitpid是系统提供的接口,获取子进程的退出信息是由操作系统做的。为什么父进程不能直接获取,而要让操作系统介入呢?
子进程退出后,其相关数据被释放掉,退出信息被保存在内核数据结构中,一个进程在上层代表的是用户,如果一个进程可以直接拿到子进程的退出信息,那么说明该进程可以直接访问内核数据结构,换句话说用户可以通过代码访问内核数据结构,那么操作系统的安全性怎么得到保证呢?
父进程和子进程都代码共享了,不能在代码层面拿到退出信息吗
进程是系统层面的概念,如果不用系统提供的接口,子进程的退出信息是不会反馈给父进程的代码上的。

详谈status

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

比特位 0~6 对应了接受到的信号,注意这里还通过 127 0x7f 定义了 STOP 状态

比特位 7 用来标示是否有生成 core 文件

比特位 8~15 通过 exit() 接口退出进程,也就是意味着错误码最大为 255 ,如果是 256 那么实际上是 0 ;

头文件中提供了宏,无需进行位运算

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

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

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

相关文章

7-43 排列问题

排列问题 分数 10 全屏浏览 切换布局 作者 雷丽兰 单位 宜春学院 全排列问题 输出自然数1至n中n个数字的全排列&#xff08;1≤n≤9&#xff09;&#xff0c;要求所产生的任一数字序列中不允许出现重复的数字。 输入格式: 一个自然数 输出格式: 由1到n中n个数字组成的…

Python魔法之旅专栏(导航)

目录 推荐阅读 1、Python筑基之旅 2、Python函数之旅 3、Python算法之旅 4、博客个人主页 首先&#xff0c;感谢老铁们一直以来对我的支持与厚爱&#xff0c;让我能坚持把Python魔法方法专栏更新完毕&#xff01; 其次&#xff0c;为了方便大家查阅&#xff0c;我将此专栏…

freertos中的链表1 - 链表的数据结构

1.概述 freertos中链表的实现在 list.c 和 list.h。旨在通过学习freertos中的链表的数据结构&#xff0c;对freertos中的链表实现有一个整体的认识。freertos使用了三个数据结构来描述链表&#xff0c;分别是&#xff1a;List_t&#xff0c; MiniListItem_t&#xff0c;ListIt…

【Linux】进程6——环境变量

1.什么是环境变量 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数 比如&#xff1a;我们在编写C/C代码的时候&#xff0c;在链接的时候&#xff0c;从来不知道我们的所链接的动态静态库在哪里&#xff0c;但是照样可以链接成功&…

Windows 10 找不到Microsoft Edge 浏览器

下载链接 了解 Microsoft Edge 手动下载浏览器 问题说明 一般来说&#xff0c;windows10系统应该是自带浏览器edge的&#xff0c;但有的电脑就是没有找到edge浏览器&#xff0c;可能系统是精简过的&#xff0c;可能是被卸载了。如下&#xff0c;控制面板确实没找到程序。 ​ …

笔记本充电出现了问题。

不知道为什么。电池充电图片一直显示的空。谁能救救我&#xff01;

C++命名空间的定义、C++命名空间的使用、C++输入输出等的介绍

文章目录 前言一、C命名空间的定义1. C命名空间产生的原因2. 作用域限定符3. C变量的访问顺序 二、C命名空间的使用1. 加命名空间名称及作用域限定符2. 使用using将命名空间中某个成员引入3. 使用using namespace 命名空间名称 引入4. 嵌套命名空间使用 三、 C输入&输出总结…

天才程序员周弈帆 | Stable Diffusion 解读(二):论文精读

本文来源公众号“天才程序员周弈帆”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;Stable Diffusion 解读&#xff08;二&#xff09;&#xff1a;论文精读 【小小题外话】端午安康&#xff01; 在上一篇文章天才程序员周弈帆 …

C#操作MySQL从入门到精通(14)——汇总数据

前言 我们有时候需要对数据库查询的值进行一些处理,比如求平均值等操作,本文就是详细讲解这些用法,本文测试使用的数据库数据如下: 1、求平均值 求所有student_age 列的平均值 string sql = string.Empty; if (radioButton_AVG.Checked) {sql = “select AVG( student_…

《Vue》系列文章目录

Vue (发音为 /vjuː/&#xff0c;类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助你高效地开发用户界面。无论是简单还是复杂的界面&#xff0c;Vue 都可以…

真北游记|三江交汇,碧海苍梧,端午去梧州吃龟苓膏

准备 t-14&#xff1a;高铁抢票&#xff08;A&#xff09; t-14&#xff1a;订行程(B)酒店&#xff08;C&#xff09; T-2&#xff1a;准备水、零食 T-1&#xff1a;物质准备&#xff1a;衣服、纸巾、毛巾、雨伞&#x1f302;、拖鞋、口罩&#x1f637;&#xff08;D&#xff0…

phpstudy的安装dvwa

phpstudy安装dvwa 1. 下载phpstudy Windows版phpstudy下载 - 小皮面板(phpstudy) (xp.cn) 2. 搭建dvwa靶场 下载地址&#xff1a;https://github.com/ethicalhack3r/DVWA/archive/master.zip 将其放入www文件夹中 3. 修改配置文件 将\DVWA-master\config中config.inc.php…

【深度揭秘】ChatGPT如何成为智能对话的领头羊?万字详解其背后的核心技术!

最近&#xff0c;OpenAI的预训练模型ChatGPT给人工智能领域的研究人员留下了深刻的印象和启发。毫无疑问&#xff0c;它非常强大&#xff0c;与之交流十分有趣&#xff0c;还能编写代码。它在多个方面的表现远超自然语言处理研究者的预期。因此&#xff0c;我们不禁要问&#x…

Nginx之正向代理配置示例和说明

一、NGINX正向代理功能简介 Nginx的正向代理功能允许局域网中的客户端通过代理服务器访问Internet资源。具体来说&#xff0c;Nginx作为一种流行的Web服务器和反向代理服务器&#xff0c;在正向代理方面的应用也相当实用。以下是其正向代理功能的几个关键点&#xff1a; 访问外…

[AI Google] 使用 Gemini 取得更多成就:试用 1.5 Pro 和更多智能功能

总结 Google 正在为超过 35 种语言的 Gemini Advanced 订阅者推出 Gemini 1.5 Pro。此次更新包括 100 万个 token 的上下文窗口、改进的数据分析功能和增强的多模态图像理解。新功能包括用于自然对话的 Gemini Live、先进的规划工具和可定制的 Gems。更新还集成了更多 Google …

【Python项目】——爬虫爬取柯南的剧集数和集名(有错求指点)

项目目的&#xff1a;因为柯宝除了在CSDN写博客这一爱好外&#xff0c;还比较喜欢追番剪辑&#xff0c;尤其是柯南&#xff0c;想到了爬取这个&#xff01;&#xff01;参考Github大佬&#xff0c;原项目链接&#xff1a; 原作者Githubhttps://github.com/LorentzForceWorkEnz…

博客摘录「 AXI三种接口及DMA DDR XDMA介绍(应用于vivado中的ip调用)」2024年6月10日

关键要点&#xff1a; 1.AXI Stream经过协议转换可使用AXI_FULL&#xff08;PS与PL间的接口&#xff0c;如GP、HP和ACP&#xff09;。 2.传输数据类里就涉及一个握手协议&#xff0c;即在主从双方数据通信前&#xff0c;有一个握手的过程。基本内容&#xff1a;数据的传输源会…

Docker与Docker-Compose详解

1、Docker是什么&#xff1f; 在计算机中&#xff0c;虚拟化(英语: Virtualization) 是一种资源管理技术&#xff0c;是将计算机的各种实体资源&#xff0c;如服务器、网络、内存及存储等&#xff0c;予以抽象、转换后呈现出来&#xff0c;打破实体结构间的不可切割的障碍&…

【服务实现读写分离】

文章目录 什么是读写分离基于Spring实现实现读写分离项目中常用的数据源切换依赖包 什么是读写分离 服务读写分离&#xff08;Service Read-Write Splitting&#xff09;是一种常见的数据库架构设计模式&#xff0c;旨在提高系统的性能和可扩展性。通过将读操作和写操作分离到…

ChatTTS改良版 - 新增精选高品质音色,新增超长文本推理,新增api接口

这个版本是ChatTTS的一个分支&#xff0c;基于ChatTTS修改&#xff0c;由6drf21e大佬改良&#xff0c;大佬GitHub地址 GitHub - 6drf21e/ChatTTS_colab: &#x1f680; 一键部署&#xff08;含离线整合包&#xff09;&#xff01;基于 ChatTTS &#xff0c;支持音色抽卡、长音频…