设备模型.docx
《设备模型.docx》由会员分享,可在线阅读,更多相关《设备模型.docx(37页珍藏版)》请在冰豆网上搜索。
设备模型
Linux设备模型
(1)
2010-12-0900:
21bywwang,2747visits,收藏,编辑
随着计算机的周边外设越来越丰富,设备管理已经成为现代操作系统的一项重要任务,这对于Linux来说也是同样的情况。
每次Linux内核新版本的发布,都会伴随着一批设备驱动进入内核。
在Linux内核里,驱动程序的代码量占有了相当大的比重。
下图是我在网络上搜索到的一幅Linux内核代码量的统计图,对应的内核版本是2.6.29。
我们可以很明显的看到,在Linux内核中驱动程序的比例已经非常高了。
Linux2.6内核最初为了应付电源管理的需要,提出了一个设备模型来管理所有的设备。
在物理上,外设之间是有一种层次关系的,比如把一个U盘插到笔记本上,实际上这个U盘是接在一个USBHub上,USBHub又是接在USB2.0HostController(EHCI)上,最终EHCI又是一个挂在PCIBus上的设备。
这里的一个层次关系是:
PCI->EHCI->USBHub->USBDisk。
如果操作系统要进入休眠状态,首先要逐层通知所有的外设进入休眠模式,然后整个系统才可以休眠。
因此,需要有一个树状的结构可以把所有的外设组织起来。
这就是最初建立Linux设备模型的目的。
当然,Linux设备模型给我们带来的便利远不止如此。
既然已经建立了一个组织所有设备和驱动的树状结构,用户就可以通过这棵树去遍历所有的设备,建立设备和驱动程序之间的联系,根据类型不同也可以对设备进行归类,这样就可以更清晰的去“看”这颗枝繁叶茂的大树。
另外,Linux驱动模型把很多设备共有的一些操作抽象出来,大大减少了重复造轮子的可能。
同时Linux设备模型提供了一些辅助的机制,比如引用计数,让开发者可以安全高效的开发驱动程序。
达成了以上这些好处之后,我们还得到了一个非常方便的副产品,这就是sysfs----一个虚拟的文件系统。
sysfs给用户提供了一个从用户空间去访问内核设备的方法,它在Linux里的路径是/sys。
这个目录并不是存储在硬盘上的真实的文件系统,只有在系统启动之后才会建起来。
下面这个命令可以用来显示sysfs的大致结构:
tree/sys
这个命令的信息量非常大,我就不贴出来了,如果有兴趣的话可以看看这里,或者自己动手实验一下。
我们来看看第一层目录结构:
/sys
|--block
|--bus
|--class
|--dev
|--devices
|--firmware
|--fs
|--kernel
|--module
`--power
这里有10个子目录,但并不是说这10个目录代表了10种完全不同的设备类型,实际上这些目录只是给我们提供了如何去看整个设备模型的不同的视角。
其实从不同的目录出发都有可能找到同一个设备的。
那真正的设备信息到底放在哪里呢?
看看目录的名称就应该能猜到,对,就是devices子目录,Linux的所有设备都可以在这个目录里找到。
这里是一个大杂烩,虽然五脏俱全但我们却无从下手。
这里还是以U盘为例,插上U盘之后,在devices目录里如何找到这支U盘呢?
真得很难办到。
但是如果知道这个U盘在系统里的设备文件名(暂且假设为sdb),那就可以从block目录着手。
透过block目录,我们很容易就可以找到这个U盘设备,符号链接device正是指向devices目录下的位置。
到这里,我们总结一下/sys目录下各个子目录的作用。
block目录是从块设备的角度来组织设备;bus目录是从系统总线这个角度来组织设备,这里的总线可以是具有实际意义的总线比如PCI总线,也可以是完全逻辑上的总线比如USB总线;class目录把看问题的视角提高到了类别的高度,比如PCI设备或者USB设备等;dev目录的视角是设备节点;devices目录在前面提到了,这里是所有设备的大本营;firmware目录包含了一些比较低阶的子系统,比如ACPI、EFI等;fs目录里看到的是系统支持的所有文件系统;kernel目录下包含的是一些内核的配置选项;modules目录下包含的是所有内核模块的信息,内核模块实际上和设备之间是有对应关系的,通过这个目录顺藤摸瓜找到devices或者反过来都是可以做到的;power目录存放的是系统电源管理的数据,用户可以通过它来查询目前的电源状态,甚至可以直接“命令”系统进入休眠等省电模式。
sysfs正是用户和内核设备模型之间的一座桥梁,通过这个桥梁我们可以从内核中读取信息,也可以向内核里写入信息。
在Linux里也可以找到一些图形化的工具来查询设备信息。
比如GNOME下基于HAL的DeviceManager:
或者KDE下基于Solid的KInfoCenter:
这些图形化的工具提供了更加直观的方式来访问设备,但是它们的提供的信息还不够全面,而且没有向内核设备写数据的功能。
如果具体到某一类型的设备,Linux下还有一些专用的工具可以使用。
比如面向PCI设备的pciutils,面向USB设备的usbutils,以及面向SCSI设备的lsscsi等。
对于Linux开发者来说,有时使用这些专用的工具更加方便。
我们如果要写程序来访问sysfs,可以像读写普通文件一样来操作/sys目录下的文件,或者,也可以使用libsysfs。
不过需要注意的是,Linux内核社区并不推荐用libsysfs,因为这个API的更新不够快,赶不上内核的变化。
libsysfs已经逐渐背离最初创建它的目标,这个lib带来的问题似乎比它解决的还要多。
当然,如果只是要访问设备,一般很少会直接操作sysfs,它太细节太底层了,大部分情况下可以使用更加方便的DeviceKit或者libudev。
总结一下,本文主要简单介绍了Linux设备模型的基本概念和虚拟文件系统sysfs。
接下来的篇章里将和大家继续探讨设备模型在内核空间的一些细节。
Linux设备模型
(2)
2010-12-1600:
11bywwang,1943visits,收藏,编辑
上一篇文章《Linux设备模型
(1)》主要介绍了Linux设备模型在用户空间的接口sysfs,用户通过这个接口可以一览内核设备的全貌。
本文将从Linux内核的角度来看一看这个设备模型是如何构建的。
在Linux内核里,kobject是组成Linux设备模型的基础,一个kobject对应sysfs里的一个目录。
从面向对象的角度来说,kobject可以看作是所有设备对象的基类,因为C语言并没有面向对象的语法,所以一般是把kobject内嵌到其他结构体里来实现类似的作用,这里的其他结构体可以看作是kobject的派生类。
Kobject为Linux设备模型提供了很多有用的功能,比如引用计数,接口抽象,父子关系等等。
引用计数本质上就是利用kref实现的,至于kref的细节可以参考我之前的文章《Linux内核里的“智能指针”》。
另外,Linux设备模型还有一个重要的数据结构kset。
Kset本身也是一个kobject,所以它在sysfs里同样表现为一个目录,但它和kobject的不同之处在于kset可以看作是一个容器,如果你把它类比为C++里的容器类如list也无不可。
Kset之所以能作为容器来使用,其内部正是内嵌了一个双向链表结构structlist_head。
对于list_head的细节可以参考《玩转C链表》一文。
下面这幅图可以用来表示kobject和kset在内核里关系。
在接下来的篇幅里我们会逐步看到这个关系图在内核里是如何建立的。
本文的示例代码可以从这里下载,下文中的两个实作都在这个示例代码里。
Kobject
Kobject在Linux内核里的定义如下:
structkobject{
constchar*name;
structlist_headentry;
structkobject*parent;
structkset*kset;
structkobj_type*ktype;
structsysfs_dirent*sd;
structkrefkref;
unsignedintstate_initialized:
1;
unsignedintstate_in_sysfs:
1;
unsignedintstate_add_uevent_sent:
1;
unsignedintstate_remove_uevent_sent:
1;
unsignedintuevent_suppress:
1;
};
在《Linux设备模型
(1)》里面我们介绍到内核里的设备之间是以树状形式组织的,在这种组织架构里比较靠上层的节点可以看作是下层节点的父节点,反映到sysfs里就是上级目录和下级目录之间的关系,在内核里,正是kobject帮助我们实现这种父子关系。
在kobject的定义里,name表示的是kobject在sysfs中的名字;指针parent用来指向kobject的父对象;Kref大家应该比较熟悉了,kobject通过它来实现引用计数;Kset指针用来指向这个kobject所属的kset,下文会再详细描述kset的用法;对于ktype,如果只是望文生义的话,应该是用来描述kobject的类型信息。
Ktype的定义如下:
structkobj_type{
void(*release)(structkobject*kobj);
conststructsysfs_ops*sysfs_ops;
structattribute**default_attrs;
};
函数指针release是给kref使用的,当引用计数为0这个指针指向的函数会被调用来释放内存。
sysfs_ops和attribute是做什么用的呢?
前文里提到,一个kobject对应sysfs里的一个目录,而目录下的文件就是由sysfs_ops和attribute来实现的,其中,attribute定义了kobject的属性,在sysfs里对应一个文件,sysfs_ops用来定义读写这个文件的方法。
Ktype里的attribute是默认的属性,另外也可以使用更加灵活的手段,本文的重点还是放在defaultattribute。
下面看一个实作。
在这个实作里,我们定义一个内嵌kobject的结构。
structmy_kobj{
intval;
structkobjectkobj;
};
最终我们的目的是在内核里构建这样的架构。
对应sysfs里的目录关系是:
mykobj1/
|--mykobj2
||--name
|`--val
|--name
`--val
这是module_init代码。
staticint__initmykobj_init(void)
{
printk(KERN_INFO"mykobj_init\n");
obj1=kzalloc(sizeof(structmy_kobj),GFP_KERNEL);
if(!
obj1){
return-ENOMEM;
}
obj1->val=1;
obj2=kzalloc(sizeof(structmy_kobj),GFP_KERNEL);
if(!
obj2){
kfree(obj1);
return-ENOMEM;
}
obj2->val=2;
my_type.release=obj_release;
my_type.default_attrs=my_attrs;
my_type.sysfs_ops=&my_sysfsops;
kobject_init_and_add(&obj1->kobj,&my_type,NULL,"mykobj1");
kobject_init_and_add(&obj2->kobj,&my_type,&obj1->kobj,"mykobj2");
return0;
}
这段代码可以分作三个部分。
第一部分是分配obj1和obj2并赋值;第二部分是初始化kobj_type变量my_type;第三部分是调用kobject_init_and_add函数来初始化kobject并把它加入到设备模型的体系架构(也就是上文中提到的内核中的那棵树)中。
kobject_init_and_add是简化的写法,这个函数也可以分两步完成:
kobject_init和kobject_add。
intkobject_init_and_add(structkobject*kobj,structkobj_type*ktype,
structkobject*parent,constchar*fmt,...);
voidkobject_init(structkobject*kobj,structkobj_type*ktype);
intkobject_add(structkobject*kobj,structkobject*parent,
constchar*fmt,...);
kobject_init用来初始化kobject结构,kobject_add用来把kobj加入到设备模型之中。
在实作中,我们先对obj1进行初始化和添加的动作,调用参数里,parent被赋为NULL,表示obj1没有父对象,反映到sysfs里,my_kobj1的目录会出现在/sys下,obj2的父对象设定为obj1,那么my_kobj2的目录会出现在/sys/my_kobj1下面。
前面提到,kobject也提供了引用计数的功能,虽然本质上是利用kref,但也提供了另外的接口供用户使用。
structkobject*kobject_get(structkobject*kobj);
voidkobject_put(structkobject*kobj);
kobject_init_and_add和kobject_init这两个函数被调用后,kobj的引用计数会初始化为1,所以在module_exit时要记得用kobject_put来释放引用计数。
我们再回到实作中,看看如何使用ktype。
代码里,my_attrs是这样定义的:
structattributename_attr={
.name="name",
.mode=0444,
};
structattributeval_attr={
.name="val",
.mode=0666,
};
structattribute*my_attrs[]={
&name_attr,
&val_attr,
NULL,
};
结构体structattribute里的name变量用来指定文件名,mode变量用来指定文件的访问权限。
这里需要着重指出的是,数组my_attrs的最后一项一定要赋为NULL,否则会造成内核oops。
sysfs_ops的代码如下:
ssize_tmy_show(structkobject*kobj,structattribute*attr,char*buffer)
{
structmy_kobj*obj=container_of(kobj,structmy_kobj,kobj);
ssize_tcount=0;
if(strcmp(attr->name,"name")==0){
count=sprintf(buffer,"%s\n",kobject_name(kobj));
}elseif(strcmp(attr->name,"val")==0){
count=sprintf(buffer,"%d\n",obj->val);
}
returncount;
}
ssize_tmy_store(structkobject*kobj,structattribute*attr,constchar*buffer,size_tsize)
{
structmy_kobj*obj=container_of(kobj,structmy_kobj,kobj);
if(strcmp(attr->name,"val")==0){
sscanf(buffer,"%d",&obj->val);
}
returnsize;
}
structsysfs_opsmy_sysfsops={
.show=my_show,
.store=my_store,
};
读文件会调用my_show,写文件会调用my_store。
最后是module_exit:
staticvoid__exitmykobj_exit(void)
{
printk(KERN_INFO"mykobj_exit\n");
kobject_del(&obj2->kobj);
kobject_put(&obj2->kobj);
kobject_del(&obj1->kobj);
kobject_put(&obj1->kobj);
return;
}
kobject_del的作用是把kobject从设备模型的那棵树里摘掉,同时sysfs里相应的目录也会删除。
这里需要指出的是,释放的顺序应该是先子对象,后父对象。
因为kobject_init_and_add和kobject_add这两个函数会调用kobject_get来增加父对象的引用计数,所以kobject_del需要调用kobject_put来减少父对象的引用计数。
在本例中,如果先通过kobject_put来释放obj1,那kobject_del(&obj2->kobj)就会出现内存错误。
在这个实作中,我们建立了两个对象obj1和obj2,obj1是obj2的父对象,如果推广开来,obj1可以有更多的子对象。
在Linux内核中,这种架构方式其实并无太大的实际价值,有限的用处之一是在sysfs里创建子目录(Linux内核里有这种用法,这种情况下,直接调用内核提供的kobject_create来实现,不需要自定义数据结构并内嵌kobject),而且,创建子目录也是有其他的办法的。
我们知道,Linux设备模型最初的目的是为了方便电源管理,这就需要从上到下的遍历,在这种架构里,通过obj1并无法访问其所有的子对象。
这个实作最大的意义在于可以让我们比较清晰的理解kobject如何使用。
通常情况下,kobject只需要在叶节点里使用,上层的节点要使用kset。
Kset
Kset的定义如下:
structkset{
structlist_headlist;
spinlock_tlist_lock;
structkobjectkobj;
conststructkset_uevent_ops*uevent_ops;
};
Kset结构里的kobj表明它也是一个kobject,list变量用来组织它所有的子对象。
我们直接看一个实作。
在这个实作里,我们将构建如下的架构。
对应sysfs里的目录关系是:
my_kset/
|--mykobj1
| |--name
| `--val
`--mykobj2
|--name
`--val
这个实作和前一个差别很小,下面只简略的引用一些代码。
staticint__initmykset_init(void)
{
printk(KERN_INFO"mykset_init\n");
my_kset=kset_create_and_add("my_kset",NULL,NULL);
if(!
my_kset){
return-ENOMEM;
}
//Allocateobj1andobj2
//...
obj1->kobj.kset=my_kset;
obj2->kobj.kset=my_kset;
//Initmy_type
//...
kobject_init_and_add(&obj1->kobj,&my_type,NULL,"mykobj1");
kobject_init_and_add(&obj2->kobj,&my_type,NULL,"mykobj2");
return0;
}
staticvoid__exitmykset_exit(void)
{
printk(KERN_INFO"mykset_exit\n");
//Releaseobj1andobj2
//...
kset_unregister(my_kset);
return;
}
在module_init里,我们首先调用kset_create_and_add创建my_kset,接下来把my_kset赋给obj1和obj2,最后调用kobject_init_and_add来添加obj1和obj2。
这里需要注意的是,kobject_init_and_add参数里的parent都是NULL,在这种情况下,obj1和obj2的父对象由kobject结构里的kset指针决定,在这个实作里就是my_kset。
在module_exit里,我们还需要额外调用kset_unregister来释放之前创建的my_kset。
注:
看过LDD3的读者应该对LinuxDeviceModel一章中的subsystem还有印象,我在这里注明一下,从2.6.23开始Linux内核就抛弃了subsystem,subsystem其实只是kset的一个马甲,所以抛弃它对Linux设备模型并没什么影响。
Linux设备模型(3)
2010-12-2119:
51bywwang,2211visits,收藏,编辑
在上文中,我们介绍到如何使用defaultattribute。
Defaultattribute使用很方便,但不够灵活。
比如上篇文章在Kobject一节中提到的那个例子,name和val这两个attribute使用同一个show/store函数来访问,如果attribute非常多,show/store函数里的分支就会很凌乱。
为了解决这个问题,我们可以参考内核提供的kobj_attribute。
在内核里,kobj_attibute是这样定义的:
viewsourceprint?
1
structkobj_attribute{
2
structattributeattr;
3
ssize_t(*show)(structkobject*kobj,structkobj_attribute*attr,
4
char*buf);
5
ssize_t(*store)(structkobject*kobj,structkobj_attribute*attr,
6
constchar*bu