Linux设备模型(不涉及文件系统):
包括以下对象kref, kobject, subsystem, device,driver,bus_type
Kernel编译后,生成几个全局的subsystem对象,包括classes_subsys, bus_subsys, devices_subsys, 这些是顶层子系统,其他所
有总线注册,设备注册,驱动注册都挂靠在这几个子系统下
设备和驱动的注册都是在某一条总线上,所以我们首先分析总线结构,以下结构只跟踪结构中相关的特性。
struct bus_type {
const char *name; 总线名称,总线对象的名字就被设定为该名字
struct subsystem subsys; 总线也被认为是一种子系统,所以它继承子系统的特性
struct kset devices; 是挂靠在该总线下设备对象的集合
struct kset drivers; 是挂靠在该总线下驱动对象的集合
struct klist klist_devices; 该总线下设备节点的knode_bus指向此字段
struct klist klist_drivers; 该总线下驱动节点的knode_bus指向此字段
在bus下加驱动及设备的时候,做匹配,通常比较驱动及设备的名字是否一致
int (*match)(struct device *dev, struct device_driver *drv);
};
和总线相关的函数主要有4个:
int bus_register(struct bus_type *bus); 注册总线的时候,通常结构字段里面要对name和match赋初值,
以下是总线注册的流程
(1) bus->subsys.kset.kobj.kset = bus_subsys.kset 在bus_subsys下注册
(2) 注册2个kset,包括devices和drivers,使
bus->devices.subsys=&bus->subsys;
bus->devices.kobj.parent = &bus->subsys.kset.kobj;
(3) 初始化klist_devices和klist_drivers
注册完总线后,可以看到,相关字段devices,drivers,klist_devices,klist_drivers链表都还是空
接下来,我们开始分析驱动程序的注册
驱动程序注册的目的是将其注册在某一条总线下,总线的klist_drivers下含有该驱动节点,所以在注册驱动的时候,要指定注册在
哪条总线下,同时,驱动程序应该可以对某一类设备通用,所以需要有一个链表,链接所有使用这个驱动的设备klist_devices
struct device_driver {
const char * name; 驱动名
struct bus_type * bus; 驱动属于哪条总线
struct kobject kobj; 驱动对象
struct klist klist_devices; 使用该驱动的设备链表头
struct klist_node knode_bus; 指向总线下的klist_drivers
驱动程序通用函数,注册的驱动可以重载这些函数
int (*probe) (struct device * dev);
int (*remove) (struct device * dev);
void (*shutdown) (struct device * dev);
int (*suspend) (struct device * dev, pm_message_t state);
int (*resume) (struct device * dev);
};
驱动注册,主要就是将驱动加到某条总线下,并且更新相关字段
(1) 首先将驱动对象的kset指向所属总线下的drivers
(2) 轮寻所属总线下的所有设备,对那些还没有指向驱动的设备,用bus下的match函数进行匹配,如果设备名和驱动名相同,则
将设备的驱动指针指向待添加的驱动程序,之后调用驱动程序的probe函数,完成封装device_drivers的驱动程序实例的初始化工作
,所以,在用户编写完驱动,调用module_init时候,该函数参数一般都是驱动程序的注册,会调用驱动程序的probe函数,之后会将
找到的设备的knode_driver添加进入注册的驱动程序的klist_devices列表
(3) 将驱动节点加入bus->klist_drivers,
(4) klist_add_tail(&drv->knode_bus, &bus->klist_drivers);
驱动的注销就是驱动注册的反安装
设备的注册:
首先要指定总线id,因为设备也是注册在某条总线下,
struct device {
struct klist klist_children; 链接所有子设备
struct klist_node knode_parent; 对父设备的子节点
struct klist_node knode_driver; 链接到某驱动的klist_devices下
struct klist_node knode_bus; 设备节点,总线链表是klist_devices
struct device * parent; 父设备
struct kobject kobj; 设备对象
char bus_id[BUS_ID_SIZE]; 注册前指点
struct bus_type * bus; 所属总线
struct device_driver *driver; 所属驱动,第一次没有,在probe下赋值
void *driver_data; 可以由上层传入一些数据,在底层进行交换,编程的方法
void *platform_data 同理
};
设备使用前要注册,调用device_register
(1) 将设备挂接在devices_subsys下,所有设备都挂在其下
(2) 用设备总线id给设备对象赋值
(3) 如果设备的驱动指针已经有值,则绑定dev->knode_driver到dev->driver->klist_devices
(4) 如果设备有驱动指针,则不会调用Probe
(5) 如果初次设备没有指定驱动程序,则所属总线会轮寻总线下所有驱动,调用驱动指向的总线下的Match函数,匹配名字字符
串相同的,如果找到,将这个驱动赋给设备的驱动指针,执行驱动的probe,绑定dev->knode_driver到dev->driver->klist_devices
(6) klist_add_tail(&dev->knode_bus, &bus->klist_devices);
在设计驱动模型的时候,主要要考虑抽象模型之间的关系。比如,Kobject是最基本的对象,在其中包含引用计数,对象名,链表节
点等基本信息,并且在Kobject层维护自身,
然后肯定是要在系统中,设定几个全局的变量,所以,定义了子系统的概念,,子系统有kset,可以将不同的对象链接在链表中,有
了全局的子系统的根,其他对象才能链接上去
在抽象模型中,总线是第一级,其他的设备和驱动,都挂接在某条总线下,设备对象是最下层的概念,设备挂接在devices_subsys和
总线下还有作为驱动的一个节点,驱动挂接在总线下,同时提供链表链接了符合该驱动的所有设备,这些都符合现实中的事物特征,
有了这些分析以后,就可以通过对象的注册方式,将不同的对象链接在一个系统中,
记住,系统中,肯定会有全局的对象,作为连接整个系统的根,在驱动模型中就是devices_subsys,bus_subsys,classes_subsys,
再例如,混杂设备,misc_list就是一个全局的变量,通过它其他混杂设备被连接在一起。