一、进程与线程基础概念
1. 进程和线程的区别(内存空间、调度单元、共享资源)
要点
-
进程(Process):拥有独立的地址空间(虚拟内存),通常包含自己的代码段、数据段、堆、栈、文件描述符表、信号处理表等。进程是资源分配的基本单位。
-
线程(Thread):内核调度的最小单位(在 Linux 中通常每个线程对应一个
task_struct)。线程共享所属进程的地址空间(若用clone(CLONE_VM))、文件描述符表等资源,但有自己的寄存器和内核栈。线程是执行的基本单位。 -
调度开销:进程上下文切换一般比线程切换开销大(因为可能需要切换地址空间/页表),线程切换如果共享
mm则无需切换页表,从而更轻量。 -
隔离性:进程间隔离好,安全性高;线程间共享多,通信快但更容易出现数据竞争。
面试角度补充
-
能用
clone()的标志组合说明进程/线程的资源共享粒度(CLONE_VM,CLONE_FS,CLONE_FILES,CLONE_SIGHAND等)。 -
在嵌入式环境中,常以线程代替多进程以节约内存与上下文切换开销,特殊场景仍用多进程以增加隔离(例如安全沙箱)。
2. pid、tgid、ppid 分别代表什么,有什么区别?
要点
-
PID:每个内核任务(
task_struct)对应的唯一标识(在内核中实际是struct pid *,表示 pid 的 namespace 可扩展)。线程(同一进程内的不同线程)各自有自己的 pid。 -
TGID(Thread Group ID):线程组标识,等于线程组 leader(通常是创建该线程组的最初进程)的 pid。在单线程进程中
pid == tgid。在多线程进程中,所有线程共享同一tgid,但有不同pid。 -
PPID(Parent PID):父进程 ID,指向创建该进程的父任务的 pid(对于线程,父进程通常是创建线程的进程/线程)。
源码/路径
-
task_struct包含pid的引用与tgid字段(include/linux/sched.h)。 -
/proc/[pid]/status可以看到Pid、PPid、Tgid。
面试常问
-
怎样在
/proc下验证tgid与pid的区别?(看/proc/[tgid]/task/下的 entries) -
调度时使用 pid 还是 tgid?(调度以
task_struct为基本单位,也即以 pid 表示的任务;tgid 主要用于表示线程组和与用户态工具兼容性)
3. 线程是如何在 Linux 内核中实现的?
要点
-
Linux 把线程和进程同等对待:每个线程对应一个
task_struct,即调度实体。线程组通过tgid联系起来。 -
内核通过
clone()系统调用创建线程/进程,clone()的标志决定了资源共享的粒度(CLONE_VM共享地址空间、CLONE_FILES共享文件表、CLONE_SIGHAND共享信号表等)。 -
用户态线程库(如 pthread)在内核中映射为内核线程(1:1),即每个 POSIX 线程映射到一个内核线程(Linux NPTL)。
实现细节
-
do_fork()/copy_process()负责创建新的task_struct,根据 clone 参数设置flags,决定哪些copy_*函数会被调用或共享。 -
exit/exit_group区分:线程组内线程退出与整个进程组退出(exit()只是退出线程,exit_group()终止整个线程组)。
4. 用户态线程和内核态线程的区别?
要点
-
用户态线程(user-level threads):由线程库在用户空间管理,内核不可见(常见于早期 M:N 实现)。上下文切换在用户态完成,快速但阻塞系统调用会阻塞整个进程。
-
内核态线程(kernel-level threads):内核知道每个线程并直接调度(Linux 的实现)。阻塞不会影响其他线程,支持多核真正并行。
面试补充
-
为什么 Linux 采用 1:1 模型(N:1 在现代多核场景不可行)?因为多核需要内核级的调度与资源管理,且用户级线程阻塞在系统调用时会阻塞整个进程。
5. 轻量级进程(LWP)的概念和实现方式
要点
-
LWP(Light Weight Process)通常指代“线程”或“共享大部分资源的进程变体”,在不同系统中表述不一。Linux 中可理解为使用
clone()创建的线程(共享mm、files等)。 -
LWP 提供比传统进程更小的创建/切换开销,但保留一定的隔离性。
6. 嵌入式系统中为什么常用线程替代多进程?
要点
-
嵌入式设备往往内存受限,线程共享
mm、files等可显著节省内存开销。 -
线程切换更轻,实时性好(尤其是共享
mm时,无需切换页表)。 -
但线程共享导致容错性差,开发复杂(需更多锁保护),安全隔离差——在安全/隔离要求高时仍会用进程。
二、进程状态与 ps/top 分析
7. ps 输出中的 R、S、D、T、Z 各是什么意思?
答案
-
R(Running or Runnable):正在运行或可运行(在 runqueue 上)。 -
S(Interruptible sleep):可中断睡眠,等待事件或信号唤醒(常见于等待 IO、信号)。 -
D(Uninterruptible sleep):不可中断睡眠,通常等待硬件/IO,不能被信号唤醒(常见于磁盘操作、某些驱动)。 -
T(Stopped):停止,可能由SIGSTOP或调试器造成。 -
Z(Zombie):僵尸进程,已退出但父进程尚未回收。
调试命令
-
ps -o pid,stat,cmd,wchan:wchan显示正在等待的内核函数名。 -
cat /proc/[pid]/stack:查看内核堆栈(如果内核配置允许)。
面试深挖
-
D状态不能被kill -9杀死,为什么?因为SIGKILL只是设置 pending 信号,进程在TASK_UNINTERRUPTIBLE状态不检查信号直到返回,可通过找到阻塞点修复驱动或硬件问题。 -
嵌入式中
D状态占用 CPU/资源如何影响系统稳定性及排查方法。
8. D 状态进程为什么不能被 kill -9 杀掉?
答案
-
kill -9(SIGKILL)是不可忽略的信号,但它的递送仍需进程返回内核可处理信号的点。TASK_UNINTERRUPTIBLE(D)状态的进程在阻塞内核态等待 I/O 或硬件响应,此时内核不会处理信号,直到系统调用返回或等待被解除。 -
因此
SIGKILL会被记录为 pending,但不能立即终止目标任务,必须修复底层阻塞(如驱动故障、挂起的 DMA、NFS 问题)或等待硬件超时/完成。
排查方法
-
ps -o pid,stat,wchan,cmd+/proc/[pid]/stack查阻塞的内核函数与调用栈。 -
检查相关驱动、设备链路(例如 SATA / NFS),参考内核日志(
dmesg)是否有 I/O 错误。
9. 僵尸进程是怎么产生的?如何处理?
原理
-
进程调用
exit()后会释放大部分资源,但内核保留少量信息(退出码、PID 等)直到父进程调用wait()/waitpid()收集子进程状态。若父进程没有回收,子进程变为僵尸(Z)。 -
僵尸本身占用很少内存,但占用 PID。大量僵尸可能耗尽可用 PID,导致系统无法创建新进程。
处理方法
-
最直接:让父进程调用
wait()/waitpid()或实现 SIGCHLD 处理。 -
若父进程死掉,僵尸会被
init(或系统的 PID 1)收养,init会自动调用wait回收。 -
临时手段:若无法修改父进程,重启父进程或杀掉父进程(
kill -9 parent,但是如果父是关键进程需谨慎)。
面试细节
-
讲出
do_exit()、exit_notify()、wait的源码调用路径(kernel/exit.c、kernel/fork.c等)。 -
说出
SIGCHLD與SA_NOCLDWAIT的用法(不想回收时可以设置SA_NOCLDWAIT)。
10. top 命令中 %CPU、%MEM 的含义
%CPU
-
表示该进程使用的 CPU 时间占整个 CPU 的百分比(在多核系统上,取值可超过 100% 表示多核并行使用)。不同 top 实现计算方式略有不同(采样时间窗口)。
%MEM -
表示进程占用物理内存(RSS)与系统总物理内存的百分比。RSS(Resident Set Size)是进程实际驻留在 RAM 的页数,不包括换出的页。
面试提示 -
解释 RSS 与 VIRT(虚拟大小)和 SHR(共享内存大小)的区别。
ps aux中常见字段:VSZ(虚拟内存大小)、RSS。 -
讨论 PSS(Proportional Set Size)在分摊共享库内存时的优势(更精确反映进程真实占用)。
11. STAT 字段里的 <、N、L、+ 分别代表什么?
含义(ps/top 的标志)
-
<:high-priority(非抢占,通常是实时优先级或较高优先权)。 -
N:low-priority(nice 值为正,即降低优先级)。 -
L:has pages locked into memory(mlock 等调用导致页面被锁定)。 -
+:位于前台进程组(foreground process group)。
面试要点
-
能从
ps的STAT字段解读进程的优先级与运行上下文(如实时进程<、锁页L)。
三、进程创建与退出
12. fork / vfork / clone 区别和使用场景
fork()
-
创建子进程,父子进程拥有独立的地址空间(初始共享物理页,通过 COW 实现),父子各自拷贝
task_struct的大部分信息。父/子都从fork()返回(父返回子 pid,子返回 0)。
vfork() -
传统上
vfork()创建子进程后,父进程挂起(直到子调用exec或exit),子与父共享地址空间(避免复制页表),用于子紧接着exec的场景以提高效率。但需小心:子不能修改父进程的数据或返回。现代实现中posix_spawn/clone可替代vfork。
clone() -
提供按标志选择性共享资源(
CLONE_VM、CLONE_FILES、CLONE_SIGHAND等),可实现线程(共享mm)或进程(不同mm),是更底层、更灵活的接口。
使用场景
-
想复制整个进程:
fork()(常见) -
子进程马上
exec(),希望减少开销:vfork()或posix_spawn更安全 -
创建线程或细粒度共享:
clone()。
面试追问
-
vfork 的危险性与替代方案(推荐使用 posix_spawn 或直接使用线程库)。
13. fork 为什么要用写时复制(COW)?
原理和目的
-
写时复制(Copy-On-Write, COW)在
fork()时不会立即把父进程所有页复制给子进程,而是让父子共享同一物理页并将页表设为只读;当任一进程写入时才触发缺页异常,内核分配新的物理页并复制内容。
优点 -
大幅降低内存消耗和
fork()的开销(尤其是父进程后续exec()的常见模式)。
实现细节 -
在
copy_mm()中构建新的mm_struct时,实际只是复制 VMA 列表和页表引用计数。页表条目更新为只读并设置 COW 标志。
面试延伸 -
如何处理 COW 与内存保护(
mprotect)、如何在 NUMA 系统上影响性能。 -
fork()后儿子马上exec()的优化(vfork、posix_spawn)。
14. exec 系列函数和 fork 的关系
要点
-
execve()替换当前进程的用户空间映像(代码段、数据段、堆、栈),但保留 PID、文件描述符(除非标记FD_CLOEXEC)、某些信号处理状态等。exec系列函数用于在现有进程中载入并运行新的程序。 -
常见模式:父进程
fork()出子进程,子进程exec()新程序——这是创建新进程并运行指定新程序的标准方式。
面试追问 -
exec如何影响文件描述符(FD_CLOEXEC)与信号处理(默认信号处理器是否保留)?(SIG_IGN等的行为)
15. 进程结束的完整流程(do_exit → exit_mm → exit_files → exit_signal)
调用路径(高层)
-
用户态进程调用
exit(status),内核执行do_exit()(kernel/exit.c)。do_exit()负责:-
清理地址空间:
exit_mm()(释放mm_struct、页表、VMAs) -
关闭文件描述符:
exit_files()(释放files_struct) -
清理信号:
exit_signals()、exit_task_work()等 -
通知父进程并设置子为僵尸(等待父回收)或直接唤醒
wait等。 -
最后调用
schedule()切换到其他任务并完成release_task()。
-
面试补充
-
讲明
exit_group()与do_exit()的区别(exit_group()会终止线程组,即整个“进程”的所有线程)。 -
do_exit()执行过程中需要考虑锁顺序、避免死锁(内核设计细节)。
16. 父进程如何回收子进程资源?
机制
-
父进程调用
wait()、waitpid()或waitid()。内核wait系列函数会查找子进程的退出状态、回收task_struct和其他内核资源,返回子进程退出码。 -
如果父进程没有回收,子进程变僵尸。若父进程终止,子进程会被
init(或 PID 1)收养,然后init会回收。
面试补充
-
讲清
wait的非阻塞版本(WNOHANG)与如何通过SIGCHLD自动处理子进程终止事件(sigaction、SA_NOCLDSTOP)。 -
解释
waitid()返回更详细信息(如siginfo_t)。
17. wait / waitpid / waitid 的区别
区别总结
-
wait():阻塞等待任一子进程结束,只返回子进程的 pid。 -
waitpid(pid, &status, options):可指定 pid(某一子进程 / 子进程组 / 任一子进程),并支持非阻塞(WNOHANG)或选项(WUNTRACED等)。 -
waitid(idtype, id, infop, options):更丰富,使用idtype指定P_PID/P_PGID/P_ALL,返回siginfo_t,更适合高级程序。
面试要点
-
能描述
WNOHANG、WUNTRACED、WCONTINUED的含义与用法。
四、进程调度与上下文切换
18. 什么是上下文切换?需要保存哪些内容?
定义
-
上下文切换是 CPU 从运行一个任务(进程/线程)切换到另一个任务的过程。要保存当前任务的执行状态以便将来恢复,并加载新任务的状态。
必须保存的内容 -
CPU 寄存器(通用寄存器、程序计数器 PC、栈指针 SP)
-
内核栈指针(每个线程/进程的内核栈)
-
进程控制块(task_struct)中的上下文(优先级、调度实体、状态)
-
页表基址寄存器(如 x86 的 CR3、ARM 的 TTBR)——只有在
mm不共享时需要切换(switch_mm()) -
浮点/向量寄存器(如需要保存 FP/SIMD 状态)
可不保存或共享的内容 -
如果线程共享
mm(CLONE_VM),无需切换页表。
实现路径 -
schedule()→context_switch()(kernel/sched/core.c)→switch_mm()→switch_to()(arch 相关汇编)完成寄存器保存/恢复。
面试延伸
-
解释上下文切换会导致 TLB 失效(如果切换页表),及对性能的影响。
19. 进程上下文切换和线程上下文切换的区别
要点
-
线程上下文切换(线程共享
mm)通常只需要保存/恢复寄存器和内核栈,无需切换页表、无须刷新 TLB(减少开销)。 -
进程上下文切换(不同
mm)需切换页表基址寄存器(CR3/TTBR)、刷新 TLB(或使用 ASID),额外开销较大。
面试补充
-
在面试中举例说明为何多线程适合高并发短任务场景:减少页表切换与内存复制。
20. Linux 调度器的调度类有哪些?(CFS、RT、Deadline)
主要调度类
-
CFS(Completely Fair Scheduler):默认的公平调度,用红黑树维护运行队列,目标是公平分配 CPU 时间。
-
RT(Realtime, SCHED_FIFO, SCHED_RR):实时调度类,优先级更高,调度基于固定优先级(
SCHED_FIFO无时间片、SCHED_RR有轮转时间片)。 -
Deadline(SCHED_DEADLINE):基于实时调度算法(Earliest Deadline First),适合有明确截止时间的任务。
面试延伸
-
说明 CFS 用
vruntime来衡量任务执行时间,如何给出合适的惩罚/补偿机制。 -
嵌入式系统中可能只开启 RT,禁用或裁剪 CFS 以降低复杂度。
21. 时间片是如何分配的?
CFS
-
CFS 并不直接使用固定时间片概念,而是使用
vruntime值(虚拟运行时间),允许在统计上公平分配 CPU。任务运行导致vruntime增加,调度决定选vruntime最小的任务运行。
RT(SCHED_RR) -
使用固定时间片(由系统或用户配置)并轮转。
SCHED_FIFO不使用时间片,只在更高优先的任务出现或任务自愿让出时发生切换。
面试要点
-
能解释
nice值如何影响 CFS 的权重与vruntime增长速度(较低 priority 导致vruntime增长更快,从而使调度器更快地让出 CPU)。
22. 调度延迟(latency)和吞吐量(throughput)的权衡
概念
-
调度延迟:从任务可运行(就绪)到真正获得 CPU 的延迟。实时性要求低延迟。
-
吞吐量:单位时间内完成工作的数量,关注总体系统产出。
权衡点 -
更短的时间片或更激进的抢占可以降低延迟,但会增大上下文切换次数,降低吞吐量与增加开销。
-
CFS 的设计就是在公平性与响应性间的折中。实时场景中选择 RT 策略以保障低延迟,但可能牺牲其他任务的吞吐量。
面试建议
-
描述具体优化思路:减小调度延迟(优先级调整、使用 RT 策略、减少锁争用)、提高吞吐量(批处理、减少上下文切换、合并 I/O)。
23. 嵌入式系统中如何减少上下文切换次数?
措施
-
使用线程而非进程(共享
mm)以避免页表切换。 -
采用合适的优先级策略,将频繁交互的小任务放在同一线程里。
-
减少锁的持有时间、使用无锁队列或更细粒度锁以避免互斥等待导致调度切换。
-
用中断处理做必要的快速路径,把耗时操作放到工作队列或线程中(避免在中断上下文进行重工作)。
面试亮点
-
能举出具体场景与数据:例如每减少一次上下文切换节约的 CPU 周期范围(根据架构),以及如何测量(
perf stat、ftracesched_switch事件)。
24. 软中断、硬中断与进程调度的关系
简介
-
硬中断(Hardware IRQ):CPU 直接响应外设中断,中断处理在中断上下文执行,优先级高,不能睡眠,不会触发正常的任务上下文切换(但可能设置需要调度的标志)。
-
软中断(SoftIRQ)/任务let/Workqueue:是下半部,处理需在软中断或工作队列中完成的工作。软中断也在特殊上下文运行,影响调度。
与调度的关系 -
中断处理可能会唤醒某些等待的进程(通过
wake_up()),从而使调度器在中断返回时选择新任务。 -
如果中断处理耗时过长,会延迟调度和任务响应,影响系统实时性。通常把耗时工作分发到工作队列(可以 sleep 的上下文)以减轻中断处理。
面试补充
-
描述中断屏蔽、临界区、软中断风暴(softirq 过载)对系统的影响以及定位方法(
/proc/softirqs、top -i、perf)。
五、进程间通信(IPC)
25. 管道(pipe、FIFO)实现原理和使用场景
实现
-
pipe():创建匿名管道,内核为管道分配缓冲区(环形缓冲区),读写通过内核拷贝或直接内存映射(splice)实现。适用于父子或相关进程间简单数据流传输。
-
FIFO(named pipe):同 pipe,但有文件系统入口(可跨不相关进程)。
使用场景 -
用于流式、半双工或全双工通信(两个方向各一个 pipe),简单且低延迟。
面试补充
-
细说
pipe()内核实现(pipefs/pipe的底层结构)、阻塞/非阻塞行为、O_NONBLOCK、如何处理 EOF 与 broken pipe(SIGPIPE)。
26. System V IPC(消息队列、共享内存、信号量)区别
概览
-
消息队列(msgget/msgsnd/msgrcv):内核维护队列,进程通过消息类型读写,适合短消息传递与排队。
-
共享内存(shmget/shmat):进程共享一片内存区域,最快的 IPC,但需要同步机制(信号量/自旋锁)来避免竞态。
-
信号量(semget/semop):用于同步与互斥,多进程/多线程可用。
面试点
-
优劣比较:共享内存最快但最危险(需要同步);消息队列内核控制更方便但涉及拷贝;信号量适合同步。
-
实战中,嵌入式设备上通常用共享内存 + ring buffer + lock-free 技术以获得最高性能。
27. POSIX IPC 和 System V IPC 的差异
差异
-
命名与接口风格:POSIX(
shm_open/shm_unlink,mq_open等)接口更现代、语义清晰且支持文件描述符语义;System V 使用 key/id 机制。 -
可移植与兼容性:POSIX 更 POSIX 标准化,System V 是历史遗留。
-
权限与生命周期:两者在权限控制与内核对象生命周期上有差异(POSIX 对象可以被
unlink)。
面试提示
-
如果项目需要跨平台或符合现代接口,优先选 POSIX IPC。
28. socket 在本地进程通信中的作用
Unix Domain Socket
-
Unix domain socket(
AF_UNIX)提供本机进程间高性能双向通信,支持sendmsg、recvmsg,可以传递文件描述符(ancillary data),比 TCP 更高效且无需网络栈开销。
面试要点 -
说出何时用 Unix socket:需要双向连接、需要传 FD、需要安全性与权限检查(通过文件系统路径的权限)。
29. 嵌入式设备上如何选择合适的 IPC 机制?
依据
-
延迟敏感:选择共享内存 + lock-free ring-buffer(最低延迟)。
-
可靠性/隔离:选择消息队列或 Unix domain socket(内核管理,易于处理错误)。
-
资源受限:避免内核分配大量缓冲区(如大型 message queues),考虑轻量 ring buffer。
面试示例 -
给出某个摄像头数据传输场景:相机线程写帧到共享内存,消费者通过条件变量/事件读取并处理;如果需要复用/传 FD,则用 Unix socket。
30. 共享内存如何避免竞争?(锁、原子操作)
方法
-
互斥锁(mutex):进程间可用 futex、pthread mutex(需设置为进程间可共享);适合复杂同步。
-
原子操作/无锁环形缓冲区:使用原子读写索引(
__sync_*或atomic_t),避免锁开销,适合单生产者单消费者或用 memory barriers 做多生产者/多消费者。 -
信号量:System V/POSIX 信号量用于进程间同步,适合复杂场景。
面试加分 -
能写出一个单生产者单消费者 ring-buffer 的伪代码,说明为什么无锁是安全的(只要遵守内存屏障/顺序原则)。
六、信号与异常处理
31. 信号的产生、传递和处理流程
流程
-
产生:内核事件(如
SIGCHLD)、硬件异常、其他进程kill()、用户按键(Ctrl-C)等。 -
传递:内核将信号放入目标进程的 pending 信号集(
task_struct->pending),或对线程组广播。 -
处理:当进程从内核态返回用户态或在特定检查点时,内核检查 pending 信号并执行相应动作(调用信号处理函数、忽略、或终止进程)。实时信号队列按照优先级排列。
面试要点
-
说明信号是异步事件,处理时需要注意 reentrancy、安全性(在 signal handler 中能调用的函数有限,称为 async-signal-safe)。
32. 常见信号及其作用(SIGKILL、SIGSTOP、SIGCHLD…)
常见信号
-
SIGKILL:立即终止(不能被捕获/忽略)。 -
SIGSTOP:停止进程(不能被捕获/忽略)。 -
SIGTERM:请求终止(可捕获,允许清理)。 -
SIGCHLD:子进程状态改变时发送给父进程。 -
SIGSEGV:段错误(非法内存访问)。
面试追问 -
你如何正确处理子进程退出以避免僵尸(使用
sigaction+SA_NOCLDWAIT或在 handler 中循环waitpid)?
33. 信号是异步的还是同步的?
说明
-
异步信号:由外部事件触发(如
kill()、Ctrl-C、硬件中断)——通常被称为异步信号。 -
同步信号:由进程自身的动作产生(如
SIGFPE、SIGSEGV、SIGBUS)——由硬件异常或错误导致。
面试点 -
能解释为何异步信号处理更复杂(race condition、重入问题),并给出处理建议(最小处理、设置标志、在主循环中处理)。
34. sigaction 和 signal 的区别
区别
-
sigaction是更现代、可重入和功能更强的接口,支持详细配置(sa_flags、sa_mask、可接收siginfo_t的SA_SIGINFO)。 -
signal()行为依赖实现,历史上在不同 Unix 实现中语义不一(是否重置处理器)。推荐使用sigaction。
面试补充
-
举例
SA_RESTART的作用(使某些系统调用在信号返回后自动重启)。
35. 如何阻塞/屏蔽某些信号?
方法
-
在用户态使用
sigprocmask()或pthread_sigmask()阻塞信号(常先在主线程阻塞,然后创建线程),用sigsuspend()暂时等待非屏蔽信号。 -
通过
sigaction设置处理函数和阻塞掩码(sa_mask)在处理一个信号时屏蔽其它信号。
面试亮点
-
讲出常见做法:在多线程程序中通常在主线程屏蔽所有信号,然后创建一个专门的“信号处理线程”来接收
sigwaitinfo()。
36. 内核是如何将信号送达进程的?
机制
-
内核将信号插入
task_struct->pending的信号位图或实时信号的队列。 -
当进程即将从内核返回用户态(或在某些检查点)时,内核调用
handle_signal()(或相关函数)来投递信号:修改用户栈,创建sigframe,把用户空间的程序计数器指向信号处理函数。对于实时信号还会传递siginfo_t。
面试细节 -
能描述
sigreturn的作用(用户处理完信号后调用以恢复上下文)。
七、内核数据结构与源码实现
37. task_struct 主要字段及作用
关键字段
-
pid/tgid/tgid:任务标识。 -
state:任务状态(TASK_RUNNING,TASK_INTERRUPTIBLE,TASK_UNINTERRUPTIBLE等)。 -
mm:mm_struct *(NULL 表示内核线程),进程的地址空间描述。 -
files:files_struct *,文件描述符表。 -
signal:信号处理/信号队列结构指针(signal_struct或signal_handlers)。 -
sched_entity:调度器所需信息(CFS 的se等)。 -
stack/thread_info:线程相关的低层信息。
源码
-
include/linux/sched.h定义了task_struct。
38. mm_struct、files_struct、signal_struct 的作用
mm_struct:描述虚拟地址空间(页表基、VMA 列表 vm_area_struct 等)。
files_struct:持有进程的文件描述符表与引用计数,便于 fork 时复制或共享。
signal_struct / sighand:管理信号处理函数、pending 信号等。
面试要点
-
在多线程时
mm_struct是否共享(CLONE_VM)决定是否需要切换页表。
39. thread_info 的位置(栈底/栈顶)和作用
作用
-
thread_info保存线程的低层信息,如 flags、指向task_struct的指针、系统调用相关短期变量等。
位置 -
传统上
thread_info放在内核栈底部(便于在异常/中断时快速获得当前thread_info),但在后来的内核中有变化(一些架构将thread_info与task_struct分离,依赖 CPU 寄存器或current宏)。
面试细节
-
说明
current宏如何实现:在一些架构上通过栈指针与thread_info的已知偏移获得task_struct。
40. task_struct 是如何链接成调度队列的?
实现
-
task_struct包含struct list_head或树/链表节点用来加入 runqueue(如 CFS 红黑树,RT 使用链表)。调度器维护 runqueue、各优先级队列与调度类的数据结构来选择下一个任务。
面试要点
-
能指出 CFS 使用红黑树(
struct rb_root)来维护se->vruntime的顺序,pick_next_task_fair从左端取最小vruntime。
41. 运行队列(runqueue)的实现方式
CFS
-
每个 CPU 有一个 runqueue(
rq),包含 CFS 的红黑树、RT 的队列、延迟计时等。rq在多核场景下需要并发访问保护(自旋锁)。
面试细节
-
rq的锁策略与分解(per CPU runqueue,以减少跨 CPU 竞争),以及 load balancing(load balancing 工作线程)在多核环境中如何迁移任务。
42. 如何通过 /proc 读取内核中的进程信息?
常见文件
-
/proc/[pid]/status:可读的状态摘要(Pid/Tgid/State/VMSize/…)。 -
/proc/[pid]/stat:单行详细统计(解析复杂)。 -
/proc/[pid]/smaps:详细内存使用(按 VMA 列出 RSS/PSS)。 -
/proc/[pid]/stack:内核堆栈(受限)。
面试示例 -
用
/proc信息结合ps/top来定位问题:ps -eo pid,ppid,stat,wchan,cmd。
八、调试与性能优化
43. 如何定位 D 状态进程的阻塞点?
步骤
-
ps -o pid,stat,wchan,cmd:wchan给出等待的内核函数名。 -
cat /proc/[pid]/stack:查看内核堆栈(如内核配置允许)。 -
dmesg或journalctl:查找相关 I/O 错误或驱动日志。 -
如果涉及设备,检查驱动代码/硬件链路(DMA、PHY、总线)。
示例
-
wchan显示为blk_wait_for_request或wait_for_completion等,可能是磁盘或驱动问题。
面试补充
-
说明如何在驱动中增加超时/错误恢复,或在用户态添加超时逻辑以避免永久 D。
44. 如何减少僵尸进程出现?
措施
-
父进程显式
waitpid()或处理SIGCHLD:在SIGCHLDhandler 中循环waitpid(-1, &status, WNOHANG)回收所有子。 -
如果不关心子退出码,设置
SA_NOCLDWAIT在sigaction中(使内核自动回收)。 -
设计父进程应尽量避免长时间不处理子退出(例如使用事件驱动或专门线程处理子状态)。
面试点
-
给出
SIGCHLD信号处理示例代码并说明 reentrancy 风险。
45. 如何分析进程的内存占用(pmap、smaps)?
工具
-
pmap -x PID(显示各 VMA 的 RSS/VSZ) -
cat /proc/PID/smaps:分段详细信息(RSS、PSS、Shared_Clean/Dirty) -
smem:生成更好的 PSS 报表(可分摊共享库)
面试补充 -
解释 PSS(按比例分配共享内存)比 RSS 更能反映实际内存占用,适合多进程共享相同库时的分析。
46. 如何分析进程的 CPU 占用(perf、ftrace)?
工具与方法
-
perf top:实时热点分析(函数级别)。 -
perf record -g+perf report:采样堆栈,定位热点调用路径。 -
ftrace:内核级函数跟踪,可跟踪sched_switch、syscalls 等事件(/sys/kernel/debug/tracing)。 -
pidstat、top -H查看线程级 CPU 占用。
面试补充
-
解释采样法与插装法的差异,何时用
perf(采样)何时用ftrace(事件驱动)。
47. 嵌入式设备上减少 fork 带来的内存浪费的方法
方法
-
替换
fork()→vfork()或更安全的posix_spawn(),若子马上exec,避免复制页表。 -
使用线程(
clone(CLONE_VM))而非多进程。 -
设计长生命周期守护进程,不频繁创建新进程。
-
使用预分配的工作线程/线程池模式来处理短任务。
面试亮点
-
讲述在内存紧张的环境中,如何衡量并选择替代方案(测量 COW 页数、页表开销、进行 meminfo/ps 的前后对比)。
48. 如何用 strace 调试进程行为?
用途
-
strace跟踪系统调用,查看程序做了哪些系统调用、参数和返回值(例如 open/read/write、fork/exec、ioctl)。
实战 -
用
strace -f跟踪子进程,-o输出到文件,-e trace=file过滤文件相关调用。
面试补充 -
解释
strace对性能有影响(插装开销),在产线系统慎用。
49. 如何用 gdb 调试多线程程序?
技巧
-
使用
gdb的info threads列出线程,thread <id>切换线程,使用bt打印线程堆栈。 -
设置
set follow-fork-mode child或parent控制fork后的调试目标。 -
当有 core dump 时,用
gdb加载 core 文件:gdb /path/to/exe core分析当时状态。
面试补充
-
说明用
gdbserver+gdb在嵌入式板子上远程调试的流程。
九、嵌入式场景特有问题
50. 为什么嵌入式 Linux 中常裁剪掉 CFS 调度器?
原因
-
一些嵌入式/实时系统只需要简单、确定的实时调度(如
SCHED_FIFO/SCHED_RR),CFS 的复杂性和公平性机制在严格资源/实时需求下多余且占用代码/内存。 -
为减少内核体积与潜在不可预知延迟,定制内核可能只保留实时调度或更简单的调度策略。
面试补充
-
讨论 trade-off:去掉 CFS 导致普通任务饥饿的风险,如何通过限定优先级策略和守护平衡。
51. 如何在无 MMU 系统中模拟进程管理?
要点
-
无 MMU(无虚拟内存)系统不能支持传统的进程隔离。通常采用:
-
使用 RTOS(线程/任务模型)代替 Linux 进程模型;
-
或在 Linux 上使用
uClinux(支持无 MMU 的 Linux 变体),但进程内存隔离弱,需谨慎。
面试补充
-
-
讨论无 MMU 的限制:无法实现 COW、无法每进程独立页表、信任应用层隔离机制或使用硬件 MPU(Memory Protection Unit)做分区保护。
52. 嵌入式实时任务如何保证优先级不被打断?
方法
-
使用实时调度策略(
SCHED_FIFO或SCHED_RR),给予任务最高实时优先级(系统必须谨慎设置以免饿死普通任务)。 -
在设计中尽量减少阻塞点(避免持有锁时间过长、减少 I/O 阻塞),使用中断优先级与中断屏蔽策略。
-
使用实时内核补丁(PREEMPT_RT)以优化延迟与抢占行为。
面试要点
-
解释优先级反转问题与解决(priority inheritance / priority ceiling)。
53. OOM Killer 触发的条件及嵌入式如何避免
触发条件
-
当系统物理内存耗尽且无法回收足够内存时,内核触发 OOM killer,选择某些进程终止以回收内存(选择依据是 oom_score/oom_adj、RSS、进程重要性)。
避免措施 -
使用
cgroups限制进程内存使用; -
设置
oom_kill_allocating_task/oom_score_adj保护关键进程; -
优化程序内存使用(避免内存泄漏、使用内存池);
-
在嵌入式设置交换区(swap)与合理内存限制(但 swap 在闪存上会影响寿命)。
面试补充
-
说明如何通过
dmesg查看 OOM 日志与oom_reaper的作用。
54. 如何限制进程的内存/CPU 占用(cgroups/rlimit)?
方法
-
rlimit(setrlimit):每进程或进程组的资源限制(
RLIMIT_AS、RLIMIT_CPU等),进程自主调用设置或由父进程设置。 -
cgroups(control groups):更强大、层级化的系统资源控制(memory、cpu、cpuset、blkio 等),适合容器/嵌入式资源隔离。
面试示例 -
用
systemd或cgcreate/cgset配置 cgroups,限制内存并设置 OOM 行为。
55. watchdog 与进程管理的关系
要点
-
Watchdog(看门狗)用来检测系统或关键进程是否卡死并做重启或恢复动作。通常嵌入式将关键守护进程绑定到 watchdog,定时喂狗;若进程挂起或系统不可响应,则 watchdog 超时自动重启。
面试补充 -
设计实践:守护进程做健康检查、监控关键子进程并在异常时清理并重启,避免过度依赖硬重启,增加软恢复策略。
总结与面试实战建议(简短清单)
-
面试回答结构:先给定义 → 再给流程/实现 → 最后说定位/工程实践(体现实战能力)。
-
源码背书:常引
kernel/fork.c、kernel/sched/core.c、include/linux/sched.h、arch 下的switch_to、mm/mmap.c等路径,会显得回答可信。 -
调试工具:
ps/top、/proc/*、strace、gdb/gdbserver、perf、ftrace、dmesg,能给出具体命令与输出解读。 -
嵌入式要点:内存受限、实时性、驱动层问题(导致
D状态)、是否有 MMU、是否需要裁剪内核功能(CFS、文件系统等)。 -
练习题:把上面 55 题做成问答卡片,逐一口述并尝试结合你自己的开发板(例如 QCA9531 或 RK3399)做复现/测量。
如果你希望,我可以把上面每一道题进一步扩展成**源码级别(函数名 + 关键行注释)**的答案,或把其中 10 个“高频深坑题”生成面试模拟问答(我当面试官,你答,我给反馈)。你想先从哪一部分更深一步?
647

被折叠的 条评论
为什么被折叠?



