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、块设备八大类。每题含答案要点、详细解答和追问。
一、LED/蜂鸣器驱动
Q1: 在 Linux 下控制一个 LED 灯,有哪些方法?
答案要点:
- sysfs 方式(/sys/class/gpio)—— 最简单,无需写驱动
- 直接编写字符设备驱动 —— 最灵活,可自定义协议
- LED 子系统(/sys/class/leds)—— 内核标准框架,支持触发器
- 设备树 + GPIO 子系统 —— 现代推荐方式
| 方法 |
优点 |
缺点 |
适用场景 |
| sysfs |
无需驱动 |
效率低 |
快速调试 |
| 字符设备驱动 |
完全自定义 |
需手动管理设备号 |
产品级驱动 |
| LED 子系统 |
内核标准 |
功能受限 |
标准 LED 控制 |
| 设备树+GPIO子系统 |
硬件解耦 |
需设备树知识 |
现代产品 |
典型字符设备 LED 驱动流程:
// 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 驱动蜂鸣器?
答案要点:
- LED 是 GPIO 输出(高/低电平),蜂鸣器可用 GPIO 通断或 PWM 控制音调
- 有源蜂鸣器:GPIO 高低电平即可驱动
- 无源蜂鸣器:需要特定频率方波,需 PWM 或定时器翻转 GPIO
- PWM 通过占空比和频率控制音量和音调
有源 vs 无源蜂鸣器:
- 有源:内部有振荡电路,给电即响
无源:需要外部方波信号,频率决定音调
buzzer { compatible = "my,buzzer"; pwms = <&pwm1 0 5000000 0>; };
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 驱动中需实现哪些?
答案要点:
- file_operations 是字符设备驱动核心,定义用户空间操作映射
- LED 驱动至少需 open、write、release
owner 字段防止模块在使用中被卸载
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?设备树如何描述?
答案要点:
- 设备树用 led-gpios 或子节点描述多个 LED
- 驱动用结构体数组管理
- of_property_read_u32 读取设备树属性
of_get_named_gpio 获取 GPIO 编号
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 按键驱动有哪几种实现方式?
答案要点:
- 轮询方式 -- 定时读取 GPIO,简单但 CPU 占用高
- 中断方式 -- GPIO 中断触发,效率高,需处理消抖
- Input 子系统 -- 内核标准框架,上报 EV_KEY 事件
- 设备树 + 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: 按键消抖有哪些方法?内核中如何实现定时器消抖?
答案要点:
- 硬件消抖 -- RC 电路,简单可靠但增加 BOM
- 软件延时消抖 -- msleep/mdelay 阻塞,不推荐
- 定时器消抖 -- 中断中启动定时器,回调中读取状态,最常用
- 工作队列消抖 -- 中断中调度 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 秒超时)
- 定时器超时前松开 -> 短按
- 定时器超时后仍按下 -> 长按
- 松开时停止定时器,根据定时器状态判断
`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: 中断处理函数和定时器回调的执行上下文区别?
答案要点:
- 中断处理函数运行在硬中断上下文(hardirq),不可睡眠
- 定时器回调运行在软中断上下文(softirq),也不可睡眠
- 工作队列运行在进程上下文,可以睡眠
- 中断中不能做耗时操作,应尽快返回
| 执行上下文 |
可否睡眠 |
典型用途 |
| 硬中断 |
否 |
响应硬件事件 |
| 软中断 |
否 |
定时器、tasklet |
| 工作队列 |
是 |
耗时操作 |
| 进程上下文 |
是 |
驱动 probe |
追问:
- 为什么中断中不能调用 msleep?-- 中断上下文不允许睡眠,会导致内核死锁
- tasklet vs workqueue?-- tasklet 不可睡眠,workqueue 可睡眠
- 如何从定时器回调安全访问设备数据?-- from_timer 宏
Q9: Linux Framebuffer 驱动的核心架构是什么?
答案要点:
- 用户空间 -> /dev/fbX 设备节点
- 文件操作层 -> fb_fops(fbmem.c)
- Framebuffer 核心层 -> fb_info 结构体管理
- 硬件驱动层 -> 具体 SoC 的 LCD 控制器驱动
- 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 核心流程?
答案要点:
- 设备树:pinctrl、LCD 时序参数、显示属性
- probe:分配 fb_info -> 映射寄存器 -> 初始化 -> 注册 framebuffer
- LCD 屏幕参数通过 display 子节点传递
- 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 时序参数含义?
答案要点:
- 旋转通过 fb_var_screeninfo 的 rotate 字段设置
- HSYNC 是水平同步信号,标志一行像素的开始
- 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 背光?
答案要点:
- PWM 背光:通过占空比调节亮度,可实现多级亮度
- GPIO 背光:简单开关,只有亮/灭两档
- 内核 LED 子系统可统一管理背光
- backlight_device 是背光设备的内核表示
PWM 背光设备树:pwms = <&pwm1 0 5000000 0>; brightness-levels = <0 4 8 16 32 64 128 255>;
GPIO 背光设备树:gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>;
追问:
- brightness-levels 的作用?-- 定义离散亮度等级
- 如何实现背光渐变?-- 定时器+渐变步进,或硬件 PWM 自带渐变
- LED 子系统 vs backlight?-- LED 适合开/关,backlight 支持多级亮度
四、触摸屏驱动
Q13: 触摸屏驱动属于 Input 子系统还是 IIO 框架?
答案要点:
- 触摸屏驱动属于 Input 子系统
- Input 处理输入事件(按键、触摸、鼠标)
- IIO 处理模拟传感器(ADC、加速度计)
- 触摸屏上报 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: 电容触摸屏和电阻触摸屏的驱动区别?
答案要点:
- 电阻屏:电压分压获取坐标,需 ADC
- 电容屏:电容感应检测手指,支持多点触控
- 电阻屏用 IIO 或直接 ADC 驱动
- 电容屏用 I2C/SPI + Input 子系统
| 特性 |
电阻屏 |
电容屏 |
| 原理 |
压力分压 |
电容耦合 |
| 多点触控 |
不支持 |
支持(最多10点) |
| 透光率 |
~75% |
~90% |
| 驱动接口 |
ADC + GPIO |
I2C/SPI + GPIO |
| Linux 驱动 |
IIO 或自定义 |
goodix、ft5x06 |
追问:
- 电容屏通常用什么通信接口?-- I2C(如 Goodix GT9147)
- 设备树中如何描述 I2C 触摸屏?-- I2C 控制器下添加子节点
- reset 和 irq 引脚如何配置?-- GPIO 输出和 GPIO 中断
Q15: 如何实现多点触控?设备树如何描述触摸屏?
答案要点:
- 使用 MT(Multi-Touch)协议
- MT-B 通过 input_mt_slot 区分手指
- 设备树在 I2C/SPI 控制器下添加子节点
- 配置中断、复位、分辨率参数
设备树示例:compatible = "goodix,gt9147"; reg = <0x5d>; interrupt-parent/irq-gpios; reset-gpios; goodix,x-max/y-max
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 驱动框架的层次结构?
答案要点:
- 用户空间:alsa-lib -> /dev/snd/*
- ALSA 核心层:PCM、Control 通用接口
- Machine 驱动:声卡、CPU DAI、Codec DAI 连接
- Platform 驱动:CPU 侧 DMA 和 DAI
- Codec 驱动:Codec 芯片寄存器和 DAI
| 组件 |
职责 |
示例 |
| Platform |
CPU 侧:DMA、CPU DAI |
imx-sai.c |
| Codec |
Codec 芯片:DAC/ADC、DAPM |
wm8960.c |
| Machine |
绑定 Platform 和 Codec |
imx-sgtl5000.c |
追问:
- ASoC vs 传统 ALSA?-- ASoC 拆分为 Platform/Codec/Machine,支持模块化
- snd_soc_dai_link 的作用?-- 描述 CPU DAI 和 Codec DAI 的连接方式
- DMA 在音频驱动中的作用?-- 搬运数据到 SAI 外设,不占用 CPU
Q17: I2S 和 PCM 接口的区别?DAI 配置的关键参数?
答案要点:
- I2S:3线制(BCLK、LRCK、SDATA),立体声
- PCM/TDM:时分复用,多通道
- DAI 参数:采样率、位宽、通道数、主从模式
- 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 管理?
答案要点:
- DMA 搬运数据,CPU 不参与
- 环形缓冲区(Ring Buffer)实现连续播放
- DMA 中断通知传输完成
- 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 驱动框架是什么?如何设置和读取时间?
答案要点:
- RTC 子系统提供统一的时间访问接口
- 用户空间通过 /dev/rtc0 或 hwclock 命令访问
- rtc_device 结构体是 RTC 设备的内核表示
- 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 和使用流程?
答案要点:
- devm_pwm_get 获取 PWM 设备
- pwm_config 配置周期和占空比
- pwm_enable/pwm_disable 使能/关闭
- 设备树中用 pwms 属性配置 PWM
设备树格式:pwms = ;
驱动 API:devm_pwm_get -> pwm_config(pwm, duty_ns, period_ns) -> pwm_enable
追问:
- PWM 周期和频率的关系?-- 频率 = 1 / 周期
- 如何实现呼吸灯效果?-- 定时器 + 渐变占空比
- devm_pwm_get vs pwm_get?-- 前者带设备资源管理
七、ADC/IIO 驱动
Q21: IIO 框架的核心架构?与 Input 子系统的区别?
答案要点:
- IIO(Industrial I/O)处理模拟传感器
- 核心数据结构:iio_dev、iio_chan_spec、iio_info
- 用户空间接口:/sys/bus/iio/devices/iio:deviceX/
- 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 驱动的核心流程?设备树如何配置?
答案要点:
- ADC 驱动文件:vf610_adc.c,基于 IIO 框架
- 设备树配置:pinctrl、vref-supply、num-channels
- probe 流程:分配 iio_dev -> 映射寄存器 -> 申请中断 -> 注册 IIO
- 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 值?
答案要点:
- IIO 框架自动创建通道对应的 sysfs 文件
- 用户空间读取 in_voltageX_raw 和 in_voltage_scale
- 驱动中通过 iio_chan_spec 定义通道
- 多通道时每次读取需指定通道号
通道定义: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 或使用 trigger
- ADC 精度受什么影响?-- 参考电压、噪声、量化误差
Q24: IIO 框架中 trigger 和 buffer 的作用?
答案要点:
- Trigger 定义 ADC 转换的触发源(定时器、外部中断)
- Buffer 用于连续采样数据的缓存
- iio_triggered_buffer_setup 设置 buffer
- 用户空间通过 /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: 块设备驱动和字符设备驱动的区别?
答案要点:
- 块设备以块为单位传输数据(如 512B、4KB),支持随机访问
- 字符设备以字节为单位传输数据,顺序访问
- 块设备有 I/O 调度层,字符设备直接操作
- 块设备通过 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 的关系?
答案要点:
- bio 是块设备 I/O 的基本单位,描述一段连续的内存缓冲区
- request 是一个或多个 bio 合并后的 I/O 请求
- request_queue 管理所有待处理的 request
- 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: 如何实现一个简单的块设备驱动?核心步骤?
答案要点:
- 分配和初始化 gendisk
- 设置 request_queue 和请求处理函数
- set_capacity 设置容量
- add_disk 注册磁盘
步骤:alloc_chrdev_region -> alloc_disk -> blk_init_queue(my_request_fn, &lock) -> 设置 major/first_minor/fops -> set_capacity(disk, SECTORS) -> add_disk
请求处理函数:blk_fetch_request 从队列取 request -> 根据 req_data_dir 判断读写 -> 执行操作 -> __blk_mq_end_request 完成
追问:
- blk_init_queue 的两个参数?-- 请求处理函数和自旋锁
- set_capacity 的单位?-- 扇区数(每扇区 512 字节)
- __blk_mq_end_request 的作用?-- 标记一个请求处理完成
Q28: 块设备驱动中如何处理 I/O 请求?读写操作的流程?
答案要点:
- I/O 请求通过 request_queue 到达驱动
- 请求处理函数从队列中取出 request
- 根据 request 的方向(读/写)执行相应操作
- 处理完毕后调用 __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 如何识别存储设备?
答案要点:
- SD 卡使用 MMC/SD 子系统
- eMMC 也通过 MMC 子系统管理
- MMC 核心层处理协议,host 驱动处理硬件
- 设备树中描述 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)架构是什么?
答案要点:
- 传统单队列在高并发下存在锁竞争问题
- Blk-mq 使用 per-CPU 软件队列 + 硬件队列
- 软件队列减少锁竞争,硬件队列直接提交到设备
- 适合 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 表示不调度直接下发