http://blog.chinaunix.net/uid-22547469-id-4590385.html?utm_source=jiancool
Linux设备模型就是一栋规模宏大的建筑,为了构建它,需要基本的建筑材料钢筋:kobject、若干钢筋组成的钢架结构:kset,还需要一种机制sysfs,来向外界(用户空间的程序)展示其内部构造。并且通过文件接口的方式实现与外界沟通与互动。
设备文件系统:Linux下一切皆是文件,而设备文件系统包括设备文件、设备节点以及设备特定文件,它们是驱动程序的接口 ,而在文件系统中,他们就像普通文件一样。
devfs:是一种Linux2.4内核引入的设备文件系统,用来解决Linux中设备管理混乱的问题位于内核空间,可以通过程序在设备初始化时在/dev下创建设备文件,卸载时将它删除,现已被udev取代。
udev:udev是一种工具,它能够根据系统中的硬件设备的状况动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev目录下,使用udev后,在/dev下面只包含系统中真实存在的设备。它于硬件平台无关的,位于用户空间,需要内核sysfs和tmpfs的支持,sysfs为udev提供设备入口和uevent通道,tmpfs为udev设备文件提供存放空间。
mdev:在嵌入式系统中,通常可以用udev的轻量级版本mdev,集成于busybox。
sysfs:是Linux2.6所提供的一种跟devfs一样的虚拟文件系统,基于ramfs(内存文件系统)开发,挂在到/sys目录,也是用来对系统的设备进行管理的。sysfs以文件目录层次结构展示了系统硬件设备间的一个拓扑图。
设备、驱动、总线:这些都是对象,已经是宏伟建筑给外界的展现形式的那部分了,主要是与这三个打交道。
详细实现:
1、kobject[钢筋]
kobject是组成设备模型的一个最底层的核心数据结构,与sysfs文件系统自然的绑定在一起:每个kobject对应sysfs文件系统中一个目录。kobject结构所能处理的任务以及所支持的代码包括:
(1)引用计数;
(2)维持容器的层次列表和组。
(3)为容器的属性提供一种用户态查看的视图。
(4)热插拔事件的处理:当系统中的硬件热插拔时,在kset(其内嵌有kobject)内核对象的控制下,将产生事件以通知用户空间,该结构一般没有单独定义而是嵌入到其他设备结构体中。
kobject定义如下:
点击(此处)折叠或打开
- <kobject.h>
- struct kobject{
- const char *name; //该内核对象的名称,在sysfs中,表现形式是一个新的目录名
- struct list_head entry;//用来将一些列的内核对象构成链表
- struct kobject *parent;//指向设备分层的上一层的节点,构建层次化关系
- struct kset *kset; //所属的kset对象的指针
- struct kobj_type ktype;//用于保存属性,在sysfs中表现为属性文件
- struct sysfs_dirent *sd;
- struct kref kref;//对象引用计数
- ......
- }
创建kobject的时候,都回、会给每个kobject一系列默认属性。这些属性保存在kobj_type结构中。
点击(此处)折叠或打开
- <kobject.h>
- struct kobj_type {
- void (*release)(struct kobject *);
- struct sysfs_ops * sysfs_ops;//真正实现这些属性
- struct attribute ** default_attrs;//属性列表,最后一个元素用零填充。
- };
- <sysfs.h>
- struct attribute {
- const char * name;//属性的名字
- struct module * owner;//指向模块的指针
- mode_t mode;//保护位,只读:S_IRUGO。可写:S_IWUSR
- };
- struct sysfs_ops {
- ssize_t (*show)(struct kobject *, struct attribute *,char *);
- ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t);
- }
kobject一般不单独定义,常内嵌于其他结构体中。
经常遇到相反的问题,对于给定的一个kobject指针,如何获得包含他的结构指针呢?嘎嘎,大名鼎鼎的container_of宏有有用了。
比如kp是指向kobj的指针,而kobj是cdev device的一个成员,如何根据kp获取只想cdev的指针,如下:
struct cdev *device=container_of(kp,struct cdev,kobj);
kobject相关函数:
(1)初始化前第一个操作,将整个kobject设置为0,通常使用memset函数,例如:memset(&kobj,0,sizeof(struct kobject));
(2)设定kobject中的name成员,使用如下函数:
int kobject_set_name(struct kobject *kobj,const char *fmt,...)
(3)初始化一个内核对象的kobject结构,建立kojbect对象间的层次关系,在sysfs中建立一个目录:
int kobject_init_and_add(struct kobject *kobj,
struct kobj_type ktype,struct kobject *parent,const char *fmt,...)
(4)从linux设备层次中删除kobj对象:
void kobject_del(struct kobject *kobj);
(5)增加kobject引用计数:
struct kobject * kobject_get(struct kobject * kobj)
(6)减少kobject引用计数:
void kobject_put(struct kobject * kobj)
(7)每个kobject都必须有一个release方法,该方法保存在kobject相关联的kobj_type中。
在sysfs中,kobject对应目录,kobject的属性对应这个目录下的文件。调用show和store函数来读写文件,就可以得到属性中的内容。
点击(此处)折叠或打开
- kobject.c
- #include <linux/device.h>
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/init.h>
- #include <linux/string.h>
- #include <linux/sysfs.h>
- #include <linux/stat.h>
- #include <linux/mm.h>
-
- /* 每个kobject必须有一个release方法,该方法保存在kobject相关联的kobj_type中 */
- void kobj_release(struct kobject *kobj)
- {
- printk("obj_release!\n");
- }
-
- ssize_t ops_show(struct kobject *kobj,struct attribute *attr,char *c)
- {
- printk("ops_show\n");
- printk("attr->name:%s \n",attr->name);
- return strlen(attr->name);
- }
-
- ssize_t ops_store(struct kobject *kobj,struct attribute *attr,const char *buf,size_t count)
- {
- printk("ops_store\n");
- printk("buf:%s\n",buf);
- return count;
- }
-
- struct attribute first_attr={
- .name="first_attr",//属性名字
- .mode=S_IRWXUGO, //读写执行权限
- };
- struct attribute second_attr={
- .name="second_attr",//属性名字
- .mode=S_IRUGO, //只读
- };
- static struct attribute *def_attrs[]={//指针数组,每个成员存储了一个attribute的地址。
- &first_attr,
- &second_attr,
- NULL,
- };
- struct sysfs_ops sysops={ //定义sysops
- .show = ops_show,
- .store = ops_store,
- };
-
- struct kobj_type ktype={ //定义ktype
- .release = kobj_release, //每个kojbect必须包含的release方法
- .sysfs_ops = &sysops, //实现属性的函数
- .default_attrs = def_attrs,//属性列表,二级指针,最后一个元素必须用NULL填充
- };
-
- struct kobject kobj; //定义kobject结构体变量
-
- static __init int kobj_test_init(void) { //加载kobject.ko时执行该函数
- printk("\n kobj_test_init\n");
- memset(&kobj,0,sizeof(struct kobject));
- printk("memset kobj\n");
- kobject_init_and_add(&kobj,&ktype,NULL,"kobject_test");
- printk("kobject_init_and_add\n");
- return 0;
- }
-
- static __exit void kobj_test_exit(void) { //卸载kobject时执行此函数
- printk("kobj_test_exit\n");
- kobject_del(&kobj);
- //return 0;
- }
-
- module_init(kobj_test_init);
- module_exit(kobj_test_exit);
- MODULE_AUTHOR("MANGO");
- MODULE_LICENSE("Dual BSD/GPL")
编译完成后生成kobject.ko文件,执行sudo insmod kobject.ko
加载成功,因该kobject的parent是NULL,会在sys的目录之间建立如下文件:
打开文件夹后,会看到两个属性文件:
first_attr是777权限,second_attr是只读权限,和程序中写的相同。
接下来,执行 cat first_attr,会发现驱动层自动调用了ktype中的sysfs_ops的show函数:
同理,执行echo “123” > first_attr,应该调用ops_store函数,执行结果如下:
和预想完全相同。同理可测试second_attr文件,只是不能写而已。
2、kset[由若干个kobject(钢筋)组成的钢架结构]
kset是同种类型的kobject对象的集合,也可以说是对象的容器,通过kset数据结构可以将kobjects组成一颗层次树。kset的名字保存在内嵌的kobject中。
包含在kset中的所有kobject被组织成一个双向循环链表,list是这个链表的链表头。kset数据结构还内嵌了一个kobject对象(kobj),所有属于这个kset的kobject对象的parent域均指向这个内嵌的对象。此外,kset还依赖kobj维护引用计数。
kset和它的kobject的关系如下图所示:
黑线:kset child list
绿线:kobject->parent
蓝线:kobject->kset
注意:一个kobject的父节点不一定是包含他的kset。
数据结构如下:
点击(此处)折叠或打开
- struct kset {
- struct kobj_type * ktype;
- struct list_head list;//用于连接该kset中所有kobject的链表头
- spinlock_t list_lock;//用于互斥访问
- struct kobject kobj;//嵌入的kobject
- struct kset_uevent_ops * uevent_ops;
- }
kset相关函数:
创建一个对象时,通常要把一个kobject添加到kset中去。两个步骤:
先把kobject内的kset指针指向目的kset
int kobject_add(struct kobject *kobj);
(1)初始化一个kset对象:
void kset_init(struct kset * k);
(2)用来初始化并向系统注册一个kset对象:
int kobject_register(struct kobject *kobj);
(3)注销kset
void kset_unregister(struct kset *k);
(4)某些时候,将kobject从kset中删除,使用:
void kobject_del(struct kobject *kobj);
点击(此处)折叠或打开
- kset.c
- #include <linux/device.h>
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/init.h>
- #include <linux/string.h>
- #include <linux/sysfs.h>
- #include <linux/stat.h>
- #include <linux/mm.h>
-
- struct kset kset_p; //父级kset结构体变量
- struct kset kset_c; //子级kset结构体变量
-
- int kset_filter(struct kset *kset,struct kobject *kobj) {
- printk("kset_filter()kobj->name:%s\n",kobj->name);
- return 1;
- }
- const char *kset_name(struct kset *kset,struct kobject *kobj) {
- static char buf[20];
- printk("kset_name()kobj->name:%s\n",kobj->name);
- sprintf(buf,"%s","kset_name");
- return buf;
- }
-
- int kset_uevent(struct kset *kset,struct kobject *kobj,struct kobj_uevent_env *env){
- int i=0;
- printk("kset_uevent()kobj->name:%s\n",kobj->name);
- printk("env:\n");
- while(i<env->envp_idx){
- printk("%s\n",env->envp[i]);
- i++;
- }
- return 0;
- }
- struct kset_uevent_ops uevent_ops={
- .filter = kset_filter,
- .name = kset_name,
- .uevent = kset_uevent,
- };
-
- struct kobj_type my_kobj_type; //定义ktype变量
-
- static __init int kset_test_init(void) { //sudo insmod kset.ko调用此函数
- printk("\n kset_test_init\n");
- kobject_set_name(&kset_p.kobj,"kset_p"); //由于kset名字保存在内嵌的kobject中
- kset_p.uevent_ops=&uevent_ops; //事件通知机制。
- kset_p.kobj.ktype=&my_kobj_type; //为了规避oops的问题。
- if(kset_register(&kset_p)){ //向系统注册一个kset对象
- printk("register kset p error\n");
- return -1;
- }
- kobject_set_name(&kset_c.kobj,"kset_c");
- kset_c.kobj.kset=&kset_p;
- kset_c.kobj.ktype=&my_kobj_type;
- if(kset_register(&kset_c)){
- printk("register kset c error\n");
- return -1;
- }
- return 0;
- }
-
- static __exit void kset_test_exit(void) {
- printk("\nkset exit\n");
- kset_unregister(&kset_p);
- kset_unregister(&kset_c);
- }
-
- module_init(kset_test_init);
- module_exit(kset_test_exit);
- MODULE_AUTHOR("MANGO");
- MODULE_LICENSE("Dual BSD/GPL");
执行sudo insmod kset.ko:出现如下信息;
在/sys文件夹,会生成kset_p文件夹,打开该文件夹后,出现kset_c文件夹。
3、子系统(一个子系统其实就是对kset和一个信号量的封装)
点击(此处)折叠或打开
- struct subsystem{
- struct kset kset;
- struct rw_semaphore rwsem;
- }
子系统函数列表如下:
void subsystem_init(struct kset *);
int subsystem_register(struct kset *);
void subsystem_unregister(struct kset *);
struct kset *subsys_get(struct kset *s);
void subsys_put(struct kset *s);
4、属性
非默认属性
如果希望在kobject的sysfs目录中添加新的属性,只需要填写一个attribute结构,并把它传递给下面的函数。
int sysfs_create_file(struct kobject *kobj,struct attribute *attr);
删除属性:
int sysfs_remove_file(struct kobject *kobj,struct attribute *attr);
二进制属性(比如支持向设备上载固件的功能)
先定义一个二进制属性结构:
struct bin_attribute {
struct attribute attr;//名字,所有者,二进制属性的权限
size_t size;//最大长度,如果没有最大值设置为0
void *private;
ssize_t (*read)(struct kobject *, char *, loff_t, size_t);
ssize_t (*write)(struct kobject *, char *, loff_t, size_t);
int (*mmap)(struct kobject *, struct bin_attribute *attr,
struct vm_area_struct *vma);
};
必须显式的创建二进制属性
int sysfs_create_bin_file(struct kobject *kobj,struct bin_attribute *attr);
void sysfs_remove_bin_file(struct kobject *kobj,struct bin_attribute *attr);
符号链接
sysfs具有常用的树形结构,但是内核中各对象之间的关系远比这复杂。这些树并不能表示驱动程序及其管理的设备之间的关系,为了表示这种关系还需要其他的指针,那就是符号链接。
int sysfs_create_link(struct kobject *kobj,struct kobject *target,const char *name);
void sysfs_remove_link(struct kobject *, const char * name);
5、热插拔机制(从内核空间发送到用户空间的通知)
当一个设备动态加入系统时(比如插入一个U盘),设备驱动程序可以检查到这种设备状态的变化(加入或移除),然后通过某种机制使得在用户空间找到该设备对应的驱动程序模块并加载之。在linux系统上有两种机制可以在设备状态发生变化时,通知用户空间去加载或者卸载该设备所对应的驱动程序模块:一个是udev,另一个是/sbin/hotplug。在Linux早期阶段只有唯一的/sbin/hotplug工具,随着内核的发展,udev机制逐渐取代了/sbin/hotplug。很多ubuntu系统已经不提供/sbin/hotplug工具了。
udev热插拔机制:以守护进程的形式运行,通过侦听内核发出来的 uevent 来管理 /dev目录下的设备文件。不像之前的设备管理工具,udev 在用户空间 (user space) 运行,而不在内核空间 (kernel space) 运行。(验证:ps aux | grep udevd);当设备添加 / 删除时,udev的守护进程侦听来自内核的uevent,以此添加或者删除 /dev下的设备文件,所以udev只为已经连接的设备产生设备文件,而不会在/dev下产生大量虚无的设备文件;通过 Linux 默认的规则文件,udev在/dev/ 里为所有的设备定义了内核设备名称,比如/dev/sda、/dev/hda、/dev/fd等等。由于udev是在用户空间(user space)运行,Linux 用户可以通过自定义的规则文件,灵活地产生标识性强的设备文件名,比如 /dev/boot_disk、/dev/root_disk、/dev/color_printer等等。