ldd随笔(1)-linux设备模型
一下只是个人学习后的理解,可能有很多不对的地方。
要学习linux的设备驱动模型,首先必须要知道kobject和kset的概念,下面是kobject在2.6.38的源码中的实现。
struct kobject { const char *name; //名称,可能在sysfs中创建对应的目录 struct list_head entry; //标准链表,用于被kset连接起来 struct kobject *parent; //指向父kobject的指针 struct kset *kset; //指向所属的kset的指针 struct kobj_type *ktype; //包含的kobj_type, 用指向不同的ktype来表示不同的object struct sysfs_dirent *sd; //在sysfs中创建目录时用到的结构 struct kref kref; //引用计数 unsigned int state_initialized:1; unsigned int state_in_sysfs:1; unsigned int state_add_uevent_sent:1; unsigned int state_remove_uevent_sent:1; unsigned int uevent_suppress:1; };
kobject里面比较重要的一个是kobj_type结构的指针:
struct kobj_type { void (*release)(struct kobject *kobj); //当此kobj被完全释放(引用为0)时调用 const struct sysfs_ops *sysfs_ops; //与sysfs相关的操作 struct attribute **default_attrs; //代表了一系列属性。 const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj); const void *(*namespace)(struct kobject *kobj); };
kobject中的ktype指向不同的kobj_type可以代表着不同的类型,以及所执行的不同操作。kobject可能在sysfs中表现为一个目录,而它指向的kobj_type中包含的一系列struct attribute则代表着这个目录下的文件。每一个属性对应了一个文件,可以设置它的读写权限,名称(文件名)还有就是当读或者写这些文件时,调用的show和store函数。可以利用这些属性文件,进行信息的展示和配置的修改等。
kobject只是代表着某个结点,并不能构成特定的结构,所以还存在一个kset,使得它们可以构成一个树状结构。kset的实现如下:
struct kset { struct list_head list; //链表头,用以将下层的kobject串起来 spinlock_t list_lock; //链表的锁,只能串行操作 struct kobject kobj; //kset包含一个kobject const struct kset_uevent_ops *uevent_ops; //为了支持热插拔所提供的结构 };
kset里面包含了一个kobject,而不止是一个指针。所以可以把kset看成是一个kobject的扩展,所有对kobject的操作,都适用与kset(操作其中的kobject,但是内核为kobject和kset分别提供了一套类似的操作)。如果用C++表述,kobject就是一个基类,而kset是它的派生类。kset可以通过它的list包含住很多子kobject,而这些子kobject又可以通过parent和kset指针指向它的父kobject(kset);并且kset也是(包含了)一个kobject,所以它还可以被其它的kset当做子kobject。这样就构成了一个树状结构,其中kobject是叶子结点,而kset是非叶子结点或者根结点,正好和目录与文件的关系类似。目录本身是一个文件,但是它可以包含其它文件或目录,同时又被其它目录含(根目录除外)。所以sysfs中的文件结构通常对应了内核中的kobject结构。每一个目录代表一个kobject(或者kset),而此目录里面的子目录又代表了这个kobject下面的子kobject,目录里的文件代表了kobject的一系列属性。
而kset_uevent_ops(对应老版内核中的hotplug,热插拔)指向了一组操作,包括过滤,热插拔等,当往一个kset中添加新的kobject时,便会触发这些操作,首先会调用filter表示是否过滤这个事件,然后在调用热插拔处理函数来向环境变量里添加值,从而达到通知用户空间的目的。假如次kset没有设置这个uevent_ops指针,便会往它的父kobject迭代查找,直到找到为止(若到根kobject都没有,就忽略)。再进行调用。
kobject通常不单独使用,而是嵌入到其他结构,代表就是pci core和usb core,它们是内核提供的,利用kobject来构造的树状结构驱动框架。
linux内核利用以上的框架,包装出了三个概念,那就是总线,设备,和驱动。总线可以看成是树状结构中的非叶子结点(包括根结点),设备和驱动可以看成其中的叶子结点;当有设备插入,便可利用上面uevent_ops的机制,添加环境变量,然后调用udev(hotplug)来判断插入设备的类型,然后根据类型查找modules.***map表,找到合适的驱动程序,加载其入内核;当驱动程序加载到内核的时候,同样会想总线注册,也会激活uevent_ops的操作,这时系统会对设备和驱动进行匹配,若匹配正确,就会调用驱动程序里的probe函数。当设备取下时,系统会调用驱动程序的remove函数。这样就可以支持热插拔。
linux内核还提供了usb和pci等总线的抽象,它们是在bus,device和driver之上进行进一步的包装。内核已经把总线和设备的部分完成了,驱动作者只需要关注驱动程序的实现即可。例如,linux内核提供的usb core已经完成了usb的大部分工作,包括总线驱动,以及usb框架,还有设备的识别和初始化等,以及设备和驱动之间进行通信的urb方式,甚至还包含了大部分设备的驱动程序,但是有的设备驱动内核并没有。我们会在windows经常看到一种情况,某个设备被识别了,但是没有驱动程序而不能被使用,其中识别出设备就是内核所完成的,而这样的驱动往往就必须由设备提供者自己实现了。总之在内核抽象出一套驱动框架之上,驱动作者可以免去很多的工作。