POSIX 线程与同步原语与 POSIX 信号与进程控制

共 62 题
📑 题目列表 62 题
#
★★★

1. POSIX.1-2017 定义的 pthread_create/pthread_join/pthread_exit 与 Linux 内核 task_struct 的对应

POSIX.1-2017 定义的 pthread_create/pthread_join/pthread_exit 与 Linux 内核 task_struct 如何对应?

  • pthread 线程 API 与内核对线程的表示
  • 用户态线程与内核调度实体
  • clone 系统调用

POSIX 线程(pthread)在 Linux 上由内核线程实现,每个 pthread 对应一个内核的 task_struct(通过 clone 系统调用创建,共享地址空间等资源)。pthread_create 用 clone 创建线程(共享 VMA、文件描述符、信号处理等),返回内核线程;pthread_join 等待线程结束(内核线程退出后回收其资源);pthread_exit 退出线程(内核线程退出,释放资源)。对应关系:用户态 pthread 是内核 task_struct 的封装,pthread 的 tid 即内核线程的 pid(tgid 是进程/task group)。pthread_join 对应内核的 wait/reap 线程的退出状态。

pthread 是 POSIX 线程 API,底层由内核 task_struct 承担,Linux 用 clone 复用进程机制实现线程。pthread_join 回收内核线程资源,理解对应关系便于排查线程问题。

#
★★

2. pthread_key_t 的线程局部存储(TLS)API,pthread_setspecific 与 pthread_getspecific 的用法

pthread_key_t 的线程局部存储(TLS)API:pthread_setspecific 与 pthread_getspecific 如何工作?

  • pthread_key_t 与 TLS
  • setspecific/getspecific
  • 资源清理

pthread_key_t 是线程局部存储(TLS)的键,用 pthread_key_create 创建,每个线程通过该键维护独立的线程局部值。pthread_setspecific 把线程特有的值绑定到键,pthread_getspecific 读取,各线程互不影响。用途:errno、线程自定义上下文、线程私有缓存。pthread_key_create 可注册析构函数(destructor),线程退出时自动清理该键对应的值。工程上:用 TLS 避免全局变量竞争,需注意健析构与线程退出顺序。

TLS 让每线程拥有独立变量,避免共享竞争。pthread_key 是键-值映射,每线程独立,构造函数注册析构函数保证清理。

#
★★

3. pthread_kill 的线程级信号投递与异步信号安全

pthread_kill 的线程级信号投递与异步信号安全如何理解?

  • pthread_kill 线程级信号
  • 异步信号安全
  • 投递目标

pthread_kill 向指定线程投递信号(线程级),而非整个进程(kill 投递到进程,由任意未阻塞线程处理)。用途:向特定线程发信号(如取消、通知)。异步信号安全(async-signal-safe)指信号处理器中可安全调用的函数集合非常有限,因为信号可能打断任意代码,若调用非安全函数(如 malloc、printf、加锁)可能死锁或破坏状态。pthread_kill 的处理器中只能调用异步信号安全函数。工程上:信号处理器中只做简单标记(如 volatile sig_atomic_t),复杂处理放到主逻辑。

pthread_kill 精确投递到线程,但处理器受异步信号安全限制。信号处理器只能调 async-signal-safe 函数,避免非安全调用。

#
★★

4. pthread_setaffinity_np 的 CPU 亲和性绑定

pthread_setaffinity_np 的 CPU 亲和性绑定如何工作?

  • CPU 亲和性
  • pthread_setaffinity_np
  • 应用场景

pthread_setaffinity_np 把线程绑定到指定 CPU 集合(CPU affinity),使线程只在指定 CPU 上运行,避免跨核迁移。用途:减少缓存抖动(线程固定核上保持 L1/L2 命中)、实时任务确定性、NUMA 优化(线程绑定其内存所在节点)。绑定后线程受 CPU 集合限制,若集合内 CPU 繁忙则排队。工程上:关键线程/实时任务绑定核,绑定需注意与 NUMA 配合(线程绑定到内存所在节点);过度绑定可能降低调度灵活性。

CPU 亲和性用"绑核"换取缓存局部性与确定性,代价是调度灵活性。适合关键/实时线程,配合 NUMA 节点优化。

#
★★

5. pthread_setschedparam 的实时线程调度策略(SCHED_FIFO/SCHED_RR)

pthread_setschedparam 的实时线程调度策略(SCHED_FIFO/SCHED_RR)如何理解?

  • 实时调度策略
  • SCHED_FIFO/SCHED_RR
  • 优先级

pthread_setschedparam 设置线程的调度策略与优先级。SCHED_FIFO(先入先出实时)——优先级高的实时线程优先运行,同优先级 FIFO 排队,不时间片轮转(除非阻塞/让出),适合紧急任务;SCHED_RR(轮转实时)——同优先级实时线程按时间片轮转,公平共享 CPU,适合多个同优先级实时任务。实时策略优先级高于普通 SCHED_OTHER。工程上:实时线程用 SCHED_FIFO/SCHED_RR 需高权限(root/CAP_SYS_NICE),设置不当会饿死其他线程,需谨慎(优先级合理、避免死循环)。

SCHED_FIFO 无时间片抢占(高优先级持续运行),SCHED_RR 同优先级轮转。实时策略保确定性但需防止饿死与死循环。

#
★★

6. pthread 的 robust mutex(ROBUST)与进程间共享锁恢复

pthread 的 robust mutex(ROBUST)与进程间共享锁恢复如何工作?

  • robust mutex 概念
  • 进程崩溃恢复
  • PTHREAD_MUTEX_ROBUST

robust mutex(健壮互斥锁)用于进程间共享锁(通过共享内存的 pthread_mutex),普通 mutex 若持有者进程崩溃,锁会永久卡死(其他进程死锁)。robust mutex 在持有者崩溃时,内核令下一个获取者收到 EOWNERDEAD 错误,并允许其恢复锁(清理共享状态后 pthread_mutex_consistent 使锁恢复可用)。工程上:进程间共享锁用 PTHREAD_MUTEX_ROBUST,处理 EOWNERDEAD 时清理崩溃遗留的共享数据并恢复,避免共享锁被永久占用。

robust mutex 解决"进程崩溃后共享锁卡死"。EOWNERDEAD 提示接管已死持有者的锁,清理后用 pthread_mutex_consistent 恢复。

#
★★

7. pthread_atfork 的 fork handler 与多线程 fork 的死锁风险

pthread_atfork 的 fork handler 与多线程 fork 的死锁风险如何理解?

  • 多线程 fork
  • pthread_atfork
  • 死锁风险

多线程进程中 fork 时,子进程只复制调用线程(其他线程消失),但继承的锁状态可能被其他线程持有(此时该线程不存在了),导致子进程内锁被"永久持有"而死锁。pthread_atfork 注册 prepare/parent/child 三个 handler:prepare 在 fork 前调用(获取所有锁)、parent 在父进程 fork 后调用(释放锁)、child 在子进程 fork 后调用(释放锁),保证 fork 时锁状态一致。工程上:多线程程序 fork 前用 pthread_atfork 保护锁,或用 posix_spawn 替代 fork(更安全)。

多线程 fork 的死锁风险是"锁被消失的线程持有"。pthread_atfork 的 prepare/parent/child 钩子在 fork 前后统一锁状态,避免死锁。

#
★★

8. fork() 的写时复制(COW)与内存共享边界

fork() 的写时复制(COW)与内存共享边界如何理解?

  • fork 与 COW
  • 内存共享边界
  • 性能优化

fork() 创建子进程时,父进程的页表被复制,但物理页通过写时复制(COW)共享——父子的内存页最初共享同一物理页,标记为只读,任一进程写时才复制页并分配新物理页。这避免了 fork 时复制全部内存,使 fork 快速、内存占用低。共享边界:fork 后父子共享内存页(只读),直到写入才分离(COW);未写出的部分保持共享。工程上:fork 依赖 COW 高效,但若 fork 后大量写会触发大量页复制(成本高);频繁 fork 的进程注意 COW 触发成本,可用 vfork/posix_spawn。

COW 让 fork 只复制页表不复制物理页,写得越多复制越多。共享边界是"只读共享,写时分离"。

#
★★

9. fork+exec 的进程创建模式 vs posix_spawn 的单次系统调用

fork+exec 的进程创建模式 vs posix_spawn 的单次系统调用有何区别?

  • fork+exec 模式
  • posix_spawn
  • 单次调用

fork+exec 是传统进程创建:fork 复制当前进程,再 exec 以新程序替换,两次系统调用(fork + exec),中间短暂存在"复制后的中间态"。posix_spawn 是 POSIX 1003.1 提供的单次进程创建+执行接口,内部可优化为一次调用(无需先复制再替换),尤其适合多线程环境(避免 fork 复制所有线程死锁风险)与轻量进程创建。工程上:posix_spawn 更安全(多线程)、更高效(避免 fork 的 COW 浪费),但灵活性略低于 fork+exec(需用 file action 和 attribute 配置)。现代系统用 posix_spawn 替代 fork+exec 创建外部进程。

posix_spawn 把"fork+exec"封装为一次调用,避免多线程 fork 死锁风险与 COW 浪费,是更安全的进程创建方式。

#
★★

10. waitpid 与 WNOHANG、WUNTRACED 标志的非阻塞子进程管理

waitpid 与 WNOHANG、WUNTRACED 标志如何实现非阻塞子进程管理?

  • waitpid 功能
  • WNOHANG/WUNTRACED
  • 非阻塞管理

waitpid 等待子进程状态变化(退出、停止、继续),选项控制行为:WNOHANG——非阻塞,若子进程未退出则立即返回 0(不阻塞),用于轮询子进程状态;WUNTRACED——报告已停止(在信号下暂停)的子进程状态,用于调试/信号停止的子进程。组合实现非阻塞子进程管理:用 WNOHANG 轮询子进程是否退出,配合 WUNTRACED 捕获停止状态,在事件循环中管理多个子进程。工程上:非阻塞 wait 配合信号/SIGCHLD 或事件循环管理子进程,避免阻塞等待。

WNOHANG 让 wait 不阻塞(轮询),WUNTRACED 捕获停止状态。非阻塞管理子进程避免阻塞主流程,配合信号/epoll 使用。

#
★★

11. 异步信号安全函数列表为何有限,malloc 因持有 arena 锁、printf 因持有 stdio 缓冲锁而为何不能在信号处理器中安全调用?

异步信号安全函数列表为何有限?为什么 malloc、printf 不能在信号处理器中安全调用?

  • 异步信号安全
  • malloc 的 arena 锁
  • printf 的 stdio 缓冲锁

异步信号安全函数(async-signal-safe)列表有限,因为信号处理器可能在任意时刻打断主程序的任意代码,若处理器调用与主程序共享状态的函数,会破坏状态。malloc 内部持有 arena 锁(管理堆的锁),若信号打断正在 malloc 的主程序(已持锁),处理器再调用 malloc 会死锁(同一线程重入加锁);printf 持有 stdio 缓冲锁(stdout 缓冲),同理可能死锁或破坏缓冲。因此信号处理器只能调用明确列出的 async-signal-safe 函数(如 write、_exit),复杂操作放到主逻辑。工程上:信号处理器只做简单标记(volatile sig_atomic_t)或 write 到管道,主逻辑处理。

关键在"重入"——信号打断持锁代码后处理器再进入同一锁会死锁。malloc/printf 非 async-signal-safe,处理器应避免。

#
★★

12. POSIX 信号在多线程程序中的"信号掩码继承"与线程投递

POSIX 信号在多线程程序中的"信号掩码继承"与线程投递如何理解?

  • 信号掩码
  • 线程创建继承
  • 线程投递

信号掩码(signal mask)定义线程阻塞哪些信号。多线程中:新线程创建时继承创建者线程的信号掩码;pthread_sigmask 设置/查询线程自己的掩码;进程级信号(kill 发)由任一未阻塞该信号的线程处理,线程级信号(pthread_kill)只投递给指定线程。掩码继承与线程投递的差异:掩码是每线程属性(创建时继承),而标准信号处理是进程级共享(handler 全进程共享)。工程上:用 pthread_sigmask 在创建线程前阻塞某些信号,避免新线程被信号打断,再用 sigwait 集中处理。

掩码是线程级(创建时继承),投递是"进程级信号任一线程处理、线程级信号定向投递"。用 pthread_sigmask + sigwait 集中处理信号。

#
★★

13. fork() 后的线程行为,仅调用线程存在、子线程消失

fork() 后的线程行为如何?为什么仅调用线程存在、子线程消失?

  • fork 后的线程
  • 子线程消失
  • 后果

fork() 后子进程只复制调用 fork 的线程,其他线程(子线程)在子进程中不存在(消失)。原因:fork 只复制调用线程的上下文,其他线程不复制。后果:子进程后续若期望其他线程存在会出错;若其他线程持有锁,子进程内该锁被"永久持有"(线程消失),可能死锁;异步信号/全局状态可能不一致。因此多线程程序 fork 后通常立即 exec(替换程序)或只做异步信号安全操作。工程上:多线程 fork 后用 posix_spawn 或立即 exec,避免子进程处于"只有一线程"的异常状态。

fork 只复制调用线程,子进程是"单线程"状态,其他线程的锁与状态可能遗留导致死锁。多线程程序 fork 后通常立即 exec。

#
★★

14. signalfd 将信号转为文件描述符的"信号即 I/O"模式

signalfd 将信号转为文件描述符的"信号即 I/O"模式如何工作?

  • signalfd
  • 信号即 I/O
  • 事件循环

signalfd 创建一个文件描述符,把指定信号转为可读事件写入该 fd,应用程序用 read 读取信号信息(struct signalfd_siginfo),从而把信号处理变成"文件 I/O"。配合 epoll/select 可把信号整合进事件循环(信号即 I/O),避免异步信号处理器(受限)而用同步读取。工程上:signalfd 需先阻塞信号(pthread_sigmask),再创建 signalfd,在事件循环中统一处理信号与其他 I/O,避免信号处理器中的异步信号安全限制。

signalfd 把信号"转成" fd 事件,让信号处理走同步 I/O 路径,配合 epoll 统一事件循环,规避异步信号安全限制。

#
★★

15. vfork() 的挂起父进程与子进程共享地址空间

vfork() 的挂起父进程与子进程共享地址空间如何理解?

  • vfork 语义
  • 挂起父进程
  • 共享地址空间

vfork() 创建子进程时,父进程被挂起(阻塞),子进程直接共享父进程的地址空间(不复制页表),子进程在 exec 或 _exit 前一直使用父进程的状态。语义:子进程应立即 exec 或 _exit,不能修改父进程内存(否则影响父进程),父进程在子进程 exec/_exit 前不恢复执行。用途:快速创建子进程并立即 exec(避免 COW 页表复制开销),但危险(子进程修改共享内存影响父进程)。现代系统多用 posix_spawn 替代 vfork。工程上:vfork 需子进程立即 exec,避免用共享内存做复杂操作。

vfork 挂起父进程、子进程共享地址空间,专为"创建后立即 exec"设计,快速但危险(共享内存易被修改)。

#
★★

16. pthread_cancel 的异步 vs 延迟取消(deferred cancellation)与 cancellation point

pthread_cancel 的异步 vs 延迟取消(deferred cancellation)与 cancellation point 如何理解?

  • pthread_cancel
  • 异步/延迟取消
  • cancellation point

pthread_cancel 请求取消线程,取消方式:异步取消(PTHREAD_CANCEL_ASYNCHRONOUS)——线程可在任意指令被取消(危险,可能打断资源清理);延迟取消(PTHREAD_CANCEL_DEFERRED,默认)——线程只在 cancellation point(取消点,如阻塞 I/O、pthread_join 等系统调用)检查取消请求并退出,更安全。cancellation point 是允许取消发生的点,线程到达该点才响应取消。工程上:用延迟取消(默认),用 pthread_cleanup_push 注册清理函数,线程在取消点退出时执行清理;避免异步取消(资源泄漏风险)。

延迟取消在 cancellation point 才响应,配合清理函数保证资源释放;异步取消随时打断,危险。默认延迟取消更安全。

#
★★

17. pthread_mutex 的 priority inversion 与 PTHREAD_PRIO_INHERIT 协议

pthread_mutex 的 priority inversion 与 PTHREAD_PRIO_INHERIT 协议如何理解?

  • priority inversion
  • 优先级继承
  • 实时系统

priority inversion(优先级反转)指低优先级线程持有锁,高优先级线程等待该锁,导致高优先级线程被低优先级线程阻塞(优先级倒挂),可能长时间被拖延。PTHREAD_PRIO_INHERIT(优先级继承互斥锁)缓解:当高优先级线程等待低优先级线程持有的锁时,把锁持有者的优先级临时提升到等待者的优先级,使低优先级线程能尽快运行释放锁,减少反转。用于实时系统/关键任务。工程上:实时线程用 PRIO_INHERIT 互斥锁避免优先级反转,配合 PTHREAD_PRIO_PROTECT 等协议。

优先级反转是"持锁优先级倒挂"。优先级继承把持锁者临时提权,加速释放锁,是实时系统的标准防反转手段。

#
★★

18. pthread_mutex 底层如何用 futex 实现,慢路径如何进入内核睡眠,为什么说无竞争时加解锁不产生系统调用?

pthread_mutex 底层如何用 futex 实现?为什么无竞争时加解锁不产生系统调用?

  • futex 机制
  • 快速路径 vs 慢路径
  • 无竞争无系统调用

pthread_mutex 底层用 futex(fast userspace mutex)实现。快速路径(无竞争):用原子 CAS 在用户态尝试获取锁,成功则无需系统调用(只改用户态计数器);慢路径(有竞争):CAS 失败,通过 futex 系统调用进入内核睡眠(FUTEX_WAIT),释放时用 FUTEX_WAKE 唤醒。无竞争时加解锁只做用户态原子操作,不进入内核,因此不产生系统调用(开销极小);只有竞争时才进内核睡眠/唤醒。这是 futex 的核心设计:把无竞争情况留在用户态,竞争才进内核。

futex 用"用户态原子 + 内核等待/唤醒"两级。无竞争纯用户态(无 syscall),竞争才进内核,兼顾快速与阻塞。

#
★★

19. POSIX 信号的分类,标准信号(SIGTERM/SIGINT)与实时信号(SIGRTMIN+)的差异

POSIX 信号的分类:标准信号(SIGTERM/SIGINT)与实时信号(SIGRTMIN+)有何差异?

  • 标准信号 vs 实时信号
  • 排队行为
  • 信号携带数据

标准信号(SIGTERM、SIGINT 等)与实时信号(SIGRTMIN 到 SIGRTMAX,如 SIGRTMIN+1)差异:排队——标准信号不排队(同信号多次投递可能合并为一个),实时信号排队(多个同信号依次投递,可靠);携带数据——实时信号可携带数据(sigqueue + union sigval/值),标准信号不能;优先级——实时信号有优先级(数字小的先投递)。实时信号用于可靠事件通知(不丢事件、有序、可携带数据)。工程上:可靠事件通知/附带数据用实时信号,标准信号用于常规控制。

核心差异是"排队 + 附带数据 + 优先级"。标准信号不排队(可能丢),实时信号排队且可携带值,适合可靠通知。

#
★★

20. sigaction 的 SA_RESTART、SA_NOCLDWAIT、SA_NODEFER 标志的工程语义

sigaction 的 SA_RESTART、SA_NOCLDWAIT、SA_NODEFER 标志的工程语义是什么?

  • sigaction 标志
  • SA_RESTART/SA_NOCLDWAIT/SA_NODEFER
  • 语义

sigaction 标志:SA_RESTART——被信号打断的慢速系统调用(read、write 等)自动重启(不返回 EINTR),简化处理;SA_NOCLDWAIT——收到 SIGCHLD 时自动回收子进程(不产生僵尸进程),需慎用(无法再 wait 获取状态);SA_NODEFER——信号处理期间不自动阻塞该信号(允许嵌套递送),默认处理器执行期间同信号被阻塞(SA_NODEFER 关闭这层保护)。工程上:SA_RESTART 让 I/O 自动重启(少处理 EINTR),SA_NOCLDWAIT 用于自动回收子进程,SA_NODEFER 用于需嵌套的场景(谨慎)。

三个标志控制信号处理的行为:RESTART 重启 I/O、NOCLDWAIT 自动回收子进程、NODEFER 允许嵌套,按需组合。

#
★★

21. sigwait 与同步信号接收的"信号即事件"模式

sigwait 与同步信号接收的"信号即事件"模式如何工作?

  • sigwait
  • 同步信号接收
  • 信号即事件

sigwait 阻塞等待指定信号(集合),收到后返回信号编号,实现"同步接收信号"(信号即事件)。与信号处理器(异步,受限)不同,sigwait 用同步方式在同一线程处理信号(可调用任意函数、加锁),避免异步信号安全限制。配合 sigtimedwait 支持超时。工程上:多线程中"信号处理线程"用 sigwait 阻塞接收信号,把信号当作事件处理(如优雅退出、重载),其他线程阻塞该信号(pthread_sigmask),避免信号被随机分发。

sigwait 把信号变成"同步事件",在专用线程处理,规避异步信号安全限制。配合掩码让信号只投递到 sigwait 线程。

#
★★

22. signalfd + epoll 的事件循环中信号统一处理

signalfd + epoll 的事件循环中如何统一处理信号?

  • signalfd 与 epoll
  • 信号统一处理
  • 事件循环

signalfd + epoll 把信号纳入统一事件循环:把信号通过 signalfd 转为 fd 可读事件,注册到 epoll,与网络/定时器等 I/O 事件统一处理。流程:先阻塞信号(pthread_sigmask),创建 signalfd(绑定信号集),把 signalfd fd 加入 epoll,epoll_wait 时统一处理信号事件与其他 I/O 事件。优点:信号处理走同步路径(可加锁、可读 signalfd 信息),避免异步信号处理器限制,单一事件循环统一管理。工程上:网络服务用 signalfd+epoll 统一处理 SIGTERM 等信号与连接事件。

signalfd 把信号转 fd,epoll 统一调度,实现"信号即事件"且同步安全处理,是高性能服务的主流做法。

#
★★

23. POSIX timer 的 SIGEV_SIGNAL、SIGEV_THREAD、SIGEV_THREAD_ID 通知模式

POSIX timer 的 SIGEV_SIGNAL、SIGEV_THREAD、SIGEV_THREAD_ID 通知模式如何理解?

  • POSIX timer 通知
  • SIGEV_SIGNAL/SIGEV_THREAD/SIGEV_THREAD_ID
  • 各模式

POSIX timer(timer_create)通知模式由 sigevent 指定:SIGEV_SIGNAL——定时器到期发信号(应用有信号处理器处理,异步信号安全受限);SIGEV_THREAD——到期时内核创建新线程调用回调函数(可在回调中做任意操作,但线程创建开销与线程安全需注意);SIGEV_THREAD_ID(Linux 扩展)——投递到指定线程,避免系统创建线程的开销。工程上:轻量回调用 SIGEV_THREAD 或 SIGEV_THREAD_ID,信号方式用 SIGEV_SIGNAL(配合 sigwait 或 signalfd)。SIGEV_THREAD 的回调线程由系统创建。

通知模式决定"到期如何通知"。SIGNAL 发信号、THREAD 创建线程回调、NONE 无通知(用 timerfd 等)。SIGEV_THREAD 回调线程由系统创建。

#
★★

24. POSIX 实时信号的排队(queued)行为与标准信号的合并

POSIX 实时信号的排队(queued)行为与标准信号的合并如何理解?

  • 实时信号排队
  • 标准信号合并
  • 可靠投递

实时信号(SIGRTMIN+)排队(queued):同一信号的多次投递(如多次 sigqueue)会按序排队,逐一投递,不丢失,可靠;标准信号(如 SIGTERM)不排队:同一信号的多次投递可能合并为一个(pending 集合只保留一个标志),后到的覆盖已 pending 的,可能"丢"(从应用角度看)。区别本质:实时信号有队列可存多个,标准信号只有标志位。工程上:需要可靠、有序、不丢事件通知用实时信号(数量有限,SIGRTMIN 到 SIGRTMAX 约 32 个),标准信号用于常规控制。

实时信号排队(可靠、有序、不丢),标准信号合并(只保留一个标志,可能丢)。可靠通知用实时信号。

#
★★

25. pthread_attr_t 的分离状态(joinable/detached)与栈大小(stacksize)的工程取舍

pthread_attr_t 的分离状态(joinable/detached)与栈大小(stacksize)的工程取舍是什么?

  • 线程属性
  • joinable/detached
  • stacksize

pthread_attr_t 配置线程属性:分离状态——joinable(可 join,需 pthread_join 回收资源)vs detached(分离,退出后自动回收资源,不可 join)。joinable 需显式 join 否则线程结束资源(如退出状态)不回收(泄漏);detached 自动回收,适合"fire-and-forget"线程。栈大小(stacksize)——线程栈大小,默认较大(如 8MB 虚拟),大量线程时默认栈会浪费虚拟内存/地址空间;设小栈(如 1MB)适用于大量线程但需防栈溢出。工程上:大量线程用较小 stacksize + detached(避免资源泄漏),需 join 的用 joinable。

分离状态决定"资源如何回收",栈大小决定"每线程内存"。大量线程用 detached + 小栈,需同步结果的用 joinable。

#
★★

26. pthread_cond_wait 的 spurious wakeup 与 while 循环守卫

pthread_cond_wait 的 spurious wakeup 与 while 循环守卫如何理解?

  • 条件变量
  • spurious wakeup
  • while 循环

pthread_cond_wait 等待条件变量,可能发生 spurious wakeup(虚假唤醒)——即使没有线程调用 signal/broadcast,wait 也可能返回。此外,即使是真唤醒,也可能有多个等待线程竞争/条件被其他线程改变。因此必须用 while 循环检查谓词(条件),而非 if:while(!predicate) pthread_cond_wait(&cond,&mutex);——wait 返回后重新检查谓词,若条件不满足继续等待。单独 if 判断可能在"唤醒但条件仍不满足"时错误继续,导致竞态。这是 wait 使用的基本规范。

spurious wakeup + 多等待者竞争要求"唤醒后重新验证条件"。while 循环守卫是条件变量的标准用法,避免 if 判断误判。

while (count == 0) {
    pthread_cond_wait(&cond, &mutex);
}
// 唤醒后重新检查谓词,条件满足才继续
#
★★

27. pthread_mutex_t 的 normal、recursive、error-checking、default 四类型差异

pthread_mutex_t 的四类型(normal、recursive、error-checking、default)有何差异?

  • mutex 类型
  • 递归/错误检测
  • 行为差异

pthread_mutex 类型:PTHREAD_MUTEX_NORMAL(普通)——非递归,同一线程重复加锁会死锁,解锁非持有者未定义;PTHREAD_MUTEX_RECURSIVE(递归)——同一线程可重复加锁(计数),需等量解锁,用于递归/可重入函数;PTHREAD_MUTEX_ERRORCHECK(错误检查)——非法操作(重复加锁、解锁非持有者)返回错误(EDEADLK/EPERM)而非死锁/未定义,利于调试;PTHREAD_MUTEX_DEFAULT——通常等同 normal,行为未定义。工程上:普通 mutex 用于一般互斥,递归锁用于可重入场景(但易掩盖设计问题、慎用),error-checking 用于调试多线程锁错误。

四类型差异在"重复加锁/解锁非持有者"的行为:normal 死锁/未定义、recursive 计数、error-check 返回错误、default 等价 normal。递归锁有计数、需等量解锁。

#
★★

28. pthread_once_t 的单次初始化(pthread_once)替代 static initializer

pthread_once_t 的单次初始化(pthread_once)如何工作?它如何替代 static initializer?

  • pthread_once
  • 单次初始化
  • 线程安全

pthread_once 保证初始化例程在多线程下只执行一次,即使多个线程并发调用。pthread_once_t 是初始化状态(通常用 PTHREAD_ONCE_INIT 静态初始化),pthread_once(&once, init) 确保 init 只执行一次(第一个调用者执行,其余等待或直接返回)。替代 static initializer:对于需要运行时初始化(动态计算、依赖环境)的全局对象,用 pthread_once 保证线程安全地初始化一次,比手工加锁更简洁可靠。工程上:全局单例、延迟初始化用 pthread_once,避免重复初始化与竞态。

pthread_once 保证"恰好一次"的线程安全初始化。用于无法静态初始化、需运行时操作的全局对象,替代手工锁。

#
★★

29. pthread_rwlock_t 的 reader/writer 偏好策略与写线程饥饿风险

pthread_rwlock_t 的 reader/writer 偏好策略与写线程饥饿风险如何理解?

  • 读写锁
  • 读写偏好
  • 写饥饿

pthread_rwlock(读写锁)允许多个读者并发读、写者独占。偏好策略:读者优先——读者多时写者可能长期等待(写饥饿);写者优先——写者等待时阻塞新读者,让写者尽快获得(减少写饥饿,但可能降低读并发)。Linux 默认写者优先(避免写饥饿)。写饥饿风险:若读者持续到来且读者优先,写者长期得不到锁,写操作延迟剧增。工程上:读多写少用读写锁提升读并发,但需注意写者饥饿;偏好策略影响公平性,写敏感场景用写者优先。

读写锁的偏好决定"读写间公平"。读者优先提升读并发但可能写饥饿,写者优先避免写饥饿但牺牲读并发。

#
★★

30. pthread_spinlock_t 的忙等待(busy-wait)与适用场景

pthread_spinlock_t 的忙等待(busy-wait)与适用场景如何理解?

  • spinlock
  • 忙等待
  • 适用场景

pthread_spinlock(自旋锁)用忙等待(busy-wait)——加锁失败时循环测试(自旋)而不睡眠,直到获得锁。优点:无上下文切换/睡眠开销,获取快(临界区短时);缺点:占用 CPU(自旋空转),不适合长临界区/多核竞争激烈。适用场景:临界区极短、持锁时间短、多核系统(自旋的 CPU 不会浪费太多)、不适合用睡眠(如实时/中断上下文)。若临界区长或竞争激烈,自旋浪费 CPU,应改用互斥锁(睡眠)。工程上:短临界区用自旋锁,长临界区/竞争用 mutex。

自旋锁"忙等"换低延迟,但占用 CPU。短临界区才划算,长临界区/高竞争应避免(浪费 CPU)。单核上自旋锁无意义。

#
★★

31. pthread_cleanup_push/pthread_cleanup_pop 的资源清理栈

pthread_cleanup_push/pthread_cleanup_pop 的资源清理栈如何工作?

  • 清理函数
  • 线程取消/退出
  • 资源清理

pthread_cleanup_push 注册清理函数(入栈),pthread_cleanup_pop 弹出(可执行或丢弃)。当线程被取消(pthread_cancel)或以某种方式退出(pthread_exit)时,已注册的清理函数按 LIFO 顺序执行,确保资源(锁、内存、fd)被释放。用例如:加锁后 push 清理函数解锁,等待/取消时自动解锁,避免死锁/泄漏。pthread_cleanup_pop(execute) 的 execute 参数决定弹出时是否执行。工程上:获取资源后 push 清理函数,保证取消/退出时释放资源;注意 push/pop 必须成对(否则编译错误)。

清理函数栈保证"线程异常退出时释放资源"。加锁/分配资源后 push 清理,取消/退出时 LIFO 执行,避免资源泄漏。

#
★★

32. pthread 的 errno 与线程局部 errno 边界

pthread 的 errno 与线程局部 errno 边界如何理解?

  • errno 线程局部
  • 线程安全
  • 错误处理

errno 是线程局部存储(TLS)的变量,每个线程有独立的 errno,互不影响。原因:早期 errno 是全局变量,多线程下系统调用失败会互相覆盖 errno,导致错误判断错误。现在 errno 定义为线程局部(如 __thread),各线程保存自己的错误码。边界:线程内系统调用失败设置的 errno 只影响本线程,其他线程不受影响。工程上:检查 errno 应在系统调用失败后立即读取(避免被其他调用覆盖,即使线程局部也防误读),多线程错误处理安全。

errno 线程局部,避免多线程间错误码互相覆盖。线程内也应在失败后立即检查,防止被后续调用覆盖。

#
★★

33. SIGCHLD 的处理与僵尸进程(zombie process)清理

SIGCHLD 的处理与僵尸进程(zombie process)清理如何理解?

  • SIGCHLD 信号
  • 僵尸进程
  • 回收

子进程退出时向父进程发送 SIGCHLD,父进程需 wait/waitpid 获取子进程退出状态并回收;若父进程不及时 wait,子进程成为僵尸进程(zombie,已退出但保留 pid 与退出状态,占用进程表)。清理:父进程在 SIGCHLD 处理器中调用 waitpid(非阻塞,WNOHANG)回收所有子进程;或设置 SA_NOCLDWAIT/SIG_IGN 使内核自动回收(不产生僵尸)。工程上:父进程循环 waitpid 回收子进程(处理 SIGCHLD),避免僵尸堆积耗尽进程表;注意信号处理器中 waitpid 需异步信号安全。

僵尸进程是"已退出未回收"的子进程。父进程收到 SIGCHLD 后 waitpid 回收,或自动回收避免僵尸堆积。

#
★★

34. exec() 家族(execl/execv/execlp/execvp/execle/execve)的语义差异

exec() 家族(execl/execv/execlp/execvp/execle/execve)的语义差异如何理解?

  • exec 家族变体
  • 参数/路径/环境
  • 语义差异

exec 家族按"参数形式、路径查找、环境"区分:参数形式——l(list,参数列表 execl/execlp)vs v(vector,参数数组 execv/execvp);路径查找——p(在 PATH 中查找 execlp/execvp)vs 无 p(用给定路径 execl/execv);环境——e(指定环境 execle/execve)vs 无 e(继承当前环境)。execve 是底层系统调用,其他是包装。语义:exec 成功后进程映像被新程序替换(不 fork),PID 不变,失败返回 -1。工程上:传递参数数组用 v 系列,需 PATH 查找用 p,自定义环境用 e。

exec 家族差异在"参数(l/v)、路径(p)、环境(e)"三个维度。execve 是底层系统调用,其余是封装。

#
★★

35. SIGRTMIN 到 SIGRTMAX 的实时信号优先级与排队(queued)行为?

SIGRTMIN 到 SIGRTMAX 的实时信号优先级与排队(queued)行为如何理解?

  • 实时信号范围
  • 优先级
  • 排队

SIGRTMIN 到 SIGRTMAX 是实时信号(Linux 上约 32 个,SIGRTMIN 到 SIGRTMAX)。优先级:数字小的实时信号优先级高(SIGRTMIN+1 高于 SIGRTMIN+2),多个实时信号同时 pending 时按优先级先投递。排队(queued):实时信号可排队,同一信号多次投递(sigqueue)会按序全部投递(不合并),可靠。用途:把实时信号映射到不同事件(如 SIGRTMIN+1 表示事件 A),按优先级处理。工程上:实时信号数量有限,合理分配优先级,用 sigqueue 可靠投递。

实时信号"数字小优先级高 + 可排队可靠"。有限数量(约 32 个),优先级与排队是其与标准信号的核心差异。

#
★★

36. posix_spawnp 在 PATH 解析与 file action(posix_spawn_file_actions_t)的工程价值?

posix_spawnp 在 PATH 解析与 file action(posix_spawn_file_actions_t)的工程价值是什么?

  • posix_spawnp
  • PATH 解析
  • file action

posix_spawnp 是 posix_spawn 的 PATH 查找变体(像 execvp),在 PATH 环境变量中查找可执行文件。file action(posix_spawn_file_actions_t)允许在 spawn 时配置文件描述符操作(如打开/关闭/重定向 fd),在子进程 exec 前应用,替代手工 fork 后手动 fd 操作。工程价值:posix_spawnp + file action 在单次调用中完成"定位程序 + 设置 fd + 创建执行",避免多线程 fork 后手动 fd 操作的死锁/竞态风险,且 fd 操作原子应用于 exec 前。适合需要重定向 fd 的进程创建。

posix_spawnp 用 PATH 查找,file action 在 exec 前原子配置 fd,避免 fork 后手动 fd 操作的竞态,是安全的进程创建方式。

#
★★

37. posix_spawn 在 POSIX 1003.1a 异步执行 fork+exec 的工程价值?

posix_spawn 在 POSIX 1003.1a 异步执行 fork+exec 的工程价值是什么?

  • posix_spawn
  • 异步执行
  • 工程价值

posix_spawn 是 POSIX 1003.1a 定义的,用于"创建并执行"新进程的接口,内部实现为 fork+exec 的异步组合(创建后立即执行新程序)。工程价值:避免 fork 复制父进程全部内存(COW 浪费)与多线程 fork 死锁风险;单次调用完成进程创建与执行;对嵌入式/实时系统(无完整 fork 或需高效)友好。相比 fork+exec,posix_spawn 更高效、更安全,且语义明确(创建后立即执行)。工程上:创建外部进程优先用 posix_spawn,替代 fork+exec。

posix_spawn 把"fork+exec"封装为原子高效的一次调用,规避多线程 fork 风险与 COW 浪费,是工程上推荐的进程创建方式。

#
★★

38. posix_spawn 在 multithread program 避免 fork deadlock 的工程价值?

posix_spawn 在 multithread program 中避免 fork deadlock 的工程价值是什么?

  • 多线程 fork 死锁
  • posix_spawn 规避
  • 工程价值

多线程程序中 fork 可能死锁:fork 只复制调用线程,若其他线程持有锁(如 malloc 的 arena 锁、stdio 锁),子进程继承这些锁被"永久持有"(其他线程消失),在子进程中调用相关函数(malloc/printf)会死锁。posix_spawn 规避:它不 fork 复制整个进程(内部实现避免复制父进程的锁与全部状态),创建的子进程直接 exec 新程序,不继承父进程可能被持有但"消失"的锁,因此不会因多线程 fork 死锁。工程上:多线程程序创建子进程用 posix_spawn,避免 fork 的锁死锁风险。

多线程 fork 死锁源于"锁被消失线程持有"。posix_spawn 不复制父进程锁状态,规避该风险,是安全的替代。

#
★★

39. getrandom 在 early boot 阻塞(flags=0)的工程边界?

getrandom 在 early boot 阻塞(flags=0)的工程边界如何理解?

  • getrandom
  • early boot 阻塞
  • 熵池

getrandom(flags=0) 在系统启动早期(熵池未初始化)会阻塞,直到内核熵池初始化完成(收集足够随机源)才返回,保证返回的随机数有足够熵。工程边界:early boot 中熵池未就绪时,getrandom 阻塞可能导致启动进程挂起(等待熵)。实际中:生产环境熵池通常很快就绪,但嵌入式/无熵源环境可能长时间阻塞。工程上:启动早期需要随机数时,用 GRND_NONBLOCK 避免阻塞(失败则降级),或用 /dev/urandom(不阻塞但熵可能不足);对安全敏感密钥用 getrandom 阻塞等待熵。

getrandom(0) 保证熵充足但可能阻塞等待熵池。early boot 用非阻塞或 /dev/urandom 避免挂起,安全敏感用阻塞模式。

#
★★

40. getrandom(2) 系统调用在 Linux 3.17+ 阻塞 vs 非阻塞模式的工程价值?

getrandom(2) 系统调用在 Linux 3.17+ 阻塞 vs 非阻塞模式的工程价值是什么?

  • getrandom 阻塞/非阻塞
  • 熵与安全
  • 工程选择

getrandom(2)(Linux 3.17+)提供安全随机数,flags 控制模式:flags=0(阻塞)——熵池未就绪时阻塞,保证随机数加密安全(用于密钥、令牌);GRND_NONBLOCK——熵池未就绪时返回 EAGAIN(不阻塞),用于非关键随机数/启动期。工程价值:安全敏感(密钥、会话、nonce)用阻塞模式确保熵充足;启动/非关键场景用非阻塞避免挂起。相比 /dev/urandom(永不阻塞但熵可能不足)与 /dev/random(恒阻塞),getrandom 提供可选的阻塞行为。工程上:安全随机数用 getrandom(0),性能/非敏感用 GRND_NONBLOCK。

getrandom 的 mode 决定"阻塞等熵还是立即返回"。安全关键用阻塞,启动/非关键用非阻塞,兼顾安全与可用性。

#
★★

41. 条件变量的丢失唤醒(lost wakeup),为什么必须先加锁再检查谓词,predicate 判断与 wait 之间如何防止竞态?

条件变量的丢失唤醒(lost wakeup)如何理解?为什么必须先加锁再检查谓词,predicate 判断与 wait 之间如何防止竞态?

  • lost wakeup
  • 加锁检查谓词
  • 原子操作

lost wakeup(丢失唤醒)指条件变量 wait 前未加锁检查谓词,导致"谓词已满足但被错过"——若在检查谓词与 wait 之间条件被满足且 signal 已发出,wait 会一直等待(错过唤醒)。防止:wait 前必须先加锁(mutex),再在锁内检查谓词,然后 pthread_cond_wait(&cond, &mutex)(wait 原子地释放锁并等待,唤醒后重新获取锁)。这样"检查谓词 + wait"在同一锁保护下(wait 释放锁是原子的),signal 也必须持锁发,保证"谓词变化与唤醒"原子,不会丢失唤醒。即使谓词在 wait 前已满足,wait 也会因谓词检查=true 而不等待(或用 while 循环)。

丢失唤醒的根因是"检查谓词与 wait 之间出现了竞态"。必须持锁检查谓词,且 wait 原子释放锁等待,signal 持锁发,保证不丢唤醒。

pthread_mutex_lock(&mutex);
while (!ready) {           // 持锁检查谓词
    pthread_cond_wait(&cond, &mutex);  // 原子释放锁并等待
}
pthread_mutex_unlock(&mutex);
#
★★

42. seqlock(顺序锁)与读写锁的差异,为什么 seqlock 写者优先且读者可能重试,适合哪些读写比场景?

seqlock(顺序锁)与读写锁的差异如何理解?为什么 seqlock 写者优先且读者可能重试,适合哪些读写比场景?

  • seqlock 机制
  • 写者优先/读者重试
  • 适用场景

seqlock(顺序锁)用计数器:写者写前递增序列号(奇数)、写后递增(偶数),读者读前读序列号、读后校验(若变化则重试)。特性:写者优先(写者不等读者,直接写,读者会因序列号变化而重试),读者可能重试(读到一半被写者打断时序列号变化)。与读写锁差异:读写锁写者会等待读者释放(写者可能饥饿/或读者阻塞),seqlock 写者不等读者(写者优先、读者自旋重试)。适用场景:读多写少且读者可容忍重试的对偶数/结构体保护(如时钟、时间戳、计数器),读者不阻塞写者、写开销小。注意:读者读到不一致数据会重试,不适合读者需要原子快照又要求低延迟的复杂数据结构。

seqlock 用序列号奇偶判定写者是否在写。写者优先(不等读者)、读者校验重试。适合读多写少、读者可自旋重试的简单共享数据。

#
★★

43. fork、vfork、posix_spawn 在地址空间复制、父进程阻塞和多线程安全性上分别有什么语义?

fork、vfork、posix_spawn 在地址空间复制、父进程阻塞和多线程安全性上分别有什么语义?

  • 三种进程创建方式
  • 地址空间/阻塞/多线程安全
  • 语义对比

三种进程创建方式对比:fork——复制父进程地址空间(COW,写时复制),父进程不阻塞(父子并发运行),多线程 fork 有死锁风险(只复制调用线程);vfork——不复制地址空间(子进程直接共享父进程),父进程阻塞(挂起直到子进程 exec/exit),多线程安全问题(共享状态);posix_spawn——不复制整个父进程地址空间(直接 exec 新程序),父进程不阻塞(返回后父继续),多线程安全(避免 fork 死锁)。语义:fork 复制+并发、vfork 共享+父阻塞、posix_spawn 直接执行+父不阻塞。

三者差异在"是否复制地址空间、父进程是否阻塞、多线程是否安全"。posix_spawn 最安全现代,fork 最通用,vfork 最快但危险。

#
★★

44. execve 成功后哪些进程属性会保留,哪些信号处置、映射和线程状态会被重置?

execve 成功后哪些进程属性会保留?哪些信号处置、映射和线程状态会被重置?

  • execve 保留属性
  • 信号处置重置
  • 映射/线程状态

execve 成功后保留:进程 PID、文件描述符(FD_CLOEXEC 标志的除外)、当前工作目录、umask、信号掩码(pending 信号保留)、进程组/会话、环境变量(部分)。被重置:信号处置——自定义信号处理器(sigaction)重置为默认(SIG_DFL)或忽略,被捕获的信号恢复默认;内存映射——旧地址空间被丢弃(新程序映像),mmap 映射清除;线程状态——exec 后进程变为单线程(其他线程消失),线程局部状态/TLS 重置;IPC 状态等。工程上:exec 后需重新设置信号处理器,fd 需考虑 CLOEXEC。

execve 替换进程映像,保留 PID/fd/掩码等"进程属性",重置信号处置、内存映射、线程状态。需重新配置信号处理器。

#
★★

45. 进程 PID、PGID、SID、TID 在会话、进程组与线程三者之间的归属与控制差异是什么?

进程 PID、PGID、SID、TID 在会话、进程组与线程三者之间的归属与控制差异是什么?

  • PID/PGID/SID/TID
  • 会话/进程组/线程
  • 归属与控制

PID——进程标识(进程组/会话的成员);PGID——进程组标识(同一进程组共享 PGID,用于信号组投递/作业控制);SID——会话标识(多进程组构成一个会话,session 用 setsid 创建,脱离控制终端);TID——线程标识(Linux 中每个线程有 TID,进程的 PID 等于其主线程的 TID,同进程线程共享 PID 但 TID 不同)。归属:进程属于进程组(PGID),进程组属于会话(SID),线程属于进程(同一 PID 下多个 TID)。控制差异:kill 负 PID 投递到进程组(PGID),setsid 建立新会话,pthread_kill 投递线程(TID)。

PID 是进程、TID 是线程、PGID 是进程组、SID 是会话。层级为"会话 > 进程组 > 进程 > 线程",信号按 PID/TID/进程组投递。

#
★★

46. fork 之后父进程先于子进程退出再被 init 接管时,子进程是否会变成孤儿并被回收?

fork 之后父进程先于子进程退出再被 init 接管时,子进程是否会变成孤儿并被回收?

  • 孤儿进程
  • init 接管
  • 回收

父进程先退出,子进程成为孤儿进程(orphan),被 init(PID 1)或最近的 subreaper 接管(adopt)。init 会 reap(回收)孤儿进程:子进程退出时,init 通过 wait 回收其退出状态,避免僵尸堆积。因此孤儿进程不会永久滞留,会被 init 接管并最终回收。过程:父进程退出 → 子进程父转而 PID 1(或 subreaper)→ 子进程退出时 PID 1 回收。工程上:孤儿进程被 init 接管是正常机制,恶意/异常进程可能被 init 无限期保活(需靠其他机制管理)。

孤儿进程被 init/subreaper 接管,init 负责回收(wait),避免僵尸。父先退不会导致子进程失控,只是父权转移。

#
★★

47. 写时复制(COW)页在 fork 后由谁触发复制,触发后哪些资源会同步增长?

写时复制(COW)页在 fork 后由谁触发复制,复制后哪些资源会同步增长?

  • COW 触发
  • 页复制
  • 资源增长

fork 后 COW 页的复制由"任一进程对共享页执行写操作"触发:父子进程共享只读页(COW),任一进程写入时,内核为写入进程分配新物理页并复制内容,更新页表,触发缺页(page fault)处理。复制的资源:触发写入的进程获得独立物理页(RSS 增长),页表项更新;父进程若也写则各自复制。未写入的页保持共享。复制后:内存占用(RSS)随写入的页数增长,页表项分叉。工程上:fork 后大量写会触发大量 COW 复制,内存与 CPU 开销增加;只读数据保持共享不增长。

COW 由"写入"触发,写者获得独立页。复制的资源是物理页与页表,写越多 RSS 增长越多,只读共享不增长。

#
★★

48. 设计守护进程优雅退出流程时,如何协调 SIGTERM、在途请求、子进程回收与退出码?

设计守护进程优雅退出流程时,如何协调 SIGTERM、在途请求、子进程回收与退出码?

  • 优雅退出
  • SIGTERM 处理
  • 在途请求/子进程/退出码

守护进程优雅退出流程:收到 SIGTERM → 停止接收新请求(从负载均衡摘除/disconnect)→ 等待在途请求完成(超时上限)→ 回收子进程/工作线程 → 清理资源(连接、文件、缓存落盘)→ 以合理退出码退出(0 成功,非 0 异常)。协调要点:SIGTERM 处理器中只设标志(异步信号安全),主循环检查标志并逐步关闭;在途请求有超时(防无限等待);子进程/线程先回收(join)再退出;退出码反映是否正常完成。工程上:用信号 + 优雅关闭流程(graceful shutdown),配合健康检查摘除流量。

优雅退出是"通知→停止接新→等完成→回收→退出"的流程。SIGTERM 只设标志,主流程逐步关闭,保证在途请求完成且资源清理。

#
★★

49. 容器内进程 1 接管僵尸进程后是否仍可观测到退出码与资源占用,差别来自 procfs 还是 cgroup?

容器内进程 1 接管僵尸进程后是否仍可观测到退出码与资源占用,差别来自 procfs 还是 cgroup?

  • 容器内 PID 1
  • 僵尸进程接管
  • procfs/cgroup

容器内 PID 1(通常为 init/应用)接管其子进程的僵尸进程(reap),退出码可从 wait 获取(若 PID 1 调 wait)。但容器外管理视角:僵尸进程的退出码与资源占用在容器内通过 procfs 可观测(进程表),资源占用(CPU/内存)统计在 cgroup 层级(容器 cgroup 聚合)。差别来源:进程与退出码在 procfs(进程视角),容器整体资源用 cgroup(资源视角)。若容器 PID 1 不 reap 子进程,僵尸可能堆积在容器内核态(procfs 可见),但 cgroup 资源会随进程退出释放。工程上:容器 PID 1 应正确 reap 子进程,避免僵尸堆积;资源统计看 cgroup。

退出码/进程在 procfs,资源占用在 cgroup。容器 PID 1 接管僵尸(procfs 层面),资源随进程退出由 cgroup 统计释放。

#
★★

50. 多线程进程中 fork 仅复制调用线程,调用 pthread_atfork 注册的 prepare/parent/child 钩子各自承担什么责任?

多线程进程中 fork 仅复制调用线程,调用 pthread_atfork 注册的 prepare/parent/child 钩子各自承担什么责任?

  • pthread_atfork
  • prepare/parent/child
  • 责任

多线程 fork 只复制调用线程,pthread_atfork 注册三个钩子协调锁:prepare(fork 前,父进程调用)——获取所有进程内锁(准备一致状态),确保 fork 时锁状态一致(被其他线程持有的锁在 prepare 中获取);parent(fork 后,父进程调用)——释放之前获取的锁(父进程恢复);child(fork 后,子进程调用)——释放之前获取的锁(子进程恢复,避免锁被"永久持有")。责任:prepare 统一拿锁,parent/child 各自释放,保证父子进程的锁状态一致,避免死锁。

prepare 在 fork 前拿锁,parent/child 在 fork 后各自释放,使锁状态在父子中一致。责任分工是"prepare 统一、parent/child 各自恢复"。

#
★★

51. posix_spawn 与 fork+exec 相比在文件描述符继承、信号掩码重置和 attribute flag 上做了哪些工程取舍?

posix_spawn 与 fork+exec 相比在文件描述符继承、信号掩码重置和 attribute flag 上做了哪些工程取舍?

  • posix_spawn vs fork+exec
  • fd 继承/信号掩码/attribute flag
  • 工程取舍

posix_spawn 相比 fork+exec 的工程取舍:文件描述符继承——posix_spawn 通过 file action(posix_spawn_file_actions_t)显式配置 fd 操作(打开/关闭/重定向),更可控;fork+exec 需手动在 fork 后操作 fd(且受 CLOEXEC 影响)。信号掩码重置——posix_spawn 通过 attribute 的 flags(POSIX_SPAWN_SETSIGMASK、POSIX_SPAWN_SETSIGDEF)显式设置信号掩码/默认处置,fork+exec 中信号处置在 exec 时重置(自定义处理器恢复默认),掩码保留。attribute flag——posix_spawn 的 posix_spawnattr_t 统一配置调度、进程组、信号等,比 fork+exec 的手动设置更集中、原子。取舍:posix_spawn 更显式、安全、原子,fork+exec 更灵活但需手动管理。

posix_spawn 用 file action 与 attribute flag 显式配置 fd 与信号,比 fork+exec 手动管理更可控、原子、安全。

#

52. 信号掩码(sigprocmask、pthread_sigmask)与信号阻塞的工程应用

信号掩码(sigprocmask、pthread_sigmask)与信号阻塞的工程应用如何理解?

  • 信号掩码
  • sigprocmask/pthread_sigmask
  • 阻塞应用

信号掩码(signal mask)定义线程阻塞哪些信号,阻塞的信号被挂起(pending)直到解除阻塞。sigprocmask 用于单线程进程设置掩码,pthread_sigmask 用于多线程(每线程独立掩码)。工程应用:阻塞信号防止处理器打断临界区(如操作共享状态时暂时阻塞);配合 sigwait/signalfd 让信号由专用线程处理(先阻塞再同步接收);多线程中控制信号投递到特定线程。注意:sigprocmask 在多线程中行为未定义,应使用 pthread_sigmask。

信号掩码阻塞信号(挂起)。工程上用于保护临界区、让专用线程同步处理信号(sigwait/signalfd)。多线程用 pthread_sigmask。

#

53. sigprocmask 阻塞期间产生的信号如何进入 pending 集合,sigpending 查询与解除阻塞后的投递如何交互?

sigprocmask 阻塞期间产生的信号如何进入 pending 集合,sigpending 查询与解除阻塞后的投递如何交互?

  • pending 集合
  • sigpending
  • 解除阻塞投递

信号被阻塞期间产生时,进入进程/线程的 pending 集合(挂起),不立即投递。sigpending 查询当前 pending 的信号集合(阻塞且未投递的)。解除阻塞后(用 sigprocmask/pthread_sigmask 移除阻塞),pending 信号被投递(处理器执行或默认动作)。交互:阻塞期间信号 pending,解除阻塞时才投递;标准信号在 pending 中合并(同一信号一个标志),实时信号排队。工程上:sigpending 用于检查是否有未处理的信号;解除阻塞前确保处理器就绪(先注册处理器再解除)。

阻塞信号进 pending,sigpending 查询,解除阻塞投递。标准信号 pending 合并、实时信号排队。

#

54. kill、raise、pthread_kill 的进程内/进程间投递差异

kill、raise、pthread_kill 的进程内/进程间投递差异如何理解?

  • kill/raise/pthread_kill
  • 投递目标
  • 差异

kill(pid, sig)——向进程投递信号(进程间或进程内),pid 可为进程 ID 或进程组(负 pid);raise(sig)——向本进程(调用线程)投递信号(进程内),等价于 kill(getpid(), sig) 或 pthread_kill(pthread_self(), sig);pthread_kill(tid, sig)——向指定线程投递信号(线程级)。差异:kill 是进程级(投递到进程,由任一未阻塞线程处理),raise 是本进程自身,pthread_kill 是线程级(指定线程)。工程上:精确控制线程用 pthread_kill,进程组广播用 kill(负 pid),本进程用 raise。

投递粒度不同:kill 进程级、raise 本进程、pthread_kill 线程级。信号的处理线程与投递目标相关。

#

55. setrlimit 的 RLIMIT_NOFILE、RLIMIT_NPROC、RLIMIT_AS 的资源限制

setrlimit 的 RLIMIT_NOFILE、RLIMIT_NPROC、RLIMIT_AS 的资源限制如何理解?

  • setrlimit
  • RLIMIT_NOFILE/NPROC/AS
  • 资源限制

setrlimit 设置进程资源限制(soft/hard):RLIMIT_NOFILE——最大打开文件描述符数(限制连接数/文件数,过高连接需调大);RLIMIT_NPROC——最大进程数/线程数(限制可创建的子进程/线程,超限报错 EAGAIN);RLIMIT_AS——虚拟地址空间大小(限制进程可用内存,超限分配失败)。工程上:高并发服务需调大 RLIMIT_NOFILE(如 ulimit -n);限制线程数/进程数用 RLIMIT_NPROC;防止内存失控用 RLIMIT_AS。调整需注意 hard limit 与权限(普通用户只能降 soft)。

三个限制分别约束 fd 数、进程/线程数、地址空间。高并发调大 NOFILE,防失控用 NPROC/AS。

#

56. SA_SIGINFO 处理器通过 siginfo_t 能取得 si_addr、si_pid 等哪些额外信息,常用于区分故障来源?

SA_SIGINFO 处理器通过 siginfo_t 能取得 si_addr、si_pid 等哪些额外信息,常用于区分故障来源?

  • SA_SIGINFO
  • siginfo_t 字段
  • 故障来源

SA_SIGINFO 使信号处理器接收 siginfo_t(和 ucontext),包含额外信息:si_addr——故障地址(如 SIGSEGV/SIGBUS 的非法访问地址)、si_pid/si_uid——发送者进程/用户、si_code——信号来源码(如 SEGV_MAPERR 映射错误、SEGV_ACCERR 权限错误)、si_value——信号附带值(sigqueue)、si_signo——信号编号。这些信息用于区分故障来源:si_addr 定位非法访问地址,si_code 区分是映射错误还是权限错误,si_pid 定位发送者。工程上:信号处理器用 si_addr/si_code 诊断崩溃原因(非法访问、对齐错误)。

SA_SIGINFO 提供 si_addr(故障地址)、si_code(来源)、si_pid(发送者)等,用于精确诊断信号来源(段错误地址、谁发的)。

#

57. sigqueue(2) 在附带 union sigval(int 或 ptr)的工程价值?

sigqueue(2) 在附带 union sigval(int 或 ptr)的工程价值是什么?

  • sigqueue 与实时信号
  • union sigval 数据
  • 事件通知

sigqueue(2) 用于向指定进程发送带数据的实时信号:配合 sigval 联合体(int 或 void* 值)携带额外数据,接收方在 SA_SIGINFO 处理器中通过 siginfo_t.si_value 读取。相较 kill(无数据),sigqueue 可附带数据且实时信号排队(不丢失)。工程价值:用于"信号即事件"模式——把自定义数据(如整数 ID、指针、状态码)随信号投递,接收方无需额外共享内存即可取得数据;结合 sigwait 同步接收,实现可靠的进程间事件通知。注意:仅实时信号(SIGRTMIN+)支持排队与数据,标准信号不排队。

sigqueue 用 sigval 携带数据,实时信号排队不丢。比 kill 多了数据载荷,适合事件通知 + 数据传递。

#

58. C++11 std::thread 无 cancellation token 的工程差异?

C++11 std::thread 无 cancellation token 的工程差异是什么?

  • std::thread
  • 无取消机制
  • 替代方案

C++11 std::thread 没有内置的线程取消机制(不同于 pthread_cancel),无法直接请求取消线程。工程差异:需要协作式取消——用原子标志(std::atomic)或 std::jthread 的 stop_token(C++20)让线程检查取消标志并自行退出;无法异步强制终止线程(std::thread 无等价 pthread_cancel)。替代:共享原子标志 + 线程轮询检查、std::condition_variable 中断等待、std::jthread(C++20 提供 stop_token 协作取消)。工程上:C++ 用协作式取消(标志 + 检查点),避免 pthread_cancel 的异步取消风险;require 线程及时响应取消标志。

std::thread 无 pthread_cancel,靠协作式取消(原子标志 / stop_token)。线程自行检查标志退出,更安全但需线程配合。

#

59. getentropy(3) 在 Linux/BSD/macOS 兼容性封装的工程价值?

getentropy(3) 在 Linux/BSD/macOS 兼容性封装的工程价值是什么?

  • getentropy
  • 跨平台兼容
  • 工程价值

getentropy(3) 是获取安全随机数的高层接口(基于内核随机源),在 Linux(3.17+)、BSD、macOS 等系统提供一致性封装。工程价值:跨平台统一的安全随机数接口,C 库(如 glibc 提供 getentropy)封装内核随机源,应用程序无需关心底层(/dev/urandom、getrandom 等差异),且保证加密安全随机数。相比直接读 /dev/urandom 或调 getrandom,getentropy 提供可移植的封装。工程上:跨平台安全随机数用 getentropy,替代平台特定实现,保证加密安全与可移植性。

getentropy 统一跨平台的安全随机数接口,封装内核随机源,保证可移植与加密安全,避免平台差异。

#

60. volatile sig_atomic_t 在信号处理器中的角色,为什么普通全局变量的读写可能不是原子的,如何正确共享标志?

volatile sig_atomic_t 在信号处理器中的角色如何理解?为什么普通全局变量的读写可能不是原子的,如何正确共享标志?

  • volatile sig_atomic_t
  • 原子性
  • 信号标志共享

signal 处理器中共享标志需用 volatile sig_atomic_t:sig_atomic_t 是保证"信号处理器中访问是原子的"整数类型(读写一个 sig_atomic_t 是原子的,不被打断),volatile 防止编译器优化(缓存/重排读)。普通全局变量读写可能不是原子的(如 long、结构体需多指令),且编译器可能优化掉重复读,导致处理器与主程序间的标志不一致。正确做法:用 volatile sig_atomic_t 声明标志(处理器写、主程序读),主程序定期检查;对复杂状态用 sig_atomic_t 标志 + 主逻辑处理(处理器只设标志)。

volatile sig_atomic_t 保证信号安全的原子读写标志。普通变量可能非原子且被编译优化,处理器只应设简单标志。

#

61. Unix 会话(session)、进程组与控制终端的关系,setsid 为何能创建新会话并脱离控制终端,守护进程还需哪些步骤彻底脱离终端,终端关闭时 SIGHUP 的发送对象是谁?

Unix 会话(session)、进程组与控制终端的关系如何理解?setsid 为何能创建新会话并脱离控制终端,守护进程还需哪些步骤彻底脱离终端,终端关闭时 SIGHUP 的发送对象是谁?

  • 会话/进程组/控制终端
  • setsid 脱离终端
  • SIGHUP 发送对象

会话(session)含多个进程组,进程组含多个进程,控制终端与会话关联(一次一个会话)。setsid 创建新会话:调用进程成为新会话首进程(session leader)且新进程组首进程,并脱离控制终端(无控制终端)。守护进程彻底脱离终端还需:fork 后 setsid(脱离会话/终端)、chdir("/")(避免占用挂载点)、umask(0)、关闭 stdin/stdout/stderr(重定向到 /dev/null)、二次 fork(防止重新获得终端)。终端关闭时 SIGHUP 发送给控制终端所属会话的前台进程组(session leader/前台进程),使终端退出时挂起相关进程。守护进程脱离终端后不接收终端 SIGHUP。

setsid 创建新会话并脱离控制终端,是守护进程化的第一步;还需 fork、chdir、重定向 fd 彻底脱离终端。终端关闭发 SIGHUP 给前台进程组。

#

62. 僵尸进程与孤儿进程如何产生,subreaper 与 PID 1 分别承担什么回收职责?

僵尸进程与孤儿进程如何产生,subreaper 与 PID 1 分别承担什么回收职责?

  • 僵尸/孤儿进程
  • subreaper/PID 1
  • 回收职责

僵尸进程(zombie):子进程已退出但父进程未 wait 回收,保留退出状态与 pid(占进程表);孤儿进程(orphan):父进程先退出,子进程被 init 或 subreaper 接管。回收职责:PID 1(init)——系统级孤儿回收,收养所有被抛弃的孤儿进程,在其退出时 wait 回收;subreaper——通过 prctl(PR_SET_CHILD_SUBREAPER) 设置的进程,成为其子树中孤儿进程的新父(接管并回收),用于容器/服务管理(容器内 PID 1 可作 subreaper 回收其子进程)。区别:PID 1 是全局孤儿回收者,subreaper 是局部(子树)回收者,避免孤儿被全局 init 接管而无法管理。

僵尸是"未回收"(父没 wait),孤儿是"父先退"(被接管)。PID 1 全局回收,subreaper 局部接管子树孤儿,便于容器管理。