--- title: 面试-外设驱动实战 tags: [ 面试, LED, 按键, LCD, 触摸屏, 音频, RTC, PWM, ADC, 块设备, 嵌入式Linux, 驱动开发, ] created: 2026-09-16 updated: 2026-09-17 --- # 面试-外设驱动实战 > 本模块涵盖嵌入式 Linux 外设驱动开发的核心面试题,覆盖 GPIO/LED、按键输入、LCD/Framebuffer、触摸屏、音频 ALSA、RTC/PWM、ADC/IIO、块设备八大类。每题含答案要点、详细解答和追问。 > ⚠️ **来源说明**:本文件为面试题汇编,不属于《I.MX6U嵌入式Linux驱动开发指南》正文内容,为扩展知识。涉及原书章节(音频第65章、RTC第60章、PWM第73章、IIO第75章、ADC第76章、块设备第68章、多点电容触摸屏第64章)的事实已按原书相应章节核对。 --- ## 一、LED/蜂鸣器驱动 ### Q1: 在 Linux 下控制一个 LED 灯,有哪些方法? **答案要点**: 1. sysfs 方式(/sys/class/gpio)—— 最简单,无需写驱动 2. 直接编写字符设备驱动 —— 最灵活,可自定义协议 3. LED 子系统(/sys/class/leds)—— 内核标准框架,支持触发器 4. 设备树 + GPIO 子系统 —— 现代推荐方式 | 方法 | 优点 | 缺点 | 适用场景 | | ----------------- | ---------- | ---------------- | ------------- | | sysfs | 无需驱动 | 效率低 | 快速调试 | | 字符设备驱动 | 完全自定义 | 需手动管理设备号 | 产品级驱动 | | LED 子系统 | 内核标准 | 功能受限 | 标准 LED 控制 | | 设备树+GPIO子系统 | 硬件解耦 | 需设备树知识 | 现代产品 | **典型字符设备 LED 驱动流程**: ```c // 1. ioremap 映射寄存器 IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4); GPIO1_DR = ioremap(GPIO1_DR_BASE, 4); // 2. 配置 GPIO 时钟、复用、方向 writel(val, GPIO1_GDIR); // 3. 注册字符设备 register_chrdev(LED_MAJOR, LED_NAME, &led_fops); // 4. 在 write 中控制 GPIO val = readl(GPIO1_DR); val &= ~(1 << 3); writel(val, GPIO1_DR); // 5. 卸载时 iounmap + unregister iounmap(GPIO1_DR); unregister_chrdev(LED_MAJOR, LED_NAME); ``` **追问**: - 为什么现代驱动推荐 GPIO 子系统?—— 处理引脚复用、电源管理、并发保护 - copy_from_user 在 LED 驱动中的作用?—— 内核与用户空间数据拷贝 - module_init/exit 执行时机?—— insmod 时 init,rmmod 时 exit --- ### Q2: 蜂鸣器驱动与 LED 驱动的核心区别?如何用 PWM 驱动蜂鸣器? **答案要点**: 1. LED 是 GPIO 输出(高/低电平),蜂鸣器可用 GPIO 通断或 PWM 控制音调 2. 有源蜂鸣器:GPIO 高低电平即可驱动 3. 无源蜂鸣器:需要特定频率方波,需 PWM 或定时器翻转 GPIO 4. PWM 通过占空比和频率控制音量和音调 **有源 vs 无源蜂鸣器**: - 有源:内部有振荡电路,给电即响 - 无源:需要外部方波信号,频率决定音调 ```c /* I.MX6ULL PWM 的 #pwm-cells = <2>,即 */ buzzer { compatible = "my,buzzer"; pwms = <&pwm1 0 5000000>; }; struct pwm_device *pwm = devm_pwm_get(&pdev->dev, NULL); pwm_config(pwm, period * duty_cycle / 100, period); pwm_enable(pwm); ``` **追问**: - PWM 频率和占空比分别影响什么?—— 频率决定音调(Hz),占空比决定音量 - PWM 框架核心 API?—— devm_pwm_get、pwm_config、pwm_enable、pwm_disable --- ### Q3: file_operations 结构体各成员作用?LED 驱动中需实现哪些? **答案要点**: 1. file_operations 是字符设备驱动核心,定义用户空间操作映射 2. LED 驱动至少需 open、write、release 3. owner 字段防止模块在使用中被卸载 ```c static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .read = led_read, .write = led_write, .release = led_release, }; ``` | 成员 | 用户空间调用 | 作用 | | -------- | ------------------ | ------------------------------- | | .open | open("/dev/led") | 初始化,设置 filp->private_data | | .read | read(fd, buf, len) | 读取 LED 状态 | | .write | write(fd, &val, 1) | 控制 LED 开/关 | | .release | close(fd) | 释放资源 | **追问**: - filp->private_data 的典型用法?—— open 中存入设备结构体指针 - 不实现 read 会怎样?—— 返回 -EINVAL - owner 为何重要?—— 用户持有文件描述符时阻止 rmmod 卸载驱动 --- ### Q4: 如何管理多个 LED?设备树如何描述? **答案要点**: 1. 设备树用 led-gpios 或子节点描述多个 LED 2. 驱动用结构体数组管理 3. of_property_read_u32 读取设备树属性 4. of_get_named_gpio 获取 GPIO 编号 ```dts gpioleds { compatible = "my,gpio-leds"; led0 { label = "red"; gpios = <&gpio1 3 GPIO_ACTIVE_LOW>; }; led1 { label = "green"; gpios = <&gpio1 4 GPIO_ACTIVE_LOW>; }; }; ``` **追问**: - of_get_named_gpio vs devm_gpio_request_one?—— 前者获取 GPIO 编号,后者申请并配置 - 如何支持 LED 闪烁?—— timer_list 或 hrtimer 定时翻转 GPIO - default-state 如何使用?—— probe 时读取设置 LED 初始状态 --- ## 二、按键驱动 ### Q5: Linux 按键驱动有哪几种实现方式? **答案要点**: 1. 轮询方式 -- 定时读取 GPIO,简单但 CPU 占用高 2. 中断方式 -- GPIO 中断触发,效率高,需处理消抖 3. Input 子系统 -- 内核标准框架,上报 EV_KEY 事件 4. 设备树 + GPIO 中断 -- 现代标准方式 | 方式 | 复杂度 | CPU 占用 | 响应延迟 | | ------------ | ------ | -------- | -------- | | 轮询 | 低 | 高 | 高 | | 中断 | 中 | 低 | 低 | | Input 子系统 | 中 | 低 | 低 | | 设备树+中断 | 中 | 低 | 低 | ```c input_report_key(input_dev, KEY_0, 1); input_sync(input_dev); input_report_key(input_dev, KEY_0, 0); input_sync(input_dev); ``` **追问**: - Input 事件如何到达用户空间?-- /dev/input/eventX,read 获取 input_event - input_sync 的作用?-- 标记一组完整事件的结束 - 为什么推荐 Input 子系统?-- 统一接口,可被 evtest/getevent 直接测试 --- ### Q6: 按键消抖有哪些方法?内核中如何实现定时器消抖? **答案要点**: 1. 硬件消抖 -- RC 电路,简单可靠但增加 BOM 2. 软件延时消抖 -- msleep/mdelay 阻塞,不推荐 3. 定时器消抖 -- 中断中启动定时器,回调中读取状态,最常用 4. 工作队列消抖 -- 中断中调度 work,work 中读取状态 ```c static irqreturn_t key_irq_handler(int irq, void *dev_id) { struct key_dev *dev = dev_id; mod_timer(&dev->timer, jiffies + msecs_to_jiffies(15)); return IRQ_HANDLED; } static void key_timer_func(struct timer_list *t) { struct key_dev *dev = from_timer(dev, t, timer); int val = gpio_get_value(dev->gpio); if (val == 0) { input_report_key(dev->input, KEY_0, 1); input_sync(dev->input); } else { input_report_key(dev->input, KEY_0, 0); input_sync(dev->input); } } ``` **追问**: - 为什么推荐定时器消抖?-- msleep 会阻塞进程,中断上下文不能睡眠 - 消抖时间一般设多少?-- 10~20ms,取决于按键机械特性 - 多个按键共享定时器?-- 每个按键独立定时器,或用工作队列 --- ### Q7: 如何实现按键的长短按检测? **答案要点**: 1. 中断触发时启动定时器(如 1 秒超时) 2. 定时器超时前松开 -> 短按 3. 定时器超时后仍按下 -> 长按 4. 松开时停止定时器,根据定时器状态判断 ```c static irqreturn_t key_irq_handler(int irq, void *dev_id) { struct key_dev *dev = dev_id; if (!dev->is_pressed) { dev->is_pressed = true; mod_timer(&dev->long_press_timer, jiffies + msecs_to_jiffies(1000)); } else { dev->is_pressed = false; if (del_timer(&dev->long_press_timer)) { /* short press */ } } return IRQ_HANDLED; } static void long_press_timer_func(struct timer_list *t) { struct key_dev *dev = from_timer(dev, t, long_press_timer); input_report_key(dev->input, KEY_0, 1); input_sync(dev->input); } ``` **追问**: - 如何支持三击、四击?-- 状态机 + 定时器组合 - del_timer vs del_timer_sync?-- 后者等待回调完成,卸载驱动时必须用后者 - 长按后按住不放?-- 长按定时器中上报按下,松开中断中上报释放 --- ### Q8: 中断处理函数和定时器回调的执行上下文区别? **答案要点**: 1. 中断处理函数运行在硬中断上下文(hardirq),不可睡眠 2. 定时器回调运行在软中断上下文(softirq),也不可睡眠 3. 工作队列运行在进程上下文,可以睡眠 4. 中断中不能做耗时操作,应尽快返回 | 执行上下文 | 可否睡眠 | 典型用途 | | ---------- | -------- | --------------- | | 硬中断 | 否 | 响应硬件事件 | | 软中断 | 否 | 定时器、tasklet | | 工作队列 | 是 | 耗时操作 | | 进程上下文 | 是 | 驱动 probe | **追问**: - 为什么中断中不能调用 msleep?-- 中断上下文不允许睡眠,会导致内核死锁 - tasklet vs workqueue?-- tasklet 不可睡眠,workqueue 可睡眠 - 如何从定时器回调安全访问设备数据?-- from_timer 宏 --- ### Q9: Linux Framebuffer 驱动的核心架构是什么? **答案要点**: 1. 用户空间 -> /dev/fbX 设备节点 2. 文件操作层 -> fb_fops(fbmem.c) 3. Framebuffer 核心层 -> fb_info 结构体管理 4. 硬件驱动层 -> 具体 SoC 的 LCD 控制器驱动 5. fb_info 是核心数据结构 fb_info 关键成员:var(可变参数:分辨率、bpp)、fix(固定参数:行字节数)、fbops(操作函数集)、screen_base(显存虚拟地址)、screen_size(显存大小)、pseudo_palette(伪调色板) **追问**: - fb_var_screeninfo vs fb_fix_screeninfo?-- var 可修改,fix 不可修改 - register_framebuffer 后内核做了什么?-- 创建 /dev/fbX 节点 - 为什么 Framebuffer 已过时?-- DRM/KMS 支持硬件加速 --- ### Q10: LCD 设备树配置?mxsfb_probe 核心流程? **答案要点**: 1. 设备树:pinctrl、LCD 时序参数、显示属性 2. probe:分配 fb_info -> 映射寄存器 -> 初始化 -> 注册 framebuffer 3. LCD 屏幕参数通过 display 子节点传递 4. eLCDIF 寄存器基地址 0x021C8000 设备树配置:pinctrl_lcdif_dat(24位数据线)、pinctrl_lcdif_ctrl(CLK/ENABLE/HSYNC/VSYNC)、display 子节点(bits-per-pixel、bus-width、时序参数、分辨率) mxsfb_probe 流程:platform_get_resource -> framebuffer_alloc -> devm_ioremap_resource -> devm_request_irq -> mxsfb_init_fbinfo -> mxsfb_set_par -> mxsfb_enable_controller -> register_framebuffer **追问**: - HSYNC/VSYNC 的作用?-- 水平/垂直同步信号 - bits-per-pixel vs bus-width?-- bpp 是像素位数,bus-width 是数据总线宽度 - 为什么 LCD 驱动通常不需要修改?-- NXP 已实现 mxsfb.c --- ### Q11: Framebuffer 如何实现屏幕旋转?LCD 时序参数含义? **答案要点**: 1. 旋转通过 fb_var_screeninfo 的 rotate 字段设置 2. HSYNC 是水平同步信号,标志一行像素的开始 3. VSYNC 是垂直同步信号,标志一帧图像的开始 用户空间通过 ioctl 设置旋转:var.rotate = FB_ROTATE_90,同时交换 xres 和 yres LCD 时序:一行 = HBP + HSYNC active + HFP + 有效像素数据;一帧 = VBP + VSYNC active + VFP + 有效行数据 **追问**: - 内核支持的旋转角度?-- 0/90/180/270 度 - 旋转对性能的影响?-- 硬件旋转几乎无开销,软件旋转需额外内存拷贝 - pseudo_palette 的作用?-- 16 位色深时存储调色板 --- ### Q12: LCD 背光控制如何实现?PWM vs GPIO 背光? **答案要点**: 1. PWM 背光:通过占空比调节亮度,可实现多级亮度 2. GPIO 背光:简单开关,只有亮/灭两档 3. 内核 LED 子系统可统一管理背光 4. backlight_device 是背光设备的内核表示 PWM 背光设备树:`pwms = <&pwm1 0 5000000>;`(周期 5000000ns=200Hz)、`brightness-levels = <0 4 8 16 32 64 128 255>;`、`default-brightness-level = <7>;` GPIO 背光设备树:`gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>;` **追问**: - brightness-levels 的作用?-- 定义离散亮度等级 - 如何实现背光渐变?-- 定时器+渐变步进,或硬件 PWM 自带渐变 - LED 子系统 vs backlight?-- LED 适合开/关,backlight 支持多级亮度 --- ## 四、触摸屏驱动 ### Q13: 触摸屏驱动属于 Input 子系统还是 IIO 框架? **答案要点**: 1. 触摸屏驱动属于 Input 子系统 2. Input 处理输入事件(按键、触摸、鼠标) 3. IIO 处理模拟传感器(ADC、加速度计) 4. 触摸屏上报 EV_ABS(绝对坐标)事件 上报流程:input_set_abs_params 设置坐标范围 -> input_report_abs 上报坐标 -> input_sync 标记事件帧结束 **追问**: - MT-A 和 MT-B 协议区别?-- MT-A 用 ABS_MT_TRACKING_ID,MT-B 用 input_mt_slot - input_sync 为什么必须调用?-- 标记事件帧结束 - 如何处理中断嵌套?-- input_mt_sync_frame 区分每帧 --- ### Q14: 电容触摸屏和电阻触摸屏的驱动区别? **答案要点**: 1. 电阻屏:电压分压获取坐标,需 ADC 2. 电容屏:电容感应检测手指,支持多点触控 3. 电阻屏用 IIO 或直接 ADC 驱动 4. 电容屏用 I2C/SPI + Input 子系统 | 特性 | 电阻屏 | 电容屏 | | ---------- | ------------ | ------------------------ | | 原理 | 压力分压 | 电容耦合 | | 多点触控 | 不支持 | 支持(最多10点) | | 透光率 | ~75% | ~90% | | 驱动接口 | ADC + GPIO | I2C/SPI + GPIO | | Linux 驱动 | IIO 或自定义 | ft5x06(FT5426)、goodix | **追问**: - 电容屏通常用什么通信接口?-- I2C(如 Goodix GT9147) - 设备树中如何描述 I2C 触摸屏?-- I2C 控制器下添加子节点 - reset 和 irq 引脚如何配置?-- GPIO 输出和 GPIO 中断 --- ### Q15: 如何实现多点触控?设备树如何描述触摸屏? **答案要点**: 1. 使用 MT(Multi-Touch)协议 2. MT-B 通过 input_mt_slot 区分手指 3. 设备树在 I2C/SPI 控制器下添加子节点 4. 配置中断、复位、分辨率参数 设备树示例(ALPHA 板 FT5426,挂 I2C2,原书示例代码 64.3.1.4):`compatible = "edt,edt-ft5426"; reg = <0x38>;` 并配置 `reset-gpios` 与中断 GPIO(`interrupt-parent`/`interrupts`)。使用 Goodix 方案的板子则为 `compatible = "goodix,gt9147"; reg = <0x5d>;` MT-B 协议:input_mt_slot -> input_mt_report_slot_state -> input_report_abs(X/Y) -> input_mt_sync_frame -> input_sync **追问**: - input_mt_slot vs input_report_key?-- slot 是多点触控专用 - 如何获取分辨率?-- of_property_read_u32 - 为什么禁用中断?-- 防止中断嵌套导致数据混乱 --- ## 五、音频驱动 ### Q16: ALSA 驱动框架的层次结构? **答案要点**: 1. 用户空间:alsa-lib -> /dev/snd/* 2. ALSA 核心层:PCM、Control 通用接口 3. Machine 驱动:声卡、CPU DAI、Codec DAI 连接 4. Platform 驱动:CPU 侧 DMA 和 DAI 5. Codec 驱动:Codec 芯片寄存器和 DAI | 组件 | 职责 | 示例(ALPHA 板) | | -------- | ------------------------- | ------------------- | | Platform | CPU 侧:DMA、CPU DAI | fsl_sai.c | | Codec | Codec 芯片:DAC/ADC、DAPM | wm8960.c / es8388.c | | Machine | 绑定 Platform 和 Codec | imx-audio-wm8960.c | **追问**: - ASoC vs 传统 ALSA?-- ASoC 拆分为 Platform/Codec/Machine,支持模块化 - snd_soc_dai_link 的作用?-- 描述 CPU DAI 和 Codec DAI 的连接方式 - DMA 在音频驱动中的作用?-- 搬运数据到 SAI 外设,不占用 CPU --- ### Q17: I2S 和 PCM 接口的区别?DAI 配置的关键参数? **答案要点**: 1. I2S:3线制(BCLK、LRCK、SDATA),立体声 2. PCM/TDM:时分复用,多通道 3. DAI 参数:采样率、位宽、通道数、主从模式 4. snd_soc_dai_ops 定义配置操作 I2S 时序:BCLK 位时钟,LRCK 区分左右声道(频率=采样率),SDATA 串行数据 PCM/TDM 时序:SYNC 同步信号区分时隙,每个时隙对应一个通道 **追问**: - LRCK 频率等于什么?-- 采样率,BCLK = LRCK x 位宽 x 2 - 主从模式区别?-- 主模式 SoC 产生时钟,从模式 Codec 产生 - TDM 如何区分通道?-- SYNC 信号的时隙编号 --- ### Q18: DMA 在音频驱动中的工作方式?Ring Buffer 管理? **答案要点**: 1. DMA 搬运数据,CPU 不参与 2. 环形缓冲区(Ring Buffer)实现连续播放 3. DMA 中断通知传输完成 4. period_size x periods = buffer_size 流程:应用层 write() -> DMA 写入 ring buffer -> DMA 控制器搬运到 SAI FIFO -> SAI 通过 I2S 发送到 Codec -> DMA 传输完一个块 -> 中断 -> 通知 ALSA **追问**: - period_size 和 buffer_size 的关系?-- buffer_size = period_size x periods - 为什么用 ring buffer?-- 支持循环播放 - snd_pcm_period_elapsed 的作用?-- 通知 ALSA 一个 period 完成 --- ## 六、RTC/PWM 驱动 ### Q19: RTC 驱动框架是什么?如何设置和读取时间? **答案要点**: 1. RTC 子系统提供统一的时间访问接口 2. 用户空间通过 /dev/rtc0 或 hwclock 命令访问 3. rtc_device 结构体是 RTC 设备的内核表示 4. rtc_class_ops 定义读写时间等操作 驱动中实现 rtc_class_ops(read_time/set_time/read_alarm/set_alarm),probe 中调用 devm_rtc_device_register 注册 用户空间:ioctl(fd, RTC_RD_TIME, &tm) 读取时间,ioctl(fd, RTC_SET_TIME, &tm) 设置时间 **追问**: - RTC 和系统时钟的区别?-- RTC 是硬件时钟(电池供电),系统时钟是内核维护的软时钟 - 如何读取 RTC 寄存器?-- I2C/SPI 读取 RTC 芯片寄存器(如 PCF8563) - RTC alarm 的实现原理?-- 设置闹钟时间,RTC 芯片产生中断唤醒系统 --- ### Q20: Linux PWM 框架的核心 API 和使用流程? **答案要点**: 1. devm_pwm_get 获取 PWM 设备 2. pwm_config 配置周期和占空比 3. pwm_enable/pwm_disable 使能/关闭 4. 设备树中用 pwms 属性配置 PWM 设备树格式:`pwms = <&pwm1 0 5000000>;`,即 ``(I.MX6ULL `#pwm-cells = <2>`) 驱动 API:devm_pwm_get -> pwm_config(pwm, duty_ns, period_ns) -> pwm_enable -> pwm_disable **追问**: - PWM 周期和频率的关系?-- 频率 = 1 / 周期 - 如何实现呼吸灯效果?-- 定时器 + 渐变占空比 - devm_pwm_get vs pwm_get?-- 前者带设备资源管理 --- ## 七、ADC/IIO 驱动 ### Q21: IIO 框架的核心架构?与 Input 子系统的区别? **答案要点**: 1. IIO(Industrial I/O)处理模拟传感器 2. 核心数据结构:iio_dev、iio_chan_spec、iio_info 3. 用户空间接口:/sys/bus/iio/devices/iio:deviceX/ 4. IIO 处理 ADC/传感器,Input 处理输入事件 | 特性 | IIO 框架 | Input 子系统 | | -------- | --------------------- | ------------------ | | 用途 | ADC、加速度计、陀螺仪 | 键盘、触摸屏、鼠标 | | 数据类型 | 模拟值 | 离散事件 | | 用户接口 | sysfs 文件 | /dev/input/eventX | **追问**: - read_raw 回调的作用?-- 读取 ADC 原始值 - IIO sysfs 文件命名规则?-- in_voltageX_raw、in_voltage_scale - 如何计算实际电压?-- 实际电压 = raw x scale(单位 mV) --- ### Q22: I.MX6ULL ADC 驱动的核心流程?设备树如何配置? **答案要点**: 1. ADC 驱动文件:vf610_adc.c,基于 IIO 框架 2. 设备树配置:pinctrl、vref-supply、num-channels 3. probe 流程:分配 iio_dev -> 映射寄存器 -> 申请中断 -> 注册 IIO 4. ADC 转换通过中断完成 设备树:pinctrl_adc1 配置 GPIO1_IO01;reg_vref_adc 提供 3.3V 参考电压;adc1 节点设置 num-channels 和 vref-supply vf610_adc_probe:devm_iio_device_alloc -> devm_ioremap_resource -> devm_request_irq -> devm_clk_get -> devm_regulator_get -> vf610_adc_cfg_init -> vf610_adc_hw_init -> iio_device_register **追问**: - vf610_adc_isr 中断处理做了什么?-- 读取 ADC 数据,complete 通知 - wait_for_completion_interruptible_timeout 的作用?-- 等待 ADC 转换完成 - vref-supply 的作用?-- 提供参考电压,影响 ADC 量程 --- ### Q23: ADC 驱动中如何读取多通道数据?用户空间如何获取 ADC 值? **答案要点**: 1. IIO 框架自动创建通道对应的 sysfs 文件 2. 用户空间读取 in_voltageX_raw 和 in_voltage_scale 3. 驱动中通过 iio_chan_spec 定义通道 4. 多通道时每次读取需指定通道号 通道定义:iio_chan_spec 设置 type=IIO_VOLTAGE、channel 编号、scan_type(realbits/storagebits) 用户空间:cat /sys/bus/iio/devices/iio:device0/in_voltage1_raw **追问**: - realbits vs storagebits?-- realbits 是实际有效位数,storagebits 是存储占用位数 - 如何触发一次 ADC 转换?-- 读取 in_voltage1_raw(读取即触发一次转换,见 vf610_read_raw)或使用 trigger - ADC 精度受什么影响?-- 参考电压、噪声、量化误差 --- ### Q24: IIO 框架中 trigger 和 buffer 的作用? **答案要点**: 1. Trigger 定义 ADC 转换的触发源(定时器、外部中断) 2. Buffer 用于连续采样数据的缓存 3. iio_triggered_buffer_setup 设置 buffer 4. 用户空间通过 /dev/iio:deviceX 读取 buffer 数据 实现:iio_triggered_buffer_setup 设置 pollfunc 和 trigger handler;trigger handler 中读取数据并调用 iio_push_to_buffers_with_timestamp 写入 ring buffer **追问**: - Trigger 模式 vs 轮询模式?-- Trigger 由硬件触发延迟低,轮询定时读取延迟高 - iio_push_to_buffers_with_timestamp 的作用?-- 将数据和时间戳写入 ring buffer - Buffer 满了怎么办?-- 可配置 watermark,或覆盖旧数据 --- ## 八、块设备驱动 ### Q25: 块设备驱动和字符设备驱动的区别? **答案要点**: 1. 块设备以块为单位传输数据(如 512B、4KB),支持随机访问 2. 字符设备以字节为单位传输数据,顺序访问 3. 块设备有 I/O 调度层,字符设备直接操作 4. 块设备通过 gendisk 和 request_queue 管理 | 特性 | 块设备 | 字符设备 | | -------- | ----------------------- | -------- | | 数据单位 | 块(512B~4KB) | 字节 | | 访问方式 | 随机 | 顺序 | | I/O 调度 | 有 | 无 | | 缓存 | 有 Page Cache | 无 | | 内核结构 | gendisk + request_queue | cdev | **追问**: - gendisk 和 request_queue 的关系?-- 一个 gendisk 对应一个 request_queue - 为什么块设备有 I/O 调度?-- 合并相邻请求、排序减少寻道时间 - 什么是请求合并?-- 将相邻的 I/O 请求合并为一个大请求 --- ### Q26: 块设备的 request_queue 和 bio 的关系? **答案要点**: 1. bio 是块设备 I/O 的基本单位,描述一段连续的内存缓冲区 2. request 是一个或多个 bio 合并后的 I/O 请求 3. request_queue 管理所有待处理的 request 4. I/O 调度器在 request_queue 层工作 流程:用户空间 read/write -> VFS -> 文件系统 -> 通用块层(生成 bio)-> I/O 调度器(合并、排序)-> request_queue -> 块设备驱动 -> 硬件 I/O **追问**: - bio 和 request 的关系?-- bio 是原始 I/O 描述,request 是调度后合并的结果 - I/O 调度器有哪些?-- noop、deadline、cfq、mq-deadline、bfq - 什么是 elevator?-- I/O 调度器的旧称 --- ### Q27: 如何实现一个简单的块设备驱动?核心步骤? **答案要点**: 1. 分配和初始化 gendisk 2. 设置 request_queue 和请求处理函数 3. set_capacity 设置容量 4. add_disk 注册磁盘 步骤(基于 blk_init_queue 的传统路径,原书第六十八章):`register_blkdev` -> `alloc_disk` -> `blk_init_queue(my_request_fn, &lock)` -> 设置 `major`/`first_minor`/`fops`/`queue` -> `set_capacity(disk, SECTORS)` -> `add_disk` 请求处理函数:`blk_fetch_request` 从队列取 request -> 根据 `rq_data_dir(req)` 判断读写 -> 用 `blk_rq_pos`/`blk_rq_cur_bytes`/`bio_data` 完成传输 -> `__blk_end_request_cur(req, err)` 完成 **追问**: - blk_init_queue 的两个参数?-- 请求处理函数和自旋锁 - set_capacity 的单位?-- 扇区数(每扇区 512 字节) - `__blk_end_request_cur` 的作用?-- 标记当前请求的 chunk 处理完成(需持有队列锁) --- ### Q28: 块设备驱动中如何处理 I/O 请求?读写操作的流程? **答案要点**: 1. I/O 请求通过 request_queue 到达驱动 2. 请求处理函数从队列中取出 request 3. 根据 request 的方向(读/写)执行相应操作 4. 处理完毕后调用 `__blk_end_request_cur` 完成(Blk-mq 架构下为 `blk_mq_end_request`) 读写流程:应用层 read() -> VFS -> 文件系统 -> 通用块层(查找 Page Cache,未命中生成 bio)-> I/O 调度器 -> 块设备驱动 -> 硬件操作 -> 完成中断 -> 唤醒等待进程 **追问**: - Page Cache 的作用?-- 缓存磁盘数据,减少实际 I/O 次数 - 什么是 direct I/O?-- 绕过 Page Cache 直接访问块设备 - O_DIRECT 标志的作用?-- 告诉内核绕过 Page Cache --- ### Q29: SD卡/eMMC 块设备驱动架构?Linux 如何识别存储设备? **答案要点**: 1. SD 卡使用 MMC/SD 子系统 2. eMMC 也通过 MMC 子系统管理 3. MMC 核心层处理协议,host 驱动处理硬件 4. 设备树中描述 MMC 控制器节点 MMC 子系统架构:用户空间 -> /dev/mmcblkX -> MMC 核心层 -> MMC 主机驱动 -> MMC/SD/eMMC 硬件 设备树配置:usdhc1 节点设置 bus-width、cap-sdio-irq、keep-power-in-suspend 等属性 **追问**: - SD 卡和 eMMC 的区别?-- SD 卡可拆卸,eMMC 焊接在板上 - MMC 核心层做了什么?-- 协议处理、命令发送、数据传输 - 如何查看已识别的块设备?-- lsblk 或 cat /proc/partitions --- ### Q30: 块设备的多队列(Blk-mq)架构是什么? **答案要点**: 1. 传统单队列在高并发下存在锁竞争问题 2. Blk-mq 使用 per-CPU 软件队列 + 硬件队列 3. 软件队列减少锁竞争,硬件队列直接提交到设备 4. 适合 NVMe、SCSI 等多队列设备 架构:应用层 -> I/O 调度器 -> 软件队列(per-CPU)-> 硬件队列(映射到 CPU 核心)-> 设备 核心结构:blk_mq_ops(queue_rq/hardware_queue)、blk_mq_hw_ctx(硬件上下文)、blk_mq_ctx(软件上下文) **追问**: - Blk-mq vs 传统 blk?-- Blk-mq 消除全局锁,支持真正并行 I/O - 什么时候用 Blk-mq?-- NVMe、多队列 SCSI、现代 SSD - I/O 调度器在 Blk-mq 中的位置?-- 可选(mq-deadline/bfq/none),none 表示不调度直接下发 --- **内容来源**: 《I.MX6U嵌入式Linux驱动开发指南》第47/52/55/60/64/65/68/73/75/76章(并发与竞争、阻塞和非阻塞IO、设备树下的platform驱动、RTC、多点电容触摸屏、音频、块设备、PWM、IIO、ADC驱动实验)