700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Linux内核部件分析 设备驱动模型之bus

Linux内核部件分析 设备驱动模型之bus

时间:2023-05-25 16:52:22

相关推荐

Linux内核部件分析  设备驱动模型之bus

前面我们分析了设备驱动模型中的device和driver,device和driver本来是不相关的东西,只因为bus的存在,才被联系到了一起。本节就来看看设备驱动模型中起枢纽作用的bus。本节的头文件在include/linux/device.h和drivers/base/base.h,实现代码主要在bus.c中。因为在bus中有很多代码时为了device找到driver或者driver找到device而定义的,本节先尽量忽略这部分,专注于bus的注册和注销,属性定义等内容。剩下的留到讨论device和driver关系时在分析。

先来看看bus的数据结构。

structbus_type{constchar*name;structbus_attribute*bus_attrs;structdevice_attribute*dev_attrs;structdriver_attribute*drv_attrs;int(*match)(structdevice*dev,structdevice_driver*drv);int(*uevent)(structdevice*dev,structkobj_uevent_env*env);int(*probe)(structdevice*dev);int(*remove)(structdevice*dev);void(*shutdown)(structdevice*dev);int(*suspend)(structdevice*dev,pm_message_tstate);int(*resume)(structdevice*dev);conststructdev_pm_ops*pm;structbus_type_private*p;};

struct bus_type是bus的通用数据结构。

name是bus的名称,注意到这里也是const char类型的,在sysfs中使用的还是kobj中动态创建的名称,这里的name只是初始名。

bus_attrs是bus为自己定义的一系列属性,dev_attrs是bus为旗下的device定义的一系列属性,drv_attrs是bus为旗下的driver定义的一系列属性。其中dev_attrs在bus_add_device()->device_add_attrs()中被加入dev目录下,drv_attrs在bus_add_driver()->driver_add_attrs()中被加入driver目录下。

match函数匹配总线中的dev和driver,返回值为1代表匹配成功,为0则失败。

uevent函数用于总线对uevent的环境变量添加,但在总线下设备的dev_uevent处理函数也有对它的调用。

probe函数是总线在匹配成功时调用的函数,bus->probe和drv->probe中只会有一个起效,同时存在时使用bus->probe。

remove函数在总线上设备或者驱动要删除时调用,bus->remove和drv->remove中同样只会有一个起效。

shutdown函数在所有设备都关闭时调用,即在core.c中的device_shutdown()函数中调用,bus->shutdown和drv->shutdown同样只会有一个起效。

suspend函数是在总线上设备休眠时调用。

resume函数是在总线上设备恢复时调用。

pm是struct dev_pm_ops类型,其中定义了一系列电源管理的函数。

p是指向bus_type_private的指针,其中定义了将bus同其它组件联系起来的变量。

structbus_type_private{structksetsubsys;structkset*drivers_kset;structkset*devices_kset;structklistklist_devices;structklistklist_drivers;structblocking_notifier_headbus_notifier;unsignedintdrivers_autoprobe:1;structbus_type*bus;};#defineto_bus(obj)container_of(obj,structbus_type_private,subsys.kobj)

struct bus_type_private是将bus同device、driver、sysfs联系起来的结构。

subsys是kset类型,代表bus在sysfs中的类型。

drivers_kset代表bus目录下的drivers子目录。

devices_kset代表bus目录下地devices子目录。

klist_devices是bus的设备链表,klist_drivers是bus的驱动链表。

bus_notifier用于在总线上内容发送变化时调用特定的函数,这里略过。

driver_autoprobe标志定义是否允许device和driver自动匹配,如果允许会在device或者driver注册时就进行匹配工作。

bus指针指向struct bus_type类型。

使用struct bus_type_private可以将struct bus_type中的部分细节屏蔽掉,利于外界使用bus_type。struct driver_private和struct device_private都有类似的功能。

structbus_attribute{structattributeattr;ssize_t(*show)(structbus_type*bus,char*buf);ssize_t(*store)(structbus_type*bus,constchar*buf,size_tcount);};#defineBUS_ATTR(_name,_mode,_show,_store)\structbus_attributebus_attr_##_name=__ATTR(_name,_mode,_show,_store)#defineto_bus_attr(_attr)container_of(_attr,structbus_attribute,attr)

struct bus_attribute是bus对struct attribute类型的封装,更方便总线属性的定义。

staticssize_tbus_attr_show(structkobject*kobj,structattribute*attr,char*buf){structbus_attribute*bus_attr=to_bus_attr(attr);structbus_type_private*bus_priv=to_bus(kobj);ssize_tret=0;if(bus_attr->show)ret=bus_attr->show(bus_priv->bus,buf);returnret;}staticssize_tbus_attr_store(structkobject*kobj,structattribute*attr,constchar*buf,size_tcount){structbus_attribute*bus_attr=to_bus_attr(attr);structbus_type_private*bus_priv=to_bus(kobj);ssize_tret=0;if(bus_attr->store)ret=bus_attr->store(bus_priv->bus,buf,count);returnret;}staticstructsysfs_opsbus_sysfs_ops={.show=bus_attr_show,.store=bus_attr_store,};staticstructkobj_typebus_ktype={.sysfs_ops=&bus_sysfs_ops,};

以上应该是我们最熟悉的部分,bus_ktype中定义了bus对应的kset应该使用的kobj_type实例。与此类似,driver使用的是自定义的driver_ktype,device使用的是自定义的device_ktype。只是这里仅仅定义了sysfs_ops,并未定义release函数,不知bus_type_private打算何时释放。

intbus_create_file(structbus_type*bus,structbus_attribute*attr){interror;if(bus_get(bus)){error=sysfs_create_file(&bus->p->subsys.kobj,&attr->attr);bus_put(bus);}elseerror=-EINVAL;returnerror;}voidbus_remove_file(structbus_type*bus,structbus_attribute*attr){if(bus_get(bus)){sysfs_remove_file(&bus->p->subsys.kobj,&attr->attr);bus_put(bus);}}

bus_create_file()在bus目录下创建属性文件,bus_remove_file()在bus目录下删除属性文件。类似的函数在driver和device中都有见到。

staticintbus_uevent_filter(structkset*kset,structkobject*kobj){structkobj_type*ktype=get_ktype(kobj);if(ktype==&bus_ktype)return1;return0;}staticstructkset_uevent_opsbus_uevent_ops={.filter=bus_uevent_filter,};staticstructkset*bus_kset;

可以看到这里定义了一个bus_uevent_ops变量,这是kset对uevent事件处理所用的结构,它会用在bus_kset中。

int__initbuses_init(void){bus_kset=kset_create_and_add("bus",&bus_uevent_ops,NULL);if(!bus_kset)return-ENOMEM;return0;}

在buses_init()中创建了/sys/bus目录,这是一个kset类型,使用了bus_uevent_ops的uevent操作类型。

其实这里的操作不难想象,在devices中我们有一个类似的devices_kset,可以回顾一下。

staticstructkset_uevent_opsdevice_uevent_ops={.filter=dev_uevent_filter,.name=dev_uevent_name,.uevent=dev_uevent,};/*ksettocreate/sys/devices/*/structkset*devices_kset;int__initdevices_init(void){devices_kset=kset_create_and_add("devices",&device_uevent_ops,NULL);...}voiddevice_initialize(structdevice*dev){dev->kobj.kset=devices_kset;...}

devices_kset在devices_init()中被创建,使用相应的device_uevent_ops进行uevent处理。而devices_kset又被设为每个device初始化时使用的kset。这就不难想象每个device都是以devices_kset为所属kset的,并使用device_uevent_ops中的处理函数。

只是这里还不知bus_kset会在哪里用到,或许是每个bus所属的kset吧,下面会有答案。

staticssize_tshow_drivers_autoprobe(structbus_type*bus,char*buf){returnsprintf(buf,"%d\n",bus->p->drivers_autoprobe);}staticssize_tstore_drivers_autoprobe(structbus_type*bus,constchar*buf,size_tcount){if(buf[0]=='0')bus->p->drivers_autoprobe=0;elsebus->p->drivers_autoprobe=1;returncount;}staticssize_tstore_drivers_probe(structbus_type*bus,constchar*buf,size_tcount){structdevice*dev;dev=bus_find_device_by_name(bus,NULL,buf);if(!dev)return-ENODEV;if(bus_rescan_devices_helper(dev,NULL)!=0)return-EINVAL;returncount;}staticBUS_ATTR(drivers_probe,S_IWUSR,NULL,store_drivers_probe);staticBUS_ATTR(drivers_autoprobe,S_IWUSR|S_IRUGO,show_drivers_autoprobe,store_drivers_autoprobe);

这里定义了总线下的两个属性,只写得drivers_probe,和可读写的drivers_autoprobe。至于其怎么实现的,我们现在还不关心。

staticintadd_probe_files(structbus_type*bus){intretval;retval=bus_create_file(bus,&bus_attr_drivers_probe);if(retval)gotoout;retval=bus_create_file(bus,&bus_attr_drivers_autoprobe);if(retval)bus_remove_file(bus,&bus_attr_drivers_probe);out:returnretval;}staticvoidremove_probe_files(structbus_type*bus){bus_remove_file(bus,&bus_attr_drivers_autoprobe);bus_remove_file(bus,&bus_attr_drivers_probe);}

add_probe_files()在bus目录下添加drivers_probe和drivers_autoprobe文件。

remove_probe_files()在bus目录下删除drivers_probe和drivers_autoprobe文件。

这两个函数对bus的probe类型属性进行管理,就像add_bind_files/remove_bind_files对driver的bind类型属性进行管理一样。

staticssize_tbus_uevent_store(structbus_type*bus,constchar*buf,size_tcount){enumkobject_actionaction;if(kobject_action_type(buf,count,&action)==0)kobject_uevent(&bus->p->subsys.kobj,action);returncount;}staticBUS_ATTR(uevent,S_IWUSR,NULL,bus_uevent_store);

上面定义了bus的一个属性uevent,用于bus所在的kset节点主动发起uevent消息。

同样地uevent文件在driver目录中也有见到。device目录中也有,不过除了store_uevent之外,还增加了show_uevent的功能。

staticstructdevice*next_device(structklist_iter*i){structklist_node*n=klist_next(i);structdevice*dev=NULL;structdevice_private*dev_prv;if(n){dev_prv=to_device_private_bus(n);dev=dev_prv->device;}returndev;}intbus_for_each_dev(structbus_type*bus,structdevice*start,void*data,int(*fn)(structdevice*,void*)){structklist_iteri;structdevice*dev;interror=0;if(!bus)return-EINVAL;klist_iter_init_node(&bus->p->klist_devices,&i,(start?&start->p->knode_bus:NULL));while((dev=next_device(&i))&&!error)error=fn(dev,data);klist_iter_exit(&i);returnerror;}structdevice*bus_find_device(structbus_type*bus,structdevice*start,void*data,int(*match)(structdevice*dev,void*data)){structklist_iteri;structdevice*dev;if(!bus)returnNULL;klist_iter_init_node(&bus->p->klist_devices,&i,(start?&start->p->knode_bus:NULL));while((dev=next_device(&i)))if(match(dev,data)&&get_device(dev))break;klist_iter_exit(&i);returndev;}

bus_for_each_dev()是以bus的设备链表中每个设备为参数,调用指定的处理函数。

bus_find_device()是寻找bus设备链表中的某个设备,使用指定的匹配函数。

这两个函数提供遍历bus的设备链表的方法,类似于drivers_for_each_device/drivers_find_device对driver的设备链表的遍历,device_for_each_child/device_find_child对device的子设备链表的遍历。

staticintmatch_name(structdevice*dev,void*data){constchar*name=data;returnsysfs_streq(name,dev_name(dev));}structdevice*bus_find_device_by_name(structbus_type*bus,structdevice*start,constchar*name){returnbus_find_device(bus,start,(void*)name,match_name);}

bus_find_device_by_name()给出了如何使用遍历函数的例子,寻找bus设备链表中指定名称的设备。

staticstructdevice_driver*next_driver(structklist_iter*i){structklist_node*n=klist_next(i);structdriver_private*drv_priv;if(n){drv_priv=container_of(n,structdriver_private,knode_bus);returndrv_priv->driver;}returnNULL;}intbus_for_each_drv(structbus_type*bus,structdevice_driver*start,void*data,int(*fn)(structdevice_driver*,void*)){structklist_iteri;structdevice_driver*drv;interror=0;if(!bus)return-EINVAL;klist_iter_init_node(&bus->p->klist_drivers,&i,start?&start->p->knode_bus:NULL);while((drv=next_driver(&i))&&!error)error=fn(drv,data);klist_iter_exit(&i);returnerror;}

bus_for_each_drv()对bus的驱动链表中的每个驱动调用指定的函数。

这和前面的bus_for_each_dev/bus_find_dev什么都是类似的,只是你可能怀疑为什么会没有bus_find_drv。是没有它的用武之地吗?

请看driver.c中的driver_find()函数。

structdevice_driver*driver_find(constchar*name,structbus_type*bus){structkobject*k=kset_find_obj(bus->p->drivers_kset,name);structdriver_private*priv;if(k){priv=to_driver(k);returnpriv->driver;}returnNULL;}

driver_find()函数是在bus的驱动链表中寻找指定名称的驱动,它的存在证明bus_find_drv()完全是用得上的。可linux却偏偏没有实现bus_find_drv。driver_find()的实现也因此一直走内层路线,它直接用kset_find_obj()进行kobect的名称匹配,调用to_driver()等内容将kobj转化为drv。首先这完全不同于bus_for_each_drv()等一系列遍历函数,它们走的都是在klist中寻找的路线,这里确实走的sysfs中kset内部链表。其次,这里其实也是获得了drv的一个引用计数,在kset_find_obj()中会增加匹配的kobj的引用计数,driver_find()并没有释放,就相当于获取了drv的一个引用计数。这样虽然也可以,但代码写得很不优雅。可见人无完人,linux代码还有许多可改进之处。当然,也可能在最新的linux版本中已经改正了。

staticintbus_add_attrs(structbus_type*bus){interror=0;inti;if(bus->bus_attrs){for(i=0;attr_name(bus->bus_attrs[i]);i++){error=bus_create_file(bus,&bus->bus_attrs[i]);if(error)gotoerr;}}done:returnerror;err:while(--i>=0)bus_remove_file(bus,&bus->bus_attrs[i]);gotodone;}staticvoidbus_remove_attrs(structbus_type*bus){inti;if(bus->bus_attrs){for(i=0;attr_name(bus->bus_attrs[i]);i++)bus_remove_file(bus,&bus->bus_attrs[i]);}}

bus_add_attrs()将bus->bus_attrs中定义的属性加入bus目录。

bus_remove_attrs()将bus->bus_attrs中定义的属性删除。

开始看struct bus_type时我们说到结构中的bus_attrs、dev_attrs、drv_attrs三种属性,后两者分别在device_add_attrs()和driver_add_attrs()中添加,最后的bus_attrs也终于在bus_add_attrs()中得到添加。只是它们虽然都定义在bus_type中,确实添加在完全不同的三个地方。

staticvoidklist_devices_get(structklist_node*n){structdevice_private*dev_prv=to_device_private_bus(n);structdevice*dev=dev_prv->device;get_device(dev);}staticvoidklist_devices_put(structklist_node*n){structdevice_private*dev_prv=to_device_private_bus(n);structdevice*dev=dev_prv->device;put_device(dev);}

klist_devices_get()用于bus设备链表上添加节点时增加对相应设备的引用。

klist_devices_put()用于bus设备链表上删除节点时减少对相应设备的引用。

相似的函数是device中的klist_children_get/klist_children_put,这是device的子设备链表。除此之外,bus的驱动链表和driver的设备链表,都没有这种引用计数的保护。原因还未知,也许是linux觉得驱动不太靠谱,万一突然当掉,也不至于影响device的正常管理。

/***bus_register-registerabuswiththesystem.*@bus:bus.**Oncewehavethat,weregisteredthebuswiththekobject*infrastructure,thenregisterthechildrensubsystemsithas:*thedevicesanddriversthatbelongtothebus.*/intbus_register(structbus_type*bus){intretval;structbus_type_private*priv;priv=kzalloc(sizeof(structbus_type_private),GFP_KERNEL);if(!priv)return-ENOMEM;priv->bus=bus;bus->p=priv;BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);retval=kobject_set_name(&priv->subsys.kobj,"%s",bus->name);if(retval)gotoout;priv->subsys.kobj.kset=bus_kset;priv->subsys.kobj.ktype=&bus_ktype;priv->drivers_autoprobe=1;retval=kset_register(&priv->subsys);if(retval)gotoout;retval=bus_create_file(bus,&bus_attr_uevent);if(retval)gotobus_uevent_fail;priv->devices_kset=kset_create_and_add("devices",NULL,&priv->subsys.kobj);if(!priv->devices_kset){retval=-ENOMEM;gotobus_devices_fail;}priv->drivers_kset=kset_create_and_add("drivers",NULL,&priv->subsys.kobj);if(!priv->drivers_kset){retval=-ENOMEM;gotobus_drivers_fail;}klist_init(&priv->klist_devices,klist_devices_get,klist_devices_put);klist_init(&priv->klist_drivers,NULL,NULL);retval=add_probe_files(bus);if(retval)gotobus_probe_files_fail;retval=bus_add_attrs(bus);if(retval)gotobus_attrs_fail;pr_debug("bus:'%s':registered\n",bus->name);return0;bus_attrs_fail:remove_probe_files(bus);bus_probe_files_fail:kset_unregister(bus->p->drivers_kset);bus_drivers_fail:kset_unregister(bus->p->devices_kset);bus_devices_fail:bus_remove_file(bus,&bus_attr_uevent);bus_uevent_fail:kset_unregister(&bus->p->subsys);kfree(bus->p);out:bus->p=NULL;returnretval;}

bus_register()将bus注册到系统中。

先分配并初始化bus->p,名称使用bus->name,所属的kset使用bus_kset(果然不出所料),类型使用bus_ktype。bus_ktype的使用同driver中的driver_ktype,和device中的device_ktype一样,都是自定义的kobj_type,要知道kobj_type的使用关系到release函数,和自定义属性类型能否正常发挥。

调用kset_register()将bus加入sysfs,因为只是设置了kset,所以会被加入/sys/bus目录下。与driver直接加入相关总线的drivers目录类似,却是与device复杂的寻找父节点过程相去甚远。

在bus目录下添加uevent属性。

在bus目录下创建devices子目录。它是一个kset类型的,目的是展示bus下的设备链表。

在bus目录下创建drivers子目录。它也是一个kset类型的,目的是展示bus下的驱动链表。

或许在最开始有设备驱动模型时,还需要kset来表达这种链表关系,但随着klist等结构的加入,kset的作用也越来越少,现在更多的作用是用来处理uevent消息。

之后初始化bus的设备链表和驱动链表,其中设备链表会占用设备的引用计数。

调用add_probe_files()在bus目录下添加probe相关的两个属性文件。

调用bus_add_attrs添加bus结构中添加的属性。

bus_register()中的操作出乎意料的简单。bus既不需要在哪里添加软链接,也不需要主动向谁报道,从来都是device和driver到bus这里报道的。所以bus_register()中只需要初始一下结构,添加到sysfs中,添加相关的子目录和属性文件,就行了。

voidbus_unregister(structbus_type*bus){pr_debug("bus:'%s':unregistering\n",bus->name);bus_remove_attrs(bus);remove_probe_files(bus);kset_unregister(bus->p->drivers_kset);kset_unregister(bus->p->devices_kset);bus_remove_file(bus,&bus_attr_uevent);kset_unregister(&bus->p->subsys);kfree(bus->p);bus->p=NULL;}

bus_unregister()与bus_register()相对,将bus从系统中注销。不过要把bus注销也不是那么简单的,bus中的driver和device都对bus保有一份引用计数。或许正是如此,bus把释放bus->p的动作放在了bus_unregister()中,这至少能保证较早地释放不需要的内存空间。而且在bus引用计数用完时,也不会有任何操作,bus的容错性还是很高的。

staticstructbus_type*bus_get(structbus_type*bus){if(bus){kset_get(&bus->p->subsys);returnbus;}returnNULL;}staticvoidbus_put(structbus_type*bus){if(bus)kset_put(&bus->p->subsys);}

bus_get()增加对bus的引用计数,bus_put()减少对bus的引用计数。实际上这里bus的引用计数降为零时,只是将sysfs中bus对应的目录删除。

无论是bus,还是device,还是driver,都是将主要的注销工作放在相关的unregister中。至于在引用计数降为零时的操作,大概只在device_release()中可见。这主要是因为引用计数,虽然是广泛用在设备驱动模型中,但实际支持的,绝大部分是设备的热插拔,而不是总线或者驱动的热插拔。当然,桥设备的热插拔也可能附带总线的热插拔。

/**Yes,thisforcablybreakstheklistabstractiontemporarily.It*justwantstosorttheklist,notchangereferencecountsand*take/droplocksrapidlyintheprocess.Itdoesallthiswhile*holdingthelockforthelist,soobjectscan'totherwisebe*added/removedwhilewe'reswizzling.*/staticvoiddevice_insertion_sort_klist(structdevice*a,structlist_head*list,int(*compare)(conststructdevice*a,conststructdevice*b)){structlist_head*pos;structklist_node*n;structdevice_private*dev_prv;structdevice*b;list_for_each(pos,list){n=container_of(pos,structklist_node,n_node);dev_prv=to_device_private_bus(n);b=dev_prv->device;if(compare(a,b)<=0){list_move_tail(&a->p->knode_bus.n_node,&b->p->knode_bus.n_node);return;}}list_move_tail(&a->p->knode_bus.n_node,list);}voidbus_sort_breadthfirst(structbus_type*bus,int(*compare)(conststructdevice*a,conststructdevice*b)){LIST_HEAD(sorted_devices);structlist_head*pos,*tmp;structklist_node*n;structdevice_private*dev_prv;structdevice*dev;structklist*device_klist;device_klist=bus_get_device_klist(bus);spin_lock(&device_klist->k_lock);list_for_each_safe(pos,tmp,&device_klist->k_list){n=container_of(pos,structklist_node,n_node);dev_prv=to_device_private_bus(n);dev=dev_prv->device;device_insertion_sort_klist(dev,&sorted_devices,compare);}list_splice(&sorted_devices,&device_klist->k_list);spin_unlock(&device_klist->k_lock);}

bus_sort_breadthfirst()是将bus的设备链表进行排序,使用指定的比较函数,排成降序。

本节主要分析了bus的注册注销过程,下节我们将深入分析device和driver的绑定过程,了解bus在这其中到底起了什么作用。随着我们了解的逐渐深入,未知的东西也在逐渐增多。但饭要一口一口吃,我们的分析也要一点一点来,急不得。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。