面试-外设驱动实战.md 26 KB


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 灯,有哪些方法?

答案要点

  1. sysfs 方式(/sys/class/gpio)—— 最简单,无需写驱动
  2. 直接编写字符设备驱动 —— 最灵活,可自定义协议
  3. LED 子系统(/sys/class/leds)—— 内核标准框架,支持触发器
  4. 设备树 + 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 驱动蜂鸣器?

答案要点

  1. LED 是 GPIO 输出(高/低电平),蜂鸣器可用 GPIO 通断或 PWM 控制音调
  2. 有源蜂鸣器:GPIO 高低电平即可驱动
  3. 无源蜂鸣器:需要特定频率方波,需 PWM 或定时器翻转 GPIO
  4. 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 驱动中需实现哪些?

答案要点

  1. file_operations 是字符设备驱动核心,定义用户空间操作映射
  2. LED 驱动至少需 open、write、release
  3. 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?设备树如何描述?

答案要点

  1. 设备树用 led-gpios 或子节点描述多个 LED
  2. 驱动用结构体数组管理
  3. of_property_read_u32 读取设备树属性
  4. 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 按键驱动有哪几种实现方式?

答案要点

  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 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 框架?

答案要点

  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 或自定义 goodix、ft5x06

追问

  • 电容屏通常用什么通信接口?-- I2C(如 Goodix GT9147)
  • 设备树中如何描述 I2C 触摸屏?-- I2C 控制器下添加子节点
  • reset 和 irq 引脚如何配置?-- GPIO 输出和 GPIO 中断

Q15: 如何实现多点触控?设备树如何描述触摸屏?

答案要点

  1. 使用 MT(Multi-Touch)协议
  2. MT-B 通过 input_mt_slot 区分手指
  3. 设备树在 I2C/SPI 控制器下添加子节点
  4. 配置中断、复位、分辨率参数

设备树示例: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 驱动框架的层次结构?

答案要点

  1. 用户空间:alsa-lib -> /dev/snd/*
  2. ALSA 核心层:PCM、Control 通用接口
  3. Machine 驱动:声卡、CPU DAI、Codec DAI 连接
  4. Platform 驱动:CPU 侧 DMA 和 DAI
  5. 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 配置的关键参数?

答案要点

  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 = ; 驱动 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 子系统的区别?

答案要点

  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 或使用 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 注册磁盘

步骤: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 请求?读写操作的流程?

答案要点

  1. I/O 请求通过 request_queue 到达驱动
  2. 请求处理函数从队列中取出 request
  3. 根据 request 的方向(读/写)执行相应操作
  4. 处理完毕后调用 __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 表示不调度直接下发