ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

bus_register源码研究

bus_register源码研究 kobject和kset学习bus前需要先了解kobj和ksetkobject和sysfs的关系可以看作是数据模型和视图的关系无论有没有sysfs,kobjects们的关系在内核中还是存在的,但是通过sysfs就向用户空间提供了一个视图所以下文为了方便,我将kobject的添加称为注册进sysfs的体系结构中kobjectkobj可以理解为内核设备模型中的一个最小对象(所有sysfs中的实体(最常见的就是struct device dev)都需要直接或者间接内嵌继承它),其对应一个sysfs中的目录name—kobject的名字,也就是sysfs中对应目录的名字entry—作为kset中的节点,串联起来所属的kobj*parent—kobject的父指针,这样就可以支持层级关系了,sysfs创建目录的时候就是按照这个创建的kset—当前kobject所属的kset*ktype—这个kobject的类型,决定syfs属性文件如何读写,释放时会怎么处理等等这个变量很关键,它决定了当当前kobj的引用计数为0的时候,是释放subsys结构体还是kset结构体*sd—指向kernfs层真实节点,内核维护,用户不填kref—引用计数,参与kobject的生命周期,即有人还在用,就不会释放这个kobjrelease—用于调试:延迟释放,方便捕获使用后释放的bug最后是五个状态标志位unsignedintstate_initialized:1;// 是否已经完成初始化kobject_init() 或 kobject_init_and_add()unsignedintstate_in_sysfs:1;// 是否已经成功注册到 sysfs 中目录已创建unsignedintstate_add_uevent_sent:1;// 是否已经发送过 KOBJ_ADD uevent创建时通知unsignedintstate_remove_uevent_sent:1;// 是否已经发送过 KOBJ_REMOVE uevent删除时通知unsignedintuevent_suppress:1;// 是否抑制发送 uevent用于批量操作时避免刷屏kobject_init对kobject做了一些检查,然后给其内部的一些变量进行了缺省配置比如是否已经注册到sysfs中,是否已经发送ueventkobject_addkobj初始化后,就可以调用kobject_add()函数进行添加了该函数负责将一个已经初始化好的kobject加入到sysfs体系结构中,同时先在sys下根据父子关系,创建一个目录出来,目录名字就是kobj的nameksetkset可以理解为对一批kobject的集合,其本身也是内嵌继承了kobject,所以也会有自己的目录其往往是给一个子系统(bus,class)等使用,可以在kobj加入到kset的时候自动设置kobj的parent为自己内嵌的kobj也更方便子系统访问自己下属的kobjkset实际上是 内嵌继承kobject的结构体除了通过list记录自己下面的kobject外,其最重要的作用是统一管理和发送ueventlist:记录属于当前kset的kobjects的链表lisk_lock:对于list访问的自旋锁,防止遍历过程中节点被修改kobject:内嵌到当前kset的kobjectuevent_ops:简单说它是内核用来“定制”某个 kset 下所有 kobject 发出 uevent 时额外携带哪些环境变量的一个回调机制。uevent 是内核通知用户空间设备事件的机制kset_init先调用kobject_init_internal,同样是初始化一下内嵌的kobj同时初始化了kset的链表和自旋锁,这个链表就是用来串属于当前kset的kobj的kset_register初始化并且添加一个ketset其会为当前kset创建一个目录(实际上目录是属于其内嵌的kobject的)kobject_add_internal这是对kset内嵌kobject处理的函数该函数做的事情大概如下:判断一下kset的内嵌kobj在不在另一个kset中,如果在并且还没指定parent,则将其parent设置为其所属kset的kobj如果已经指定了parent,则正常按照kobj的parent层级关系,注册进内核和sysfs层级结构中kobject_uevent向用户空间发送一个kobject被添加到内核中的uevent事件,让用户空间程序,如udev知道这个新对象出现了,并且进行处理这个事件如何理解呢?比如device_create函数,它可不是自己直接就创建/dev下面的设备节点的,而是也要在创建并注册device所内嵌的kobj到sysfs上,然后使用kobject_uevent(dev-kobj, KOBJ_ADD);通知用户空间,udev才会去在/dev下面创建新的设备节点bus_type源码bus的代表数据结构是bus_typesubsys_private源码首先bus_type内部定义了一个重要的struct subsys_private类型的指针,该结构体内部成员如下之所以设置这个结构体指针是为了从bus中将devices和drivers分离出来,将bus_type改造成一个纯粹的对外接口,防止普通驱动开发者直接读写bus-devices或bus-drivers这些核心链表极易造成并发破坏。subsys—为一个kobj的kset集合,代表了当前总线在sysfs中的位置,同时也代表了其在/sys下的目录devices_kset—为指向设备kset集合的一个指针,即指向代表当前总线下sys中devices目录对应的kset,里面记录了属于当前总线的dev的kobj是哪些(通过entry接入)注意:在bus和class的devices_kset中的链表内,device只会出现在其中一个地方,有bus优先businterfaces—总线支持的接口链表drivers_kset—为指向驱动kset集合的一个指针,即指向代表当前总线下drivers目录对应的kset,里面记录了属于当前总线的driver的kobj是哪些(通过entry接入)klist_devices和klist_drivers这两个变量是为了对注册进bus的device和driver进行记录的链表(该链表提供了引用计数以及锁)它对应的entry节点在device_private中定义了,为knode_bus关于这里的kset和klist是否多次一举的理解kset负责在sysfs中维护内核对象的层级结构和/sys/下的目录结构klist通过引用计数锁的机制 提供了并发安全性二者并不冲突,kobj提供的引用计数是为了管理device或者driver在内核中的生命周期kilst中knode提供的引用计数是为了提供并发安全的遍历机制bus_notifier–uevent通知头,热插拔,绑定,解绑等事件会通过这里通知用户空间drivers_autoprobe—自动匹配并调用驱动中probe的开关,设置为1表示开启bus_type—为指向总线类型的指针glue_dirs—为了防止命名空间冲突的一个变量,这个在class里有大作用class—当前总线和某个class绑定的时候用到bus_type源码name—总线名字dev_name—用于枚举总线上设备的变量,比如spi1,spi2等等dev_root—设置一个device作为总线的根设备dev_attrs—总线上设备的默认属性match—如何匹配总线上的driver和deviceuevent—影响给用户空间的uevent事件probe—添加device或者driver时候调用,并且会回调总线上driver中的probe函数来初始化deviceremove到resume均为总线上设备的状态发生对应变化的时候会调用的函数pm—总线上默认的电源管理操作,内部会回调对应的驱动的电源管理函数iommu_ops—设置总线上的内存映射操作struct subsys_private*—指向总线的子系统(总线的私有结构体)的指针综上,bus_type结构体是对总线上设备或者驱动变化的时候,做的对应的操作做了一个规定,也就是成了一个完全的对驱动工程师的接口类而总线在sysfs中的对应结构,需要通过bus_type的私有结构体subsys_private中的成员来实现bus_register源码bus_kset就对应着/sys/bus这个目录对应的kset这样其在kset_register后就会将priv内嵌的kobj的parent设置为/bus对应的kset的kobj了这里创建了devices和drivers对应的kset,该kset的内嵌kobj的parent当然就会是当前subsys这个kset的内嵌kobj了在/sys中的体现就是在/sys/bus/总线name/下创建了devices和drivers两个目录了klist_init初始化了两个链表,这两个链表通过独特的引用机制,提供并发安全的对knode所对应结构体(如device或者driver)的访问add_probe_files会添加一个drivers_autoprobe文件到spi_bus的目录下,其值默认为1,即添加device或者driver会自动执行probe函数并在内部回调driver的probe函数关于klist和knode引用计数的研究kobj的引用计数只管内核对象(如device)没人用的时候的内存释放klist中knode的引用计数决定了能否线程安全的释放对于devices的list来说,假设这样一种情况┌───────────┐ ┌───────────┐ ┌───────────┐... ──│ Device A │─│ Device B │─│ Device C │ ──... └───────────┘ └───────────┘ └───────────┘首先,在除了执行probe这个耗时操作之外的情况下要释放锁,对klist的遍历和删除操作前都需要对klist上锁CPU0在遍历devices链表找对应设备A后执行耗时的probe,同时CPU1在执行拔出即删除devices链表中的B设备如果仅仅使用kobj的引用计数,CPU1执行拔出操作,引用计数-1,那么B这块内存直接没了当CPU0执行完A的probe后,想往后走的时候,发现路就断了,引发内核野指针错误了但是klist提供了一种先逻辑删除,再真实脱链的引用计数措施当要删除B的时候,由于此时A的下一个就是B,即还有人需要B当跳板,所以B的引用计数在A的probe开始前被CPU0设置为1,所以此时CPU1仅仅是给B对应的knode设置一个KLIST_DELETED状态标记这样CPU0执行完A的probe后,仍然可以安全的跳过B到达C,当CPU0遍历到C后,再将B对应的knode的引用计数-1,如果CPU0发现此时B的引用计数为0且有KLIST_DELETED标记,则删除该设备当然如果B的引用计数本来就是0,说明此时没人要用B当跳板,此时该CPU又有klist的锁,那么直接删除即可
返回列表