第一章:驱动基础认知
大家好,我是这次课程的主讲。在正式开始之前,我想先聊聊一个很基础的问题——驱动程序到底是什么?
很多人刚接触驱动开发时,觉得这东西很神秘。其实说白了,驱动程序就是操作系统和硬件之间的翻译官。你想想看,操作系统不认识你的网卡、显卡、硬盘,它只知道怎么调度CPU、管理内存。那硬件怎么工作?谁来告诉操作系统「我这个设备能干啥」?嗯,就是驱动。
1.1 操作系统与硬件的关系
操作系统和硬件之间,是一种「分层协作」的关系。硬件在最底层,操作系统在中间层,应用程序在最上层。驱动就是连接操作系统和硬件的桥梁。
我刚开始做嵌入式开发时,遇到过一个问题:一块新的传感器芯片,插上后系统根本不识别。后来才发现,是内核里没有对应的驱动。操作系统只能通过驱动去访问硬件寄存器、读取状态、发送指令。没有驱动,硬件就是一块废铁。
这里有个关键点:操作系统不直接操作硬件。它通过驱动提供的接口来间接控制。比如你写一个read()函数,最终会调用驱动里的xxx_read()。这个调用链,就是驱动存在的意义。
核心理解:驱动是操作系统与硬件之间的契约。操作系统提供框架,驱动实现具体操作。
1.2 驱动分类:字符/块/网络设备
驱动按设备类型,主要分三类。我当年面试时,面试官第一个问题就是「说说字符设备和块设备的区别」。这个问题看似简单,但能看出你对驱动模型的理解深度。
| 类型 | 特点 | 典型设备 | 访问方式 |
|---|---|---|---|
| 字符设备 | 按字节流读写,顺序访问 | 串口、键盘、鼠标 | open/read/write/close |
| 块设备 | 按块读写,支持随机访问 | 硬盘、U盘、SD卡 | 通过文件系统挂载 |
| 网络设备 | 数据包收发,不通过文件节点 | 网卡、WiFi模块 | socket接口 |
字符设备最简单,你打开一个设备文件,像读写普通文件一样操作它。块设备则复杂一些,因为它要支持文件系统。网络设备最特殊,它不暴露在/dev下,而是通过协议栈和socket打交道。
我记得有一次调试一个USB转串口驱动,发现数据总是丢字节。查了半天,原来是驱动里没有处理好缓冲区。字符设备虽然简单,但细节上很容易踩坑。
1.3 驱动开发环境搭建
环境搭建是入门的第一道坎。我见过太多人卡在这一步,编译不过、内核版本不对、模块加载失败……其实只要理清步骤,没那么难。
1.3.1 获取Linux内核源码
首先,你得有一份内核源码。我个人习惯从kernel.org下载稳定版,或者直接用发行版自带的源码包。
# 下载内核源码(以5.10 LTS为例)
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.100.tar.xz
tar -xf linux-5.10.100.tar.xz
cd linux-5.10.100
小技巧:如果你用的是Ubuntu,可以直接安装linux-source包,省去下载时间。但注意版本可能不是最新的。
1.3.2 配置内核
配置内核是编译前的关键步骤。你可以用当前系统的配置作为起点:
# 使用当前系统的配置
cp /boot/config-$(uname -r) .config
# 或者生成默认配置
make defconfig
这里有个坑:如果你直接make defconfig,可能会缺少你需要的驱动选项。我曾经编译完才发现网卡驱动没选上,又得重来一遍。建议用当前系统的配置,然后手动调整。
1.3.3 编译内核
编译内核需要一点时间,取决于你的CPU核心数。我建议用-j参数加速:
# 编译内核和模块
make -j$(nproc) bzImage
make -j$(nproc) modules
编译完成后,你会得到arch/x86/boot/bzImage这个内核镜像文件。模块则分散在各个目录下。
注意:编译内核时,确保你有足够的磁盘空间(至少10GB)。我遇到过磁盘写满导致编译失败的情况,很尴尬。
1.3.4 编写第一个驱动模块
环境搭好了,我们来写一个最简单的驱动——hello world。这不是花架子,它能验证你的开发环境是否正常。
// hello.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init hello_init(void)
{
printk(KERN_INFO "Hello, Driver World!\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye, Driver World!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple hello world driver");
对应的Makefile:
obj-m += hello.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
编译并加载:
make
sudo insmod hello.ko
sudo rmmod hello.ko
dmesg | tail
如果看到「Hello, Driver World!」和「Goodbye, Driver World!」,恭喜你,环境搭建成功了。
1.4 本章知识体系
为了让你更直观地理解本章内容,我画了一张图。它展示了驱动在整个系统中的位置,以及三类设备的关系。
这张图很清楚地展示了:应用程序通过系统调用进入内核,内核再通过驱动访问硬件。驱动就是中间那一层「胶水」。三类设备在驱动层各有不同的实现方式,但最终都服务于上层应用。
本章要点回顾:
- 驱动是操作系统与硬件之间的桥梁
- 字符设备按字节流访问,块设备按块访问,网络设备通过socket通信
- 搭建开发环境需要:内核源码 + 配置 + 编译 + 模块测试
- 第一个驱动模块「hello world」能验证环境是否正常
嗯,第一章的内容就到这里。环境搭好了,后面我们才能动手写真正的驱动。下一章我们会深入字符设备驱动的实现细节,到时候会手把手带你写一个完整的驱动。
想要更多资料,联系可可