sysfs文件系统(2.6.11)
sysfs文件系统是一个特殊的文件系统。与/proc相似,通常被安装在/sys目录。/proc文件系统是首次被设计成允许用户态应用程序访问内核内部数据结构的一种文件系统。/sysfs文件系统本质上和/proc有相同的目的。但是它还提供关于内核数据结构的附加信息;此外,/sysfs的组织结构比/proc更有条理。或许,在不远的将来,/proc和/sysfs将会继续共存。
sysfs文件系统的目标是要展现设备驱动程序模型组件间的层次关系,它所表示的设备驱动程序模型组件之间的关系就像目录和文件之间符号链接的关系一样(深入理解linux操作系统P526)。
kobject
设备驱动程序模型的核心数据结构是kobject,它与sysfs文件系统自然的绑定在一起:每个kobject对应于sysfs文件系统中的一个目录。kobject被嵌入一个叫做“容器”的更大对象中,容器描述设备驱动程序模型中的组件。容器的典型例子有总线,设备,以及驱动程序的描述符。
将一个kobject嵌入容器中允许内核:
1. 为容器保持一个引用计数。
2. 维持容器的层次列表或组(例如,与块设备相关的sysfs目录为每个磁盘分区包含一个不同的子目录)
3. 为容器的属性提供一种用户态查看的视图。
数据结构:
struct kobject {
const char * k_name;
char name[KOBJ_NAME_LEN];
struct kobj_type * ktype;
};
k_name : 指向含有容器名称的字符串
name : 含有容器名称的字符串,如果他不超过20个字节
kref : 容器的引用计数器
entry : 用于kobject所插入的链表的指针
parent :指向父kobject(如果有)
kset : 指向包含的kset
ktype : 指向kobject的类型描述符
dentry :指向与kobject相对应的sysfs文件的dentry数据结构
kset
122 structkset_uevent_ops * uevent_ops;
123 };
subsys : 指向subsystem描述符
ktype : 指向kset的kobject类型描述符
list : 包含在kset中的kobject链表首部
kobj : 嵌入的kobject结构
hotplug_ops : 指向用于处理kobject结构的过滤和热插拔操作的回调函数表
subsystem
167 structrw_semaphore rwsem;
168 };
kset : 指向嵌入的kset
rwsem : 串行访问kset内部的链表
subsystem 的结构如下:
一个subsystem包含一个内嵌的kset,而kset包含一个内嵌的kobject。每个kset都必须属于一个子系统。然而,每个kobject可以不属于kset,但他必须属于某种类型(作为嵌入结构)。
设备模型的层次结构
看了很久都不知道设备模型的层次结构是如何组织的,于是对Linux源码(2.6.11)进行了跟踪观察。再结合网上资料,特别是http://blog.csdn.net/lijierson8/article/details/5939165博客中所提及的内容,让我有了点灵感。首先看看这个层次结构:
一个bus子系统包含了一个pci子系统,pci子系统又依次包含驱动程序的一个kset。这个kset包含了一个窗口kobject(具有唯一new_id属性的串口对应的设备驱动)【深入理解Linux内核 page529】。
再来看看我理解的subsystem, kset, kobject他们的关系拓扑图:
_
网上有个简版:
1.在图中所有被包含的kobject,实际上是被嵌入到其他类型中,甚至可能是其他的kset
2.一个kobject的父节点不一定是包含它的kset(这种结构比较少见)。[引用]
下面,我将解释Linux2.6.11的设备模型的层次关系图为什么会是这样的:
首先,我们来看最大机关——subsystem的注册操作
484 int subsystem_register(struct subsystem *s)
485 {
489 pr_debug("subsystem %s: registering\n",s->kset.kobj.name);
491 if (!(error =kset_add(&s->kset))) {
494 }
496 }
有两个关键函数:subsystem_init(s), kset_add(&s->kset)。第一个操作是初始化子系统。
第二个函数将子系统的内嵌kset加入到kset双向链表中。
进入到第一个函数 subsystem_init(s)
void subsystem_init(struct subsystem * s)
{
init_rwsem(&s->rwsem);//初始化信号量结构
kset_init(&s->kset);
}
进入到kset_init
void kset_init(struct kset * k)
{
kobject_init(&k->kobj);
INIT_LIST_HEAD(&k->list);//list双向链表next ,prev指向自身
}
进入到kobject_init
void kobject_init(struct kobject * kobj)
{
kref_init(&kobj->kref);//应用计数 = 1
INIT_LIST_HEAD(&kobj->entry);//entry双向量表 指向自身
kobj->kset = kset_get(kobj->kset);//kobj->kset所指向的kset计数加1,可能返回空
}
需要指出的是 kset_get函数的操作流程,其操作为:
return k ? to_kset(kobject_get(&k->kobj)) : NULL;
如果kobj的kset不存在则返回NULL,如果存在则增加k->kobj引用计数并返回自己.这似乎让人费解。为何不直接增加kobj引用计数,kobj不是内嵌在kset中,代表kset吗?似乎此代码有点画蛇添足。其实,并不是这样的,因为kobj的容器(也就是kobj所嵌入的对象)不一定是kset,我以惯性思维去理解kobj嵌入在kset中,始终想不通!引以为鉴!如果kobj内嵌的对象不是kset那么kobject->kset指向的是什么呢?是什么时候初始化的呢?kobject->kset指向和该设备同类的设备集合。意思就是输入属于统一个设备集(kset)那么就可以利用kobject->kset这个指针遍历完整个设备集(当然真个系统不只一个kset)!
kobject对象中有几个成员是很重要的,必须初始化,或者直接或者间接,比如ktype、kset和parent。在调用kobject_register()函数之前,应该先设置好这些字段。
ktype 负责对该kobject类型进行跟踪的structkobj_type的指针,保存着该类kobject对象的文件操作和自我释放函数等的指针。
kset :指向所属的设备集合。
parent : 指向层次结构的上一层kobject
name[] : kobject容器的设备名,
例如:
struct gendisk * disk;// a block device
...
disk->kset.kobj.kset = &block_kset;
disk->kset.ktype = &partition_ktype;
kset_register(&disk->kset);
又如源码:fs/partitions/check.c,line 289
316 void add_partition(struct gendisk *disk, int part,sector_t start, sector_t len)
317 {
320 p = kmalloc(sizeof(*p),GFP_KERNEL);
322 return;
329 devfs_mk_bdev(MKDEV(disk->major, disk->first_minor + part),
331 "%s/part%d", disk->devfs_name, part);
333 if (isdigit(disk->kobj.name[strlen(disk->kobj.name)-1]))
334 snprintf(p->kobj.name,KOBJ_NAME_LEN,"%sp%d",disk->kobj.name,part);
335 else
336 snprintf(p->kobj.name,KOBJ_NAME_LEN,"%s%d",disk->kobj.name,part);
337 p->kobj.parent = &disk->kobj;
338 p->kobj.ktype = &ktype_part;
339 kobject_register(&p->kobj);
341 }
我们来看一下subsystem_init初始化都做了些什么:
我们看到 ktype, name, kset并没有subsystem_init函数中被初始化。这些重要的成员必须在这个函数外,直接或间接被初始化!另外kset的kobj成员是不一定将kobj->kset指向嵌入的kset,它可能指向其他设备集。
回到第二个函数 kset_add(&s->kset)
int kset_add(struct kset * k)
{
if (!k->kobj.parent && !k->kobj.kset && k->subsys)
k->kobj.parent = &k->subsys->kset.kobj;
return kobject_add(&k->kobj);
}
if语句执行的是将kset->kobj的父指针指向子系统(subsystem)的内嵌object
进入kobject_add
int kobject_add(struct kobject * kobj)
{
int error = 0;
struct kobject * parent;
if (!(kobj = kobject_get(kobj)))
return -ENOENT;
if (!kobj->k_name)//检查k_name
kobj->k_name = kobj->name;
parent = kobject_get(kobj->parent);
pr_debug("kobject %s: registering. parent: %s, set: %s\n",
kobject_name(kobj), parent ? kobject_name(parent) : "<NULL>", kobj->kset ? kobj->kset->kobj.name : "<NULL>" );
if (kobj->kset) {//如果kobj->kset存在
down_write(&kobj->kset->subsys->rwsem);
if (!parent)
parent = kobject_get(&kobj->kset->kobj);
list_add_tail(&kobj->entry,&kobj->kset->list);//加入设备链表
up_write(&kobj->kset->subsys->rwsem);
}
kobj->parent = parent;
error = create_dir(kobj);//创建一个目录
if (error) {
/* unlink does the kobject_put() for us */
unlink(kobj);
if (parent)
kobject_put(parent);
} else {
kobject_hotplug(kobj, KOBJ_ADD);
}
return error;
}
kobject_add主要就是链表的链接操作(链接到同类设备集链表上)和一些检查操作,并创建一个目录。从subsystem_register来看,他并没有初始化很多参数,只是简单的几个。其他参数(尤其是它的内嵌kobject的一些参数)都必须在之前被手动的初始化,不然会注册失败。
device
设备驱动程序模型建立在几个基本数据结构之上,这些结构描述了总线,设备,设备驱动器等等。
设备驱动程序模型的组件device(Linux2.6.11)
{306 structklist klist_children;
307 structklist_node knode_parent; /* node in sibling list */
308 structklist_node knode_driver;
309 structklist_node knode_bus;
313 char bus_id[BUS_ID_SIZE]; /* position on parent bus */
314 structdevice_attribute uevent_attr;
316 structsemaphore sem; /* semaphore to synchronize calls to
317 * its driver.
318 */
320 structbus_type * bus; /* type of bus device is on */
321 structdevice_driver *driver; /* which driver has allocated this
322 device */
323 void *driver_data; /* data private to the driver */
324 void *platform_data;/* Platform specific data, device
325 core doesn't touch it */
326 void *firmware_data;/* Firmware specific data (e.g. ACPI,
327 BIOS data),reserved for device core*/
328 structdev_pm_info power;
330 u64 *dma_mask; /* dma mask (if dma'able device) */
331 u64 coherent_dma_mask;/* Like dma_mask, but for
332 alloc_coherent mappings as
333 not all hardware supports
334 64 bit addresses for consistent
335 allocations such descriptors. */
337 structlist_head dma_pools; /* dma pools (if dma'ble) */
339 structdma_coherent_mem *dma_mem; /* internal for coherent me
340 override */
342 void (*release)(structdevice * dev);
343 };
device对象全部收集在devices_subsys子系统中,该子系统对应的目录为/sys/devices。设备是按照层次关系组织的:一个设备是某个“孩子”的“父亲”,其条件为子设备离开父设备无法正常工作。
每个设备驱动程序都保持一个device对象链表,其中链接了所有可被管理的设备;device对象的driver_list字段存放指向相邻对象的指针,而driver字段指向设备驱动程序的描述符。此外对于任何总线类型来说,都有一个链表存放连接到该类型总线上的所有设备;device对象的bus_list字段存放指向相邻对象的指针,而bus字段指向总线类型描述符。【深入理解Linux内核 page531】
驱动程序
设备驱动程序模型中的每个驱动程序都可由device_driver对象描述,其各字段如下
94 struct device_driver {
98 structcompletion unloaded;
100 structklist klist_devices;
101 structklist_node knode_bus;
105 int (*probe) (structdevice * dev);
106 int (*remove) (structdevice * dev);
107 void (*shutdown) (structdevice * dev);
108 int (*suspend) (structdevice * dev,pm_message_t state);
109 int (*resume) (structdevice * dev);
110 };
probe : 用于处理热插拔,即插即用和电源管理。当总线设备驱动程序发现一个可能由它处理的设备时就会调用probe方法;相应的函数将会探测该硬件,从而对该设备进行更进一步的检查。
remove :当移走一个可热插拔的设备时驱动程序会调用remove方法,驱动程序本身被卸载时,他所处理的每个设备也会调用remove方法。
shutdown, suspend和resume 是内核必须改变设备的供电状态时被调用。
总线
内核所支持的每一种总线类型都由一个bus_type对象描述,其各字段如下
44
45 structbus_attribute * bus_attrs;
46 structdevice_attribute * dev_attrs;
47 structdriver_attribute * drv_attrs;
49 int (*match)(structdevice * dev, structdevice_driver * drv);
50 int (*uevent)(structdevice *dev, char **envp,
51 int num_envp, char *buffer, intbuffer_size);
52 int (*probe)(structdevice * dev);
53 int (*remove)(structdevice * dev);
54 void (*shutdown)(structdevice * dev);
55 int (*suspend)(structdevice * dev,pm_message_t state);
56 int (*resume)(structdevice * dev);
57 };
name :总线类型名
subsys :与总线类型相关的kobject子系统。
drivers :驱动程序的kobject集合
device :设备的kobject集合
bus_attrs : 指向对象的指针,该对象包含总线属性和用于导出此属性到sysfs文件系统的方法。
dev_attrs : 指向对象指针,该对象包含设备属性和用于到处此属性到sysfs文件系统的方法
drv_attrs :指向对象指针,该对象包含设备属性和用于到处此属性到sysfs文件系统的方法
match : 检验给定的设备驱动程序是否支持特定设备的方法
每个bus_type类型的对象都包含一个内嵌的子系统;存放于bus_subsys变量中的子系统把嵌入在bus_type对象中的所有子系统都集合在一起。bus_subsys子系统与目录/sys/bus是对应的。【深入理解Linux内核page534】
类
每个类由一个class对象描述的,所有的类对象都属于与/sys/class目录想对应的class_subsys子系统。此外每个类对象还包括一个内嵌的子系统;因此,例如有一个/sys/class/input目录,它就与设备驱动程序模型的input类相对应
最后让我们来看一看一段源码——怎么初始化kobject的
字符设备注册:
int register_chrdev(unsigned int major, const char *name,
struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
char *s;
int err = -ENOMEM;
cd = __register_chrdev_region(major, 0, 256, name);//申请设备号
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc();//在此对objec.ktype赋值。见cdev_alloc函数
if (!cdev)
goto out2;
cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);//对kobj.name初始化
for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/'))
*s = '!';
err = cdev_add(cdev, MKDEV(cd->major, 0), 256);//设备号与设备驱动映射
if (err)
goto out;
cd->cdev = cdev;
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, 0, 256));
return err;
}
struct cdev *cdev_alloc(void)
{
struct cdev *p = kmalloc(sizeof(struct cdev), GFP_KERNEL);
if (p) {
memset(p, 0, sizeof(struct cdev));
p->kobj.ktype = &ktype_cdev_dynamic;//对ktype初始化
INIT_LIST_HEAD(&p->list);
kobject_init(&p->kobj);
}
return p;
}
找了很久也没有找到kobject结构中的 parent, kset在那里初始化的?!后来,觉得是不是因为字符设备没有在sysfs层次结构下创建节点?!因为他并没有调用kobject_add()函数将其加入到层次结构中。?!!!还希望高手指点!!!!
再看一下设备的初始化
引用自: http://www.cnblogs.com/gaomaolin_88/archive/2010/05/28/1746600.html
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
device_register-->device_initialize(dev);//初始化设备各个字段
voiddevice_initialize(struct device *dev)
{
kobj_set_kset_s(dev, devices_subsys); //所有的dev属于devices_subsys这个集合
kobject_init(&dev->kobj); //初始kobj
klist_init(&dev->klist_children, klist_children_get,
klist_children_put);
INIT_LIST_HEAD(&dev->dma_pools);
INIT_LIST_HEAD(&dev->node);
init_MUTEX(&dev->sem);
device_init_wakeup(dev, 0);
}
device_register-->device_add(dev);
intdevice_add(struct device *dev) //主要流程
{
dev = get_device(dev);
parent = get_device(dev->parent);
kobject_set_name(&dev->kobj, "%s", dev->bus_id);//name初始化
dev->kobj.parent =&parent->kobj;//parent初始化
kobject_add(&dev->kobj);//将自身kobject加入到层次结构中,并且建立sysfs entry.
//设置uevent_attr:
dev->uevent_attr.attr.name = "uevent";
dev->uevent_attr.attr.mode = S_IWUSR;
if (dev->driver)
dev->uevent_attr.attr.owner =dev->driver->owner;
dev->uevent_attr.store = store_uevent;
device_create_file(dev, &dev->uevent_attr);
//建立显示设备号的sysfs入口,即当前设备入口下的"dev"文件显示设备主从设备号。
if(MAJOR(dev->devt)) {
attr->attr.name = "dev";
attr->attr.mode = S_IRUGO;
if (dev->driver)
attr->attr.owner = dev->driver->owner;
attr->show = show_dev;
error = device_create_file(dev, attr);
}
//建立类的sysfs符号连接
if (dev->class) {
sysfs_create_link(&dev->kobj, &dev->class->subsys.kset.kobj,"subsystem");
sysfs_create_link(&dev->class->subsys.kset.kobj,&dev->kobj,dev->bus_id);}
sysfs_create_link(&dev->kobj, &dev->parent->kobj,"device");
class_name = make_class_name(dev->class->name, &dev->kobj);
sysfs_create_link(&dev->parent->kobj, &dev->kobj,class_name);
}
error= bus_add_device(dev);//添加一些bus相关的sysfs符号连接
/*设置环境变量,然后调用call_usermodehelper (argv[0], argv, envp, 0); 引起热拔插事件用户空间脚本执行。*/
kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_attach_device(dev); /*如果dev->driver已经存在,调用device_bind_driver(dev);进行绑定,否则遍历dev->bus上 drivers列表,调用dev->bus.match(dev,drv)来看是否有一个驱动与该dev匹配。如果匹配则绑定。*/
}OK,上述是主要流程。。
注:以上内容借鉴了很多网上资料和《深入理解Linux内核》书上知识,也结合了小弟自己看书的笔记,很多地方难免有所错误,还望大牛们不吝赐教!