第2章:Linux驱动核心概念与内核模块

本章拆解面试最高频的三个驱动基础问题:内核模块结构、file_operations、用户态与内核态数据交互。掌握这些概念等于拿下60%的实习生面试题。

本章考点权重: 内核模块生命周期 ★★★★★ | file_operations ★★★★☆ | 数据拷贝函数 ★★★★☆
内核模块核心框架 module_init/exit MODULE_LICENSE MODULE_AUTHOR 模块参数传递 生命周期:init → exit 编译:Makefile → .ko module_param(name, type, perm) insmod xxx.ko name=value insmod / rmmod / lsmod
面试加分项: 回答时主动提及 MODULE_DESCRIPTION、__init / __exit 宏、以及通过 /sys/module 查看参数,可体现对内核模块细节的掌握。

📌 面试问题1:请简述Linux内核模块的基本结构,并说明如何加载和卸载?

考察点 内核模块编译体系、生命周期、命令行操作
回答框架: ① 头文件引用 → ② 入口/出口函数声明(module_init/module_exit) → ③ 必填LICENSE(GPL) → ④ 可选AUTHOR/DESCRIPTION/参数 → ⑤ Makefile编译生成.ko → ⑥ insmod加载→rmmod卸载
示例回答:
“内核模块通过 module_init 指定入口函数,module_exit 指定清理函数;必须声明 MODULE_LICENSE("GPL") 才能使用内核导出符号。编译后生成 .ko 文件,通过 insmod 加载、rmmod 卸载。建议使用 modprobe 自动处理依赖。”
可能追问
  • 如果模块加载失败,如何排查?(查看dmesg/内核日志)
  • module_init的优先级(late_initcall等)?
  • 同一模块能否多次加载?(不可,需要先卸载)
⚠️ 避坑提醒:忘记写 MODULE_LICENSE 会导致内核污染(taint);入口/出口函数必须标记 __init/__exit 以节省内存。

📌 面试问题2:解释Linux驱动中的 file_operations 结构体,主要回调函数有哪些?

考察点 字符设备驱动模型、回调机制、每个回调的典型用途
回答框架: ① 定义 struct file_operations 并赋值 → ② 关键成员:.open .release .read .write .unlocked_ioctl → ③ 注册:alloc_chrdev_region → cdev_init → cdev_add → ④ 用户调用open/read/write时自动触发回调
示例回答:
“file_operations 是驱动程序与VFS的接口,常用的有 .open 用于初始化设备状态,.release 释放资源,.read/.write 完成数据读写,.unlocked_ioctl 处理自定义命令。例如GPIO驱动中,.write写入引脚电平,.read读取状态。”
可能追问
  • .unlocked_ioctl 与 .ioctl 的区别?(锁模型不同)
  • .mmap 何时使用?(高速数据共享,如帧缓冲)
  • 如何实现异步通知?(结合 .poll 和 fasync)
⚠️ 避坑提醒:.read/.write 返回字节数必须精确,否则应用层阻塞;.open 中需检查私有数据 filp->private_data 的初始化。

📌 面试问题3:用户空间与内核空间数据拷贝的常用函数有哪些?它们有什么安全注意事项?

考察点 地址隔离、copy_*_user 系列、get_user/put_user、潜在的安全漏洞
回答框架: ① 内核不能直接访问用户指针 → ② 首选 copy_from_user/copy_to_user(返回未拷贝字节数) → ③ 简单类型用 get_user/put_user → ④ 必须检查返回值,失败则返回 -EFAULT → ⑤ 注意避免用户态传非法地址导致崩溃
示例回答:
“常用 copy_from_user(buf, user_buf, count) 将用户空间数据拷贝到内核缓冲区。若返回非0,说明部分拷贝失败,应返回 -EFAULT。简单类型如 int 可用 get_user(val, user_ptr)。注意内核5.0+引入 copy_struct_from_user 检查结构体边界。”
可能追问
  • 用户指针为NULL时会发生什么?(copy_*_user 会返回-EFAULT,不会panic)
  • 可以用 memcpy 直接拷贝吗?(不可以,会破坏地址空间隔离)
  • 如何提高大块数据拷贝效率?(使用 mmap 映射或 splice 零拷贝)
⚠️ 避坑提醒:不能信任用户提供的长度参数,必须用 min(count, kernel_buffer_size) 限制;拷贝前用 access_ok 验证指针安全性(新内核已自动检查,但习惯仍保留)。
📊 内核模块关键要素速查表
要素说明示例代码常见错误
module_init指定模块入口函数module_init(gpio_init);函数未加 __init
module_exit指定模块清理函数module_exit(gpio_exit);未释放注册的资源
MODULE_LICENSE声明许可证(必填)MODULE_LICENSE("GPL v2");缺失导致 kernel tainted
module_param模块参数传递module_param(irq, int, 0644);权限设置错误
加载命令insmod / rmmod / lsmodinsmod gpio.ko irq=17忘记依赖模块时用 modprobe

🔧 真实项目模板:基于Linux的GPIO按键驱动开发

项目名称 智能家居面板GPIO按键驱动模块
业务背景 为嵌入式Linux设备(ARM Cortex-A7)添加4个物理按键的驱动,通过中断上报按键事件,用户态程序通过输入子系统获取键值。
规模指标 驱动代码约200行,支持4个按键,中断延迟<5ms,测试覆盖50万次按压无误报。
技术栈 Linux内核5.10 + Device Tree + GPIO中断 + input子系统 + workqueue去抖
核心难点 ① 硬件抖动导致误触发(使用定时器去抖) ② 中断上下文中不能睡眠(使用workqueue推迟处理) ③ 与用户态input event的校验码映射。
面试高频追问 (点击展开)
  • 为什么使用 workqueue 而不是 tasklet?(workqueue允许睡眠,适合需要mutex的场景)
  • 如果按键按下时驱动崩溃,如何debug?(打开 CONFIG_DEBUG_GPIO,使用 gpio debugfs)
  • 如何验证中断响应时间是否符合要求?(用 ftrace 或 trace_printk 打时间戳)
  • 设备树配置错误会导致什么?(gpio号映射失败,驱动probe直接返回-ENODEV)
可迁移回答话术: “在GPIO按键驱动中,我使用了 devm_gpio_request_one 自动管理资源,结合 request_irq 注册中断,并通过 schedule_work 将耗时操作推入内核工作队列。这套设计模式同样适用于I2C触控中断、UART GPIO唤醒等场景。”