如果你正在准备嵌入式 Linux 应用开发岗位面试,那么“进程”这一块几乎是绕不开的重点。
很多人学到 fork()、wait() 就停了,但真正到了面试里,面试官往往会继续追问:
什么是僵尸进程?wait() 和 waitpid() 有什么区别?为什么要用 fork + exec?子进程异常退出怎么处理?如何避免僵尸进程?多进程和多线程怎么选?
这篇文章,我把嵌入式 Linux 应用开发岗位里,进程部分需要掌握的知识点,系统整理给你。你可以把它当作一篇进程专题总复习,也可以当作面试前的冲刺资料。
一、为什么嵌入式 Linux 岗位必须重视“进程”?
在嵌入式 Linux 应用开发里,很多功能模块天然就是以进程形式运行的,比如:
数据采集
网络通信
日志记录
升级程序
后台监控
脚本调用
守护服务
如果进程管理能力不过关,实际项目里就很容易出现这些问题:
子进程退出了没人回收,产生僵尸进程
子进程异常崩溃,主程序无感知
多个子进程不好统一管理
资源泄漏
服务崩溃后没有自动恢复机制
二、进程部分必须掌握的核心知识点
程序是静态的可执行文件。 进程是程序的一次运行实例,是动态的,有自己的 PID、地址空间、资源和状态。
一句话记忆:
程序是静态代码,进程是运行中的程序。
你至少要知道:
getpid():获取当前进程 PID
getppid():获取当前进程父进程 PID
fork() 是 Linux 进程控制里最核心的函数之一。
调用一次 fork() 后,会产生两个进程:
父进程
子进程
父子进程都会从 fork() 之后继续执行,但返回值不同:
< 0:创建失败
== 0:当前在子进程中
0:当前在父进程中,返回子进程 PID
- 进程退出与回收:exit()、wait()、waitpid()
子进程退出以后,并不意味着所有资源都会立刻彻底消失。 父进程需要调用 wait() 或 waitpid() 去回收子进程退出信息。这一点非常关键。
wait()
特点:
阻塞等待
等任意一个子进程
不只是“等”,更重要的是回收
waitpid()
特点:
可以指定等待哪个子进程
可以配合 WNOHANG 非阻塞使用
工程里更灵活
这是嵌入式 Linux 面试里绝对高频的考点。
这是进程专题里的高频题王。
定义: 子进程已经退出,但父进程还没有调用 wait() 或 waitpid() 回收它,这时子进程会以僵尸状态存在。
本质是:
子进程代码已经不再运行
但内核还保留它的退出状态和部分进程表项
等父进程来“回收”
危害:
占用进程表项
大量积累会浪费资源
严重时可能导致系统无法继续创建新进程
对资源本就有限的嵌入式设备尤其危险
解决方法:
父进程调用 wait() / waitpid()
处理 SIGCHLD,在信号处理里回收
某些场景下可用双 fork 规避僵尸问题
定义: 父进程先退出,而子进程还在运行,这时子进程会被 init 或 systemd 接管。
典型现象:
子进程中用 getppid() 看,父进程 ID 可能变成 1
一句话区分孤儿:
父进程死了,子进程还活着。
fork():创建新进程
exec():让当前进程去执行一个新的程序,替换当前进程映像
常见的 exec 系列有:
面试里经常问:
fork() 和 exec() 区别是什么?
为什么 fork + exec 常一起用?
标准理解:
父进程先 fork() 创建子进程,子进程再 exec() 加载外部程序。 这是工程里拉起独立外部程序最常见的方式。
- system() 和 fork + exec 的区别
system()
优点:
写起来简单
缺点:
依赖 shell
开销更大
可控性更差
工程上不够精细
fork + exec
优点:
更底层
更可控
更适合正式工程开发
所以你在面试里最好说:
简单场景可以用 system(),但复杂的工程里更推荐 fork + exec。
管道
匿名管道 pipe
命名管道 fifo
消息队列
按消息收发
和管道不同
共享内存
速度快
多个进程可访问同一块内存
但同步要自己处理
信号量
用于进程间互斥与同步
常和共享内存一起问
Socket
Unix domain socket
网络 socket
你至少要知道这些常见信号:
SIGINT
SIGTERM
SIGKILL
SIGCHLD
SIGSEGV
SIGALRM
还要知道:
signal() 可以处理信号
更推荐 sigaction()
SIGKILL 和 SIGSTOP 不能被捕获
子进程状态变化时,父进程可能收到 SIGCHLD
尤其是 SIGCHLD,它和子进程回收直接相关。
标准答案:
进程
线程
CPU 调度的基本单位
同进程线程共享地址空间和大部分资源
切换开销相对更小
追问通常是:
多进程和多线程怎么选?
回答思路:
隔离性要求高:选多进程
共享数据频繁、性能要求高:常选多线程
理论上常见状态:
Linux 常见状态字符:
R:运行态S:可中断睡眠态D:不可中断睡眠态T:暂停 / 被跟踪态Z:僵尸进程
fork() 后文件描述符会怎样?
子进程会继承父进程打开的文件描述符。
也就是说,父子进程都可以访问同一个文件、管道、socket 等资源。
进一步一点的回答:
虽然父子进程里文件描述符编号可能一样,但底层往往共享同一个内核打开文件对象,因此文件偏移等状态可能有关联。
重定向
面试里还会问:
dup
dup2
尤其是 fork + exec 时,怎么把子进程标准输入输出重定向到文件或管道。
中级岗位比较容易问。
你至少要知道:
守护进程是什么
为什么要脱离终端
为什么后台长期运行
常见创建方式
为什么有时会双 fork
常见场景:
这是很贴近项目的一类题。
面试官可能会问:
如果子进程异常退出怎么办?
父进程怎么知道子进程挂了?
怎么防止关键工作进程挂掉没人管?
多个子进程怎么统一管理?
你可以这样回答:
父进程通过 wait() / waitpid() 或 SIGCHLD 感知子进程退出
拿到退出状态后可做日志记录、资源清理、按策略拉起重启、上报故障
若有多个子进程,可循环 wait() 或 waitpid() 回收
在 SIGCHLD 处理逻辑里可循环 waitpid(-1, &status, WNOHANG) 回收多个已退出子进程
这类回答会明显更像“做过项目的人”。
三、嵌入式 Linux 进程部分高频面试题 + 标准答案
下面这部分,你可以直接拿来背。
面试题 1:程序和进程有什么区别?
答案: 程序是静态的可执行文件,存放在磁盘上;进程是程序的一次运行实例,是动态的,运行时有自己的 PID、地址空间、资源和状态。 简单说,程序是静态代码,进程是运行中的程序。
面试题 2:fork() 的作用是什么?
答案: fork() 用来创建子进程。调用一次 fork() 后,会产生两个进程:父进程和子进程。它们都会从 fork() 之后继续执行,但返回值不同:父进程返回子进程 PID,子进程返回 0,失败返回负数。
面试题 3:父子进程执行顺序固定吗?
答案: 不固定。fork() 后父子进程由调度器决定谁先运行,因此输出顺序不一定一致。 面试时不要说“父进程一定先打印”。
面试题 4:什么是僵尸进程?如何解决?
答案: 僵尸进程是指子进程已经退出,但父进程还没有调用 wait() 或 waitpid() 回收它。 危害是会占用进程表项,积累多了可能影响系统创建新进程。 解决方法是父进程及时调用 wait() / waitpid(),或者处理 SIGCHLD 来回收。
面试题 5:什么是孤儿进程?
答案: 孤儿进程是指父进程先退出,而子进程还在运行。此时子进程会被 init 或 systemd 接管。
面试题 6:wait() 和 waitpid() 有什么区别?
答案: wait() 用于阻塞等待任意一个子进程退出并回收它。 waitpid() 更灵活,可以指定等待某个子进程,也可以通过 WNOHANG 实现非阻塞等待。 工程里通常 waitpid() 更常用。
面试题 7:什么是 exec?和 fork() 有什么区别?
答案: fork() 是创建一个新进程。 exec() 是让当前进程去执行另一个程序,会替换当前进程映像。 通常在工程里会把两者组合起来使用:父进程 fork() 出子进程,子进程 exec() 加载新程序。
面试题 8:为什么 fork + exec 经常一起用?
答案: 因为这样可以让父进程保留原有控制逻辑,同时由子进程去执行新的外部程序。 这是启动独立任务、脚本、工具程序时很常见的方式。
面试题 9:system() 和 fork + exec 有什么区别?
答案: system() 调用简单,但依赖 shell,开销相对大,可控性差。 fork + exec 更底层、更灵活、更适合工程开发。
面试题 10:fork() 后变量和文件描述符分别是什么关系?
答案: 变量在父子进程中各自独立; 但打开的文件描述符会被子进程继承,因此父子进程都可访问同一个文件、管道或 socket。
面试题 11:什么是 SIGCHLD?
答案: SIGCHLD 是子进程状态发生变化时,内核发给父进程的信号,通常子进程退出时父进程会收到它。 父进程可以在信号处理逻辑中调用 wait() / waitpid() 回收子进程。
面试题 12:多个子进程怎么回收?
答案: 如果存在多个子进程,父进程要循环调用 wait() 或 waitpid() 回收,而不能只调用一次。 常见做法是在 SIGCHLD 处理逻辑里循环 waitpid(-1, &status, WNOHANG)。
面试题 13:进程和线程的区别是什么?
答案: 进程是资源分配的基本单位,地址空间独立; 线程是 CPU 调度的基本单位,同一进程内线程共享地址空间和大部分资源。 通常进程切换开销更大,线程切换开销更小。
面试题 14:为什么嵌入式 Linux 应用开发中要重视进程管理?
答案: 因为嵌入式系统中很多功能模块都会以进程形式运行,如果进程创建、监控、回收、异常处理做不好,就容易出现僵尸进程、资源泄漏、服务崩溃后无法恢复等问题。
四、嵌入式 Linux 进程部分常见笔试题 + 答案
笔试题 1
fork() 在父进程中的返回值是什么?
答案: 子进程 PID。
笔试题 2
fork() 在子进程中的返回值是什么?
答案: 0。
笔试题 3
wait() 的核心作用是什么?
答案: 等待并回收子进程。 注意,不能只答“等待”。
笔试题 4
子进程退出后,父进程没有调用 wait() / waitpid(),此时子进程处于什么状态?
答案: 僵尸进程。
笔试题 5
下面哪个函数可以实现非阻塞等待子进程?
A. wait() B. waitpid(..., WNOHANG) C. sleep() D. exit()
答案: B。
笔试题 6
下列哪个信号不能被捕获?
A. SIGINT B. SIGTERM C. SIGKILL D. SIGCHLD
答案: C。 同类还可以记住 SIGSTOP 也不能捕获。
笔试题 7
fork() 后子进程是否继承父进程打开的文件描述符?
答案: 是,会继承。
笔试题 8
exec() 的作用是什么?
答案: 让当前进程执行一个新的程序,替换当前进程映像。
笔试题 9
孤儿进程是指什么?
答案: 父进程先退出,子进程还在运行的进程。
笔试题 10
Linux 中 Z 状态表示什么?
答案: 僵尸进程。
五、嵌入式 Linux 进程部分高频编程题 + 答案思路
这是面试里最容易拉开差距的一部分。
编程题 1:写一个 fork() 程序,分别打印父子进程 PID
考点:
fork() 返回值
getpid()
getppid()
答案思路:
调用 fork()
pid < 0 处理错误
pid == 0 分支打印子进程 PID、PPID
pid > 0 分支打印父进程 PID 和子进程 PID
编程题 2:父进程等待子进程退出,并打印退出码
考点:
wait()
status
WIFEXITED
WEXITSTATUS
答案思路:
子进程 sleep(2) 后 exit(42)
父进程 wait(&status)
回收成功后用 WIFEXITED(status) 和 WEXITSTATUS(status) 解析退出码
编程题 3:演示僵尸进程的产生
考点:
子进程退出
父进程不回收
用 ps 查看状态
答案思路:
子进程直接 exit(0)
父进程 sleep(30),故意不 wait
另开终端 ps -o pid,ppid,state,cmd -p 父PID,子PID
观察子进程为 Z
编程题 4:修复僵尸进程
考点:
wait() / waitpid()
正确回收子进程
答案思路:
在父进程中添加 wait(&status) 或 waitpid(pid, &status, 0)
保证子进程退出后被父进程及时回收
编程题 5:使用 waitpid(WNOHANG) 实现非阻塞等待
考点:
waitpid
WNOHANG
轮询检查
答案思路:
子进程工作 5 秒后退出
父进程进入循环
调用 waitpid(pid, &status, WNOHANG)
若返回 0,表示子进程仍在运行
若返回 pid,表示子进程已退出
再解析退出状态
编程题 6:用 fork + exec 执行外部程序
考点:
fork
exec 系列
父进程等待子进程
答案思路:
父进程 fork()
子进程中调用 execlp("ls", "ls", "-l", NULL) 或其他外部命令
exec 失败时 perror
父进程 waitpid() 回收
这题非常贴近工程场景。
编程题 7:父进程和子进程通过匿名管道通信
考点:
pipe()
fork()
关闭不用的一端
父写子读 / 子写父读
这是 IPC 的经典起步题,嵌入式 Linux 面试很常见。
编程题 8:多个子进程的创建与回收
考点:
循环 fork()
循环 wait() / waitpid()
防止产生多个僵尸
答案思路:
创建 3 个子进程
父进程用循环 wait() 全部回收
或使用 waitpid(-1, &status, 0) 循环回收
这题常作为面试加码题。
编程题 9:使用 SIGCHLD 自动回收子进程
考点:
sigaction
SIGCHLD
在信号处理逻辑里回收子进程
答案思路:
注册 SIGCHLD 处理函数
在处理函数中循环 waitpid(-1, &status, WNOHANG)
防止多个子进程退出时遗漏回收
编程题 10:用 dup2() 做输出重定向,再 exec 外部程序
考点:
文件描述符继承
dup2()
fork + exec
重定向标准输出
答案思路:
父进程创建子进程
子进程打开文件
调用 dup2(fd, STDOUT_FILENO)
再 exec 外部程序
让外部程序输出落到指定文件中
六、面试时最容易被追问的点
因为父子进程都会从 fork() 后继续执行,但它们拿到的返回值不同。
因为更核心的是回收子进程资源与退出状态。
因为 status 里包含的是编码后的退出状态信息,需要用 WIFEXITED、WEXITSTATUS 等宏解析。
因为父进程先退出了,当前进程成了孤儿进程,被系统接管。
可能在等待 IO、等待子进程、等待锁、sleep()、等待管道/信号等。
表示子进程已经退出,但还没有被父进程回收。
七、如果面试官让你概括“Linux 进程管理核心点”,怎么答?
你可以直接背下面这段:
Linux 进程管理的核心包括进程创建、进程退出、子进程回收和进程间关系。 常见创建方式是 fork(),父子进程通过返回值区分。子进程退出后,父进程需要通过 wait() 或 waitpid() 回收,否则会产生僵尸进程。 如果父进程先退出,子进程会成为孤儿进程并被系统接管。 在实际 开发中,常见模式是 fork + exec 启动外部程序,再通过 waitpid() 或 SIGCHLD 监控子进程状态。
八、嵌入式 Linux 进程部分,面试至少要学到什么程度?
必须会写
fork 创建子进程
wait 等待子进程
waitpid 非阻塞等待
fork + exec 执行其他程序
简单 pipe 通信 demo
必须会解释
僵尸进程
孤儿进程
进程和线程区别
wait 和 waitpid 区别
system 和 exec 区别
文件描述符继承
必须会排查
为什么 ppid 变成 1
为什么会出现 Z
为什么程序卡住
为什么子进程没被回收
怎么用 ps / top 看进程状态
九、进程部分建议学习顺序
如果你是按面试实用性来学,建议顺序如下:
fork()
wait()
僵尸进程与孤儿进程
waitpid(WNOHANG)
exec 系列
fork + exec
pipe
signal / sigaction
共享内存、消息队列、信号量
守护进程
文件描述符继承与重定向
多子进程管理与异常拉起机制
十、写在最后:进程这一块,真正拉开差距的不是“背定义”,而是“会不会落地”
很多人准备面试的时候,进程部分只会背:
什么是进程
什么是线程
什么是僵尸进程
但到了真正的嵌入式 Linux 应用开发岗位,面试官更在意的是:
你会不会用 fork() 写出父子进程 demo
你会不会用 wait() / waitpid() 回收子进程
你知不知道如何避免僵尸进程
你会不会 fork + exec
你能不能说出子进程异常退出的处理思路
你是否理解文件描述符继承、重定向、IPC 和信号
所以,最好的准备方式不是只背概念,而是:
概念 + 标准回答 + demo 代码 + 现象观察 + 项目化表达 一起练。
当你能把这篇文章里的内容讲顺、写顺、答顺,嵌入式 Linux 应用开发岗位里的“进程”专题,基本就不会再虚了。
记得反复复习~