江湖传言:写驱动如同练武,既要懂内功心法(内核框架),也要精外功招式(硬件操作),更要熟轻功身法(调试技巧)。 若不得其法,轻则走火入魔(Kernel Panic),重则经脉尽断(系统崩溃)。

今日本使将Linux内核驱动开发的精髓,化用金庸与古龙江湖中的绝世武功,呈现一部“驱动开发武功秘籍大全”。以武侠之名,悟代码之道。

一、总纲:武林门派与内功心法

1.1 驱动开发武功体系总览

1.2 各门派武学与内核组件对照表

武学门派对应武学内核组件/技术修炼难度走火风险
少林派易筋经驱动模型框架(字符/块/网络)★★★☆☆★★★☆☆
逍遥派小无相功Platform驱动框架★★★☆☆★★☆☆☆
明教乾坤大挪移电源管理Runtime PM★★★★☆★★★★☆
日月神教吸星大法DMA内存管理★★★★★★★★★★
古龙·移花宫移花接玉中断处理与下半部★★★★☆★★★★☆
古龙·小李飞刀例不虚发精准调试ftrace★★★☆☆★☆☆☆☆

二、少林派《易筋经》——驱动模型框架

2.1 心法要诀

《易筋经》乃少林镇寺之宝,修得此功,可通全身经脉。 对应到内核驱动,便是理解字符设备、块设备、网络设备三大基础模型。

2.2 招式图解:字符设备框架树


2.3 源码心法注解

// 路径: include/linux/fs.h
/**
 * struct file_operations - 字符设备的“武功招式”映射表
 * @owner:      模块所有者,通常为 THIS_MODULE
 * @llseek:     寻址招式,类似“凌波微步”定位内存
 * @read:       读取招式,从硬件取数据给用户
 * @write:      写入招式,从用户发数据给硬件
 * @poll:       轮询招式,检查设备状态
 * @unlocked_ioctl: 控制招式,设备专属命令
 * @mmap:       内存映射招式,将硬件内存映射到用户空间
 * @open:       开门招式,设备被首次打开时调用
 * @release:    收功招式,设备被关闭时调用
 *
 * !!! 修炼要点:每个招式都必须考虑并发安全 !!!
 */
struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    __poll_t (*poll) (struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*release) (struct inode *, struct file *);
    /* ... 其他招式略 ... */
};

2.4 修炼示例:最简单的字符设备

// 源自 openEuler 驱动开发指南 
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
​
#define DEVICE_NAME "shaolin_sword"
​
static dev_t dev_num;
static struct cdev my_cdev;
static char internal_buffer[256];
​
/**
 * shaolin_open - 开门迎客招式
 * 对应《易筋经》“开经”式
 */
static int shaolin_open(struct inode *inode, struct file *filp) {
    pr_info("shaolin_sword: 山门已开,欢迎施主\n");
    return 0;
}
​
/**
 * shaolin_read - 读取心法招式
 * 对应《易筋经》“观想”式 - 从内观想,传于外界
 */
static ssize_t shaolin_read(struct file *filp, char __user *buf, 
                            size_t len, loff_t *off) {
    size_t to_copy = min(len, sizeof(internal_buffer));
    if (copy_to_user(buf, internal_buffer, to_copy))
        return -EFAULT;  /* 内力传输受阻 */
    return to_copy;
}
​
static const struct file_operations shaolin_fops = {
    .owner = THIS_MODULE,
    .open = shaolin_open,
    .read = shaolin_read,
    /* ... 其他招式 ... */
};
​
static int __init shaolin_init(void) {
    /* 分配法号(设备号)*/
    alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
    
    /* 初始化武功秘籍结构 */
    cdev_init(&my_cdev, &shaolin_fops);
    
    /* 将秘籍登记入藏经阁(内核)*/
    cdev_add(&my_cdev, dev_num, 1);
    
    pr_info("shaolin_sword: 少林剑法已入藏经阁,法号 %d\n", MAJOR(dev_num));
    return 0;
}
​
module_init(shaolin_init);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("少林扫地僧");

三、古龙·移花宫《移花接玉》——中断处理与下半部

3.1 心法要诀

移花宫绝学“移花接玉”,讲究借力打力,以柔克刚。 对应内核中断处理:上半部(硬中断)快速响应,下半部(软中断/tasklet/workqueue)慢速处理,绝不可以在硬中断中久留,否则如同硬接对手掌力,必受内伤。

3.2 招式图解:中断处理流程树


3.3 源码心法注解

// 路径: include/linux/interrupt.h
/**
 * struct irqaction - 中断动作描述符,对应“移花接玉”的招式映射
 * @handler:     上半部处理函数,必须快如闪电(不可睡眠)
 * @thread_fn:   线程化处理函数(可睡眠),对应移花宫的“玉手”后着
 * @name:        招式名称,/proc/interrupts 中可见
 * @dev_id:      设备标识,用于共享中断线
 * @irq:         中断号
 * @flags:       中断标志(如 IRQF_SHARED 共享中断线)
 * @thread:      内核线程指针
 *
 * !!! 修炼要点:handler 中绝对不能调用可能导致睡眠的函数 !!!
 */
struct irqaction {
    irq_handler_t       handler;        /* 上半部,快招 */
    irq_handler_t       thread_fn;      /* 下半部线程,慢招 */
    void                *dev_id;
    const char          *name;
    unsigned int        irq;
    unsigned int        flags;
    struct task_struct  *thread;        /* 移花宫弟子,专门处理后续 */
    /* ... 其他字段 ... */
};
​
/**
 * request_threaded_irq - 申请“移花接玉”式中断
 * @irq:     中断号
 * @handler: 上半部处理函数(可为 NULL 则使用默认的硬中断处理)
 * @thread_fn: 下半部线程函数
 * @flags:    中断标志
 * @name:     设备名
 * @dev_id:   设备标识
 *
 * 返回值:0 成功,负值错误码
 */
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
                         irq_handler_t thread_fn, unsigned long flags,
                         const char *name, void *dev_id);

3.4 修炼示例:带下半部的按键中断

#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/input.h>
​
struct sword_data {
    int irq_num;
    struct input_dev *input;
    struct work_struct work;  /* 移花宫“借力”工作项 */
};
​
/**
 * irq_top_half - 上半部,移花宫的“接招”式
 * !!! 必须快:只做必要的硬件应答,记录事件 !!!
 */
static irqreturn_t irq_top_half(int irq, void *dev_id)
{
    struct sword_data *data = dev_id;
    
    /* 记录中断发生,唤醒下半部 */
    schedule_work(&data->work);  /* 移花接玉:将力借给下半部 */
    
    return IRQ_HANDLED;
}
​
/**
 * irq_bottom_half - 下半部,移花宫的“发招”式
 * !!! 可慢:在这里做耗时处理,上报输入事件 !!!
 */
static void irq_bottom_half(struct work_struct *work)
{
    struct sword_data *data = container_of(work, struct sword_data, work);
    
    /* 处理按键,上报事件 */
    input_event(data->input, EV_KEY, KEY_POWER, 1);
    input_sync(data->input);
    
    /* 告知PM核心:处理完毕,可以休眠 */
    pm_relax(data->input->dev.parent);
}
​
/**
 * request_sword_irq - 申请“移花剑法”中断
 */
static int request_sword_irq(struct platform_device *pdev)
{
    struct sword_data *data = platform_get_drvdata(pdev);
    int ret;
    
    /* 初始化移花接玉的工作项 */
    INIT_WORK(&data->work, irq_bottom_half);
    
    /* 申请线程化中断 - 这才是完整的移花接玉 */
    ret = request_threaded_irq(data->irq_num, 
                               irq_top_half,     /* 上半部接招 */
                               NULL,              /* 使用通用thread_fn */
                               IRQF_TRIGGER_RISING,
                               "sword_irq", data);
    
    if (ret) {
        dev_err(&pdev->dev, "移花接玉修炼失败: %d\n", ret);
        return ret;
    }
    
    dev_info(&pdev->dev, "移花接玉功成,可借力打力\n");
    return 0;
}

四、日月神教《吸星大法》——DMA内存管理

4.1 心法要诀

《吸星大法》可吸收他人内力为己用,但若控制不当,反噬自身。 对应DMA(直接内存访问),让硬件直接读写内存,绕过CPU,性能极高,但必须解决Cache一致性问题,否则数据错乱,如同内力冲突,经脉错乱。

4.2 招式图解:DMA内存分配与映射


4.3 源码心法注解

// 路径: include/linux/dma-mapping.h
/**
 * dma_alloc_coherent - 分配一致性DMA内存(吸星大法·正练)
 * @dev:      设备指针
 * @size:     要分配的大小
 * @dma_handle: 输出参数,硬件的DMA地址
 * @gfp:      分配标志(如 GFP_KERNEL)
 *
 * !!! 核心要点:此函数保证CPU和硬件看到的是一致的,
 * !!! 内核已自动处理Cache一致性,无需手动同步
 *
 * 返回值:CPU虚拟地址,失败返回 NULL
 */
static inline void *dma_alloc_coherent(struct device *dev, size_t size,
                                       dma_addr_t *dma_handle, gfp_t gfp);
​
/**
 * dma_map_single - 映射流式DMA缓冲区(吸星大法·逆练)
 * @dev:      设备指针
 * @ptr:      CPU虚拟地址
 * @size:     缓冲区大小
 * @dir:      DMA方向(DMA_TO_DEVICE / DMA_FROM_DEVICE)
 *
 * !!! 危险警告:此方式需手动同步,极易出错
 * !!! 操作完成后必须调用 dma_unmap_single
 *
 * 返回值:硬件的DMA地址
 */
static inline dma_addr_t dma_map_single(struct device *dev, void *ptr,
                                        size_t size, enum dma_data_direction dir);
​
/**
 * dma_sync_single_for_device - 将数据同步给设备(吸星大法·内力外放)
 * @dev:      设备指针
 * @dma_handle: DMA地址
 * @size:     大小
 * @dir:      方向
 *
 * 在 CPU 修改完数据,交给硬件前调用
 */
static inline void dma_sync_single_for_device(struct device *dev,
                                              dma_addr_t dma_handle,
                                              size_t size,
                                              enum dma_data_direction dir);
​
/**
 * dma_sync_single_for_cpu - 将数据同步回CPU(吸星大法·内力回收)
 * @dev:      设备指针
 * @dma_handle: DMA地址
 * @size:     大小
 * @dir:      方向
 *
 * 在硬件修改完数据,CPU读取前调用
 */
static inline void dma_sync_single_for_cpu(struct device *dev,
                                           dma_addr_t dma_handle,
                                           size_t size,
                                           enum dma_data_direction dir);

4.4 修炼心法对比

修炼方式对应招式优点缺点适用场景
dma_alloc_coherent吸星大法·正练无需操心同步,内力自动流转分配较慢,可能消耗较大长时间使用的缓冲区(如网络收包)
dma_map_single + 同步吸星大法·逆练灵活高效,可复用内存极易出错,同步顺序颠倒则数据错乱短时使用的缓冲区(如SCSI命令)

⚠️ 走火入魔警示:ARM64架构上Cache一致性尤为复杂,务必阅读 Documentation/DMA-API.txt,否则轻则数据损坏,重则系统崩溃。

五、古龙·小李飞刀《例不虚发》——ftrace精准调试

5.1 心法要诀

小李飞刀,例不虚发,一招致命,绝不浪费。 对应内核调试神器 ftrace,可以精准追踪指定函数的调用流程,而不像printk那样刷屏,是诊断疑难杂症的终极暗器。

5.2 招式图解:ftrace使用流程


5.3 修炼口诀与实战

5.3.1 第一步:布阵(准备环境)
# 检查是否已挂载 debugfs
mount | grep debugfs
​
# 若未挂载,手动布阵
mount -t debugfs none /sys/kernel/debug/
​
# 进入 ftrace 修炼密室
cd /sys/kernel/debug/tracing
5.3.2 第二步:选器(选择追踪模式)
# 查看所有可用的追踪器(小李飞刀的各式飞刀)
cat available_tracers
​
# 选择 function_graph 模式(最直观的函数调用图)
echo function_graph > current_tracer
5.3.3 第三步:瞄准(设置目标函数)
# 只追踪 do_sys_open 函数及其子调用
echo do_sys_open > set_graph_function
​
# 也可过滤不想看到的函数
echo common_* > set_graph_notrace
5.3.4 第四步:发刀(开始追踪)
# 清空之前的追踪结果
echo > trace
​
# 开启追踪
echo 1 > tracing_on
​
# 运行测试程序,触发驱动代码
./your_test_program
​
# 停止追踪
echo 0 > tracing_on
5.3.5 第五步:验伤(查看结果)
# 查看飞刀命中的轨迹
cat trace | less
​
# 输出示例:
#   do_sys_open() {
#     do_filp_open() {
#       path_openat() {
#         get_empty_flp() {
#           kmem_cache_alloc() = 0xffffffc010
#         }
#         alloc_fd() = 5
#       } = 0
#     } = 0
#   } = 0

5.4 进阶暗器:trace-cmd(小李飞刀的“飞刀盒”)

# 安装飞刀盒
apt-get install trace-cmd
​
# 一条命令完成追踪
trace-cmd record -p function_graph -g do_sys_open -o mytrace.dat
​
# 查看结果
trace-cmd report mytrace.dat

✨ 小李飞刀心法:ftrace的最大优势是性能开销极小,可在生产环境使用。当怀疑某个函数执行异常时,一“刀”下去,调用链无所遁形。

六、全真教《天罡北斗阵》——并发控制与锁机制

6.1 心法要诀

全真七子的天罡北斗阵,七人各守一位,互不干扰,又能合力御敌。 对应内核并发控制,多核CPU同时访问同一资源,必须用锁来保证“阵法不乱”。

6.2 招式图解:各种锁的适用场景


6.3 源码心法对比表

锁类型对应阵法特点使用口诀危险之处
spinlock_t北斗阵·急字诀忙等待,不睡眠“速战速决,不可久留”持有时间过长导致CPU空转
mutex北斗阵·缓字诀可睡眠,进程切换“困了就睡,醒了再战”不能在中断上下文使用
rwlock_t北斗阵·分字诀读读不互斥,读写互斥“多人可读,一人写时全禁”写者可能被读者饿死
RCU北斗阵·幻字诀读无锁,写时复制更新“读如清风过岗,写如移形换位”理解难度高,易用错

6.4 修炼示例:自旋锁保护关键数据

#include <linux/spinlock.h>
​
struct quanzen_data {
    int important_counter;
    spinlock_t lock;  /* 北斗阵的阵眼 */
};
​
/**
 * update_counter - 更新计数器,需阵法保护
 */
void update_counter(struct quanzen_data *data)
{
    unsigned long flags;
    
    /* 入阵:抢占关闭,自旋锁锁定 */
    spin_lock_irqsave(&data->lock, flags);
    
    /* !!! 危险区:只能做简单操作,不能睡眠 !!! */
    data->important_counter++;
    
    /* 出阵:恢复之前状态 */
    spin_unlock_irqrestore(&data->lock, flags);
}
​
/**
 * read_counter - 读取计数器,也需要保护(防止读取时写入)
 */
int read_counter(struct quanzen_data *data)
{
    unsigned long flags;
    int val;
    
    spin_lock_irqsave(&data->lock, flags);
    val = data->important_counter;
    spin_unlock_irqrestore(&data->lock, flags);
    
    return val;
}

七、武当派《真武七截阵》——错误处理与资源管理

7.1 心法要诀

武当派的真武七截阵,攻防一体,进退有据,绝不留下破绽。 对应驱动中的错误处理,必须在所有失败路径上正确释放已分配的资源,否则内存泄漏如同阵法露出破绽,被对手(Bug)一击必杀。

7.2 招式图解:goto语句的优雅使用


7.3 源码心法注解

// 路径: drivers/net/ethernet/hisilicon/hibmcge/hbg_main.c (参考 )
/**
 * wudang_init - 武当派驱动初始化,真武七截阵的完美体现
 * 
 * !!! 阵法精髓:每个错误标签对应一个已分配的资源 !!!
 * !!! 从下往上释放,绝不遗漏 !!!
 */
static int __init wudang_init(void)
{
    struct my_drv *drv;
    int ret;
    
    /* 第一式:分配驱动结构体 */
    drv = kzalloc(sizeof(*drv), GFP_KERNEL);
    if (!drv) {
        ret = -ENOMEM;
        goto err_out;  /* 直接跳出,因为还没分配任何资源 */
    }
    
    /* 第二式:请求IO内存资源 */
    ret = request_mem_region(drv->base, drv->size, "wudang");
    if (ret) {
        ret = -EBUSY;
        goto err_free_drv;  /* 跳转到释放drv */
    }
    
    /* 第三式:申请中断 */
    ret = request_irq(drv->irq, wudang_irq_handler, 0, "wudang", drv);
    if (ret)
        goto err_release_mem;  /* 跳转到释放内存资源 */
    
    /* 第四式:创建字符设备 */
    ret = register_chrdev(0, "wudang", &wudang_fops);
    if (ret < 0)
        goto err_free_irq;  /* 跳转到释放中断 */
    
    drv->major = ret;
    
    pr_info("武当真武七截阵布阵成功,阵眼位于 major %d\n", drv->major);
    return 0;
​
    /* !!! 七截阵的“收式”:逆序释放 !!! */
err_free_irq:
    free_irq(drv->irq, drv);
err_release_mem:
    release_mem_region(drv->base, drv->size);
err_free_drv:
    kfree(drv);
err_out:
    return ret;
}

7.4 修炼口诀:真武七截阵七要诀

  1. 资源申请成对出:request 必有 release,alloc 必有 free

  2. 错误路径逆序走:先分配的后释放,后分配的先释放

  3. 每一跳都有意义:goto标签名清晰,err_free_X 表示释放X

  4. 错误码要传回去:返回标准错误码(-ENOMEM, -EINVAL)

  5. 日志要留案:关键错误用 dev_err 记录,方便事后追查

  6. 超时要有耐心:wait_event_timeout 必须检查返回值

  7. 恢复机制要备:可恢复的错误自动重试

八、移植新功能秘籍:如何将“别派武功”融入本门

8.1 心法要诀

当从GitHub或别处获得一个驱动源码(别派武功),要将其融入自己的内核(本门),如同吸纳别派内力,需经“化功大法”改造。

8.2 移植步骤树(以WiFi驱动为例 )


8.3 实战示例:移植rtl8723bu WiFi驱动

8.3.1 第一步:取经(获取源码)
git clone https://github.com/lwfinger/rtl8723bu.git
8.3.2 第二步:入室(放入内核源码树)
# 假设内核源码在 ~/linux-kernel
cp -r rtl8723bu ~/linux-kernel/drivers/net/wireless/
8.3.3 第三步:改名换姓(修改Kconfig和Makefile)
# 在 drivers/net/wireless/Kconfig 中添加
source "drivers/net/wireless/rtl8723bu/Kconfig"
​
# 在 drivers/net/wireless/Makefile 中添加
obj-$(CONFIG_RTL8723BU) += rtl8723bu/
8.3.4 第四步:易筋(修改驱动自身的Makefile适配平台)
# 在 rtl8723bu/Makefile 中,原本是 x86 平台
# 将其改为 n,添加自己的平台
​
# 原配置
CONFIG_PLATFORM_I386_PC = n  # 改为 n
​
# 新增配置
CONFIG_PLATFORM_MY_ARM = y
​
ifeq ($(CONFIG_PLATFORM_MY_ARM), y)
EXTRA_CFLAGS += -DCONFIG_LITTLE_ENDIAN
EXTRA_CFLAGS += -DCONFIG_IOCTL_CFG80211 -DRTW_USE_CFG80211_STA_EVENT
ARCH := arm
CROSS_COMPILE := arm-linux-gnueabihf-
KSRC := /home/user/linux-kernel  # 内核源码路径
endif
8.3.5 第五步:激活(配置内核)
make menuconfig
# 进入 Device Drivers → Network device support → Wireless LAN
# 选中 rtl8723bu 支持(可编成模块 M)
8.3.6 第六步:渡劫(编译验证)
make modules
# 若成功,会在 drivers/net/wireless/rtl8723bu/ 下生成 8723bu.ko

九、总结:武林高手的进阶之路

9.1 各阶段修炼路线图

阶段对应武侠境界应掌握的武功内核对应技能出师标准
初入江湖少林俗家弟子罗汉拳字符设备驱动、printk调试能写LED驱动,会看dmesg
小有名气全真七子天罡北斗阵、金雁功并发控制、中断处理、procfs能写按键驱动,会用锁
一派宗师张三丰太极拳、真武七截阵DMA、复杂错误处理、电源管理能写网卡驱动,懂PM
超凡入圣独孤求败独孤九剑、无招胜有招内核核心贡献、新框架设计能重构子系统,提交patch到主线

9.2 终极心法:六字真言

先 模 仿, 后 创 新
先 运 行, 后 优 化
先 调 试, 后 提 交

每一次 Kernel Panic,都是一次顿悟的机会;每一次内存泄漏,都是一次内功的锤炼。正如 openEuler 社区的前辈所言:“写驱动要有耐心——每一次 crash 的背后,都是离理解内核又近了一步。”

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐