简介:面向准备 Linux 嵌入式开发岗位笔试面试的求职者与在校学生,这份资料围绕 C 语言与系统编程高频考点做系统梳理,帮助读者在有限时间内建立清晰的知识框架并查漏补缺。内容覆盖进程与线程的基本概念、多进程与多线程的优缺点及选用场景、进程与线程的状态转换、父子进程关系与 fork 返回值差异,还延伸到进程通信与线程通信的方式对比、各类锁的作用、死锁成因与规避策略等,均以问答形式呈现,便于直接对照复习。资源包共 1 个 docx 文档,约 75KB,体量轻便,适合打印或导入笔记软件随时翻阅。目前已有 843 人学习下载,可作为嵌入式 C 语言面试前的速查清单与知识点自测材料。
1. 嵌入式 C 语言面试的知识地图:从进程线程到内存模型
嵌入式 Linux 的 C 语言笔试题,看起来像一堆零散概念——进程、线程、TCP、堆栈、中断、大小端——但真正拉开差距的,是能不能把它们串成一张图。这张图有三条主线:第一条是并发与调度,进程线程、同步互斥、死锁都挂在这条线上;第二条是内存与存储,堆栈、虚拟内存、大小端、Flash 与 RAM 的取舍归这里;第三条是系统与硬件交互,系统调用、中断异常、Bootloader 启动流程、UART/I2C/SPI 这些总线协议是它的分支。网络编程则横跨并发与系统交互,是第三条线上的高频考点。
这份笔记面向两类人:正在系统学嵌入式 Linux、想把零散知识收拢成体系的人,以及临近笔试面试、需要快速查漏补缺的人。梳理下来会发现,题目本身不难,难的是面试官追问一句「为什么」的时候,你能不能从内存模型或调度机制上给出解释。下面按模块拆开讲,最后落到一份可执行的复习清单。
2. 并发三问:进程线程、同步机制与死锁的复习重点
并发是嵌入式 C 面试的第一大块,几乎每场笔试都会出现。它的核心问题只有三个:进程和线程怎么选、共享资源怎么保护、死锁怎么防。把这三个问题答清楚,这一块基本就稳了。
2.1 进程与线程:区别、选型与 fork 的返回值
进程是资源分配的基本单位,拥有独立的内存空间和执行环境;线程是处理器调度的基本单位,同一进程内的多个线程共享内存和资源。这句话是标准答案,但面试官更想听的是选型逻辑:需要资源隔离和稳定性时用进程,一个进程崩溃不拖垮其他进程;需要并发执行、资源共享和低开销时用线程,线程创建和切换的成本远小于进程。
fork() 是必考点,最容易翻车的地方在返回值。父进程拿到子进程的 PID,子进程拿到 0,出错返回 -1。很多人只背了「父进程返回子进程 ID」,却答不出子进程为什么返回 0——因为子进程需要知道自己是被创建出来的那一个,而它没有「自己的子进程」这个概念,返回 0 是最省事的区分方式。
#include <stdio.h> #include <unistd.h> int main(void) { pid_t pid = fork(); /* 创建子进程,父子进程从此各走各的 */ if (pid < 0) { perror("fork failed"); /* 资源不足等错误,返回 -1 */ } else if (pid == 0) { printf("child process, pid=%d\n", getpid()); /* 子进程分支 */ } else { printf("parent process, child pid=%d\n", pid); /* 父进程分支 */ } return 0; }这段代码的关键不在语法,而在理解 fork 之后父子进程是两个独立的执行流,各自从 fork 返回处继续往下走。子进程是父进程的副本,代码、数据、资源都复制一份,但内存是写时复制,不是立刻全量拷贝。面试里常追问「fork 之后父子进程共享什么」,答案是共享文件描述符表指向的打开文件,但各自的地址空间独立。
进程和线程的状态转换也是高频题。就绪态是已获得除 CPU 外的所有资源、排队等调度;阻塞态是等待某个事件或资源,比如等 I/O 完成、等锁释放。两者的区别在于:就绪态只差 CPU,阻塞态差的是事件。这个区分在画状态图时最容易画错。
2.2 同步机制对照表:进程与线程各用什么
同步是并发部分的重头戏,进程和线程的同步手段有重叠也有差异,用一张表对照记忆效率最高。
| 同步手段 | 适用对象 | 核心作用 | 典型场景 |
|---|---|---|---|
| 互斥锁 Mutex | 进程、线程 | 临界区一次只允许一个访问者 | 保护共享计数器 |
| 信号量 Semaphore | 进程、线程 | 用计数器控制同时访问的数量 | 限制连接池并发数 |
| 条件变量 | 进程、线程 | 等待特定条件成立再唤醒 | 生产者消费者队列 |
| 读写锁 | 线程为主 | 读并发、写独占 | 配置表读多写少 |
| 屏障 Barrier | 线程 | 多线程在固定点对齐后再继续 | 分阶段并行计算 |
| 文件锁 | 进程 | 控制对共享文件的访问 | 多进程写同一日志 |
这张表要能默写。面试里常见的追问是「互斥锁和信号量的区别」:互斥锁是二值的所有权概念,谁加锁谁解锁;信号量是计数器,可以由一个执行流释放、另一个执行流获取,所以能用于生产者消费者这种跨执行流的通知。
进程通信方式则是另一组必背内容:共享内存、消息传递、管道、套接字、信号量。共享内存速度最快、适合大量数据交换,但必须自己做同步,隔离性差;管道简单但只能用于有血缘关系的进程,且单向;套接字灵活、可跨主机,但相对复杂、性能偏低。选型时按数据量、性能要求、进程关系和隔离需求四个维度权衡,这个答题框架比死记优缺点更管用。
2.3 死锁的四个条件与六种规避手段
死锁的定义要答准:两个或多个进程(线程)互相占用对方所需的资源,形成循环等待,谁也无法继续。避免死锁的手段原文列了六条,梳理下来可以归成三类:破坏循环等待(按统一顺序申请资源)、破坏持有并等待(资源有序分配、一次只占一个)、引入外部干预(超时放弃、资源剥夺、死锁检测与恢复)。再加上合理规划资源和定期审查代码这两条工程习惯。
答题时最好点出死锁的四个必要条件——互斥、持有并等待、不可剥夺、循环等待——然后说明每种手段破坏了哪一个。这样答比罗列六条方法更有层次,面试官能看出你理解的是机制而不是背诵。
3. 网络编程主线:TCP/UDP 选型、握手挥手与粘包处理
网络编程在嵌入式岗位里权重不低,因为设备联网是常态。这一块的考点集中在协议选型、连接过程和流式数据的边界处理上,三者层层递进。
3.1 TCP 与 UDP:可靠性从哪来,代价是什么
TCP 面向连接、可靠、有序,UDP 无连接、简单、低延迟。这个对比人人会背,但面试官常追问「TCP 为什么可靠」。答案有四条机制:应答与超时重传保证数据到达;顺序号与重组保证数据有序;滑动窗口与流量控制防止接收方缓冲区溢出;再加上拥塞控制。能把这四条说全,比只说「因为有确认机制」高一个档次。
选型逻辑要能脱口而出:文件传输、长连接应用、需要严格有序的场景用 TCP;音视频流媒体、语音通话、广播多播这类实时性优先、能容忍少量丢包的场景用 UDP。嵌入式里还有个隐含考点——资源受限设备上 UDP 更轻量,协议栈占用小,这也是选型时值得提一句的角度。
3.2 三次握手与四次挥手:每一步在解决什么问题
三次握手的流程是 SYN、SYN+ACK、ACK。要理解它为什么是三次而不是两次:两次握手无法让服务端确认客户端的接收能力,也无法防止历史失效连接请求突然到达造成的资源浪费。四次挥手是 FIN、ACK、FIN、ACK,之所以比握手多一次,是因为 TCP 是全双工的,两个方向要分别关闭,服务端收到 FIN 后可能还有数据要发,所以 ACK 和 FIN 不能合并。
这里有个容易忽略的细节:挥手时主动关闭方最后要进入 TIME_WAIT 状态等待一段时间。面试常问「为什么需要 TIME_WAIT」,答案是确保最后一个 ACK 能到达对方,以及让本次连接的残留报文在网络中消散,避免影响下一个同端口的新连接。
3.3 MTU 与粘包:流式协议的边界问题
MTU 是链路层能传输的最大数据包长度,以太网帧的数据字段上限是 1500 字节,所以常见 MTU 为 1500。这个数字要能解释来源,而不是死记。实际网络中 MTU 可能被调小以适应特殊环境,这点在答题时提一句会显得有实战意识。
粘包是 TCP 这类流式协议的固有问题:数据没有固定边界,发送方多个小包可能被合并,接收方无法区分。三种解决方案要能对比:
| 方案 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 定长包 | 每包固定长度,按长度切 | 实现最简单 | 浪费空间,灵活性差 |
| 分隔符 | 包之间插入特殊分隔符 | 实现简单、可读性好 | 内容不能含分隔符 |
| 包头+包体 | 包头存长度,先读头再读体 | 灵活、无浪费 | 实现稍复杂 |
嵌入式项目里最常用的是第三种,包头加包体,因为协议可控、效率高。答题时能说出「为什么嵌入式偏爱包头+包体」,比单纯罗列三种方案更有说服力。
4. 内存与存储:堆栈、虚拟内存、大小端与链接机制
内存是嵌入式 C 的基本功,也是笔试里最容易出细节题的地方。这一块的知识点看似分散,其实围绕一个问题:数据存在哪、怎么访问、边界在哪。
4.1 堆与栈:分配方式、生命周期与两类内存错误
堆是动态分配区,用 malloc/free 手动管理,大小灵活、生命周期由程序员控制;栈是线程专用的临时区,存局部变量、函数参数和返回地址,由编译器自动分配释放,遵循后进先出,大小有限。两者的对比要能说清分配方式、管理机制、大小与生命周期三个维度。
内存泄漏和内存溢出是必考的两个概念,也是最容易混淆的一对。泄漏是分配了没释放,内存被占着但再也访问不到,长期运行会耗尽资源;溢出是写超了已分配的边界,可能覆盖相邻数据,导致崩溃或安全漏洞。嵌入式设备长期无人值守,泄漏的后果尤其严重,这点在答题时结合场景说一句会加分。
4.2 大小端:判断方法与使用场景
大端是高字节存低地址,小端是低字节存低地址。判断方法原文给了三种:指针地址比较、联合体、位操作。联合体法最简洁,也最适合手写:
#include <stdio.h> int is_little_endian(void) { union { int i; /* 多字节类型 */ char c[sizeof(int)]; /* 按字节访问同一块内存 */ } u; u.i = 1; /* 小端时最低字节为 1,存在低地址 */ return u.c[0] == 1; /* 读第一个字节即可判断 */ }原理是联合体成员共享同一块内存,给 int 赋值 1 后读第一个字节,小端机器上低地址存的是最低有效字节,所以读到 1;大端机器读到 0。这个写法比指针强转更干净,面试手写时不容易出错。
使用场景要能对应上:大端对网络通信友好,所以网络字节序是大端;小端对处理器访问最低有效字节更高效,主流处理器架构多采用小端。跨平台通信时必须在发送前统一转成网络字节序,这是嵌入式联网项目里的常规操作。
4.3 虚拟内存与地址转换:MMU 在中间做了什么
虚拟内存把磁盘交换空间作为逻辑地址空间的延伸,让程序可以使用连续的逻辑地址,而不要求物理内存连续。虚拟地址到物理地址的转换由 MMU 完成,靠页表或段表做映射。程序访问虚拟地址时,MMU 查表得到物理地址,再实际访问内存。
这条链路要能讲完整:程序用虚拟地址 → MMU 查页表 → 得到物理地址 → 访问物理内存。页表项里通常还带权限位,这也是用户态程序无法直接访问内核空间的原因之一。面试追问「虚拟内存的好处」时,答出隔离性、可以运行大于物理内存的程序、地址空间连续便于管理这三点就够了。
4.4 硬链接与软链接、静态与 extern “C”
硬链接是多个文件名指向同一个 inode,共享内容和属性,删掉一个不影响其他,只有全部删除才释放空间,且不能跨文件系统。软链接是独立文件,内容是目标路径,类似快捷方式,可以跨文件系统、可以指向目录,但原文件删除后链接失效。这个对比用「inode 共享与否」一句话就能记住。
extern “C” 是 C++ 与 C 混合编译的桥梁,作用是让 C++ 编译器按 C 的规则处理函数名,避免名称修饰导致的链接错误。嵌入式项目里 C 和 C++ 混用很常见,这个点虽然小,但答不出来会显得基础不牢。
5. 系统与硬件交互:内核机制、启动流程与总线协议
这一块是嵌入式岗位区别于通用 C 岗位的地方,考的是你对「程序之下」的理解:系统调用怎么进内核、中断怎么发生、板子怎么启动、外设怎么连。
5.1 用户态与内核态:系统调用到底做了什么
内核态是特权级别,可以访问所有资源和指令;用户态受操作系统保护,只能通过系统调用请求内核服务。两者的区别从特权级别、资源访问、安全性三个角度答。
系统调用与普通函数调用的核心差异在于上下文切换和权限检查:系统调用会从用户态陷入内核态,内核做权限校验后执行特权操作,再返回用户态;普通函数调用在同一特权级别内完成,没有切换开销。以 read/write 为例,内核要做的事包括接收请求、检查权限、验证数据合法性、在用户空间和内核空间之间拷贝数据、返回结果。能说出「数据拷贝」这一步,说明你理解系统调用的真实成本。
用户空间与内核通信的方式有系统调用、中断、文件操作(设备文件)、IPC 机制。这四条要能各举一个例子,比如设备文件读写就是驱动交互的常见方式。
5.2 中断与异常:触发源和处理流程的区别
中断由外部事件触发,比如键盘、定时器、外设请求,会打断当前程序,转去执行中断服务程序,处理完再返回。异常是程序执行中遇到的错误情况,比如除零、非法内存访问,由处理器检测并触发处理机制。两者的关键区别在触发源:中断来自外部,异常来自内部执行。
中断处理流程四步:发生(设备发 IRQ,查中断向量表)、响应(保存上下文、跳转服务程序)、处理(读设备状态、处理数据)、恢复(还原上下文、返回被中断程序)。这四步要能按顺序说清,尤其是「保存上下文」这一步,是理解中断为什么能正确返回的关键。
5.3 Bootloader 启动链路:从上电到 init
启动流程是嵌入式面试的经典大题,要能按顺序讲完整条链路:上电后固件自检并加载 Bootloader;Bootloader 初始化硬件、加载内核和根文件系统到内存;设置内核启动参数并跳转到内核入口;内核初始化系统配置和资源;挂载根文件系统;启动第一个用户空间进程 init。
Bootloader、内核、根文件系统三者的关系要能一句话概括:Bootloader 是硬件与操作系统之间的桥梁,负责把控制权从固件交给内核,并准备好内核运行所需的内核镜像和根文件系统。根文件系统提供 /bin、/sbin、/etc、/lib 这些基础目录,是系统运行时的文件基础。
5.4 总线协议对比:UART、I2C、SPI、USB、以太网
外设通信方式是嵌入式笔试的固定题型,线数、速率、优缺点三个维度要能对照记忆。
| 接口 | 线数 | 典型速率 | 优点 | 局限 |
|---|---|---|---|---|
| UART | 2(TX/RX) | 几十 bps 到数 Mbps | 简单、成本低 | 点对点、速率有限 |
| I2C | 2(SCL/SDA) | 100k/400k/3.4M bps | 引脚少、可挂多设备 | 速率低、适合短距 |
| SPI | 4(MOSI/MISO/SCK/CS) | 几十 MHz 量级 | 高速、全双工 | 引脚多、无多主机 |
| USB | 4(D+/D-/VCC/GND) | 480Mbps 到 20Gbps | 高速、热插拔 | 复杂、需专用控制器 |
| 以太网 | 至少 4 根 | 10M 到 100Gbps | 高速可靠、可组网 | 硬件和协议较重 |
这张表要能默写线数和速率量级。面试追问「为什么 I2C 只用两根线就能挂多个设备」,答案是它有寻址机制,每个从设备有独立地址,主机通过地址选择通信对象;而 UART 没有寻址,只能点对点。这个对比能体现你理解的是协议设计而不是参数背诵。
6. 易错点排查:五个最容易在笔试里翻车的地方
复习到这一步,知识点已经铺开,但真正丢分往往不是因为不会,而是因为记混。下面五条是梳理下来最容易出错或漏掉的地方,每条按「易漏点 → 为什么漏 → 怎么补」处理。
易漏点一:fork 返回值记反。很多人背成「父进程返回 0」,实际是父进程返回子进程 PID、子进程返回 0。漏的原因是只记了「有区别」没记方向。补法是手写一遍 fork 示例,把两个分支的打印都跑一遍,形成肌肉记忆。
易漏点二:内存泄漏和内存溢出混为一谈。泄漏是没释放,溢出是写越界,一个是资源管理问题,一个是边界安全问题。漏的原因是中文词形相近。补法是用一句话区分:泄漏是「该还的没还」,溢出是「写到了别人的地盘」。
易漏点三:TCP 可靠性的机制只答一条。只答「有确认重传」会显得单薄,完整答案要覆盖重传、排序、流量控制、拥塞控制四层。漏的原因是平时只记了最显眼的那条。补法是把四条机制和它们各自解决的问题对应起来记。
易漏点四:粘包解决方案只背名字不说适用条件。定长包浪费空间、分隔符要求内容不含分隔符、包头+包体最灵活但实现稍复杂,这三条限制条件才是得分点。漏的原因是只记了方案名。补法是每个方案配一个「什么时候不能用」的例子。
易漏点五:中断和异常的触发源搞反。中断来自外部设备,异常来自程序内部执行错误。漏的原因是两者处理流程相似,容易只记流程不记来源。补法是用「外部打断」和「内部出错」两个短语锚定。
提示:这五条建议在复习最后阶段单独过一遍,比重新翻一遍全部知识点性价比更高。
7. 复习清单:按优先级排一张可执行的查漏表
把上面的内容收成一张清单,按「必答、常追问、加分项」三档排优先级,复习时从高到低过,时间不够就只保必答档。
| 优先级 | 知识点 | 自检标准 |
|---|---|---|
| 必答 | 进程线程区别与选型 | 能说出资源分配单位与调度单位的差异,并给出选型场景 |
| 必答 | fork 返回值 | 父进程得子 PID、子进程得 0,能解释原因 |
| 必答 | 互斥锁与信号量区别 | 能说清所有权与计数器的差异 |
| 必答 | TCP 三次握手四次挥手 | 能逐步说明,并解释 TIME_WAIT 的作用 |
| 必答 | 堆栈区别 | 分配方式、管理机制、生命周期三维度齐全 |
| 必答 | 大小端判断 | 能手写联合体判断代码并解释原理 |
| 常追问 | 死锁四条件与规避 | 每种手段能对应到破坏哪个条件 |
| 常追问 | 粘包三种方案 | 能说出各自适用与不适用条件 |
| 常追问 | 系统调用开销 | 能说出上下文切换与数据拷贝两步成本 |
| 常追问 | Bootloader 启动链路 | 六步顺序不乱,三者关系能一句话概括 |
| 加分项 | 总线协议线数与速率 | 表格能默写,能解释 I2C 寻址机制 |
| 加分项 | 虚拟内存与 MMU | 能讲完整地址转换链路 |
| 加分项 | extern “C” 作用 | 能结合 C/C++ 混合编译场景说明 |
复习方法上,建议按「先默写表格、再手写代码、最后口述流程」三步走。表格类知识(同步机制、总线协议、粘包方案)用默写检验;代码类知识(fork、大小端判断、malloc 使用)用白纸手写检验;流程类知识(三次握手、中断处理、启动链路)用口述检验,能对着空气讲顺才算过关。
查漏补缺的具体技巧是反向提问:不要只问「什么是死锁」,而是问「如果面试官追问死锁的四个必要条件分别被哪种方法破坏,我能不能答上来」。每复习一个知识点,给自己加一层追问,答不上来的就是漏洞。这个方法比刷题更有效,因为笔试题的变体太多,而追问的深度是有限的。
我自己的习惯是每次复习完一个模块,合上资料,用三分钟把这一块的核心链路口述一遍,卡壳的地方立刻回去补。有一次在讲虚拟地址转换时卡在「页表项里除了物理地址还有什么」,回头补了权限位这一层,后来面试正好被问到用户态为什么不能直接访问内核空间,答起来就很顺。从那以后我每次复习任何模块,都强制走一遍「合上资料口述链路」的流程,卡壳点就是当天的复习重点。希望这份清单能帮到你,把零散的知识点真正连成一张能随时调用的地图。
本文还有配套的精品资源,点击获取