进程控制详解

1.进程创建

1.1fork函数的详细理解

进程创建在Linux中使用的就是fork函数,fork函数会基于此进程创建一个子进程;fork函数最特别的地方就是一次调用,两个返回值;

cpp 复制代码
#include <unistd.h>
pid_t fork(void);

创建成功之后给父进程返回子进程的pid,给子进程返回0;创建失败给父进程返回-1;

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

✅ ①分配新的内核数据结构和内存资源给子进程

内核会为子进程创建一个新的 进程描述符 以及子进程所需的必要内核数据结构(如 task_struct)。同时,为子进程分配一个唯一的 PID。

✅ ② 复制父进程的部分数据结构内容到子进程

子进程不是完全复制父进程的所有内容,而是复制父进程的 许多内核数据结构(如打开的文件描述符表、信号处理设置等)。但对于内存管理,现代 Linux 采用 写时拷贝 技术:

子进程和父进程共享相同的物理内存页;内核将这些内存页标记为 只读;只有当其中一个进程尝试写入时,才会复制该页;(缺页中断的写时拷贝)

✅ ③将子进程添加到系统进程列表

内核将子进程的进程描述符加入到系统的进程链表(如任务列表)中,使其成为系统调度的一个可运行实体。

✅ ④ fork 返回,开始调度器调度

fork 在内核中完成创建工作后,会准备返回到用户空间;但由于现在有两个进程(父进程和子进程),内核会调用调度器来决定接下来哪个进程获得 CPU

最终:①父进程从 fork 调用点返回子进程的 PID;②子进程从相同的调用点返回 0;③两个进程继续执行后续代码

所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定;

1.2 fork函数的使用场景

1.3fork函数失败的原因

系统中进程过多

实际用户的进程数量超过限

2.进程终止

进程终止的核心目的就是归还所有占用的系统资源,让操作系统能够重新分配这些资源给其他进程使用。

2.1进程退出的场景

cpp 复制代码
// 正常终止方式
exit(0);        // 标准C库函数
_exit(0);       // 系统调用
return n;       // main函数返回

// 异常终止方式
abort();        // 发送SIGABRT信号
被信号杀死      // 如段错误、用户kill

进程的终止可以分为两大类:正常终止异常终止。无论哪种方式,最终都会进入内核的终止流程,释放进程占用的资源。

对于进程异常退出:操作系统会受到具体的信号,然后把对应的进程杀掉;进程异常退出的时候,进程退出的信息中会记录下来自己的退出信号;退出信号为0时表示没有异常退出的情况,但是结果对不对还要看我们的退出码判断;

main函数的返回值是返回给系统或者是父进程的;所以main函数的返回值就是用来确定当前程序的执行情况;

从main返回:return n; → 退出码 = n ->让程序结束

调用exit():

exit(n); → 会执行清理(atexit、缓冲区刷新 -> 属于语言级别->会让进程直接结束;

调用_exit():

_exit(n); → 直接进内核,不清理/缓冲区不刷新 ->属于系统级别

补充:

①系统头文件必须加.h

② 语言级缓冲区,会导致有些结果刷新不及时,系统级缓冲区在进程退出的时候会自动刷新;

2.2退出码

退出码(退出状态)可以告诉我们最后⼀次执行的命令的状态。在命令结束以后,我们可以知道命令是成功完成的还是以错误结束的。其基本思想是,程序返回退出代码 0 时表示执行成功,没有问题。代码 1 或 0 以外的任何代码都被视为不成功。

退出码也可以自己规定;

XShell中常见的退出码

可以使⽤ strerror 函数来获取退出码对应的描述。

2.3 return退出

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

2.4 exit()函数

cpp 复制代码
#include <unistd.h>
void exit(int status);

status是进程的退出码,status 就是进程留给父进程的"遗言",告诉父进程自己死得怎么样。

3.进程等待

其实就是父进程创建子进程就要对子进程负责,至少要让子进程被回收;如果子进程一直不退出,父进程就要一直阻塞等待;

3.1进程等待的必要性

①回收资源,防止僵尸进程

子进程退出后,若父进程不管,子进程会变成僵尸进程(只保留task_struct在内核)

僵尸进程不释放内核资源,长期积累会导致内存泄漏

僵尸进程无法杀死(包括kill -9),因为它已经死了,只等父进程来收尸

②获取子进程的退出信息

父进程需要知道:

子进程任务完成得怎么样?(退出码判断成功/失败)

子进程是正常退出还是被信号杀死?

③进程等待的作用

父进程通过调用wait()或waitpid():

✅ 回收子进程残留的内核资源(释放task_struct)

✅ 获取子进程的终止状态(退出码/终止信号)

✅ 彻底消除僵尸进程

3.2如何进行进程等待

3.2.1使用wait( )函数;

3.2.2 想要获取子进程的退出信息:使用waitpid( );

status中不只有进程的退出码(这是包括进程正常退出的信息),还有异常突出;status就是一个输出型参数;

能否使用全局变量来获取子进程的退出码呢?

不可以,因此进程具有独立性,子进程将这个变量更改之后父进程看不到;这也就是 为什么我们只能通过系统调用来获取我们子进程的退出信息;

进程的退出信息是由我们的PCB存储的;

我们wait成功了并不代表我们的子进程也是成功运行了,具体情况我们可以使用下面的两个宏

3.3进程等待方式

① 阻塞等待最常用

② 非阻塞等待

在等待过程中不断地对进程进行询问,这种情况叫做非阻塞轮询;这个循环调用是由程序员自己进行的;

4.进程程序替换

4.1什么是程序替换

程序替换(exec 系列函数)的核心作用是:让一个进程加载并运行一个全新的程序;

fork() 之后,⽗⼦各⾃执⾏⽗进程代码的⼀部分如果⼦进程就想执⾏⼀个全新的程序呢?进程的程序

替换来完成这个功能!程序替换是通过特定的接⼝,加载磁盘上的⼀个全新的程序(代码和数据),加载到调⽤进程的地址空间中!

4.2进程程序替换的原理

⽤fork创建⼦进程后执⾏的是和⽗进程相同的程序(但有可能执⾏不同的代码分⽀),⼦进程往往要调⽤⼀种 exec 函数以执⾏另⼀个程序。当进程调⽤⼀种 exec 函数时,该进程的⽤⼾空间代码和数据完全被新程序替换,从新程序的启动例程开始执⾏。调⽤ exec 并不创建新进程,所以调⽤ exec 前后该进程的 id 并未改变。

其实就是这个新的程序会将我们原来程序的数据和代码替换掉;谁调我这个接口,我就用哪个进程的代码和数据覆盖掉我的代码和数据;所以进程替换不是创建新的进程;所以其实linux中的子进程拷贝一些父进程的代码和数据,本质上也是用来execl,因此linux上所有的进程都是通过execl和fork跑起来的;呢我们不就可以使用程序替换让我们的代码可以用JAVA、PYTHON跑都可以了;

4.3替换函数

execl失败返回-1,也就说只要返回必定是失败的;

相关推荐
liuyao_xianhui2 小时前
优选算法_丢失的数字_位运算_C++
linux·数据结构·c++·算法·动态规划·哈希算法·散列表
CCPC不拿奖不改名2 小时前
RAG基础:评测系统RAGAS的四大指标
linux·服务器·人工智能·计算机视觉·前端框架·知识库搭建
阳光下的米雪2 小时前
存储过程的使用以及介绍
java·服务器·数据库·pgsql
NGC_66112 小时前
TCP可靠传输怎么实现的
服务器·网络·php
橘子132 小时前
ICMP协议
运维·服务器·网络
智能工业品检测-奇妙智能2 小时前
openclaw使用硅基流动免费模型
服务器·人工智能·spring boot·电商·openclaw
chalmers_152 小时前
封禁暴力破解SSH的攻击IP
linux·服务器·ssh
xiaokangzhe2 小时前
Nginx性能优化与监控
运维·nginx·性能优化
梓䈑2 小时前
【CMake】CMake工程构建全流程 以及 相关命令行工具的介绍
linux·cmake