1. Linux驱动开发概述

1.1 什么是设备驱动

设备驱动是操作系统内核中负责管理特定硬件设备的软件模块。它充当硬件设备与用户应用程序之间的“翻译官”,将应用程序发出的通用操作请求(如读、写、控制)转换为硬件设备能够理解的特定指令序列。

从本质上看,设备驱动实现了以下核心功能:

  • 硬件抽象:屏蔽不同硬件设备的差异,向上层提供统一的编程接口。
  • 资源管理:管理设备的寄存器、中断、DMA通道、内存映射等硬件资源。
  • 数据交换:在用户空间与硬件设备之间高效传输数据。
  • 事件通知:处理硬件产生的中断,并通知上层应用程序。
理解要点:没有驱动,操作系统就无法识别和控制硬件设备。在嵌入式Linux系统中,驱动开发是连接软件与硬件的核心环节,也是系统稳定性和性能的关键保障。

1.2 驱动分类:字符设备、块设备与网络设备

Linux内核将设备驱动分为三大基本类型,每种类型对应不同的硬件特性和访问模式:

驱动类型 典型设备 数据访问方式 关键特征
字符设备 串口、GPIO、I2C、SPI、LED、按键 顺序字节流,按字符读写 无缓冲区,直接操作;通过/dev节点访问
块设备 eMMC、NAND Flash、SD卡、硬盘 随机块访问,以块为单位 有内核缓冲区(page cache);支持文件系统挂载
网络设备 以太网、Wi-Fi、蓝牙、CAN 数据包(sk_buff)收发 不通过/dev节点;通过socket接口访问

本课程的核心是字符设备驱动,它是嵌入式Linux驱动开发中最基础、最常用、最适合入门学习的驱动类型。字符设备驱动的结构清晰、接口简单,掌握它之后,学习块设备和网络设备将事半功倍。

课程聚焦:字符设备驱动是嵌入式Linux驱动开发的“Hello World”。从字符设备入手,可以快速理解驱动框架、文件操作接口、内核内存管理、中断处理等核心概念。
Linux设备驱动三大分类 用户空间 (User Space) 系统调用接口 (read/write/ioctl/socket) 字符设备驱动 串口 / GPIO / I2C / SPI 顺序字节流 · 无缓冲区 块设备驱动 eMMC / NAND / SD卡 随机块访问 · 有缓冲区 网络设备驱动 以太网 / Wi-Fi / CAN 数据包收发 · socket接口 硬件设备层 (Hardware) 本课程聚焦字符设备驱动 —— 嵌入式Linux驱动开发的基础与核心 掌握字符设备后,块设备和网络设备的学习将更加顺畅

1.3 内核空间与用户空间

Linux系统将虚拟内存划分为两个特权级别不同的区域:内核空间和用户空间。这种隔离机制是系统安全性和稳定性的基石。

1.3.1 核心差异对比

对比维度 内核空间 用户空间
运行级别 特权模式(Ring 0 / EL1) 非特权模式(Ring 3 / EL0)
访问权限 可访问所有硬件资源和内存 只能访问受限内存,不能直接操作硬件
崩溃影响 内核崩溃 → 系统宕机 进程崩溃 → 仅该进程退出
代码运行 内核模块、驱动、核心调度 应用程序、库、Shell脚本
内存地址 高地址区域(通常1GB以上) 低地址区域

1.3.2 用户态与内核态的切换

应用程序不能直接调用内核函数或操作硬件。当用户程序需要执行特权操作时,必须通过系统调用(System Call)陷入内核态,由内核代表用户程序完成操作后再返回用户态。常见的系统调用包括:

  • open() — 打开设备文件
  • read() / write() — 读写数据
  • ioctl() — 设备控制
  • mmap() — 内存映射
驱动开发注意事项:驱动代码运行在内核空间,拥有最高权限。因此驱动中的任何错误(空指针、缓冲区溢出、死锁)都可能导致整个系统崩溃。驱动开发者必须比应用程序开发者更加谨慎。
内核空间与用户空间交互架构 用户空间 (User Space) 应用程序A open/read/write 应用程序B ioctl/mmap 应用程序C socket/send/recv 系统调用接口 (System Call Interface) 内核空间 (Kernel Space) VFS (虚拟文件系统) 统一文件操作接口 设备驱动模块 字符/块/网络驱动 硬件抽象层 寄存器/中断/DMA 硬件设备 (Hardware)

1.4 驱动开发环境搭建

在开始编写字符设备驱动之前,必须搭建完整的开发环境。嵌入式Linux驱动开发环境的核心组件包括:交叉编译工具链和内核源码树。

1.4.1 交叉编译工具链

嵌入式设备的CPU架构通常与开发主机不同(如ARM、RISC-V、MIPS),因此需要在主机上安装交叉编译工具链,用于生成目标设备上可执行的代码。

常见交叉编译工具链示例:

目标架构 工具链前缀 示例
ARM (32位) arm-linux-gnueabihf- arm-linux-gnueabihf-gcc
ARM64 (AArch64) aarch64-linux-gnu- aarch64-linux-gnu-gcc
RISC-V (64位) riscv64-linux-gnu- riscv64-linux-gnu-gcc

1.4.2 内核源码树

驱动代码需要依赖内核头文件和编译框架,因此必须准备与目标设备内核版本匹配的内核源码树。源码树通常通过以下方式获取:

  • 官方内核源码:从 kernel.org 下载主线内核
  • 厂商BSP源码:芯片厂商提供的板级支持包(如NXP、TI、Rockchip)
  • 开发板源码:开发板厂商提供的定制内核(如Raspberry Pi、BeagleBone)

1.4.3 环境搭建步骤

  1. 安装交叉编译工具链
    # 以ARM 32位为例,使用apt安装
    sudo apt-get install gcc-arm-linux-gnueabihf
    
    # 验证安装
    arm-linux-gnueabihf-gcc --version
  2. 获取并解压内核源码
    # 下载内核源码(以5.10版本为例)
    wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.tar.xz
    tar -xf linux-5.10.tar.xz
    cd linux-5.10
  3. 配置内核
    # 使用目标设备的默认配置
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig
    
    # 或使用厂商提供的配置文件
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- myboard_defconfig
  4. 编译内核(可选,但建议先编译一次)
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4
  5. 编译内核模块(生成Module.symvers)
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
环境验证:完成上述步骤后,内核源码树根目录下应包含 vmlinux、Module.symvers、include/generated/autoconf.h 等关键文件。这些文件是后续编译驱动模块的基础。

1.4.4 驱动模块编译示例(Makefile)

以下是一个最简单的字符设备驱动模块的Makefile模板,用于验证环境是否搭建成功:

# Makefile - 字符设备驱动模块编译
obj-m := hello_drv.o

KDIR := /home/user/linux-5.10
CROSS := arm-linux-gnueabihf-

all:
    make -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS) modules

clean:
    make -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS) clean
关键变量说明:
  • obj-m:指定要编译成模块的目标文件
  • KDIR:内核源码树路径
  • CROSS:交叉编译工具链前缀
  • M=$(PWD):指定当前目录为模块源码目录

1.5 本章小结

本章建立了嵌入式Linux驱动开发的基础认知框架:

  • 设备驱动是连接硬件与软件的桥梁,负责硬件抽象和资源管理。
  • 驱动分为字符设备、块设备和网络设备三大类,本课程聚焦字符设备。
  • 内核空间与用户空间的隔离机制保障了系统安全,驱动运行在内核空间。
  • 交叉编译工具链和内核源码树是驱动开发的基础环境,必须正确配置。