【知识讲解】 Linux进程认识及深度理解


目录

前言

[Part1. fork失败的两个原因](#Part1. fork失败的两个原因)

[Part2. 进程退出](#Part2. 进程退出)

[Part2.1. 进程退出的场景](#Part2.1. 进程退出的场景)

[Part2.2. return底层原理](#Part2.2. return底层原理)

[Part2.3. 获取退出码](#Part2.3. 获取退出码)

[Part2.4. 僵尸进程](#Part2.4. 僵尸进程)

[Part3. 进程等待 wait、waitpid](#Part3. 进程等待 wait、waitpid)

[Part3.1. wait函数](#Part3.1. wait函数)

[2. waitpid函数](#2. waitpid函数)

[Part4. status状态位图、解析宏](#Part4. status状态位图、解析宏)

[Part5. 进程程序替换、exec系列函数](#Part5. 进程程序替换、exec系列函数)

[Part5.1. 程序替换介绍](#Part5.1. 程序替换介绍)

[Part5.2. exec系列函数](#Part5.2. exec系列函数)

[Part6. 整体总结](#Part6. 整体总结)

[Part7. 结语](#Part7. 结语)


前言

Linux系统中,执行任务的基本是进程,对于他的理解是非常重要的,接下来来跟随小编的视角来看看吧。


let's go!!!!!!!!


Part1. fork失败的两个原因

1. 系统内进程数量达到上限,无法创建新进程。


2. 内存资源不足,子进程需要复制页表、虚拟地址空间,内存不够时fork失败。


shell小知识点:
bash内置命令(cd、echo、export)不会创建子进程,直接在当前bash进程执行;
外部命令(ls、cat)会fork创建子进程,在子进程中执行程序。


Part2. 进程退出

Part2.1. 进程退出的场景

进程退出意味着:释放占用内存、回收各类内核资源。

三种退出场景:

1. 正常退出:代码执行完毕。

◦ main函数return;

◦ 调用库函数exit();

◦ 系统调用_exit()。

2. 异常退出1:收到信号,程序崩溃(段错误、kill杀死进程)。

3. 异常退出2:代码逻辑出错,执行异常。


main的返回值代表进程执行状态:

• 返回0:程序执行成功;

• 返回非0:程序执行出错。


Part2.2. return底层原理

• 返回值数值比较小:把返回值放到寄存器,直接给内核;

• 返回值数值大:走内存拷贝,把返回值传递给内核。


Part2.3. 获取退出码

1. shell中:echo $?,查看上一个进程的退出码。

2. 程序内:

◦ strerror(x):传入错误码,返回对应错误描述字符串,头文件<string.h>。

◦ errno:系统调用失败时会设置全局错误码,头文件<errno.h>。
注意:如果进程是被信号杀死崩溃,返回的数字不是普通return退出码,代表收到的信号编号。


Part2.4. 僵尸进程

子进程退出之后,内核不会立刻释放子进程的task_struct。

• 子进程的退出状态、退出码保存在task_struct;

• 必须等待父进程调用wait / waitpid读取这个退出信息;

• 如果父进程一直不回收,子进程就变成僵尸进程。

僵尸进程本身不占用内存,但是task_struct内核结构体残留;大量僵尸会占用pid资源,导致无法新建进程。


Part3. 进程等待 wait、waitpid

为什么需要进程等待

1. 回收子进程资源,避免产生僵尸进程。

2. 获取子进程退出信息:判断子进程是正常跑完,还是异常崩溃。


Part3.1. wait函数

cpp 复制代码
#include <sys/wait.h>
pid_t wait(int *status);

• 功能:阻塞等待任意一个子进程退出。

• 返回值:
0:成功,返回退出的子进程pid;
-1:调用失败(没有子进程)。

• 参数status:输出型参数,把子进程退出状态带回父进程。传NULL代表不关心退出状态。

缺点:wait是阻塞调用,如果子进程没有退出,父进程就卡住,不能做别的工作;只能等待任意子进程,无法指定某一个子进程。


2. waitpid函数

cpp 复制代码
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);

参数解析

1. pid_t pid:指定要等待哪个子进程

◦ pid > 0:等待pid等于该值的指定子进程;

◦ pid = -1:等待任意一个子进程,等价于wait;

◦ pid = 0:等待和调用者同组的所有子进程;

◦ pid < -1:等待该进程组内任意子进程。

2. *int status:输出参数,保存子进程退出信息。status不是简单的退出码,是一个位图,需要宏函数解析。

3. int options:选项,最常用WNOHANG。

◦ WNOHANG:非阻塞等待。如果子进程还没有退出,waitpid立刻返回0,父进程不会卡住,可以继续做其他业务;轮询检测子进程状态。

waitpid返回值

• > 0:有子进程退出,返回退出子进程pid;

• = 0:设置WNOHANG,子进程还没有退出;

• -1:出错,没有子进程。


Part4. status状态位图、解析宏

status是int,内核把多种退出信息打包到这个整数,不能直接打印status当做退出码,要用系统提供的宏解析。


核心宏

1. WIFEXITED(status)
判断进程是否正常退出(return / exit)。返回true代表正常结束。

2. WEXITSTATUS(status)
如果WIFEXITED为true,用它拿到子进程的退出码,等价main return的值。

3. WIFSIGNALED(status)
判断进程是否被信号杀死(异常崩溃),返回true代表死于信号。

4. WTERMSIG(status)
如果WIFSIGNALED为true,获取杀死进程的信号编号。


位图底层原理:
int共32bit,低7bit用来保存终止信号;次8bit保存进程退出码。
取出退出码:(status >> 8) & 0xFF。
取出信号编号:status & 0x7F。


非阻塞轮询示例代码模板

cpp 复制代码
pid_t id = waitpid(pid, &status, WNOHANG);
if(id > 0)
{
    //子进程退出,处理回收逻辑
}
else if(id == 0)
{
    //子进程还没退出,父进程做自己的业务,过一会再来waitpid
}
else
{
    //出错
}

wait底层原理:
子进程退出,内核释放绝大部分资源,task_struct保留;父进程调用wait/waitpid,内核把保存在task_struct里面的退出状态拷贝给父进程,之后彻底释放task_struct。


Part5. 进程程序替换、exec系列函数

Part5.1. 程序替换介绍

fork创建子进程之后,子进程默认复制父进程的代码和数据。
很多场景我们希望子进程跑完全不同的程序,就使用exec族函数做程序替换。

注意:程序替换不会创建新进程!

pid不变,虚拟地址空间不变,task_struct不变;
仅仅把磁盘上新程序的代码段、数据段加载到当前进程的虚拟地址空间,覆盖原来的代码数据。堆、栈会重新初始化。


Part5.2. exec系列函数

cpp 复制代码
#include <unistd.h>
//示例:执行ls -l -a
execl("/usr/bin/ls", "ls", "-l", "-a", NULL);

函数名后缀含义:

• l:list,可变参数列表传参,以NULL结尾;

• p:path,自动从环境变量PATH寻找可执行文件,不用写完整路径;

• v:vector,参数放到字符串数组传入;

• e:environment,可以自己传入自定义环境变量。

常用函数:execl、execlp、execv、execvp、execve(系统调用,其他都是库函数封装)。

exec函数返回值

exec调用成功没有返回值,直接跳转到新程序入口运行;
只有调用失败才返回-1,继续执行原来代码。


经典模型:

1. fork创建子进程;

2. 子进程调用exec做程序替换,跑新业务;

3. 父进程调用wait/waitpid等待子进程回收资源。


Part6. 整体总结

1. fork失败两大原因:系统进程数上限、内存不足。shell内置命令不创建子进程。

2. 进程退出:正常退出(return/exit)、信号异常崩溃。退出码保存在内核task_struct。

3. 僵尸进程:子进程退出,父进程没有wait回收,残留task_struct。

4. wait:阻塞等待任意子进程;waitpid可以指定子进程,支持WNOHANG非阻塞轮询。status位图,通过WIFEXITED、WEXITSTATUS解析退出信息。

5. exec程序替换:不产生新进程,覆盖当前进程代码数据;fork+exec是Linux创建执行业务进程的标准范式。


Part7. 结语

这篇文章我们认识并知道了Linux进程,接下来,小编还会带来更多Linux相关的知识,敬请期待~
最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!

最后的最后,要是觉得本文还可以的话,可以点点赞,关注小编一波,谢谢大家!~

相关推荐
律宏阔38 分钟前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔38 分钟前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴38 分钟前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千1 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei371 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei371 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei371 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
GeW1 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux
GeW1 小时前
工业场景中的Linux与数据库选型:高端化转型的技术底座
linux
yunwei371 小时前
eBPF 开发者教程: 简单的 XDP 负载均衡器
linux·后端·性能优化