小米嵌入式软件 一面被拷打实录(嵌入式Linux方向)
一面大概 45 分钟,面试官上来就直接问项目,没有任何寒暄,问完项目立刻切到技术题,节奏很快。问题不是孤立的,每道题都会顺着你的回答继续追问,感觉像是在顺着你的知识边界一路往里挖。Linux 内核、驱动、进程通信这几块问得很细,不是背概念就能过的,要能说清楚底层原理。最后手撕代码,题目不难但时间紧,建议提前练好链表和位操作。整体感受:真懂比背得多更重要。
1. 自我介绍
略,建议 2 分钟内说清楚:用过哪些芯片平台、做过什么项目、技术栈是什么,突出和嵌入式 Linux 相关的经历。
2. Linux 进程和线程的区别?内核里怎么描述它们的?
这道题很多人只说"进程是资源分配单位,线程是调度单位",面试官会继续追问内核层面,所以要答得深一点。
Linux 内核里,进程和线程都用同一个数据结构描述:task_struct。本质上 Linux 没有专门的"线程"概念,线程就是和其他 task_struct 共享资源的轻量级进程(LWP,Light Weight Process)。
区别体现在资源共享上:
- 用
fork()创建进程:子进程拥有独立的虚拟地址空间(写时复制 COW)、独立的文件描述符表、独立的信号处理 - 用
clone()创建线程(pthread_create底层就是clone):通过传入CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND等标志,让新 task 和父 task 共享地址空间、文件描述符、信号处理等
所以从内核视角看,进程和线程的区别就是 clone() 时传的 flag 不同,共享的资源多少不同。
具体资源对比:
虚拟地址空间 |
独立(COW) |
共享 |
栈 |
独立 |
各自独立 |
堆 |
独立(COW) |
共享 |
全局变量 |
独立(COW) |
共享 |
文件描述符 |
独立副本 |
共享 |
信号处理 |
独立 |
共享 |
PID |
独立 |
共享 TGID,各有 TID |
线程切换比进程切换开销小,因为不需要切换页表(地址空间相同),TLB 不需要刷新。
3. Linux 进程间通信有哪些方式?各自适用什么场景?
管道 pipe |
半双工,只能父子进程,内核缓冲区 |
shell 命令管道 |
命名管道 FIFO |
可无亲缘关系,文件系统可见 |
无关进程间简单通信 |
消息队列 |
有消息边界,支持按类型读取 |
异步消息传递 |
共享内存 |
最快,零拷贝,需配合同步机制 |
大数据量高频交换 |
信号量 semaphore |
用于同步,不传数据 |
进程/线程同步 |
信号 signal |
异步通知,数据量极少 |
异常通知、进程控制 |
Socket |
可跨机器,最通用 |
网络通信、本地跨进程 |
mmap 文件映射 |
多进程映射同一文件,共享内存变体 |
大文件共享、数据库 |
嵌入式 Linux 里最常用的是共享内存 + 信号量组合(性能最好),以及 Unix Domain Socket(比网络 socket 开销小,适合本机进程通信)。
4. Linux 内核驱动中,request_irq 注册中断时需要注意什么?中断处理函数里能不能睡眠?
request_irq 原型:
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long flags, const char *name, void *dev);
注册时注意:
flags要正确设置,比如IRQF_SHARED(共享中断线)、IRQF_TRIGGER_RISING(上升沿触发)dev在共享中断时用于区分设备,free_irq时也要传同一个指针- 中断号要从设备树或平台资源里获取,不要硬编码
中断处理函数(hardirq context)里绝对不能睡眠,原因:
- 中断上下文没有进程上下文,调度器无法切换走
- 如果调用了可能睡眠的函数(如
kmalloc(GFP_KERNEL)、mutex_lock),会导致内核 BUG 或死锁 - 内核会在 debug 模式下检测这种情况并报
BUG: scheduling while atomic
正确做法是上半部(top half)只做最少的工作(读寄存器、清中断标志),把耗时操作推到下半部:
tasklet:软中断上下文,不能睡眠,但比 hardirq 优先级低workqueue:进程上下文,可以睡眠,适合耗时操作threaded IRQ:request_threaded_irq,下半部在内核线程里跑,可以睡眠
5. 说说 Linux 设备树(Device Tree)是什么?为什么要引入它?
设备树(DTS,Device Tree Source)是描述硬件信息的数据结构,编译成 DTB(Device Tree Blob)后由 bootloader 传给内核。
引入原因:ARM 平台板级代码泛滥,每块板子都要在内核里写一堆硬编码的板级初始化代码,导致内核里充斥大量 arch/arm/mach-xxx 目录。Linus 忍不了了,2011 年强制要求 ARM 平台使用设备树,把硬件描述从内核代码里剥离出来。
设备树描述的内容:
- CPU 数量和类型
- 内存地址和大小
- 外设基地址、中断号、时钟、DMA 通道
- GPIO、pinctrl 配置
- 总线拓扑(I2C、SPI 上挂了哪些设备)
// 典型的 I2C 设备节点
&i2c1 {
status = "okay";
clock-frequency = <400000>;
sensor@48 {
compatible = "ti,tmp102";
reg = <0x48>;
interrupt-parent = <&gpio1>;
interrupts = <7 IRQ_TYPE_LEVEL_LOW>;
};
};
驱动通过 of_match_table 匹配 compatible 字符串,用 of_property_read_u32 等 API 读取属性,实现驱动和硬件描述分离。
6. mmap 的原理是什么?用户态驱动里为什么常用 mmap?
mmap 将文件或设备的物理内存映射到进程的虚拟地址空间,访问这段虚拟地址就等于直接访问物理内存,不需要 read/write 系统调用,零拷贝。
原理:
- 调用
mmap后,内核在进程的 VMA(Virtual Memory Area)链表里添加一个新的 VMA 描述符 - 此时并不立即建立页表映射(懒分配)
- 进程第一次访问该地址时触发缺页异常(page fault)
- 内核的缺页处理函数调用驱动的
mmap回调(vm_ops->fault),建立物理页到虚拟地址的映射 - 之后访问就直接走 MMU,不再陷入内核
驱动里常用 mmap 的原因:
- 用户态程序可以直接读写硬件寄存器或 DMA buffer,避免每次都走
ioctl/read的系统调用开销 - 对于视频帧缓冲、音频 DMA buffer 这类大数据量场景,零拷贝性能提升非常明显
/d就是通过 mmap 让用户态直接访问物理内存
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
这是一个全面的嵌入式面试专栏。主要内容将包括:操作系统(进程管理、内存管理、文件系统等)、嵌入式系统(启动流程、驱动开发、中断管理等)、网络通信(TCP/IP协议栈、Socket编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。


