内核版本: 2.6.58.8
参考资料:《linux设备驱动程序(第三版)》《linux内核设计与实现(第三版)》
设备模型卡了好几天,反复折腾,思绪还是有点乱,先写出来,以后发现有错误再修改。
2.6内核中引入设备模型,设备模型是一个极端复杂的数据结构,通过其间的大量链接而构成一个多层次的体系结构。Linux设备模型的目的是:为内核建立起一个对系统结构的一般性抽象描述,有了设备模型,各种复杂的设备以及他们之间的层次关系就会变的很明晰。也就是说,设备模型是一种“机制”,把复杂、凌乱的东西分类分层,使之看起来简单容易操作。
现在内核使用设备模型支持多种不同的任务:
电源管理和系统关机:设备模型使操作系统能以正确顺序遍历系统硬件。
与用户空间的通讯:sysfs 虚拟文件系统的实现与设备模型的紧密相关, 并向外界展示它所表述的结构。向用户空间提供系统信息、改变操作参数的接口正越来越多地通过sysfs,也就是设备模型来完成。
热插拔设备:设备模型包括了将设备分类的机制,在一个更高的功能层上描述这些设备, 并使设备对用户空间可见。
对象生命周期:设备模型的实现需要创建一系列机制来处理对象的生命周期、对象间的关系和对象在用户空间的表示。
以前研究过proc虚拟文件系统,它是一种基于ram(ram-base)的文件系统,proc中存放的是系统运行的动态信息,目的在于提供给用户一个接口,让用户能够查看系统的一些状态信息,还可以修改一些状态参数,比如printk。设备模型用户层的体香就是在sysfs下,sysfs也是一种基于ram的文件系统,它是把内核的一些数据结构、数据结构的属性以及他们之间个关系报告给用户。
/sys下有这些目录:
Block:在系统中发现的每个块设备在该目录下对应一个子目录。每个子目录中又包含一些属性文件,它们描述了这个块设备的各方面属性,如:设备大小。
Bus:在内核中注册的每条总线在该目录下对 应一个子目录, 如:ide pci scsi usb pcmcia 其中每个总线目录内又包含两个子目录:devices和drivers,devices 目录包含了在整个系统中发现的属于该总线类型的设备,drivers目录包含了注册到该总线的所有驱动。
Class:将设备按照功能进行的分类,如/sys/class/net 目录下包含了所有网络接口。
Devices:包含系统所有的设备。
Kernel:内核中的配置参数
Module:系统中所有模块的信息
Firmware:系统中的固件
Fs: 描述系统中的文件系统
Power:系统中电源选项
前边说过,设备模型是一个极端复杂的数据结构,通过其间的大量链接而构成一个多层次的体系结构,那这种连接的通过谁来完成的呢?是kobject,kobject实现了该结构并将其聚合在一起。分别研究下kobject和kset。
1、kobject
kobject是组成设备模型的最小单元,他是一个数据结构,kobject常被嵌入于其他类型(即:容器)中,如bus,devices,drivers都是典型的容器,用面向对象思维来说就类似于C++中的基类,这些容器通过kobject连接起来,形成了一个树,他就像一根线一样让设备和模型联系起来,表现在用户空间就是/sys下的一个个目录(后边会说到kset也是一个个目录)。
kobject可以做什么:
对象的应用计数
sysfs表述(上边说的/sys下的目录)
数据结构的关联(“纽带”的作用)
热插拔处理
kobject一些操作函数:
struct kobject {
const char*name; //显示的文件夹名
struct krefkref; //应用计数
struct list_headentry; //kobject之间的双向链表,与所属的kset形成环形链表
struct kobject*parent; //父节点,指向kset里的私有kobject
struct kset*kset; //父节点,指向上一层kset
struct kobj_type*ktype; //负责对该kobject类型进行跟踪的struct kobj_type的指针
struct sysfs_dirent*sd;
unsigned int state_initialized:1; //kobject是否初始化
unsigned int state_in_sysfs:1; //是否已经加入sysfs
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
};
cdev结构里就含有一个kobject,可以把他连接到内核的整个驱动模型中。
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
struct cdev *device = container_of(kg, struct cdev, kobj)
//得到的是指向包含kobject的结构体指针,这里边就是cdev
kobject的几个操作函数:
void *memset(void *s, int c, size_t count)
//将整个kobject设置为0
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
struct kobject *parent, const char *fmt, ...)
//kobject的初始化,并将其注册到linux系统,这里初始化之后应用计数为1
int kobject_set_name(struct kobject *kobj, const char *name, ...)
//设置名字
void kobject_del(struct kobject * kobj)
//从Linux系统中删除kobject对象
struct kobject *kobject_get(struct kobject *kobj)
//应用计数加1
void kobject_put(struct kobject *kobj)
//应用计数减1
分析几个主要函数
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
char *err_str;
if (!kobj) { //检查kobject变量是否为空
err_str = "invalid kobject pointer!";
goto error;
}
if (!ktype) { //检查kobj_type变量是否为空
err_str = "must have a ktype to be initialized properly!\n";
goto error;
}
if (kobj->state_initialized) { //是不是已经初始化过了
/* do not error out as sometimes we can recover */
printk(KERN_ERR "kobject (%p): tried to init an initialized "
"object, something is seriously wrong.\n", kobj);
dump_stack(); //调试用的
}
kobject_init_internal(kobj); //进一步初始化
kobj->ktype = ktype;
return;
error:
printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
dump_stack();
}
static void kobject_init_internal(struct kobject *kobj)
{
if (!kobj)
return;
kref_init(&kobj->kref); //调用kref_set(kref, 1),应用计数设置为1
INIT_LIST_HEAD(&kobj->entry); //初始化链表
kobj->state_in_sysfs = 0; //kobject 还没有注册到sysfs,所以设置为0
kobj->state_add_uevent_sent = 0; //事件相关的
kobj->state_remove_uevent_sent = 0;
kobj->state_initialized = 1; //设置为初始化之后
}
可以看到写了这么多代码就做了三件事:
1、kobject应用计数设为1
2、初始化kobject链表
3、设置初始化标志(state_initialized = 1)、注册和时间标志
kobject_add就是把这个kobject注册到linux系统,后边专门分析
这里要注意的几点:
1、kobject初始化之后应用计数就变为1,所以创建当创建kobject后,如果不再需要初始的应用,就要调用相应的kobject_put函数将应用计数减为0,要不会出现错误。
2、kobject中的应用计数不能够防止竞态的产生。
3、应用计数不为创建kobject的代码所直接控制,因此当kobject的最后一个应用计数不在存在时,必须异步通知,也就是说每个kobject都必须有一个release方法,这个方法并没有在kobject结构体中,而是定义到kobj_type中。
2、kobj_type
在kobj_type中的release成员中保存的是这种kobject类型的release函数指针,(我觉得多个kobject可以共用一个kobj_type,但是每个kobject都的有kobj_type,不知道对否)kobj_type关心的是对象的类。
struct kobj_type {
void (*release)(struct kobject *kobj);
/*移除kobject,在kobject_put最后会检测应用计数,
*当为0须释放该kobject,后边会说到
*/
struct sysfs_ops *sysfs_ops; //属性文件操作函数,这里只有读和写
struct attribute **default_attrs; //属性数组,表现出来是一个个文件
};
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *,char *);
//当用户读属性文件时,该函数被调用,该函数将属性值存入buffer中返回给用户态
ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t);
//当用户写属性文件时,该函数被调用,用于存储用户传入的属性值
};
struct attribute {
const char *name; //文件名
struct module *owner; //所属模块
mode_t mode; //文件读取权限
};
struct kobject *kobject_get(struct kobject *kobj)
{
if (kobj)
kref_get(&kobj->kref);
return kobj;
}
void kref_get(struct kref *kref)
{
WARN_ON(!atomic_read(&kref->refcount));
atomic_inc(&kref->refcount); //对原子变量kref->refcount原子的增加1
smp_mb__after_atomic_inc();
//后边这两个都属于原子操作的问题
//在http://www.kgdb.info/linuxdev/linux_memory_barriers/有详细描述
}
void kobject_put(struct kobject *kobj)
{
if (kobj)
kref_put(&kobj->kref, kobject_release);
}
int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
WARN_ON(release == NULL); //空警告
WARN_ON(release == (void (*)(struct kref *))kfree);
//表明释放函数不能是kfree,LDD3有说明
if (atomic_dec_and_test(&kref->refcount)) {
release(kref); //这里会用到release函数,释放该kobject
return 1;
}
return 0;
}
这里的release函数就是kobj_type里边定义的那个release,每一个kobject都有唯一的release,但是每个kobj_type不是针对于一个kobject,而是一类,也就是一个release函数可以对一类kobject释放,至于那些应该是一类?我觉得应该是所有 kobject中的parent指针指向同一个kset中的私有kobject 的kobject就应该是一类。