2021.07.25 技術シェア
【技术浅谈】Linux设备模型(下)

简介


Linux设备模型是指Linux系统中设备、总线、驱动的系统结构抽象,它的意义在于为了系统地管理所有设备。


整个linux的设备模型是一个面向对象(Object Oriented)的体系结构。由于在linux底层是用面向过程的C语言实现,所以在linux中定义了kobject结构,使其嵌入到对象结构中来实现继承的效果,这个kobject属于最基础的结构,它在作用上相当于面向对象中的基类,由多个kobject对象组成的集合便成为了kset,也可以说是对象的容器。任何一个设备模型如总线,设备,驱动都属于一个kobject 。


kobjects与ksets实现层次树的底层骨架,进一步地,通过封装这些底层结构来实现上层的设备驱动模型。这就是我们要学习的总线、设备和驱动。在linux内核中,它们对应的是bus_type、device和device_driver数据结构。所以,这些数据结构如何联系及工作是我们学习的重点。


设备模型的简易架构图如下:


图片


 

学习步骤


⑴学习底层数据结构:kobject、kset


⑵学习linux设备模型层次关系:bus_type、device、device_driver


⑶简单实例分析:设备驱动注册源码的简单分析


⑷应用分析:面向对象的思想在linux设备模型中的应用分析


 

设备模型组件

#1 devices


系统中的任一设备在设备模型中都由一个device对象描述,其对应的数据结构struct device的定义为:


struct device {
struct list_head g_list;    //全局设备链表    
struct list_head node;    //兄弟对象链表
struct list_head bus_list;    //总线设备链表
struct list_head driver_list;    //驱动设备链表
struct device *parent;    //指向父设备
struct kobject kobj;
char bus_id[BUS_ID_SIZE];
struct bus_type *bus;    //指向设备所连接的总线
struct device_driver *driver;    //指向驱动程序对象
void *driver_data;    
/* Several fields omitted */
};
g_list将该device对象挂接到全局设备链表中,所有的device对象都包含在devices_subsys中,并组织成层次结构。Node域将该对象挂接到其兄弟对象的链表中,而bus_list则用于将连接到相同总线上的设备组织成链表,driver_list则将同一驱动程序管理的所有设备组织为链表。


 


此外,children域指向该device对象子对象链表头,parent域则指向父对象。device对象还内嵌一个kobject对象,用于引用计数管理并通过它实现设备层次结构。driver域指向管理该设备的驱动程序对象,而driver_data则是提供给驱动程序的数据。bus域描述设备所连接的总线类型。


内核提供了相应的函数用于操作device对象。其中device_register()函数将一个新的device对象插入设备模型,并自动在/sys/devices下创建一个对应的目录。device_unregister()完成相反的操作,注销设备对象。get_device()和put_device()分别增加与减少设备对象的引用计数。通常device结构不单独使用,而是包含在更大的结构中作为一个子结构使用,比如描述PCI设备的struct pci_dev,其中的dev域就是一个device对象。


 

#2 drivers


系统中的每个驱动程序由一个device_driver对象描述,对应的数据结构定义为:       


struct device_driver {


char *name;    //设备驱动程序的名称
struct bus_type *bus;    //该驱动所管理的设备挂接的总线类型
struct kobject kobj;    //内嵌kobject对象
struct list_head devices;    //该驱动所管理的设备链表头
int (*probe)(struct device *dev);


//指向设备探测函数,用于探测设备是否可以被该驱动程序管理
int (*remove)(struct device *dev);    //用于删除设备的函数
/* some fields omitted*/


};


与device 结构类似,device_driver对象依靠内嵌的kobject对象实现引用计数管理和层次结构组织。内核提供类似的函数用于操作device_driver对象,如get_driver()增加引用计数,driver_register()用于向设备模型插入新的driver对象,同时在sysfs文件系统中创建对应的目录。device_driver()结构还包括几个函数,用于处理热拔插、即插即用和电源管理事件。


 

#3 buses


系统中总线由struct bus_type描述,定义为:
struct bus_type {
char *name;    //总线类型的名称
struct subsystem subsys;    //与该总线相关的subsystem
struct kset drivers;    //所有与该总线相关的驱动程序集合
struct kset devices;    //所有挂接在该总线上的设备集合
struct bus_attribute * bus_attrs;    //总线属性
struct device_attribute * dev_attrs;    //设备属性
struct driver_attribute * drv_attrs;    //驱动程序属性
int (*match)(struct device * dev, struct device_driver * drv);
int (*hotplug) (struct device *dev, char **envp, int num_envp, 


char *buffer, int buffer_size);


int (*suspend)(struct device * dev, u32 state);
int (*resume)(struct device * dev);


};


每个bus_type对象都内嵌一个subsystem对象,bus_subsys对象管理系统中所有总线类型的subsystem对象。每个bus_type对象都对应/sys/bus目录下的一个子目录,如PCI总线类型对应于/sys/bus/pci。


在每个这样的目录下都存在两个子目录:devices和drivers(分别对应于bus_type结构中的devices和drivers域)。其中devices子目录描述连接在该总线上的所有设备,而drivers目录则描述与该总线关联的所有驱动程序。与device_driver对象类似,bus_type结构还包含几个函数(match()、hotplug()等)处理相应的热插拔、即插即拔和电源管理事件。


 

#4 classes


系统中的设备类由struct class描述,表示某一类设备。所有的class对象都属于class_subsys子系统,对应于sysfs文件系统中的/sys/class目录。每个class对象包括一个class_device链表,每个class_device对象表示一个逻辑设备,并通过struct class_device中的dev域(一个指向struct device的指针)关联一个物理设备。这样,一个逻辑设备总是对应于一个物理设备,但是一个物理设备却可能对应于多个逻辑设备。此外,class结构中还包括用于处理热插拔、即插即拔和电源管理事件的函数,这与device对象和driver对象相似。


 

设备模型组件


接下来,用一个简单的PCI设备驱动注册的实例来理解linux设备模型的思想。


 

#1 PCI模型关键函数


⑴设备注册函数


⑵驱动注册函数


 

#2 PCI设备模型工作原理


当一个PCI设备被发现,PCI核心在内存中创建一个struct pci_dev类型的新变量。这个设备的总线特定的成员被PCI核心初始化(devfn,vendor,device,和其他成员),并且struct device变量的parent变量被设置为PCI 总线设备(注意总线也不仅有一个bus_type结构,还对应一个设备device)bus变量被设置指向pci_bus_type 结构。接下来name和bus_id 变量被设置,根据读自PCI设备的name和ID。


 


在PCI设备结构被初始化之后,pci设备被注册到驱动核心,调用device_register(&dev->dev);在device_register函数中,kobject被注册到驱动核心,pci设备被添加到pci总线的设备列表中,热拔插事件产生,同时kobject被添加到parent的链表中,sysfs入口也被添加。


 


PCI设备的发现是通过特定代码探测PCI空间来实现的。PCI设备由内核自动生成    的。这样在注册pci驱动的时候PCI设备已经注册,其属性如ID的信息都已经是被初始化好了。


⑴设备注册时函数调用顺序:


①device_register(dev);


②device_add(dev);


③bus_attach_device(dev);


④device_bind_driver(dev);


⑵驱动注册时函数调用顺序:


①driver_register(drv);


②bus_add_driver(drv);


③driver_attach(drv);


④driver_attach(dev,drv);


⑤driver_probe_device(drv, dev);


 


以上,就是PCI设备模型的大致工作原理,对于设备和驱动匹配流程的分析的较为简洁,对这部分的学习可以参考PCI模型关键函数中的代码来理解。


 


学习Linux设备模型对于我们今后的驱动开发有很大的帮助,它让我们不仅仅会改代码,而且会写自己的代码。这篇设备模型的学习总结只是起到了一个抛砖引玉的作用,若要深刻的理解设备模型的实现,还要在今后的学习中不断地学习和完善。


 


图片