以《LDD3》的说法:Linux设备模型这部分内容可以认为是高级教材,对于多数程序作者来说是不必要的。但是我个人认为:对于一个嵌入式Linux的底层程序员来说,这部分内容是很重要的。以我学习的ARM9为例,有很多总线(如SPI、IIC、IIS等等)在Linux下已经被编写成了子系统,无需自己写驱动;而这些总线又不像PCI、USB等在《LDD3》上有教程,有时还要自己研究它的子系统构架,甚至要自己添加一个新的总线类型。
对于这方面的学习,以下内容部分出自《LDD3》,部分出自国嵌,部分出自多位大牛博客。
---------------------------------------------------------------------------------------------------------------------------------
Linux设备模型的目的是:
为内核建立起一个统一的设备模型,从而有一个对系统结构的一般性抽象描述。
2.6 设备模型提供了这个抽象. 现在它用在内核来支持不同的任务, 包括
电源管理和系统关机:这些需要对系统结构的理解,设备模型使OS能以正确顺序遍历系统硬件。
与用户空间的通讯 : sysfs 虚拟文件系统的实现被紧密地捆绑进设备模型, 并且暴露它所代表的结构. 关于系统到用户空间的信息提供和改变操作参数的旋纽正越来越多地通过 sysfs 和 通过设备模型来完成.
可热插拔设备:计算机硬件正更多地动态变化; 外设可因用户的一时念头而进出. 在内核中使用的来处理和(特别的)与用户空间关于设备插入和拔出的通讯, 是由设备模型来管理
设备类别:系统的许多部分需要知道什么类型的设备可用. 设备模型包括一个机制来分配设备给类别, 它在一个更高的功能性的级别描述了这些设备, 并且允许它们从用户空间被发现.
对象生命期:设备模型的实现需要创建一系列机制来处理对象的生命周期、对象间的关系和对象在用户空间的表示。
----------------------------------------------------------------------------------------------------------------------
一、sysfs初探
sysfs is a ram-based filesystem initially based on ramfs. It provides
sysfs 是一个最初基于ramfs的位于内存的文件系统。它提供
a means to export kernel data structures, their attributes, and the
一些方法以导出内核的数据结构、他们的属性和
linkages between them to userspace.
他们与用户空间的连接。
sysfs is tied inherently to the kobject infrastructure. Please read
sysfs 始终与kobject的底层结构紧密相关。请阅读
Documentation/kobject.txt for more information concerning the kobject
Documentation/kobject.txt 文档以获得更多关于 kobject 接口的信息。
interface.
可以先把documentation/filesystems/sysfs.txt读一遍。文档这种东西,真正读起来就嫌少了。
Sysfs文件系统是一个类似于proc文件系统的特殊文件系统,用于将系统中的设备组织成层次结构,并向用户模式程序提供详细的内核数据结构信息。
去/sys看一看,
localhost:/sys#ls /sys/
block/ bus/ class/ devices/ firmware/ kernel/ module/ power/
Block目录:包含所有的块设备
Devices目录:包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构
Bus目录:包含系统中所有的总线类型
Drivers目录:包括内核中所有已注册的设备驱动程序
Class目录:系统中的设备类型(如网卡设备,声卡设备等)
sys下面的目录和文件反映了整台机器的系统状况。比如bus,
localhost:/sys/bus#ls
i2c/ ide/ pci/ pci express/ platform/ pnp/ scsi/ serio/ usb/
里面就包含了系统用到的一系列总线,比如pci, ide, scsi, usb等等。比如你可以在usb文件夹中发现你使用的U盘,USB鼠标的信息。
我们要讨论一个文件系统,首先要知道这个文件系统的信息来源在哪里。所谓信息来源是指文件组织存放的地点。比如,我们挂载一个分区,
mount -t vfat /dev/hda2 /mnt/C
我们就知道挂载在/mnt/C下的是一个vfat类型的文件系统,它的信息来源是在第一块硬盘的第2个分区。
但是,你可能根本没有去关心过sysfs的挂载过程,她是这样被挂载的。
mount -t sysfs sysfs /sys
ms看不出她的信息来源在哪。sysfs是一个特殊文件系统,并没有一个实际存放文件的介质。断电后就玩完了。简而言之,sysfs的信息来源是kobject层次结构,读一个sysfs文件,就是动态的从kobject结构提取信息,生成文件。
所以,首先,我要先讲一讲sysfs文件系统的信息来源 -- kobject层次结构。kobject层次结构就是linux的设备模型。
二、Kobject、Kset 和 Subsystem
1)Kobject
Kobject实现基本的面向对象管理机制,是构成LInux2.6设备模型的核心结构。
它与sysfs文件系统紧密相连,在内核中注册每个kobject对象对应sysfs文件系统中的一个目录。
kobject是一种数据结构,定义在
struct kobject {
const char * k_name;/*指向设备名称的指针 */
char name[KOBJ_NAME_LEN];/*kobject 的名字数组,设备名称*/
struct kref kref;/*kobject 的引用计数*/
struct list_head entry;/*kobject 之间的双向链表,与所属的kset形成环形链表*/
struct kobject * parent;/*在sysfs分层结构中定位对象,指向上一级kset中的struct kobject kobj*/
struct kset * kset;/*指向所属的kset*/
struct kobj_type * ktype;/*负责对该kobject类型进行跟踪的struct kobj_type的指针*/
struct dentry * dentry;/*sysfs文件系统中与该对象对应的文件节点路径指针*/
wait_queue_head_t poll;/*等待队列头*/
};
kobject 是组成设备模型的基本结构,初始它只被作为一个简单的引用计数, 但随时间的推移,其任务越来越多。现在kobject 所处理的任务和支持代码包括:
对象的引用计数 :跟踪对象生命周期的一种方法是使用引用计数。当没有内核代码持有该对象的引用时, 该对象将结束自己的有效生命期并可被删除。
sysfs 表述:在 sysfs 中出现的每个对象都对应一个 kobject, 它和内核交互来创建它的可见表述。
数据结构关联:整体来看, 设备模型是一个极端复杂的数据结构,通过其间的大量链接而构成一个多层次的体系结构。kobject 实现了该结构并将其聚合在一起。
热插拔事件处理 :kobject 子系统将产生的热插拔事件通知用户空间。
一个kobject对自身并不感兴趣,它存在的意义在于把高级对象连接到设备模型上。因此内核代码很少(甚至不知道)创建一个单独的 kobject;而kobject 被用来控制对大型域(domain)相关对象的访问,所以kobject 被嵌入到其他结构中。kobject 可被看作一个最顶层的基类,其他类都它的派生产物。 kobject 实现了一系列方法,对自身并没有特殊作用,而对其他对象却非常有效。
对于给定的kobject指针,可使用
container_of宏得到包含它的结构体的指针。
kobject 初始化
kobject的初始化较为复杂,但是
必须的步骤如下:
(1)将整个kobject清零,通常使用memset函数。
(2)调用kobject_init()函数,设置结构内部一些成员。所做的一件事情是设置kobject的引用计数为1。
void kobject_init(struct kobject *kobj);
struct kobject{
const char * k_name;/*kobject 的名字数组(sysfs 入口使用的名字)指针;如果名字数组大小小于KOBJ_NAME_LEN,它指向本数组的name,否则指向另外分配的一个名字数组空间
*/
char name[KOBJ_NAME_LEN];/*kobject 的名字数组,若名字数组大小不小于KOBJ_NAME_LEN,只储存前KOBJ_NAME_LEN个字符*/
struct kref kref;/*kobject 的引用计数*/
struct list_head entry;/*kobject 之间的双向链表,与所属的kset形成环形链表*/
struct kobject * parent;/*在sysfs分层结构中定位对象,指向上一级kset中的struct kobject kobj*/
struct kset * kset;/*指向所属的kset*/
struct kobj_type * ktype;/*负责对该kobject类型进行跟踪的struct kobj_type的指针*/
struct dentry * dentry;/*sysfs文件系统中与该对象对应的文件节点路径指针*/
wait_queue_head_t poll;/*等待队列头*/
};
(3)设置kobject的名字
int kobject_set_name(struct kobject *kobj, const char *format, ...);
(4)直接或间接设置其它成员:
ktype、kset和parent。
(重要)
对引用计数的操作
kobject 的一个重要函数是为包含它的结构设置引用计数。只要对这个对象的引用计数存在, 这个对象( 和支持它的代码) 必须继续存在。底层控制 kobject 的引用计数的函数有:
struct kobject *kobject_get(struct kobject *kobj);/*若成功,递增 kobject 的引用计数并返回一个指向 kobject 的指针,否则返回 NULL。必须始终测试返回值以免产生竞态*/
void kobject_put(struct kobject *kobj);/*递减引用计数并在可能的情况下释放这个对象*/
release 函数kobject 类型
一个重要事情是当一个 kobject 的引用计数到 0 时会发生什么?
引用计数不由创建 kobject 的代码直接控制,当 kobject 的最后引用计数消失时,必须异步通知,而后kobject中ktype所指向的kobj_type结构体包含的release函数当kobject引用计数为0时会被调用,释放kobject占用的资源。通常原型如下:
void my_object_release(struct kobject *kobj)
{
struct my_object *mine = container_of(kobj, struct my_object, kobj);
/* Perform any additional cleanup on this object, then... */
kfree(mine);
}
每个 kobject 必须有一个release函数, 并且这个 kobject 必须在release函数被调用前保持不变( 稳定状态 ) 。 这样,每一个 kobject 需要有一个关联的 kobj_type 结构,指向这个结构的指针能在 2 个不同的地方找到
:(1)kobject 结构自身包含一个成员(ktype)指向kobj_type (kobj_type 结构里包含kobject对象的一些属性);
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
kobje_type的attribute成员:
struct attribute{
char*name;//属性文件名
structmodule *owner;
mode_tmode;
}
struct attribute(属性):对应于kobject的目录下一个文件,name就是文件名。
struct sysfs_ops
{
ssize_t (*show)(structkobejct *, struct attribute *, char *name);
ssize_t (*store)(structkobejct *, struct attribute *, char *name);
}
show:当用户读属性文件时,该函数被调用,该函数将属性值存入buffer中返回给用户态;
store:当用户写属性文件时,该函数被调用,用于存储用户存入的属性值。show相当于read,store相当于write
。
(2)如果这个 kobject 是一个 kset 的成员, kset 会提供kobj_type 指针。
struct kset {
struct kobj_type * ktype; /*指向该kset对象类型的指针*/
struct list_head list;/*用于连接该kset中所有kobject以形成环形链表的链表头*/
spinlock_t list_lock;/*用于避免竞态的自旋锁*/
struct kobject kobj; /*嵌入的kobject*/
struct kset_uevent_ops * uevent_ops;
/*原有的struct kset_hotplug_ops * hotplug_ops;已经不存在,被kset_uevent_ops 结构体替换,在热插拔操作中会介绍*/
};
kobject注册函数,该函数只是kobjec_init和kobject_add_varg的简单组合。
extern int __must_check kobject_init_and_add(struct kobject *kobj,struct kobj_type *ktype,struct kobject *parent,const char *fmt, ...);
kobject 删除
void kobject_del(struct kobject *kobj);
Kobject测试程序:
#include
#include
#include
#include
#include
#include
#include
MODULE_AUTHOR("David Xie");
MODULE_LICENSE("Dual BSD/GPL");
void obj_test_release(struct kobject *kobject);
ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf);
ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count);
struct attribute test_attr = {
.name = "kobj_config",
.mode = S_IRWXUGO,
};
static struct attribute *def_attrs[] = {
&test_attr,
NULL,
};
struct sysfs_ops obj_test_sysops =
{
.show = kobj_test_show,
.store = kobj_test_store,
};
struct kobj_type ktype =
{
.release = obj_test_release,
.sysfs_ops=&obj_test_sysops,
.default_attrs=def_attrs,
};
void obj_test_release(struct kobject *kobject)
{
printk("eric_test: release .
");
}
ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf)
{
printk("have show.
");
printk("attrname:%s.
", attr->name);
sprintf(buf,"%s
",attr->name);
return strlen(attr->name)+2;
}
ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count)
{
printk("havestore
");
printk("write: %s
",buf);
return count;
}
struct kobject kobj;
static int kobj_test_init()
{
printk("kboject test init.
");
kobject_init_and_add(&kobj,&ktype,NULL,"kobject_test");
return 0;
}
static int kobj_test_exit()
{
printk("kobject test exit.
");
kobject_del(&kobj);
return 0;
}
module_init(kobj_test_init);
module_exit(kobj_test_exit);
程序分析:
----》kobj_test_init(),----》kobject_init_and_add(&kobj,&ktype,NULL,"kobject_test");----》为NULL,创建在sysfs/
----》struct kobject kobj;没赋值,正常使用
----》struct kobj_type ktype,记录kobject一些属性和操作
----》def_attrs----》指针数组,一个指针代表创建一个文件。test_attr----》文件名字kobj_config,读写权限S_IRWXUGO
----》obj_test_sysops----》.读时调用 kobj_test_show函数,写时调用kobj_test_store函数
----》obj_test_release
测试效果:
1)在sys下创建一个子目录kobject_test
2)在kobject_test创建一个文件kobj_config
3)当读kobj_config时,打印信息
4)当写kobj_config时,打印信息
----------------------------------------------------------------------------------------------------------------------
2)Kset
kset是具有相同类型的kobject的集合,在sysfs中体现成一个目录,在内核中用kset数据结构表示。
一个 kset 的主要功能是
容纳; 它可被当作顶层的给 kobjects 的容器类. 实际上, 每个 kset 在内部容纳它自己的 kobject, 并且它可以, 在许多情况下, 如同一个 kobject 相同的方式被对待. 值得注意的是 ksets 一直在 sysfs 中出现; 一旦一个 kset 已被建立并且加入到系统, 会有一个 sysfs 目录给它. kobjects 没有必要在 sysfs 中出现,
但是每个是 kset 成员的 kobject 都出现在那里.
通俗的讲,kobject建立一级的子目录,里面只能包含文件;kset可以为kobject建立多级的层次性的父目录。
struct kset {
struct subsystem * subsys; 所在的subsystem的指针
struct kobj type * ktype; 指向该kset对象类型描述符的指针
struct list head list; 用于连接该kset中所有kobject的链表头
struct kobject kobj; 嵌入的kobject
struct kset_uevent_ops * uevent_ops; 指向热插拔操作表的指针
};
kset操作:
void kset_init(struct kset *kset);
int kset_add(struct kset *kset);
int kset_register(struct kset *kset);
void kset_unregister(struct kset *kset);
热插拔事件:在linux系统中,
当系统配置发生变化时,如添加kset到系统或移动kobject,
一个通知会从内核空间发送到用户空间,这就是热插拔事件。热插拔事件会导致用户空间中的处理程序(如udev,mdev)被调用,这些处理程序会通过加载驱动程序,创建设备节点等来响应热插拔事件。比如,当数码相机通过USB线缆插入到系统时。热插拔事件会导致对/sbin/hotplug程序的调用,该程序通过加载驱动程序,创建设备节点,挂装分区,或者其他正确的动作来响应。
对热插拔事件的实际控制是由struct kset_uevent_ops结构中的函数完成的。
struct kset_uevnt_ops{
int (*filter)(struct kset *kset,struct kobject *kobj);
const char *(*name)(struct kset *kset, struct kobject *kobj );
int (*uevent)(struct kset *kset,struct kobject *kobj,struct kobj_uevent *env);
}
当kset下的kset或者kobject发生变化时,上面三个函数被调用。
功能:
filter决定是否产生事件,如果返回0,将不产生事件。
name向用户空间传递一个合适的字符串
uevent通过环境变量传递任何热插拔脚本需要的信息,他会在(udev或mdev)调用之前,提供添加环境变量的机会。
kset测试程序:
#include
#include
#include
#include
#include
#include
#include
#include
MODULE_AUTHOR("David Xie");
MODULE_LICENSE("Dual BSD/GPL");
struct kset kset_p;
struct kset kset_c;
int kset_filter(struct kset *kset, struct kobject *kobj)
{
printk("Filter: kobj %s.
",kobj->name);
return 1;
}
const char *kset_name(struct kset *kset, struct kobject *kobj)
{
static char buf[20];
printk("Name: kobj %s.
",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("uevent: kobj %s.
",kobj->name);
while( i < env->envp_idx){
printk("%s.
",env->envp[i]);
i++;
}
return 0;
}
struct kset_uevent_ops uevent_ops =
{
.filter = kset_filter,
.name = kset_name,
.uevent = kset_uevent,
};
int kset_test_init()
{
printk("kset test init.
");
kobject_set_name(&kset_p.kobj,"kset_p");
kset_p.uevent_ops = &uevent_ops;
kset_register(&kset_p);
kobject_set_name(&kset_c.kobj,"kset_c");
kset_c.kobj.kset = &kset_p;
kset_register(&kset_c);
return 0;
}
int kset_test_exit()
{
printk("kset test exit.
");
kset_unregister(&kset_p);
kset_unregister(&kset_c);
return 0;
}
module_init(kset_test_init);
module_exit(kset_test_exit);
程序分析:
---》kset_test_init初始化kset---》kobject_set_name(&kset_p.kobj,"kset_p");---》创建一个struct kset kset_p,目录名字是kset_p
---》注册kset_register(&kset_p);----》添加一个目录kset_p
---》kobject_set_name(&kset_c.kobj,"kset_c");---》创建一个struct kset kset_c,目录名字是kset_c
---》kset_c.kobj.kset = &kset_p; kset_c的父目录是kset_p,也就是讲kset_p下有一个目录kset_c
---》注册kset_register(&kset_c);----》添加一个目录kset_c
---》当在kset_p下增加一个kset_c目录,发生热插拔事件,调用uevent_ops ---》kset_filter被调用,返回0,不传递事件,返回1,传递
---》kset_name打印名字
---》kset_uevent打印信息
测试效果:
1)sys下有kset_p/kset_c
2)打印热插拔信息
如果将kset_c.kobj.kset = &kset_p;这行注释掉,也就是不产生热插拔事件,它就不打印kset_uevent的信息
原因在于当kset下的kset或者kobject发生变化时,在kset_p下增加一个kset_c目录,就会调用kset_uevnt_ops下的三个函数