【知识讲解】 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相关的知识,敬请期待~
最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!

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

相关推荐
码农小韩1 小时前
Linux应用开发(一)——Linux的标准IO
linux·运维·服务器
路弥行至1 小时前
Linux (ARM64 / Jetson) 挂载 exFAT 移动硬盘排障与离线安装指南
linux·运维·服务器·经验分享·笔记·jetson·exfat
云泽8082 小时前
Linux 系统编程实战:从零手写一个高性能终端进度条
linux·运维·服务器
Mapleay2 小时前
设备树/ACPI 与 fwnode 内核基础设施
linux
waiting9711183 小时前
Ubuntu 26.04 + Android14安装与编译教程
android·linux·ubuntu
qetfw3 小时前
Debian 部署 phpMyAdmin:Apache、PHP 与 MariaDB 管理界面配置
linux·debian·php·apache
青瓦梦滋4 小时前
NAT技术
linux·服务器·网络·网络协议·tcp/ip
啦啦啦啦啦zzzz4 小时前
自定义日志缓冲区(上)c++20
linux·服务器·c++·网络编程
邪修king4 小时前
Re: Linux系统篇(十八):进程篇(七): 进程深度解析:从 fork 创建到退出的完整旅程(附写时拷贝原理 + 代码实战)
java·linux·运维