Linux 进程核心考点与实战笔记(嵌入式/后台面试向)
一、进程本质与 PCB 管理
进程是程序的一次动态执行过程,区别于磁盘上静态的二进制文件(如 a.out)。操作系统通过进程控制块(PCB)(Linux 中为 task_struct 结构体)对进程进行全生命周期管理 。
| PID | 进程唯一标识符 | 全局唯一,回收后可能复用 |
| 资源描述 | 打开文件描述符表、内存映射、IO 设备 | 文件描述符继承机制 |
| 调度信息 | 优先级、状态、时间片统计 | CFS 调度算法基础 |
| 上下文 | 寄存器值、栈指针、程序计数器 | 进程切换时的保存与恢复 |
| 权限控制 | UID/GID、umask 掩码 | 安全性与文件访问控制 |
并发 vs 并行:
- 并发 (Concurrency):单核 CPU 通过时间片轮转快速切换任务,宏观“同时”,微观串行。
- 并行 (Parallelism):多核 CPU 多个任务在不同核心上真正同时执行 。
二、虚拟内存分布(32 位模型)
32 位系统下,4GB 地址空间通常划分为 3GB 用户空间 + 1GB 内核空间。
高地址
+——————+
| 内核空间 (1G) | <— 用户态不可直接访问
+——————+
| 栈 | <— 向下增长 (默认 8M, ulimit -s)
+——————+
| 共享库/mmap 区 | <— 动态库、文件映射
+——————+
| 堆 | <— 向上增长 (malloc/free)
+——————+
| 数据段 (BSS) | <— 未初始化全局/静态变量
+——————+
| 数据段 (Data) | <— 已初始化全局/静态变量
+——————+
| 代码段 (Text) | <— 只读指令
+——————+
低地址
资源限制查看: 使用 ulimit -a 查看进程资源约束,嵌入式开发需重点关注:
- open files (-n):默认 1024,高并发服务器需调大。
- stack size (-s):默认 8192KB,递归过深易导致 Stack Overflow 。
三、fork() 机制与写时复制 (COW)
fork() 是 Linux 创建进程的核心系统调用,其特性为**“一次调用,两次返回”**。
1. 返回值逻辑
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
// 创建失败
perror("fork failed");
exit(1);
} else if (pid == 0) {
// 子进程逻辑
printf("Child Process: PID=%d, PPID=%d
", getpid(), getppid());
exit(0);
} else {
// 父进程逻辑 printf("Parent Process: PID=%d, Child_PID=%d
", getpid(), pid);
// 必须回收子进程,防止僵尸
wait(NULL);
}
return 0;
}
2. 核心特性解析
- 代码执行点:子进程从 fork() 返回处开始执行,而非重新运行 main。
- 独立性:父子进程拥有独立的 PID 和 PCB,但初始代码逻辑相同。
- 写时复制 (Copy On Write):
- fork 瞬间:父子进程共享物理内存页,内核将页面标记为只读。
- 修改发生时:若任意一方尝试写入,内核触发缺页异常,单独拷贝一份物理内存给修改方,实现延迟拷贝,极大降低开销 。
- 调度不确定性:父子进程谁先运行由内核调度器决定,不可假设顺序。
四、进程状态流转与特殊形态
Linux 进程状态主要包含以下几种,其中 Z 态 是面试高频考点。
| R | Running | 正在运行或在就绪队列等待调度 | N/A |
| S | Interruptible Sleep | 可中断睡眠,等待资源 | 能 |
| D | Uninterruptible Sleep | 不可中断睡眠,通常在等待 IO | 不能 |
| T | Stopped | 暂停态,收到 SIGSTOP 等信号 | 能 (SIGCONT) |
| Z | Zombie | 僵死态,进程结束但 PCB 未回收 | 不能 |
重点辨析:孤儿进程 vs 僵尸进程
- 孤儿进程 (Orphan):父进程先退出,子进程仍在运行。
- 处理机制:被 init (PID 1) 或 systemd 进程收养,资源自动回收,无害。
- 僵尸进程 (Zombie):子进程先退出,父进程未调用 wait/waitpid 回收。
- 危害:用户空间资源已释放,但 PCB 仍驻留内核,占用内核内存槽位。大量僵尸进程会导致无法创建新进程 。
- 解决:父进程必须正确处理信号或调用 wait 系列函数;若父进程僵死,需杀死父进程让 init 收养。
五、进程退出的 8 种方式与 exit/_exit 区别
进程终止分为正常退出、系统调用退出和异常终止。
1. 退出方式分类
- 正常退出:
- main 函数 return。
- exit():C 库函数,执行清理工作。
- 系统调用退出: 4. _exit() / _Exit():直接陷入内核,不执行用户态清理。
- 异常终止: 5. abort():发送 SIGABRT 信号。 6. 信号杀死:kill 命令或代码触发 SIGKILL/SIGTERM。 7. 线程取消:最后一个线程被 pthread_cancel。
2. 核心面试题:exit() vs _exit()
| 归属 | C 标准库 (stdlib.h) | 系统调用 (unistd.h) |
| 缓冲区处理 | 刷新 stdio 缓冲区 (fwrite 内容写入文件) | 不刷新,直接丢弃缓冲区数据 |
| 清理钩子 | 执行 atexit 注册的回调函数 | 不执行任何清理钩子 |
| 底层动作 | 调用 _exit() 进入内核 | 直接进入内核释放资源 |
| 应用场景 | 普通程序正常结束 | fork 后的子进程出错退出 (避免重复刷新父进程缓冲区) |
代码示例:验证缓冲区差异
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
printf("Before fork: This is in buffer
"); // 此时数据在缓冲区,未写入终端
pid_t pid = fork();
if (pid == 0) {
// 子进程:使用 _exit 避免刷新父进程的缓冲区,防止输出重复
_exit(0);
} else {
// 父进程:等待后正常退出,会刷新缓冲区
sleep(1); exit(0);
}
// 若子进程也用 exit(0),"Before fork…" 会被打印两次
}
六、常用调试命令与调度算法
1. 关键命令
- ps aux:静态快照,查看 PID、STAT (状态)、CMD。
- top:动态监控,实时查看 CPU/Mem 占用,按 P 按 CPU 排序,M 按内存排序。
- pstree:树状展示进程父子关系,快速定位孤儿/僵尸链。
- kill -l:查看所有可用信号列表。
2. Linux 调度策略
现代 Linux 内核默认采用 CFS (Completely Fair Scheduler,完全公平调度) 算法,取代了早期的 O(1) 调度器。
- 核心思想:基于红黑树维护所有可运行进程,根据 vruntime (虚拟运行时间) 选择最小节点执行,确保每个进程获得公平的 CPU 时间片 。
- 其他策略:实时进程可采用 SCHED_FIFO (先进先出) 或 SCHED_RR (时间片轮转)。## 七、总结与进阶方向 进程管理是嵌入式 Linux 与后台开发的基石。面试中需熟练掌握:
后续学习路线:
- 进程回收:深入 waitpid 选项与非阻塞回收。
- 进程映像替换:exec 族函数 (execl, execvp 等) 的使用场景。
- 进程间通信 (IPC):管道 (Pipe)、消息队列、共享内存、信号量。
参考来源
- 网易后端面试全攻略:从高频考点到实战方案
- 贝壳找房Java笔试全解析:考点分布、OOM排查与排序算法实战
网硕互联帮助中心




评论前必须登录!
注册