寒武纪 Linux驱动开发 一面 面经
1. 你在实习中遇到过驱动加载失败的情况吗?怎么排查的?
遇到过,排查思路分几个层次:
首先看内核日志,dmesg | tail -50 是第一步,驱动加载失败通常会在内核日志里留下明确的错误信息,比如 probe 函数返回了错误码、设备树匹配失败、资源申请冲突等。
其次确认模块依赖,lsmod 查看已加载模块,modinfo 查看模块依赖关系,如果依赖的符号未导出或依赖模块未加载,insmod 会直接报 Unknown symbol 错误。
然后检查设备树,如果是 platform 驱动,确认 DTS 中的 compatible 字符串与驱动 of_match_table 完全一致,大小写和格式都不能有偏差,一个字符的差异就会导致匹配失败。
最后检查资源冲突,寄存器地址、中断号、GPIO 是否被其他驱动占用,ioremap 失败或 request_irq 返回 -EBUSY 都是常见原因。
2. Linux 内核中 ioremap 和 mmap 的区别是什么?驱动里什么时候用哪个?
两者都是将物理地址映射到虚拟地址,但使用场景完全不同。
ioremap 是内核空间的映射,驱动在内核态通过它访问外设寄存器或物理内存,映射后得到内核虚拟地址,用 readl/writel 等接口操作,不能直接用指针解引用(在某些架构上会有问题)。驱动初始化时调用,卸载时必须 iounmap 释放,否则内核虚拟地址空间泄漏。
mmap 是将内核空间的物理内存或设备内存映射到用户空间,用户进程可以直接读写,避免了 read/write 系统调用的数据拷贝开销。驱动需要实现 file_operations 中的 mmap 回调,调用 remap_pfn_range 完成映射。适合大数据量传输场景,比如视频帧缓冲、AI推理结果缓冲区。
简单说:ioremap 是驱动自己用来访问硬件的,mmap 是给用户进程用来直接访问驱动管理的内存的。
3. 字符设备驱动中 ioctl 和 read/write 的区别?什么情况下用 ioctl?
read/write 是标准的数据流接口,适合传输连续的数据内容,语义清晰,用户空间直接用 read()/write() 系统调用即可。
ioctl(input/output control)是设备控制接口,用于传递控制命令和配置参数,不适合用 read/write 表达的操作都可以用 ioctl。比如:设置串口波特率、配置ADC采样率、触发一次硬件复位、查询设备状态、设置DMA传输模式等。
ioctl 的 cmd 参数通常用内核提供的 _IO/_IOR/_IOW/_IOWR 宏构造,编码了方向(读/写)、类型(幻数)、序号、数据大小,内核可以据此做合法性检查。
驱动实现时需要注意:用户空间传入的指针必须用 copy_from_user / copy_to_user 访问,不能直接解引用,否则会访问用户空间地址导致内核崩溃或安全漏洞。
4. 内核中的内存屏障是什么?为什么驱动开发中必须关注它?
内存屏障(Memory Barrier)是一种硬件和编译器指令,用于保证内存访问操作的顺序性。
问题根源有两个:一是编译器优化可能对无依赖关系的读写指令重排序;二是现代CPU为了提升流水线效率,也会乱序执行内存访问。在单核单线程场景下这没有问题,但在多核或涉及硬件寄存器的场景下,乱序访问会导致严重错误。
驱动开发中的典型场景:向外设寄存器写入配置后,必须确保配置写入完成再写入启动命令,如果两条写操作被重排,设备会在配置未生效时就启动,行为不可预期。此时需要在两次写操作之间插入 wmb()(写屏障)。
Linux内核提供了几种屏障:mb()(全屏障)、rmb()(读屏障)、wmb()(写屏障),以及针对DMA的 dma_wmb()/dma_rmb()。使用 readl/writel 等IO访问函数时,内核已经内置了必要的屏障,但直接操作指针时需要手动添加。
5. 驱动中如何处理并发访问同一设备的问题?
这是驱动开发中非常实际的问题,多个进程或线程同时 open 同一个设备节点时,如果驱动没有保护,共享状态会被破坏。
常见处理方式有几种:
引用计数限制并发:在 open 回调中用原子变量 atomic_t 记录打开次数,如果设备不支持多开,超过限制直接返回 -EBUSY,close 时递减。
互斥锁保护临界区:对设备状态、寄存器配置等共享数据,用 mutex 保护,确保同一时刻只有一个进程在操作。
等待队列处理阻塞:设备忙时不直接返回错误,而是将进程加入等待队列睡眠,设备就绪后通过 wake_up 唤醒,实现阻塞式访问语义,这也是 poll/select 机制的基础。
中断与进程上下文的并发:中断处理函数和进程上下文可能同时访问共享数据,此时必须用 spin_lock_irqsave 而不是普通 mutex,因为中断上下文不能睡眠。
6. Linux 驱动的 probe 函数什么时候被调用?probe 失败会怎样?
probe 是驱动与设备匹配成功后内核自动调用的初始化函数,是驱动真正工作的起点。
调用时机:内核启动时扫描设备树或总线上的设备,将设备的 compatible 字符串与已注册驱动的 of_match_table 逐一比对,匹配成功后调用对应驱动的 probe 函数。热插拔设备(如USB)在插入时触发,platform 设备在内核初始化阶段触发。
p
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
这是一个全面的嵌入式面试专栏。主要内容将包括:操作系统(进程管理、内存管理、文件系统等)、嵌入式系统(启动流程、驱动开发、中断管理等)、网络通信(TCP/IP协议栈、Socket编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。
