1. Linux 驱动概述:设备驱动、内核角色与空间划分

设备驱动是操作系统与硬件设备之间的“翻译官”。在 Linux 系统中,驱动以内核模块的形式存在,负责将用户空间的请求转换为硬件可执行的指令序列。理解驱动在内核中的角色以及用户空间与内核空间的边界,是编写正确、高效驱动的第一步。

1.1 什么是设备驱动

设备驱动是一组控制特定硬件设备的函数和数据结构的集合。它封装了硬件操作细节,向上层提供统一的接口。Linux 将设备分为三大类,每类对应不同的驱动模型:

设备类型 典型设备 驱动接口 设备文件
字符设备 串口、GPIO、I2C 传感器 open/read/write/ioctl /dev/ttyS0, /dev/gpio0
块设备 硬盘、SD 卡、U 盘 块请求队列 + bio /dev/sda, /dev/mmcblk0
网络设备 以太网卡、WiFi 模块 socket + sk_buff eth0, wlan0(无设备文件)
课程聚焦: 本课程以字符设备驱动为主线,因为它结构清晰、接口简单,最适合初学者理解驱动框架。掌握字符设备后,块设备和网络设备的原理可以触类旁通。

1.2 驱动在内核中的角色

驱动在内核中承担三个核心角色:

  • 硬件抽象层: 将不同厂商、不同接口的硬件差异隐藏在内核中,用户程序无需关心硬件细节。
  • 资源管理者: 管理中断、DMA、寄存器、内存映射等硬件资源,防止多个进程同时访问导致冲突。
  • 数据通道: 在用户空间与硬件之间搬运数据,必要时进行格式转换、校验或协议解析。

下图展示了驱动在内核中的位置与数据流向:

Linux 驱动在内核中的角色与数据流向 用户空间 (User Space) 应用程序 (App) — 如 cat /dev/ttyS0 系统调用 (sys_open / sys_read / sys_write) 内核空间 (Kernel Space) VFS → 设备驱动 → 硬件操作函数 字符设备驱动 file_operations 块设备驱动 block_device_operations 网络设备驱动 net_device_ops 硬件设备 (Hardware) UART 控制器 / SPI 闪存 / 以太网 PHY
关键理解: 驱动是内核的一部分,运行在内核空间,拥有最高权限。它不能直接调用用户空间的库函数(如 printf),只能使用内核 API(如 printk)。驱动代码中的错误会直接导致内核崩溃(Oops),而用户程序崩溃只会影响自身进程。

1.3 用户空间与内核空间的区别

Linux 通过 CPU 的特权级机制将内存划分为两个区域。x86 架构使用 Ring 0(内核空间)和 Ring 3(用户空间),ARM 架构使用 SVC 模式和 USR 模式。两者的核心区别如下:

对比维度 用户空间 内核空间
CPU 特权级 Ring 3(受限模式) Ring 0(最高权限)
可访问内存 仅自身虚拟地址空间 全部物理内存 + 内核虚拟空间
可执行指令 普通指令,无 I/O 指令 所有指令,包括 in/out、cli/sti
直接访问硬件 不允许 允许(通过 MMIO / PIO)
崩溃影响 仅终止当前进程 系统 panic 或 Oops
调试方式 gdb 直接调试 kgdb / printk / ftrace
内存分配 malloc / free kmalloc / kfree / vmalloc

用户程序要访问硬件,必须通过系统调用陷入内核。这个过程涉及上下文切换:

  1. 用户程序调用库函数(如 read())。
  2. 库函数触发软中断(x86 的 int 0x80 或 syscall 指令)。
  3. CPU 切换到内核栈,保存用户态寄存器。
  4. 内核根据系统调用号执行对应的驱动函数。
  5. 驱动操作硬件,返回结果。
  6. 恢复用户态寄存器,返回用户空间。
性能代价: 每次系统调用都会发生用户态与内核态的切换,开销约在 50–200 ns(取决于 CPU 架构)。高频 I/O 场景(如网络包处理)应尽量减少系统调用次数,或使用 mmap、splice 等零拷贝技术。

1.4 为什么驱动开发需要理解空间划分

初学者最容易犯的错误就是混淆用户空间与内核空间的编程环境。以下对比表可以帮助你建立正确的思维模型:

编程要素 用户空间 内核空间(驱动)
头文件 <stdio.h> <stdlib.h> <unistd.h> <linux/module.h> <linux/fs.h> <linux/uaccess.h>
打印输出 printf() printk()
内存分配 malloc() / calloc() kmalloc() / kzalloc()
字符串操作 strcpy() / strlen() strcpy() / strlen()(可用,但需注意)
浮点运算 完全支持 禁止(内核不保存浮点寄存器)
睡眠/等待 sleep() / usleep() msleep() / wait_event_interruptible()
锁机制 pthread_mutex_t spinlock_t / mutex(内核版本)