欢迎光临
我们一直在努力

[Linux外设驱动详解]第一部分:基础篇 —— 驱动开发入门

Linux 设备驱动开发入门指南

硬件平台: 瑞芯微 RK3588 源码路径:

  • U-Boot: ./u-boot
  • Linux 内核: ./kernel

目录

  • 什么是 Linux 设备驱动
  • Linux 内核模块机制
  • 第一个字符设备驱动
  • 源码分析实战
  • 设备节点创建

  • 1. 什么是 Linux 设备驱动

    1.1 驱动程序的本质

    设备驱动程序 是连接 Linux 内核与硬件设备的桥梁,它为上层应用程序提供统一的访问接口,屏蔽底层硬件的具体实现细节。

    ┌─────────────────────────────────────────────────────────────┐
    │ 用户空间 (User Space) │
    │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
    │ │ 应用程序 │ │ 应用程序 │ │ Shell │ │
    │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
    │ │ │ │ │
    │ └─────────────────┼─────────────────┘ │
    │ │ │
    ├───────────────────────────┼───────────────────────────────────┤
    │ 系统调用接口 │ (read, write, ioctl, etc.) │
    ├───────────────────────────┼───────────────────────────────────┤
    │ │ │
    │ ┌────────────────────────▼─────────────────────────────┐ │
    │ │ 内核空间 (Kernel Space) │ │
    │ │ ┌────────────────────────────────────────────────┐ │ │
    │ │ │ VFS (虚拟文件系统) │ │ │
    │ │ └────────────────────────────────────────────────┘ │ │
    │ │ ┌────────────────────────────────────────────────┐ │ │
    │ │ │ 设备驱动程序 (Driver) │ │ │
    │ │ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │
    │ │ │ │ 字符设备│ │ 块设备 │ │ 网络设备│ │ │ │
    │ │ │ └────────┘ └────────┘ └────────┘ │ │ │
    │ │ └────────────────────────────────────────────────┘ │ │
    │ │ ┌────────────────────────────────────────────────┐ │ │
    │ │ │ 硬件抽象层 │ │ │
    │ │ └────────────────────────────────────────────────┘ │ │
    │ └──────────────────────────────────────────────────────┘ │
    │ │ │
    ├───────────────────────────┼───────────────────────────────────┤
    │ 硬件设备 │
    │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
    │ │ GPIO │ │ I2C │ │ SPI │ … │
    │ └──────────┘ └──────────┘ └──────────┘ │
    └─────────────────────────────────────────────────────────────┘

    关键点:

    • 用户空间程序通过系统调用访问设备
    • 驱动程序运行在内核空间,拥有最高权限
    • 设备文件(如 /dev/gpio)是用户访问驱动的入口

    1.2 设备驱动分类

    Linux 系统将设备驱动分为三大类:

    驱动类型
    特点
    访问方式
    典型设备
    源码位置
    字符设备 按字节流顺序访问,不支持随机访问 read(), write(), ioctl() 串口、GPIO、LED、键盘 kernel/drivers/char/
    块设备 可随机访问,以数据块为单位 文件系统接口 硬盘、Flash、SD卡 kernel/drivers/block/
    网络设备 处理网络数据包,不对应设备节点 Socket 接口 以太网、WiFi、蓝牙 kernel/drivers/net/

    字符设备 vs 块设备的核心区别:

    // 字符设备:顺序访问,像水流一样
    /dev/tty → 只能顺序读写
    /dev/gpio → 按字节操作

    // 块设备:可随机访问任意位置
    /dev/sda → 可以跳到任意扇区
    /dev/mmcblk0 → 支持文件系统


    2. Linux 内核模块机制

    2.1 什么是内核模块

    内核模块 (Kernel Module) 是可以动态加载到内核中的代码,也称为 LKM (Loadable Kernel Module)。

    优势:

    • 不需要重新编译整个内核
    • 按需加载,节省内存
    • 便于开发和调试

    2.2 模块的生命周期

    ┌──────────────────────────────────────────────────────────────┐
    │ 内核模块生命周期 │
    ├──────────────────────────────────────────────────────────────┤
    │ │
    │ insmod/modprobe │
    │ │ │
    │ ▼ │
    │ ┌─────────┐ module_init() ┌─────────────┐ │
    │ │ .ko 文件│ ─────────────────► │ 运行中 │ │
    │ │(磁盘上) │ │ (内核内存) │ │
    │ └─────────┘ └──────┬──────┘ │
    │ │ │
    │ │ 提供服务 │
    │ ▼ │
    │ ┌─────────┐ │
    │ │ 设备操作 │ │
    │ └─────────┘ │
    │ │ │
    │ rmmod │ │
    │ │ │ │
    │ ▼ │ │
    │ module_exit() ◄───────────────────────┘ │
    │ │ │
    │ ▼ │
    │ ┌─────────┐ │
    │ │ 卸载完成 │ │
    │ └─────────┘ │
    │ │
    └──────────────────────────────────────────────────────────────┘

    2.3 模块编程基础

    最简单的内核模块

    #include <linux/init.h> // 宏定义
    #include <linux/module.h> // 模块核心头文件
    #include <linux/kernel.h> // 内核函数

    // 模块初始化函数 – 加载时执行
    static int __init hello_init(void)
    {


    printk(KERN_INFO \”Hello: 模块已加载\\n\”);
    return 0; // 返回 0 表示成功
    }

    // 模块退出函数 – 卸载时执行
    static void __exit hello_exit(void)
    {


    printk(KERN_INFO \”Hello: 模块已卸载\\n\”);
    }

    // 注册初始化和退出函数
    module_init(hello_init);
    module_exit(hello_exit);

    // 模块元信息
    MODULE_LICENSE(\”GPL\”); // 许可证声明 (必须)
    MODULE_AUTHOR(\”Your Name\”); // 作者
    MODULE_DESCRIPTION(\”简单模块\”); // 描述
    MODULE_VERSION(\”1.0\”); // 版本

    宏说明:

    • __init: 函数在初始化后可被丢弃以节省内存
    • __exit: 函数仅在模块卸载时使用
    • module_init(): 指定模块加载入口
    • module_exit(): 指定模块卸载出口
    模块加载与卸载命令

    # 加载模块
    insmod hello.ko # 简单加载
    modprobe hello.ko # 智能加载(处理依赖)

    # 查看模块
    lsmod | grep hello # 列出已加载模块
    modinfo hello.ko # 查看模块信息

    # 卸载模块
    rmmod hello # 移除模块

    # 查看内核日志
    dmesg | tail # 查看最近的内核消息
    dmesg | grep Hello # 查看特定消息

    2.4 printk 与内核日志

    printk 是内核空间的 printf,用于输出调试信息。

    日志级别

    #define KERN_EMERG \”0\” // 紧急情况:系统崩溃
    #define KERN_ALERT \”1\” // 需要立即处理
    #define KERN_CRIT \”2\” // 严重情况
    #define KERN_ERR \”3\” // 错误
    #define KERN_WARNING \”4\” // 警告
    #define KERN_NOTICE \”5\” // 注意
    #define KERN_INFO \”6\” // 信息
    #define KERN_DEBUG \”7\” // 调试

    使用示例:

    printk(KERN_INFO \”设备号: %d\\n\”, major); // 信息级别
    printk(KERN_ERR \”设备初始化失败: %d\\n\”, ret); // 错误级别
    printk(KERN_DEBUG \”调试信息: count=%u\\n\”, count); // 调试级别

    // 简写形式(需要包含 <linux/printk.h>)
    pr_info(\”设备号: %d\\n\”, major);
    pr_err(\”初始化失败: %d\\n\”, ret);
    pr_debug(\”count=%u\\n\”, count);

    查看内核日志

    # 实时查看内核日志
    dmesg -w # 持续监控
    dmesg -w | grep my_driver # 过滤特定驱动

    # 控制台日志级别
    cat /proc/sys/kernel/printk # 查看当前级别
    echo 8 > /proc/sys/kernel/printk # 设置为调试级别


    3. 第一个字符设备驱动

    3.1 字符设备架构概览

    ┌───────────────────────────────────────────────────────────────┐
    │ 字符设备驱动架构 │
    ├───────────────────────────────────────────────────────────────┤
    │ │
    │ 用户空间 │
    │ ┌─────────────┐ open(\”/dev/mydev\”) ┌──────────────┐ │
    │ │ 应用程序 │ ─────────────────────► │ VFS 层 │ │
    │ │ │ │ │ │
    │ │ fd = open()│ read(fd, buf, len) │ 文件系统 │ │
    │ │ read(fd) │ ─────────────────────► │ │ │
    │ │ write(fd) │ └─

    赞(0)
    未经允许不得转载:171主机测评 » [Linux外设驱动详解]第一部分:基础篇 —— 驱动开发入门
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址