在一份标准 Linux 驱动文件中到处都是结构体,这是一种很工程味的设计哲学。Linux 内核将一切都当对象处理,只不过把 C++ 的 class 换成了 C 的 struct。于是驱动开发看起来就像在拼装一堆对象:谁属于谁、谁调用谁、谁管理谁。今天我们把这几个常见的结构体放在一张地图里理解。
我们先从用户空间开始想象一条路径
用户程序
-> /dev/xxx 设备文件
-> 内核字符设备
-> 驱动提供的操作函数
-> 具体硬件(gpio、I2C等)
中间这些结构体,就是这条路径的关键节点。
1、file_operations ———— 驱动的操作说明书
这是驱动最核心的结构体之一,它本质上是一张函数表,告诉内核如果用户对这个设备执行 open/read/write,该调用什么函数。
1 | struct file_operations { |
2、cdev ———— 字符设备对象
Linux 需要知道,这个驱动是一个字符设备,和其对应的 file_operations。这件事由 cdev 结构体管理:
1 | struct cdev { |
其注册过程通常是:
cdev_init(&led.cdev, &gpioled_fops);
cdev_add(&led.cdev, devid, 1);
两句话先将 gpioled_fops 绑定到 cdev,对应的是设备号为 devid 的设备。
3、class ———— 设备类别
Linux 设备模型中有一个很重要的层级
/sys/class
例如
/sys/class/leds
/sys/class/input
/sys/class/net
它的创建方式为
class_create(THIS_MODULE, "gpioled");
主要作用是:1、在 /sys/class/ 创建目录。2、给 udev 提供设备信息。3、自动创建设备节点。
4、device ———— 具体设备对象
class 是类别,device 是具体设备。
device_create(class, NULL, devid, NULL, "gpioled");
运行完这一句后,系统会生成 /dev/gpioled,同时还会在 /sys/class/gpioled/gpioled 生成对应设备。简单理解:class是分类,device 是实例。类似在 USB 的 class 下有 /dev/ttyUSB0 的设备。
5、device_node ———— 设备树节点
这个结构体来自设备树系统,驱动会这样找节点:
nd = of_find_node_by_path("/test");
其作用只有一个:读取设备树中的硬件信息,例如:
test {
gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
};
在驱动中读取后,驱动就知道 LED 接在 GPIO1_IO00。可以说,device_node就是硬件配置数据。
最终,我们把所有的结构体连起来看,真正的关系应该如下
用户程序
│
│ open/read/write
▼
file_operations
│
▼
cdev
│
▼
设备号(dev_t)
│
▼
device
│
▼
class
在设备模型中 class 是 device 的父对象。
而硬件信息来自另一条链:
设备树(dts)
│
▼
device_node
│
▼
GPIO/I2C/SPI 等资源
可以这么分类
硬件信息 → device_node
驱动操作 → file_operations
字符设备管理 → cdev
设备模型 → device
设备分类 → class
用户空间
│
▼
/dev/gpioled
│
▼
device
│
▼
class
│
▼
cdev
│
▼
file_operations
│
▼
驱动代码
│
▼
GPIO硬件