嵌入式Linux设备驱动与设备树工程 - 第1节

1. Linux驱动开发概述

在嵌入式Linux系统中,驱动(Device Driver)是操作系统内核与硬件设备之间的桥梁。它负责将上层应用的通用操作(如读、写、控制)转换为特定硬件设备能够理解的寄存器操作序列。没有驱动,操作系统就无法控制任何外设——从简单的LED灯到复杂的摄像头、Wi-Fi模块。

核心定义: Linux驱动是一段运行在内核空间的程序,它实现了对特定硬件设备的初始化、配置、数据收发和中断处理,并通过标准接口向上层应用提供服务。

驱动开发在嵌入式Linux开发中占据核心地位。据统计,在嵌入式Linux项目中,驱动开发的工作量通常占整个系统开发的 40%~60%。理解驱动的本质,是入门嵌入式Linux开发的第一步。

1.2 驱动分类:三大类型

Linux内核将设备驱动划分为三大基本类型:字符设备、块设备和网络设备。每种类型对应不同的硬件特性和访问模式。

1.2.1 字符设备(Character Device)

字符设备以字节流的形式进行数据传输,数据按顺序逐个字节地读写,不支持随机访问。典型的字符设备包括:串口、GPIO、I2C、SPI、触摸屏、键盘、鼠标等。

  • 访问方式: open / read / write / ioctl / close
  • 设备文件: 位于 /dev 目录下,如 /dev/ttyS0、/dev/input/event0
  • 主设备号: 标识驱动类型;次设备号: 标识同一驱动下的不同设备实例
  • 特点: 简单、直接、适合低速设备

1.2.2 块设备(Block Device)

块设备以数据块为单位进行数据传输,支持随机访问(可寻址)。典型的块设备包括:eMMC、NAND Flash、SD卡、硬盘、U盘等。

  • 访问方式: 通过文件系统挂载后,以文件形式读写
  • 设备文件: 位于 /dev 目录下,如 /dev/mmcblk0、/dev/sda1
  • 核心机制: 请求队列(request queue)和I/O调度
  • 特点: 支持缓存、批量读写、适合大容量存储

1.2.3 网络设备(Network Device)

网络设备负责数据包的发送和接收,它不通过设备文件访问,而是通过内核网络协议栈(如TCP/IP)与用户空间交互。典型的网络设备包括:以太网控制器、Wi-Fi模块、蓝牙、CAN总线等。

  • 访问方式: socket / netlink / 网络协议栈
  • 设备名称: 如 eth0、wlan0、can0
  • 核心机制: 中断驱动、DMA传输、NAPI轮询
  • 特点: 异步、数据包导向、不依赖设备文件

1.2.4 三大驱动类型对比

对比维度 字符设备 块设备 网络设备
数据单位 字节流 数据块(512B/4KB等) 数据包(MTU)
访问模式 顺序读写 随机读写 异步收发
用户接口 /dev/xxx 设备文件 /dev/xxx + 文件系统 socket / netlink
典型设备 串口、GPIO、I2C eMMC、SD卡、U盘 以太网、Wi-Fi、CAN
驱动复杂度 低~中 中~高 高
入门推荐 ✅ 首选 进阶 进阶
学习建议: 初学者应从字符设备驱动入手。它结构清晰、代码量少、调试方便,是理解Linux驱动框架的最佳切入点。本课程后续章节将围绕字符设备驱动展开。

1.3 内核空间与用户空间

Linux系统将内存划分为两个特权级别不同的区域:内核空间(Kernel Space)和用户空间(User Space)。这是Linux安全性和稳定性的基石。

1.3.1 概念与区别

  • 内核空间: 操作系统内核运行的地方,拥有最高权限(Ring 0),可以直接访问所有硬件资源和内存地址。
  • 用户空间: 应用程序运行的地方,权限受限(Ring 3),不能直接访问硬件,必须通过系统调用请求内核服务。

这种隔离机制确保了:即使某个应用程序崩溃,也不会影响整个系统的稳定性。驱动运行在内核空间,因此驱动代码的错误(如空指针、内存越界)可能导致整个系统崩溃——这就是驱动开发需要格外谨慎的原因。

1.3.2 用户空间与内核空间的交互方式

用户空间程序与内核空间驱动之间的数据交换,主要通过以下机制实现:

交互方式 说明 典型API
系统调用 用户程序通过软中断陷入内核 open / read / write / ioctl
/dev 设备文件 通过文件操作接口访问驱动 fopen / fread / fwrite
/sys 文件系统 通过sysfs属性文件读写驱动参数 cat / echo
/proc 文件系统 读取内核运行时信息 cat /proc/cpuinfo
netlink 内核与用户空间的socket通信 socket(AF_NETLINK)
mmap 将内核内存映射到用户空间 mmap() 系统调用

1.3.3 内核空间与用户空间数据传递示意图

用户空间与内核空间交互示意图 用户空间 (User Space) — Ring 3 应用程序 系统调用接口 C库 (glibc) 系统调用陷入内核 (软中断 / syscall) 内核空间 (Kernel Space) — Ring 0 VFS 虚拟文件系统 设备驱动框架 具体硬件驱动 硬件抽象层 (HAL) 物理硬件设备 💡 用户程序通过系统调用陷入内核,经过VFS、驱动框架、具体驱动,最终操作硬件。数据通过copy_from_user/copy_to_user在内核与用户空间之间传递。

1.3.4 数据传递的关键函数

由于内核空间和用户空间的内存地址映射不同,驱动不能直接访问用户空间传递的指针。必须使用内核提供的专用函数进行安全的数据拷贝:

// 从用户空间拷贝数据到内核空间
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);

// 从内核空间拷贝数据到用户空间
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);

// 字符串拷贝(用户空间 <-> 内核空间)
long strncpy_from_user(char *dst, const char __user *src, long count);
⚠️ 常见错误: 初学者容易犯的错误是直接解引用用户空间指针。例如:int *p = arg; *p = 0x1234; 这会导致内核崩溃。必须使用 copy_from_user / copy_to_user 系列函数。

1.4 本章小结

  • Linux驱动 是内核与硬件之间的桥梁,运行在内核空间。
  • 驱动分为 字符设备、块设备、网络设备 三大类,初学者应从字符设备入手。
  • 内核空间 拥有最高权限,直接管理硬件;用户空间 权限受限,通过系统调用请求内核服务。
  • 用户空间与内核空间的数据传递必须使用 copy_from_user / copy_to_user 等安全函数。
  • 理解这些基础概念,是后续编写第一个字符设备驱动的必要前提。