Linux 进程编程完整梳理
前言
进程是 Linux 系统编程最核心的基础,不管是嵌入式开发、后台服务开发,面试笔试都会高频考察。本文从概念、原理、API、代码示例、常见坑点完整梳理 Linux 进程相关知识点。
1. 什么是进程
程序:存放在磁盘上静态的可执行文件,只是一堆机器指令,不运行就不占用 CPU。 进程:程序被加载进内存之后,正在运行的实例,是操作系统资源分配的基本单位。
每一个进程拥有独立:虚拟地址空间、文件描述符表、环境变量、信号处理方式、运行状态。
内核使用PCB(process control block,task_struct结构体)管理每一个进程,PCB 内部保存:PID 进程号、进程状态、内存映射指针、打开的文件、优先级、父子进程关系等全部信息。
一句话区分:程序是静态文件,进程是动态运行实体。
2. 进程 vs 线程
表格
| 对比项 | 进程 | 线程 |
|---|---|---|
| 核心定位 | 资源分配的基本单位 | CPU 调度的基本单位 |
| 内存资源 | 拥有完整独立地址空间,开销大 | 共享所属进程地址空间;每个线程仅独有栈、寄存器上下文,开销小 |
| 崩溃影响 | 进程之间相互隔离;一个进程崩溃不会干扰其他进程 | 线程共享进程全部资源;任意一个线程崩溃,整个进程直接退出 |
| 通信方式 | 进程间内存隔离,必须使用 IPC 通信:管道、消息队列、共享内存、socket | 同进程线程直接读写全局变量即可通信,注意互斥锁保护 |
记忆口诀:进程分资源,线程抢 CPU。
3. Linux 进程常见状态
使用ps、top命令可以查看进程状态:
- R 运行态:进程正在 CPU 执行,或者位于运行队列等待调度。
- S 可中断睡眠:进程等待事件,收到信号可以被唤醒。
- D 不可中断睡眠:大多等待 IO 操作,不能被信号打断。
- T 停止态:进程被暂停,收到 SIGCONT 信号恢复运行。
- Z 僵尸态 (Zombie):子进程代码已经执行结束退出,但是父进程还没有调用
wait()回收 PCB 资源;用户内存已经释放,PID 和 PCB 残留在内核。
⚠️僵尸进程不占用 CPU、用户内存,但是持续占用 PID 编号,大量僵尸会耗尽系统进程表。
4. 进程创建、退出核心 API
4.1 fork () 创建子进程
pid_t fork(void); 调用一次,返回两次:
- 父进程返回值:大于 0,返回新创建子进程 PID;
- 子进程返回值:等于 0;
- 返回 - 1:fork 创建失败。
写时复制 COW(Copy‑On‑Write)
fork 之后,子进程不会立刻完整拷贝物理内存。 刚创建瞬间,父子进程共享同一份物理内存,虚拟地址完全一致; 只有父子任意一方修改变量数据的时候,内核才真正复制这一块内存,分配新物理页面。
COW 极大降低 fork 系统调用开销。
fork 之后行为特点
- fork 之后父子同时执行 fork 之后的代码,谁先运行完全由内核调度决定,顺序不可预测。
- 子进程继承父进程:文件描述符、环境变量、信号处理;地址空间逻辑上完全拷贝,物理内存 COW 机制。
- 父子地址空间相互独立,子进程修改变量,父进程完全看不到。
示例模板:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork failed");
return 1;
}
else if(pid == 0)
{
printf("子进程 PID:%d\n", getpid());
}
else
{
printf("父进程 PID:%d,子进程PID:%d\n", getpid(), pid);
wait(NULL);
}
return 0;
}
4.2 PID / PPID
getpid()获取自己进程 PIDgetppid()获取父进程 PID
PID 是内核分配唯一编号,可以被复用。 系统 1 号进程systemd/init,是系统所有孤儿进程的养父。
孤儿进程:父进程先退出,子进程还活着,子进程 PPID 改为 1,被 init 收养。 僵尸进程:子进程结束,父进程活着,但是没有 wait 回收。
4.3 exit () 进程退出
exit(退出码);
- 退出码:0 代表正常结束;非 0 代表异常退出。
- 调用 exit 之后:用户空间内存、打开文件描述符被释放;但是 PCB 进程控制块保留在内核,变成僵尸进程,等待父进程 wait 回收。
4.4 wait () 回收僵尸子进程
pid_t wait(int *wstatus);
wait(NULL):只回收僵尸进程,不关心退出状态;wait(&status):传入变量地址,内核将子进程退出信息填充到 status; 配合宏解析 status:
WIFEXITED(status):判断子进程是否正常 exit 退出WEXITSTATUS(status):提取子进程 exit 传入的退出码
重点:wait 会阻塞父进程,直到有一个子进程变成僵尸。
4.5 exec 族函数
execl / execv / execvp …
exec不会创建新进程!PID 保持不变。 使用全新程序代码,直接覆盖当前进程的代码段、数据段。 exec 调用成功,函数不会返回;只有出错才返回‑1。
经典用法:fork()创建子进程,子进程内部调用 exec,执行另外一个外部程序。
5. 进程常用命令
ps -ef/ps aux:查看进程快照top:动态实时查看进程 CPU 内存占用htop:top 增强工具kill -信号 pid:发送信号,kill -9 pid强制杀死进程pstree:树形打印父子进程关系
6. IPC 进程间通信
进程地址空间互相隔离,必须依靠内核提供的机制完成数据交互:
- 管道 pipe:匿名管道多用于父子进程单向通信;FIFO 命名管道支持无血缘关系进程。
- 消息队列:内核维护消息链表,支持双向收发消息。
- 共享内存:多个进程映射同一块物理内存,速度最快;需要信号量做同步互斥。
- 信号量:主要做同步、互斥,保护共享资源。
- Socket 套接字:本机通信,也可以跨网络主机通信。
7. 守护进程 Daemon
守护进程后台长期运行,脱离终端,例如 nginx、sshd。 创建守护进程标准步骤:
- fork,父进程退出,子进程成为孤儿,被 init 收养。
setsid()创建新会话,脱离终端控制。- chdir 切换工作目录到根目录
/。 - umask (0) 修改文件掩码。
- 关闭标准输入输出错误文件描述符,脱离终端。
8. 高频面试简答总结
- fork 调用一次返回两次,COW 写时复制原理。
- 僵尸进程、孤儿进程产生原因以及解决办法。
- wait 作用;
wait(NULL)和wait(&status)区别。 - 进程和线程核心区别。
- exec 系列函数特点,不创建新进程。
- 常见 IPC 方式各自特点。
实践建议:多写 fork 代码,观察僵尸进程,ps 命令看 Z 状态,加深理解。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)