platform设备,驱动梳理
platform device driver梳理
这边对linux内核里面的结构体定义做一个梳理:
- struct platform_device
- struct device
- struct platform_driver
- struct device_driver
platform_xxx的定义都在include/linux/platform_device.h
device,device_driver的定义都在include/linux/device.h


Linux里面的继承和多态
结构体成员变量一览
设备
基类:struct device
struct device { |
区别platform_data, driver_data

子类:struct platform_device
struct platform_device { |
驱动
基类:struct device_driver
struct device_driver { |
子类:struct platform_driver
struct platform_driver { |
内核中变量缩写命名
遵循一套非常严谨的命名规范
1. 核心对象缩写 (名词)
这些缩写通常代表结构体的实例指针。
dev(Device): 指向struct device的指针。它是所有设备的基类。pdev(Platform Device): 指向struct platform_device的指针。drv(Driver): 指向struct device_driver的指针。pdrv(Platform Driver): 指向struct platform_driver的指针。node/np(Node Pointer): 通常指struct device_node *,即设备树中的一个节点指针。privPrivate Data”(私有数据- 内核的设备类型platform_device是通用的,所以只定义了设备的共性,但是你的驱动中,常常要记录一些特有信息,比如GPIO编号,动态申请的内存地址,当前设备的运行状态,互斥锁,自旋锁
- 我们驱动里面使用的platform_device变量都是通用的。所以我们只能自己在驱动中开辟一块内存,然后自己创建设备描述结构体。然后把他们绑定到我们创建使用的通用的platform_device上(里面的指针指向我们这一块内存)
- 你不能修改内核定义的全局结构体,只能拿一个私有数据的指针,指向你驱动里面的开辟的全局内存。


关于内核申请的内存,其实是在空闲列表里面申请的,这个xv6里面搞清楚了

2. 资源管理前缀 (动词/特性)
devm_ (Device Managed)
这是 Linux 内核的“自动垃圾回收”机制。
含义:带有
devm_前缀的函数(如devm_kzalloc,devm_gpio_request)申请的资源,会挂在struct device内部的devres_head链表上。作用:当驱动卸载(执行
remove)或probe 失败时,内核会自动释放这些资源。你不需要在 remove 函数里手动写一堆 kfree 或 gpio_free。
of_ (Open Firmware)
含义:所有处理设备树 (Device Tree) 相关的函数都以
of_开头。例子:of_get_named_gpio(从设备树获取 GPIO 编号),of_property_read_u32(读取设备树里的数字属性)。
3. 常见缩写
所以看到pdev->dev, 就是获取设备的父类对象
gpiod和之前的gpio子系统



linux内核目录结构一览
linux-src/ |
platform_bus是唯一的全局变量
在 Linux 内核中,platform_bus_type 确实是一个全局唯一的 struct bus_type 实例
根据上面的目录结构分类,就很容易找到他的全局变量创建在drivers/base/platform.c中
struct bus_type platform_bus_type = { |
关于/sys, /proc和驱动的关联
/sys /proc是什么
/proc 是历史遗留的“大杂烩”,而 /sys 是伴随 Linux 设备模型(LDM)诞生的“现代化设备博物馆”
这两个目录下面都是挂载的虚拟文件系统
/proc (Process Information Pseudo-file System)
初衷:最早在 Unix 时代,
/proc仅仅是为了向用户空间导出进程(Process信息而创建的(比如现在的 /proc/1 就是 init 进程的状态)。演变(失控):随着 Linux 发展,内核开发者发现从内核向用户层传递数据太麻烦了,而 /proc 刚好是个现成的通道。于是,大家开始往里面塞各种与进程无关的东西:
内存信息(meminfo)、中断(interrupts)、命令行参数(cmdline),甚至包括你提到的设备树(device-tree)。缺点:毫无结构可言,格式混乱。有些文件里面是一大坨文本,用户层写脚本去解析这些文本非常痛苦(比如用 awk/grep 抓取特定字段)。
/proc 的常见目录
/proc/"pid"/:各个进程的状态(老本行)。/proc/interrupts:系统的中断统计。/proc/device-tree/:设备树节点(实际上在现代内核中,它通常是一个指向/sys/firmware/devicetree/base的符号链接)。
/sys (Sysfs)
初衷:在 Linux 2.6 引入设备模型(LDM)时,内核鼻祖 Linus 和其他维护者受够了 /proc 的混乱。他们决定专门为设备、总线、驱动设计一个新的文件系统,这就是
sysfs。核心规则:严格的层次结构和一个文件只干一件事(只包含一个值的原则。比如,你想看设备的电源状态,在 sysfs 里就是一个单独的文件,里面只有 0 或 1,不需要任何解析。
本质:
sysfs就是内核中kobject(内核对象的基类)树状拓扑结构在用户空间的直接映射。
/sys 的核心目录(LDM(linux设备模型) 的三维立体图)
/sys/devices/:- 族谱视角, 全局设备树最真实的物理连接图。这是系统所有物理设备的真实拓扑结构(谁挂在谁下面,一清二楚)。
/sys/bus/:- 相亲视角, 总线match视角。包含 platform、i2c、usb 等。目录下的 devices 和 drivers 实际上是指向 /sys/devices/ 的符号链接(这也是为什么你能在platform 下找到你创建的设备)。
- 在
/sys/bus/platform/下,你可以清楚地查账哪些设备挂在 platform 总线上?哪些驱动已经就绪?谁和谁配对成功了
/sys/class/:- 职业视角。不管你是通过什么总线接入的,只要你是 LED,就归类到
/sys/class/leds/;是网卡,就归类到/sys/class/net/。
- 职业视角。不管你是通过什么总线接入的,只要你是 LED,就归类到
/sys/module/:系统加载的所有内核模块(ko文件)的状态。
我如何在驱动中创建/sys下的一些东西
在 sysfs 中,有一个极其优雅的设计哲学:
目录代表“对象” (kobject),而文件代表对象的“属性” (attribute)。
- 在你的驱动里,你只需要提供两个函数(读和写),内核就会自动帮你把它们映射成 /sys 下的一个普通文本文件。
- 用户层只需使用最基础的
cat和echo命令,就能跨越内核边界,直接调用你写的 C 函数!
sysfs源码目录
sysfs这些属于VFS虚拟文件系统,所以
- Sysfs 核心机制:
fs/sysfs/- sysfs 文件系统的挂载、文件读写回调的底层实现。
- Kobject (sysfs的灵魂)
lib/kobject.c- 重点!kobject 负责维护引用计数,它的每一次注册(kobject_add)都会在 sysfs 里生成一个目录。
- 与设备模型的桥梁:
drivers/base/core.cdrivers/base/class.c- 这里包含了
class_create等函数,它们内部会调用kobject_add和sysfs 的 API,最终在 /sys/ 下建出你看到的结构。
- 这里包含了
疑问:class,device是为了在sysfs里面创建节点,那么申请设备号,注册设备的作用是什么?
在linux的内核设备-驱动-总线模型中
- 我们用dts来描述设备信息(属于总线层),
- 最终得到
platform_device - 作用,是描述硬件资源(寄存器地址、中断号),此时,内核已经知道硬件的存在了
但用户空间的程序(如你的 C 语言 App)还看不见它,没有接口
- 最终得到
- 注册字符设备/申请设备号(属于文件系统层)
- 目的:在
/dev/目录下生成一个“文件节点” Linux 的哲学是“万物皆文件”。用户 App 想要控制硬件,必须通过 open(“/dev/myled”, …)
- 申请设备号, 注册 cdev,就是为了告诉内核:“当有人
访问这个 /dev/文件时,请跳转到我驱动里写的read/write/ioctl函数去
- 目的:在
现代驱动开发的不同
上面你还要自己手动让内核把用户层的访问,路由到驱动
在现代驱动开发中,我们确实在减少直接操作设备号
- 子系统封装(如 LED 子系统):
- 如果你写的是标准的
leds-gpio驱动,你不需要申请设备号,也不需要 cdev_add - 因为 LED 子系统框架(
led-class.c)已经提前申请好了主设备号,并统一管理了/sys/class/leds/。你只需要调led_classdev_register,框架就会自动帮你搞定一切
- 如果你写的是标准的
对比
