这份资料涵盖了 Linux 系统编程 的核心内容,包括进程管理、线程编程、IPC(进程间通信)、同步互斥机制等高频面试题。
为了方便记忆和复习,我将其重新梳理为 进程管理、进程间通信 (IPC)、多线程编程、同步互斥 四大板块,并保留了所有关键名词和细节。
搭配下面的播客使用效果更佳
📂 第一板块:进程管理
这部分考察对操作系统核心概念的理解,以及进程的生命周期。
| 问题 | 级别 | 详细解答 |
|---|---|---|
| 什么是进程 | 必问 | 进程是一个正在执行的程序,也是 CPU 资源分配的基本实体。 进程在系统的进程队列 ( task list) 中调度,该队列由进程控制块 (task_struct) 组成。一个 task_struct 管理一个进程。系统会为进程分配一个虚拟内存空间,通过页表映射到物理内存。 |
| 进程控制块 (PCB) 包含哪些信息 | 了解 | 1. 身份信息:进程ID (PID)、用户ID (UID)、组ID (GID)。 2. 状态信息:程序计数器 (PC)、CPU 寄存器值、进程状态 (就绪/执行/阻塞等)。 3. 内存信息:描述虚拟内存分配的地址信息。 4. 文件信息:文件描述符表 (指向 file 结构体)、当前工作目录。5. 资源控制:资源上限、控制终端信息。 |
| 进程有哪些状态 | 重点 | 1. 创建态:申请 PCB,分配资源,未完成则卡在此状态。 2. 就绪态:已准备好,只差 CPU 时间片。 3. 执行态:正在 CPU 上运行。 4. 睡眠态 (阻塞态):调用 sleep, wait, pause 等函数,等待事件。5. 暂停态:收到暂停信号 ( SIGSTOP)。6. 僵尸态:进程退出但未被父进程回收。 7. 死亡态:进程退出且已被回收。 |
| 进程状态是如何切换的 | ● 就绪 → 运行:调度器选中。 ● 运行 → 就绪:时间片用完或被高优先级抢占。 ● 运行 → 阻塞:等待 I/O 或事件。 ● 阻塞 → 就绪:事件完成。 ● 运行 → 终止:执行结束。 |
|
| 进程的虚拟内存空间机制 | 必问 | 概念:物理 CPU 只有一个,但通过虚拟化让每个进程认为自己独占 CPU 和内存。 过程:进程加载时,内核创建 task_struct 和 mm_struct (虚拟地址描述符)。地址空间:本质是内核中的一张表,将内存分区域。进程运行时,通过 MMU 和页表将虚拟地址映射到物理内存。 |
| 设计虚拟地址的好处 | 了解 | 1. 独立性:链接器可设定固定地址,无需关心实际物理地址。 2. 节省内存:共享代码 (如库文件) 在物理内存只存一份,映射给多进程。 3. 利用碎片:虚拟地址连续,但物理地址可以离散。 |
| Linux中子进程从父进程继承了什么 | 了解 | 用户ID/组ID、环境变量、堆栈、共享内存、打开的文件描述符、当前工作目录、根目录、信号屏蔽字、资源限制、控制终端。 |
| Linux中子进程独有的属性 | 了解 | 进程号 (PID)、父进程号 (PPID)、自己的文件描述符拷贝、运行时间数据、文件锁 (子进程不继承父进程的锁)。 |
| fork() 与 vfork() 的区别 | 重点 | fork():创建子进程,子进程复制父进程的地址空间 (写时复制)。 vfork():创建子进程,子进程与父进程共享地址空间。子进程必须先调用 exec 或 exit,否则父进程无法运行。效率高但危险。 |
| system 和 exec 函数族 | 重点 | exec 族 (如 execl):执行新程序,取代当前进程的代码段、数据段。调用后原进程代码不再执行(我变成你)。system:在当前进程中调用 shell 执行命令,执行完后继续执行后面的代码(我找人帮你干活)。 |
| exit、_exit、_Exit的区别 | exit:标准 C 库函数。终止进程前会清理缓冲区 (如 printf 内容)、调用 atexit 注册的函数、关闭文件。_exit / _Exit:系统调用。立即终止进程,不清理缓冲区。 |
|
| 进程调度的方法 | 重点 | 1. SCHED_FIFO (实时):先来先服务,一直运行直到退出或放弃。 2. SCHED_RR (实时):时间片轮转。 3. SCHED_OTHER (非实时):普通分时调度 (CFS)。 实时进程会抢占非实时进程。 |
| 守护进程及设计步骤 | 了解 | 定义:后台运行,脱离终端控制,通常用于系统服务。 步骤: 1. fork() 创建子进程,父进程退出 (变孤儿)。2. setsid() 创建新会话 (脱离控制终端)。3. chdir("/") 改变工作目录 (防占用挂载点)。4. umask(0) 重设文件掩码。5. close() 关闭无用文件描述符 (0,1,2)。 |
📂 第二板块:进程间通信
这部分考察不同进程如何传递数据,是后端与嵌入式开发的重难点。
| 问题 | 级别 | 详细解答 |
|---|---|---|
| 进程的通信方式IPC有哪些 | 必问 | 1. 无名管道 (Pipe):半双工,仅限亲缘进程。 2. 有名管道 (FIFO):半双工,允许无亲缘进程,以文件形式存在。 3. 信号 (Signal):异步通信,通知事件。 4. 消息队列 (Message Queue):链表结构,支持按类型取消息。 5. 共享内存 (Shared Memory):映射同一块物理内存,速度最快。 6. 信号量 (Semaphore):计数器,主要用于同步和互斥,而非传数据。 7. 套接字 (Socket):跨机器网络通信。 |
| 哪些通信方式需要借助内核? | 了解 | 几乎所有:管道、FIFO、消息队列、信号量、共享内存、Socket 都需要内核支持。 |
| 管道通信机制 (Pipe vs FIFO) | 必问 | 本质:内核中的缓冲区。 无名管道 ( pipe):无文件名,通过文件描述符操作。读端 fd[0],写端 fd[1]。读空管道阻塞,写满管道阻塞。有名管道 ( mkfifo):有文件名,可用于任意进程。若文件已存在会报错 File exists,需先判断再打开。 |
| 信号机制 | 必问 | 定义:软件层次对中断的模拟,异步通信。 分类:非实时 (1-31),实时 (34-64)。 处理方式:1. 默认动作 (通常终止)。2. 捕获 (执行信号处理函数)。3. 忽略。 特殊: SIGKILL (9) (立即处死)和 SIGSTOP (19) (立即冻结)不能被忽略或阻塞。阻塞:将信号加入屏蔽集,暂时挂起不处理。 按下 ctrl+c,程序收到SIGINT信号 |
| 消息队列 | 必问 | 特点:链表结构,存放在内核。支持按类型读取消息(不一定要先进先出)。独立于进程,进程退出消息仍在。 |
| 共享内存机制 | 必问 | 原理:多个进程的虚拟地址映射到同一块物理内存。 优点:最快的 IPC,因为无需内核与用户空间的数据拷贝。 注意:需配合信号量使用,解决同步竞争问题。 |
📂 第三板块:多线程编程
| 问题 | 级别 | 详细解答 |
|---|---|---|
| 进程与线程的区别 | 必问 | 1. 资源:进程是资源分配的基本单位 (独立地址空间); 线程是 CPU 调度的基本单位 (共享进程资源)。 2. 开销:进程切换开销大 (切页表/上下文);线程切换开销小。 3. 通信:进程需 IPC;线程共享全局变量,通信简单但需同步。 4. 健壮性:进程崩溃不影响其他进程;线程崩溃导致整个进程死掉。 5. 场景:多进程适合高隔离、高稳定;多线程适合高并发、高性能。 |
| 线程具有相同的堆栈吗 | 重点 | 不相同。每个线程有自己独立的栈空间 (Stack),但共享堆 (Heap)、全局变量和代码段。 |
| 线程有哪些属性 | 了解 | 作用域、栈尺寸、栈地址、优先级、分离状态 (detached)、调度策略。 |
| 线程如何回收 | 必问 | 默认是 joinable 状态,必须由其他线程调用 pthread_join() 回收资源,否则产生“僵尸线程”。调用 pthread_detach() 可设置为分离态,线程结束后自动释放资源。 |
| 线程的相关函数名 | 创建: pthread_create退出: pthread_exit等待: pthread_join取消: pthread_cancel分离: pthread_detach |
|
| 线程池原理 | 重点 | 组成:1. 任务队列 (链表)。2. 工作线程 (N个)。3. 管理者线程 (1个)。 工作流程: 生产者将任务加入队列 → 唤醒条件变量中的工作线程 → 工作线程取任务执行 → 执行完继续等待。 管理者线程定期检测,根据任务量动态增删线程。 |
| 多线程与线程池区别 | 必问 | 多线程:来一个任务创建一个线程,销毁也需时间。并发极大时,频繁创建销毁开销巨大。 线程池:预先创建一组线程,复用这些线程处理任务。避免了频繁创建销毁的开销,防止资源耗尽。 |
| 协程 (Coroutine) | 重点 | 定义:用户态轻量级线程。和普通线程由系统调度不同,协程由程序员控制切换,非抢占式。 优势:单线程内实现多任务并发,无锁机制,切换极快,适合高并发 IO 密集型任务。 |
| 可重入函数 | 重点 | 定义:被多个执行流重复调用时,结果依然正确。 条件:不使用全局/静态变量,不返回静态地址,仅依赖栈上变量。 重入讨论的是函数被中断后再进入的安全性。 |
📂 第四板块:同步与互斥
这部分是多线程编程的灵魂,解决资源竞争问题。
| 问题 | 级别 | 详细解答 |
|---|---|---|
| 线程的同步方式 | 必问 | 1. 互斥锁 (Mutex):保护临界区,独占访问。 2. 信号量 (Semaphore):控制并发数量,同步。 3. 条件变量 (Cond):等待通知,协调执行顺序。 4. 读写锁 (RWLock):读共享,写独占。 |
| 互斥锁 (Mutex) | 必问 | 作用:保证同一时刻只有一个线程访问共享资源。 机制:抢不到锁的线程会阻塞 (睡眠)。 |
| 自旋锁 (Spinlock) | 重点 | 作用:抢不到锁的线程会循环等待 (忙等),不睡眠。 对比:自旋锁不切换上下文,适合持有时间极短的场景;互斥锁会睡眠,适合持有时间长的场景。 |
| 读写锁 | 重点 | 特性:写独占,读共享。写锁优先级通常高于读锁。 场景:读多写少 (大量读取,少量修改)。 |
| 信号量 (Semaphore) | 必问 | 本质:计数器。P操作 (减/等待),V操作 (加/释放)。类型: 1. 二值信号量:类似互斥锁 (0或1)。 2. 计数信号量:控制并发数 (如连接池)。 注意:信号量为0时 P 操作会阻塞。 |
| 线程的条件变量 | 特点:不能单独使用,必须配合互斥锁。 作用:让线程在满足特定条件前休眠,避免忙轮询。 pthread_cond_wait 会原子性地释放锁并进入休眠。 |
|
| 死锁如何产生及4个必要条件 | 必问 | 4个条件 (缺一不可): 拿独木桥上的两个人举例子: 1. 互斥:资源独占,(你独占了你的桥面) 2. 占有且等待:拿了资源不放,还想要新的。(你独占了你的桥面,还想要别人的桥面) 3. 不可抢占:不能强行剥夺别人的资源。(你不能强行抢了别人的桥面) 4. 循环等待:A等B,B等A。(你想要别人的桥面,别人也想要你的桥面) 常见场景:同一线程重复上锁;两线程交叉上锁;持锁时被取消。 |
| 死锁处理方式 | 重点 | 预防:破坏上面的4种条件 (如一次性申请所有资源、按序申请)。 避免:银行家算法 (分配前评估安全性)。 检测与解除:剥夺资源、撤销进程。 |
| 同步方式的选择场景 | 实战 | ● 互斥锁:简单保护独占资源。 ● 信号量:生产者-消费者模型,控制资源数量。 ● 条件变量:等待某个事件发生 (如队列非空)。 |
| 内核链表 (kernel list) | 定义:Linux 内核专用双向循环链表。 特点: 1. 指针封装在 struct list_head 中。2. 数据与链表分离:链表节点嵌入在用户数据结构中,通过 container_of 宏反推数据地址。3. 提供丰富宏操作: list_add, list_del, list_for_each_entry。 |
|
| Lvgl是什么? | 重点 | LVGL (Light and Versatile Graphics Library):开源嵌入式 GUI 库。 特点:轻量 (适合 MCU)、控件丰富、跨平台、支持多输入设备。 场景:智能手表、工业仪表、物联网设备界面。 |