Linux 驱动开发核心自测题库(面试官问答版)

Linux 驱动开发核心自测题库(面试官问答版)

这份题库基于你提供的所有文档提炼,涵盖字符设备驱动、GPIO子系统、中断处理、I2C/SPI总线、设备树、Platform模型、并发控制、调试技巧八大核心领域。每道题都是"面试官真实会问"的高频考点,按难度分级标注。建议先尝试回答,再对照解析。


文章目录

  • [Linux 驱动开发核心自测题库(面试官问答版)](#Linux 驱动开发核心自测题库(面试官问答版))
    • 一、字符设备驱动框架(基础但必考)
      • [Q1:`register_chrdev` 和 `cdev` 接口有什么区别?为什么推荐用 `cdev`?](#Q1:register_chrdevcdev 接口有什么区别?为什么推荐用 cdev?)
      • [Q2:`copy_to_user` 和 `copy_from_user` 能直接用 `memcpy` 替代吗?为什么?](#Q2:copy_to_usercopy_from_user 能直接用 memcpy 替代吗?为什么?)
      • [Q3:设备节点 `/dev/xxx` 是怎么自动创建的?需要手动 `mknod` 吗?](#Q3:设备节点 /dev/xxx 是怎么自动创建的?需要手动 mknod 吗?)
      • [Q4:`module_init` 和 `module_exit` 的作用是什么?`__init` 和 `__exit` 修饰符的意义?](#Q4:module_initmodule_exit 的作用是什么?__init__exit 修饰符的意义?)
    • [二、GPIO 子系统与中断(高频考点)](#二、GPIO 子系统与中断(高频考点))
    • 三、等待队列、阻塞/非阻塞、Poll、异步通知(核心机制)
      • [Q10:`wait_event_interruptible` 和 `wake_up_interruptible` 的配合原理是什么?](#Q10:wait_event_interruptiblewake_up_interruptible 的配合原理是什么?)
      • [Q11:阻塞 I/O 和非阻塞 I/O 在驱动层面如何实现?](#Q11:阻塞 I/O 和非阻塞 I/O 在驱动层面如何实现?)
      • [Q12:驱动的 `.poll` 函数是怎么工作的?`poll_wait` 会让进程睡眠吗?](#Q12:驱动的 .poll 函数是怎么工作的?poll_wait 会让进程睡眠吗?)
      • Q13:异步通知(`SIGIO`)的实现步骤有哪些?驱动需要做什么?
    • [四、设备树与 Platform 驱动(企业级开发核心)](#四、设备树与 Platform 驱动(企业级开发核心))
      • [Q14:设备树中的 `compatible` 属性作用是什么?驱动如何匹配?](#Q14:设备树中的 compatible 属性作用是什么?驱动如何匹配?)
      • [Q15:`probe` 函数一般要做哪些事情?](#Q15:probe 函数一般要做哪些事情?)
      • [Q16:`platform_driver` 的匹配机制有哪几种?](#Q16:platform_driver 的匹配机制有哪几种?)
      • [Q17:在设备树中描述 GPIO 引脚的标准格式是什么?](#Q17:在设备树中描述 GPIO 引脚的标准格式是什么?)
    • [五、I2C 驱动(AT24C02 案例)](#五、I2C 驱动(AT24C02 案例))
      • [Q18:I2C 驱动的 `i2c_drv_read` 函数为什么需要两条消息(两条 `i2c_msg`)?一条读消息行不行?](#Q18:I2C 驱动的 i2c_drv_read 函数为什么需要两条消息(两条 i2c_msg)?一条读消息行不行?)
      • [Q19:AT24C02 的页写限制是什么?驱动中如何处理?](#Q19:AT24C02 的页写限制是什么?驱动中如何处理?)
      • [Q20:为什么 I2C 写操作后要加 `mdelay(20)`?读操作不需要?](#Q20:为什么 I2C 写操作后要加 mdelay(20)?读操作不需要?)
    • [六、SPI 驱动(TLC5615 DAC 案例)](#六、SPI 驱动(TLC5615 DAC 案例))
      • [Q21:SPI 的四种模式(Mode 0/1/2/3)由哪两个参数决定?如何区分?](#Q21:SPI 的四种模式(Mode 0/1/2/3)由哪两个参数决定?如何区分?)
      • [Q22:Linux SPI 驱动中,`spi_transfer` 和 `spi_message` 的关系是什么?](#Q22:Linux SPI 驱动中,spi_transferspi_message 的关系是什么?)
      • [Q23:SPI 驱动中,片选信号(CS)需要驱动手动控制吗?](#Q23:SPI 驱动中,片选信号(CS)需要驱动手动控制吗?)
    • 七、并发控制与竞态条件(进阶必考)
    • 八、调试技巧与常见错误排查(实战必备)
      • [Q27:`insmod` 报 `File exists` 错误,可能原因有哪些?如何排查?](#Q27:insmodFile exists 错误,可能原因有哪些?如何排查?)
      • [Q28:`request_irq` 失败,返回 `-EBUSY` 或内核打印 `tried to flag a GPIO set as output for IRQ`,是什么原因?](#Q28:request_irq 失败,返回 -EBUSY 或内核打印 tried to flag a GPIO set as output for IRQ,是什么原因?)
      • [Q29:模块加载后 `/dev/xxx` 没有自动创建,怎么排查?](#Q29:模块加载后 /dev/xxx 没有自动创建,怎么排查?)
      • [Q30:编译内核模块时出现 `gnu/stubs-soft.h: No such file or directory` 或 `Relocations in generic ELF (EM: 40)` 错误,怎么解决?](#Q30:编译内核模块时出现 gnu/stubs-soft.h: No such file or directoryRelocations in generic ELF (EM: 40) 错误,怎么解决?)
    • 九、综合应用题(面试官最爱)
    • 十、快速记忆口诀
    • 考核前最后的叮嘱

一、字符设备驱动框架(基础但必考)

Q1:register_chrdevcdev 接口有什么区别?为什么推荐用 cdev

难度:⭐⭐
点击查看答案

区别

  • register_chrdev 一次性分配主设备号并注册 0~255 共 256 个次设备号,资源浪费严重
  • cdev 接口分步操作:alloc_chrdev_regioncdev_initcdev_add,可按需申请次设备号数量

推荐原因

  • 节省内核资源(尤其次设备号是稀缺资源)
  • 一个驱动可管理多个不同类型的设备
  • 符合 Linux 设备模型的分层设计思想

Q2:copy_to_usercopy_from_user 能直接用 memcpy 替代吗?为什么?

难度:⭐⭐
点击查看答案

绝对不能。原因:

  1. 用户空间地址可能非法或未映射,直接访问会导致内核崩溃(oops)
  2. 用户空间内存可能被换出到磁盘,memcpy 无法处理缺页异常
  3. 这两个函数会进行地址合法性检查,并处理缺页异常

正确用法 :检查返回值,非 0 表示拷贝失败,应返回 -EFAULT


Q3:设备节点 /dev/xxx 是怎么自动创建的?需要手动 mknod 吗?

难度:⭐
点击查看答案

现代驱动通过以下两步自动创建:

  1. class_create(THIS_MODULE, "xxx_class") → 在 /sys/class 下创建类目录
  2. device_create(class, NULL, MKDEV(major, 0), NULL, "xxx") → 触发 udev/mdev 自动在 /dev 下创建设备节点

不需要手动 mknod ,但如果 device_create 失败或系统没有 udev,可以手动创建调试。


Q4:module_initmodule_exit 的作用是什么?__init__exit 修饰符的意义?

难度:⭐
点击查看答案

  • module_init:指定模块加载时执行的入口函数
  • module_exit:指定模块卸载时执行的出口函数
  • __init:告诉内核该函数只在初始化阶段使用,执行完后可释放内存
  • __exit:告诉内核该函数只在卸载时使用,如果编译进内核(非模块)可忽略

二、GPIO 子系统与中断(高频考点)

Q5:怎么查看一个 GPIO 在内核中的编号?以 GPIO4_19 为例。

难度:⭐
点击查看答案

三种方法

  1. Debug 文件系统cat /sys/kernel/debug/gpio
  2. Sysfs 方式 :查看 /sys/class/gpio/gpiochip*/label 确定每组的起始编号,再计算
  3. 公式计算(i.MX6ULL)(bank - 1) × 32 + offsetGPIO4_19(4-1)×32 + 19 = 115

Q6:request_irq 的参数 dev_id 有什么作用?为什么 free_irq 需要传入相同的指针?

难度:⭐⭐
点击查看答案

  • 作用 :当多个设备共享同一根中断线(IRQF_SHARED)时,dev_id 用于区分中断来源;同时也是中断处理函数获取设备私有数据的途径
  • 释放时需要相同指针 :内核根据 irq + dev_id 唯一标识一个中断处理函数,传入不同指针无法找到对应注册项,导致释放失败或误释放其他设备的中断

Q7:中断处理函数(ISR)中能做什么?不能做什么?为什么?

难度:⭐⭐⭐
点击查看答案

能做

  • 读取/写入硬件寄存器
  • 修改只被中断访问的变量(注意加 volatile
  • 调用 wake_up_interruptible 唤醒等待队列
  • 调用 kill_fasync 发送信号
  • 调度软中断、tasklet、工作队列

绝对不能做

  • 调用 copy_to_user / copy_from_user(可能睡眠)
  • 调用 kmalloc(..., GFP_KERNEL)(可能睡眠)
  • 调用 mutex_lock(互斥锁可能睡眠)
  • 任何可能导致调度的函数

原因:ISR 运行在中断上下文,不属于任何进程,无法被调度出去,睡眠会导致内核严重问题。


Q8:按键驱动中为什么要用定时器(timer)?原理是什么?

难度:⭐⭐
点击查看答案

原因:机械按键存在抖动(几毫秒到几十毫秒),若每次中断都上报,一次按下会触发几十次事件。

原理

  1. 中断 ISR 中不直接读 GPIO,而是调用 mod_timer(&timer, jiffies + msecs_to_jiffies(20))
  2. 每次抖动中断都会"刷新"定时器,即把到期时间推迟 20ms
  3. 最后一次中断后 20ms 若无新中断,定时器到期执行回调,此时读取的 GPIO 电平已稳定
  4. 这样就实现了 去抖 + 只上报一次 的效果

Q9:什么是中断上半部和下半部?有哪些下半部机制?

难度:⭐⭐⭐
点击查看答案

上半部request_irq 注册的中断处理函数,要求快速执行,通常只做最少的事(如清除中断标志、启动下半部)

下半部:处理耗时工作的机制,允许被中断打断

常用下半部机制

机制 特点 适用场景
软中断 最快,但复杂,驱动不能直接使用 网络子系统
tasklet 基于软中断,同一类型不会并行执行 多数驱动场景
工作队列 可睡眠,进程上下文 大量数据处理、需要睡眠的操作
定时器 延迟执行 去抖、轮询

三、等待队列、阻塞/非阻塞、Poll、异步通知(核心机制)

Q10:wait_event_interruptiblewake_up_interruptible 的配合原理是什么?

难度:⭐⭐
点击查看答案

原理

  1. wait_event_interruptible(wq, condition) 检查 condition,若为假则当前进程加入 wq 队列,状态设为 TASK_INTERRUPTIBLE,然后调用 schedule() 交出 CPU
  2. 中断或其他上下文中调用 wake_up_interruptible(&wq),将队列中所有进程状态改回 TASK_RUNNING,等待调度器分配 CPU
  3. 被唤醒的进程重新检查 condition,若为真则继续执行,否则可能再次睡眠

关键点wake_up 只负责唤醒,不负责条件判断;wait_event 被唤醒后会重新检查条件,这是防止"虚假唤醒"的核心机制。


Q11:阻塞 I/O 和非阻塞 I/O 在驱动层面如何实现?

难度:⭐⭐
点击查看答案

驱动通过检查 file->f_flags & O_NONBLOCK 来决定行为:

c

复制代码
static ssize_t my_read(struct file *filp, char __user *buf, size_t size, loff_t *off)
{
    if (is_buffer_empty() && (filp->f_flags & O_NONBLOCK))
        return -EAGAIN;  // 非阻塞,立即返回
    
    wait_event_interruptible(wq, !is_buffer_empty());  // 阻塞,睡眠等待
    // ... 读取数据
}
  • 应用 open 时带 O_NONBLOCK 标志 → 驱动走非阻塞路径
  • 不带该标志或带 O_RDWR → 驱动走阻塞路径

Q12:驱动的 .poll 函数是怎么工作的?poll_wait 会让进程睡眠吗?

难度:⭐⭐⭐
点击查看答案

.poll 不会让进程立即睡眠。流程如下:

  1. 应用调用 poll(fds, nfds, timeout)
  2. 内核调用驱动的 .poll 函数
  3. .poll 内部调用 poll_wait ------ 这只是将当前进程注册到等待队列,不调度
  4. 内核检查返回值:若有数据(返回 POLLIN),立即返回;若无数据(返回 0),内核调用 schedule_timeout 让进程睡眠
  5. 超时或被 wake_up 唤醒后,重新调用 .poll 检查状态

关键点poll_wait 不等于睡眠,它只是"告诉内核将来用什么队列来唤醒我"。真正的睡眠由内核的 do_poll 循环管理。


Q13:异步通知(SIGIO)的实现步骤有哪些?驱动需要做什么?

难度:⭐⭐⭐
点击查看答案

应用层(4 步)

  1. signal(SIGIO, handler) 注册信号处理函数
  2. fcntl(fd, F_SETOWN, getpid()) 设置文件所有者
  3. fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | FASYNC) 设置异步标志
  4. 在主循环做其他事,信号触发时在 handler 中 read

驱动层

  1. 实现 .fasync 函数,内部调用 fasync_helper 管理 struct fasync_struct *
  2. 在中断 ISR 或数据就绪时调用 kill_fasync(&fasync, SIGIO, POLL_IN)

四、设备树与 Platform 驱动(企业级开发核心)

Q14:设备树中的 compatible 属性作用是什么?驱动如何匹配?

难度:⭐⭐
点击查看答案

作用compatible = "厂商,型号" 是设备与驱动的"身份证",内核用它来匹配设备和驱动。

驱动匹配方式

c

复制代码
static const struct of_device_id my_ids[] = {
    { .compatible = "100ask,gpiodemo" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_ids);

static struct platform_driver my_driver = {
    .driver = {
        .name = "my_drv",
        .of_match_table = my_ids,  // 关键:匹配设备树
    },
    .probe = my_probe,
};

设备树节点中 compatible 与驱动 of_match_table 中任意一条匹配,内核就会调用该驱动的 probe 函数。


Q15:probe 函数一般要做哪些事情?

难度:⭐⭐
点击查看答案

  1. 获取硬件资源:从设备树或 platform 资源中读取 GPIO、中断号、寄存器地址等
  2. 初始化硬件gpio_requestgpio_direction_*request_irqclk_enable
  3. 注册字符设备alloc_chrdev_region + cdev_addregister_chrdev
  4. 创建设备节点class_create + device_create
  5. 保存私有数据 :通常用 platform_set_drvdata(pdev, private_data)

Q16:platform_driver 的匹配机制有哪几种?

难度:⭐⭐⭐
点击查看答案

platform_match 函数中的尝试顺序(源码 drivers/base/platform.c):

优先级 匹配方式 说明
1 driver_override 强制覆盖匹配,很少用
2 设备树(.of_match_table 现代驱动首选 ,比较 compatible
3 ACPI 匹配 x86 平台使用
4 id_table 匹配 平台设备 ID 表
5 名字直接匹配 比较 pdev->namedrv->driver.name传统方式

Q17:在设备树中描述 GPIO 引脚的标准格式是什么?

难度:⭐⭐
点击查看答案

dts

复制代码
gpios = <&gpio4 19 GPIO_ACTIVE_LOW>;
  • &gpio4:GPIO 控制器引用
  • 19:组内偏移(引脚号)
  • GPIO_ACTIVE_LOW:有效电平(低有效),也可用 GPIO_ACTIVE_HIGH

驱动中通过 of_get_gpio(np, index)gpiod_get 获取实际 GPIO 编号。


五、I2C 驱动(AT24C02 案例)

Q18:I2C 驱动的 i2c_drv_read 函数为什么需要两条消息(两条 i2c_msg)?一条读消息行不行?

难度:⭐⭐
点击查看答案

不行。因为 AT24C02(以及其他大多数 EEPROM/传感器)的读取时序要求:

  1. 先发送内部地址(设备地址+写方向 + 寄存器/存储地址)
  2. 再发 Repeat Start,切换为读方向,读取数据

如果只用一条读消息,芯片不知道要从哪个地址读,会返回随机或无效数据。

两条消息通过 i2c_transfer 一次性提交,硬件会在它们之间插入 Repeat Start(不是 STOP),保证总线不被释放,操作原子化。


Q19:AT24C02 的页写限制是什么?驱动中如何处理?

难度:⭐⭐
点击查看答案

限制 :AT24C02 的页写缓冲区为 8 字节,一次最多写入 8 字节,超过 8 字节会发生"页翻转",覆盖同一页前面的数据。

处理 :驱动中的 i2c_drv_write 使用循环分页写入:

c

复制代码
while (size > 0) {
    len = min(size, 8);
    // 构造消息:1字节地址 + len字节数据
    i2c_transfer(adapter, &msg, 1);
    mdelay(20);  // 等待内部编程完成
    addr += len;
    size -= len;
}

Q20:为什么 I2C 写操作后要加 mdelay(20)?读操作不需要?

难度:⭐⭐
点击查看答案

  • 写操作需要 :EEPROM 在写入后需要内部编程时间(典型 5ms,最大 10ms),在此期间芯片不响应 I2C 通信(返回 NACK)。mdelay(20) 确保足够等待时间
  • 读操作不需要:读取只是从 EEPROM 中读出数据,不触发擦写过程,芯片随时可以响应

六、SPI 驱动(TLC5615 DAC 案例)

Q21:SPI 的四种模式(Mode 0/1/2/3)由哪两个参数决定?如何区分?

难度:⭐⭐
点击查看答案

CPOL(时钟极性)CPHA(时钟相位) 决定:

模式 CPOL CPHA 空闲 SCK 数据采样边沿
0 0 0 上升沿
1 0 1 下降沿
2 1 0 下降沿
3 1 1 上升沿

设备树中用 spi-cpolspi-cpha 属性指定(存在即表示 1)。


Q22:Linux SPI 驱动中,spi_transferspi_message 的关系是什么?

难度:⭐⭐
点击查看答案

  • spi_transfer:描述一次 传输,包含 tx_buf(发送缓冲区)、rx_buf(接收缓冲区)、len(长度)、speed_hz(速度)等
  • spi_message:由多个 spi_transfer 链接而成,保证它们被原子执行(CS 在整个消息期间保持有效)

常用简化函数spi_sync_transfer(spi, transfers, num) 内部自动创建 message 并提交。


Q23:SPI 驱动中,片选信号(CS)需要驱动手动控制吗?

难度:⭐
点击查看答案

不需要。Linux SPI 核心层和控制器驱动会自动管理 CS:

  • 传输开始时,控制器驱动拉低 CS(根据 spi_devicechip_selectcs_gpio
  • 传输结束时,拉高 CS
  • 驱动只需调用 spi_sync_transferspi_async,无需关心 CS 的 GPIO 操作

七、并发控制与竞态条件(进阶必考)

Q24:自旋锁(spinlock)和互斥锁(mutex)的区别?分别在什么场景使用?

难度:⭐⭐⭐
点击查看答案

特性 自旋锁 互斥锁
获取锁失败时 忙循环等待(占用 CPU) 进程睡眠,让出 CPU
能否在中断中使用 ✅ 可以(spin_lock_irqsave ❌ 不可以(可能睡眠)
持锁时间 极短(微秒级) 可长(毫秒级或更长)
上下文 中断、软中断、进程上下文均可 只能进程上下文

选择原则

  • 持锁时间短 + 可能在中断中使用 → 自旋锁
  • 持锁时间长 + 确定只在进程上下文 → 互斥锁

Q25:DS18B20 驱动中为什么要用 spin_lock_irqsave 而不是普通 spin_lock

难度:⭐⭐⭐
点击查看答案

DS18B20 的单总线时序要求微秒级精度。如果在发送 480µs 复位脉冲的过程中被中断打断,回来时序已乱,通信失败。

spin_lock_irqsave保存当前中断状态并关闭本地 CPU 中断 ,保证临界区不被任何中断打扰。spin_lock 只防止其他 CPU 核心并发,不关中断,本 CPU 的中断仍可能打断时序。

为什么不用关中断的简单方式spin_lock_irqsave 会在释放时恢复原有中断状态,避免"永久关中断"的灾难。


Q26:中断处理函数中能使用互斥锁吗?为什么?

难度:⭐⭐
点击查看答案

绝对不能。因为:

  1. 互斥锁在获取不到时会导致当前进程睡眠
  2. 中断处理函数运行在中断上下文,不属于任何进程,无法被调度,睡眠会导致内核 panic
  3. 正确做法:在 ISR 中使用自旋锁(spin_lock),或将需要睡眠的工作通过工作队列(workqueue)放到进程上下文执行

八、调试技巧与常见错误排查(实战必备)

Q27:insmodFile exists 错误,可能原因有哪些?如何排查?

难度:⭐⭐
点击查看答案

可能原因

  1. 模块已加载lsmod | grep xxx 检查,rmmod 卸载
  2. 设备节点残留rm -f /dev/xxx
  3. sysfs 类目录残留 :模块退出时 class_destroy 未执行,手动 rmdir /sys/class/xxx_class
  4. 设备号仍被占用cat /proc/devices | grep xxx,检查主设备号是否冲突

排查顺序lsmod/dev//sys/class//proc/devices → 重启(最后手段)


Q28:request_irq 失败,返回 -EBUSY 或内核打印 tried to flag a GPIO set as output for IRQ,是什么原因?

难度:⭐⭐
点击查看答案

-EBUSY :中断号已被其他设备占用(且没有设置 IRQF_SHARED),使用 cat /proc/interrupts 查看中断占用情况。

GPIO 被设为输出 :GPIO 引脚当前是输出模式,不能作为中断源。解决方法:在 request_irq 之前必须调用 gpio_direction_input 将引脚设为输入模式。

c

复制代码
gpio_request(gpio, name);
gpio_direction_input(gpio);     // 关键:必须设为输入
irq = gpio_to_irq(gpio);
request_irq(irq, handler, flags, name, dev);

Q29:模块加载后 /dev/xxx 没有自动创建,怎么排查?

难度:⭐
点击查看答案

  1. 检查 class_createdevice_create 是否调用成功(查看 dmesg 有无错误打印)
  2. 检查返回值:IS_ERR(gpio_class) 判断 class 是否创建成功
  3. 确认内核配置了 CONFIG_DEV_UREG 或用户空间有 udev/mdev
  4. 临时解决方案:手动创建 mknod /dev/xxx c <主设备号> 0
  5. 查看 /proc/devices 确认主设备号已分配

Q30:编译内核模块时出现 gnu/stubs-soft.h: No such file or directoryRelocations in generic ELF (EM: 40) 错误,怎么解决?

难度:⭐⭐⭐
点击查看答案

根本原因 :编译主机工具(如 modpost)时,环境变量 C_INCLUDE_PATHLIBRARY_PATH 被指向了交叉编译器的路径,导致主机 GCC 去读 ARM 架构的头文件和库。

解决方案

bash

复制代码
unset C_INCLUDE_PATH
unset LIBRARY_PATH
unset LD_LIBRARY_PATH
unset CFLAGS LDFLAGS

原则:编译内核时不要 source 交叉编译环境脚本,通过 make 参数传递:

bash

复制代码
make ARCH=arm CROSS_COMPILE=arm-buildroot-linux-gnueabihf- 

九、综合应用题(面试官最爱)

Q31:如果让你设计一个支持多按键的驱动,每个按键有独立的去抖定时器和键值,你会怎么设计数据结构?

难度:⭐⭐⭐
点击查看答案

c

复制代码
struct button_desc {
    int gpio;
    int irq;
    int key_code;           // 键值(如 KEY_UP, KEY_DOWN)
    struct timer_list debounce_timer;  // 每个按键独立定时器
};

static struct button_desc *buttons;
static int button_count;

// probe 中动态分配
buttons = devm_kzalloc(dev, count * sizeof(*buttons), GFP_KERNEL);
for (i = 0; i < count; i++) {
    buttons[i].gpio = of_get_gpio(np, i);
    buttons[i].key_code = KEY_A + i;  // 或其他映射
    timer_setup(&buttons[i].debounce_timer, debounce_expire, 0);
    request_irq(gpio_to_irq(buttons[i].gpio), isr, 
                IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
                "button", &buttons[i]);
}

关键点

  • 每个按键独立定时器,防止互相干扰
  • 中断 ISR 中通过 dev_id 获取对应的 button_desc
  • 定时器回调中对 button_desc 操作

Q32:一个驱动同时被多个进程打开,如何保证数据不会混乱?(以环形缓冲区为例)

难度:⭐⭐⭐
点击查看答案

方案

  1. 环形缓冲区:生产者和消费者分离,数据不互相覆盖
  2. 互斥访问:如果有多进程写入,需要互斥锁保护
  3. 私有数据 :如果需要每个进程独立的数据,在 open 中分配私有结构体,挂到 filp->private_data

按键驱动案例(单生产者:中断;多消费者:不能同时读,因为只有一份数据):

  • 使用 put_key(中断中)和 get_key(read 中)
  • 如果允许多进程同时 read,需要加互斥锁防止多个进程同时取数据
  • 常用做法:设置 O_EXCL 或使用 atomic_t 标志禁止同时打开

c

复制代码
static atomic_t open_count = ATOMIC_INIT(0);
static int my_open(struct inode *inode, struct file *filp)
{
    if (atomic_inc_return(&open_count) > 1) {
        atomic_dec(&open_count);
        return -EBUSY;
    }
    return 0;
}

Q33:I2C 和 SPI 总线的主要区别?什么场景选 I2C?什么场景选 SPI?

难度:⭐⭐
点击查看答案

特性 I2C SPI
信号线数量 2 根(SDA, SCL) 4 根(MOSI, MISO, SCK, CS)
速度 100kbps(标准),400kbps(快速),3.4Mbps(高速) 几十 Mbps
设备寻址 7/10 位地址 独立的 CS 线
全双工 半双工 全双工
功耗 较高
多设备 每个设备不同地址 每个设备独立 CS 线

选择建议

  • I2C:传感器、EEPROM、RTC、设备数量多但速度要求不高、引脚有限
  • SPI:ADC/DAC、显示屏、SD 卡、Flash、需要高速或全双工传输

Q34:DS18B20 驱动为什么用 mdelay?为什么不能直接用 msleep?而 DS18B20 中等待转换时又用 schedule_timeout

难度:⭐⭐⭐
点击查看答案

原因

  • mdelay:忙等待(busy-wait),不会让出 CPU,但精度高(微秒级)。DS18B20 的复位脉冲要求 480µs 的精确低电平 ,期间不能被打断,只能用 mdelay 或自定义 udelay
  • msleep:会调度其他进程,可能造成几十毫秒的误差,不适合微秒级时序

转换等待用 schedule_timeout

  • 温度转换需要 750ms ,如果用 mdelay(750),CPU 在 750ms 内空转,浪费严重
  • schedule_timeout 让进程睡眠,交出 CPU,750ms 后被唤醒,高效且不影响系统响应

结论 :微秒级延时用 mdelay/udelay(需关中断);毫秒级以上延时用 schedule_timeout / msleep(可调度)。


十、快速记忆口诀

text

复制代码
字符设备三要素:设备号、cdev、ops结构体
GPIO 操作四步走:请求、方向、读/写、释放
中断开发五步骤:申请 GPIO、设输入、转 IRQ、注册 ISR、free_irq
等待队列核心:wait_event 条件查,wake_up 中断发
设备树匹配靠:compatible 对 of_match_table
Platform 驱动:probe 拿资源,remove 要逆序
I2C 读写两消息:先写地址后读数据
SPI 传输用 message:多个 transfer 原子化
并发保护要记牢:自旋锁短中断用,互斥锁长进程行

考核前最后的叮嘱

  1. 动手胜过一切:把每个文档中的驱动代码自己敲一遍,编译、加载、测试
  2. 理解原理链:不要只记 API,要能讲出"APP → VFS → 驱动 → 硬件"的完整路径
  3. 十分钟速查:面试前用这份题库过一遍,确保每个问题都能说出 2-3 个关键点

祝你顺利通过考核!这些内容如果都能掌握,你已经具备 1-2 年嵌入式 Linux 驱动开发经验 的核心能力了。

本回答由 AI 生成,内容仅供参考,请仔细甄别。

相关推荐
A小辣椒20 小时前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
大树883 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质3 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux