-
大管家: struct keyinput_dev
{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
struct timer_list timer; /* 定义一个定时器*/
struct irq_keydesc *irqkeydesc; /* 按键描述数组 */
unsigned char curkeynum; /* 当前的按键号 */
int key_nums; /* 账本:记录设备树里实际配置的按键数量 */
struct input_dev *inputdev; /* input结构体 */
};
- dev_t dev_id(门牌号 ):主次设备号。就是一个**,** 本质上就是一个
u32类型的数字。高 12 位是主设备号,低 20 位是次设备号。它仅仅是个数字,用来在内核登记备案。 - struct cdev cdev(灵魂/行为):内核里代表"字符设备"的核心结构体。它里面最重要的成员是
file_operations(函数指针集)。应用层调用read/write时,内核就是通过它找到你的 C 语言驱动函数的。 - struct class *class(家族/分类):高层管理概念。比如创建一个叫
"my_led_class"的类,在系统的/sys/class/目录下就会多一个文件夹。它是为了批量管理属于同一类的设备。 struct device *device(窗户/文件):它是类(class)的孩子。它的诞生会直接触发内核在/dev/目录下生成用户可见的设备文件 (比如/dev/led)。应用层就是通过这个窗户和驱动说话的。struct device_node *nd(硬件图纸) :设备树专属结构体。它不参与上面的任何软件分配,它唯一的任务就是精准指向设备树(.dts)里的那个节点,供你解析 GPIO 编号、中断号等硬件资源。
- dev_t dev_id(门牌号 ):主次设备号。就是一个**,** 本质上就是一个
-
小管家: struct irq_keydesc
{
int gpio; /* gpio */
int irqnum; /* 中断号 */
unsigned char value; /* 按键对应的键值 */
char name10; /* 名字 */
struct keyinput_dev *back_dev; /* 【关键】反向指针,指向它的大管家 */
// irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};
-
gpio:"我负责盯着哪根硬件引脚?(比如 GPIO1_IO18)" -
irqnum:"如果有人拍这根引脚,我该通过哪个特定的电话线(中断号)向大管家汇报?" -
value:"如果确认这个人真的按下了,我该给 Input 大集团上报什么数字键值?(比如上报KEY_0还是KEY_ENTER?)" -
name:"我的工牌名字叫什么?(比如"KEY0",申请中断时给内核看的)" -
handler:"当我的电话响了(中断触发),我该用哪个具体的动作(中断服务函数指针)去接电话?",
-
struct inode ------ 停在车库里的"物理实体车"
-
比喻 :它是车库里那辆实实在在、有车架号、有固定排量的真车(比如一辆具体的五菱宏光)。
-
特点 :不管有没有人来租,这辆车都铁打不动地停在车库里。在整个系统里,一辆物理车(一个文件)有且仅有一个
inode结构体。 -
struct file (filp)------ 客户签下的"租车合同"-
比喻 :当一个客户(应用层进程)跑来调用
open()说"我要租车"时,前台营业员不会 把整辆车直接塞进客户的口袋,而是会当场打印一份"租车合同",这就是filp。 -
特点:
-
如果有 3 个客户同时
open了同一个设备文件,车库里依然只有一辆车(1个inode),但前台会打印出 3 份独立的合同(3个filp)。 -
合同(
filp)里记录的是只有这次租车才关心的动态信息 :比如你开到哪了(f_pos读写偏移量)、你是只读还是读写(f_flags权限)、以及合同特有的备注栏(private_data)。
-
-
-
struct inode和struct device区别和联系:在 Linux 内核中,
struct inode和struct device都是极其核心的底层结构体,但它们属于完全不同的两个管理维度。简单来说:
inode属于"文件系统维度"(软件抽象),而device属于"设备驱动模型维度"(硬件抽象)。 为了让你彻底搞懂它们的关系,我们先看两者的核心区别,再看它们在字符设备驱动中是如何交汇连接的。-
核心区别:它们各自管什么?
-
struct device------ 驱动模型的"硬件护照"-
所属领域:设备驱动模型(Linux Device Model)。
-
它是什么 :它是内核用来抽象、拓扑物理硬件 的基类。无论是底层的 GPIO 控制器、I2C 总线,还是你通过平台总线注册的按键(
pdev->dev),在内核眼里都是一个struct device。 -
核心职责 :建立内核的硬件树状拓扑结构(展示在
/sys/devices/体系中)。它记录了硬件的总线类型(bus)、电源管理状态(power)、父设备(parent)、以及驱动私有数据(driver_data)。 -
生命周期 :由设备总线(Platform/I2C/SPI 等)或驱动框架管理。当你在
probe阶段或通过device_create()注册硬件时,它才在内存中被创建。
-
-
struct inode------ 文件系统的"档案实体"-
所属领域:VFS(虚拟文件系统)。
-
它是什么 :它是磁盘或内存文件系统里,某个文件/节点的物理唯一标识 。在 Linux "一切皆文件"的哲学下,每个文件(无论是普通文本、目录,还是
/dev/key这样的设备文件)在内核中都有且仅有一个struct inode。 -
核心职责 :记录文件的元数据。比如:文件大小、访问权限、所有者、修改时间、以及该文件对应的主次设备号(
dev_t i_rdev)和字符设备指针(struct cdev *i_cdev)。 -
生命周期 :由文件系统管理。当你在
/dev/下看到一个节点,哪怕没有任何驱动绑定它,它的inode也已经存在于文件系统中了。
-
-
-
核心联系:它们在何处交汇?
虽然它们一个管"文件",一个管"硬件",但在字符设备驱动(或者是你正在写的 Input 子系统驱动)中,它们会经历一场完美的接力与交汇。
我们通过你在应用层执行
open("/dev/input/event1")时的底层流动,来看它们是如何发生联系的:-
桥梁一:设备号(
dev_t)这是它们产生联系的纽带。-
硬件端(
struct device) :你在probe阶段调用device_create()或者input_register_device()时,内核会为你分配一个主次设备号,并把这个设备号打上struct device的标签。 -
软件端(
struct inode) :内核的udev或mdev守护进程感知到新硬件加入后,会在/dev/下创建一个对应的设备文件。这个文件的inode->i_rdev里,就死死地写入了和硬件端一模一样的设备号。
-
-
桥梁二:通用
open的寻路过程。当应用层通过打开文件找到硬件大管家时,两条线彻底拧成一股绳:-
第一步(通过
inode抓到 cdev) : 应用层调用open("/dev/input/event1")。内核顺着路径找到该文件的struct inode。内核看了一眼inode->i_cdev,噢!它指向了你之前在probe里注册的字符设备(cdev)。 -
第二步(通过 cdev 跨界到
device) : 在标准驱动中,你的大管家结构体通常把cdev和struct device *封装在一起(或者在 Input 子系统里,evdev结构体同时关联了cdev和struct device)。 -
第三步(落脚到
device抽屉) : 通用open函数通过container_of从inode->i_cdev捞出大管家,再从大管家里拿到struct device,最终调用dev_get_drvdata()或者input_get_drvdata()提取出你寄存的内存指针,塞进filp->private_data。
-
-
-
-
struct device_node和struct device关系:
.dts 文件(你写的设备树源码)
↓ 编译
.dtb(二进制设备树)
↓ 内核启动时解析
struct device_node(内存中的树状结构,描述硬件"长什么样")
↓ 驱动 match & probe 成功后
struct device(驱动模型中的设备对象,代表"这个硬件正在被管理")