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 生成,内容仅供参考,请仔细甄别。

相关推荐
想唱rap1 小时前
传输层协议之UDP
java·linux·网络·c++·网络协议·mysql·udp
AIGC设计所1 小时前
网络安全8大就业领域和待遇对比!
运维·开发语言·网络·安全·web安全·php
网安薯条2 小时前
Kali Linux 虚拟机安装与基础配置保姆级图文教程
linux·运维·网络·安全·web安全·网络安全
良木生香2 小时前
【Linux系统编程】Linux基础指令(1)---一文带你了解Linux的基础指令
linux·运维·服务器·ubuntu·机器学习·系统架构·centos
济6172 小时前
ARM Linux 驱动开发篇---Linux字符设备驱动代码阅读指南---附设备树LED驱动实战案例
linux·嵌入式·嵌入式linux驱动开发
Cx330❀2 小时前
深度解析:从原理到实战,一文吃透 Linux 信号机制(上)
大数据·linux·运维·服务器·人工智能·elasticsearch
hj2862512 小时前
Linux基础知识day06
linux·运维·服务器
Shadow(⊙o⊙)2 小时前
linux基础指令2.0
linux·运维·服务器·学习·apache
代码中介商2 小时前
Linux TCP/UDP 网络编程完全指南:从基础到实践
linux·网络·tcp/ip