内核版本: 2.6.58.8
参考资料:《linux设备驱动程序(第三版)》《linux设备驱动程序详解(第二版)》
设备模型卡了好几天,反复折腾,思绪还是有点乱,先写出来,以后发现有错误再修改。
2.6内核中引入设备模型,主要是想建立一个对系统抽象的描述,其实是反应内核设备的拓扑结构,从用户层看就是/sys下的各种目录文件以及他们之间的关系,有了设备模型,各种复杂的设备以及他们之间的关系就会变的有条理,相当于形成一个“网络”(目前是这么理解的)。以前研究过proc虚拟文件系统,它是一种基于ram(ram-base)的文件系统,proc中存放的是系统运行的动态信息,目的在于提供给用户一个接口,让用户能够查看系统的一些状态信息,还可以修改一些状态参数,比如printk。sysfs也是一种基于ram的文件系统,它是把内核的一些数据结构、数据结构的属性以及他们之间个关系报告给用户。
/sys下有这些目录:
Block:在系统中发现的每个块设备在该目录下对应一个子目录。每个子目录中又包含一些属性文件,它们描述了这个块设备的各方面属性,如:设备大小。
Bus:在内核中注册的每条总线在该目录下对 应一个子目录, 如:ide pci scsi usb pcmcia 其中每个总线目录内又包含两个子目录:devices和drivers,devices 目录包含了在整个系统中发现的属于该总线类型的设备,drivers目录包含了注册到该总线的所有驱动。
Class:将设备按照功能进行的分类,如/sys/class/net 目录下包含了所有网络接口。
Devices:包含系统所有的设备。
Kernel:内核中的配置参数
Module:系统中所有模块的信息
Firmware:系统中的固件
Fs: 描述系统中的文件系统
Power:系统中电源选项
前边说设备模型相当于一个“网络”,那么kset和kobject等相当于网络中的一些节点,俺自己理解画一个图:
分别研究下kobject和kset
1、kobject
kobject是组成设备模型的最小单元,他是一个数据结构,kobject常被嵌入于其他类型(即:容器)中,如bus,devices,drivers都是典型的容器,用面向对象思维来说就类似于C++中的基类,这些容器通过kobject连接起来,形成了一个树,他就像一根线一样让设备和模型联系起来,表现在用户空间就是/sys下的一个个目录。
kobject可以做什么:
对象的应用计数
sysfs表述(上边说的/sys下的目录)
数据结构的关联(“线”的作用)
热插拔处理
kobject一些操作函数:
struct kobject {
const char *name; //显示的文件夹名
struct kref kref; //应用计数
struct list_head entry;
struct kobject *parent; //父节点,kset里的私有kobject
struct kset *kset; //父节点,kset
struct kobj_type *ktype; //操作函数集
struct sysfs_dirent *sd;
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
};
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
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
这里要注意的几点:
1、kobject初始化之后应用计数就变为1,所以创建当创建kobject后,如果不再需要初始的应用,就要调用相应的kobject_put函数将应用计数减为0,要不会出现错误。
2、kobject中的应用计数不能够防止竞态的产生。
3、应用计数不为创建kobject的代码所直接控制,因此当kobject的最后一个应用计数不在存在时,必须异步通知,也就是说每个kobject都必须有一个release方法。
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
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; //文件读取权限
};
后边会研究属性的建立。
3、kset
kset是嵌入相同类型的kobject的集合,相当于一个目录下的几个子目录。和kobject不同的是,kset创建的目录下边还可以创建子目录,kobject目录下边只能有属性文件,不能有目录,另外一个kset下边还可以有kset目录。
kset操作和kobject类似,其实对kset的操作就是对kset下kobject的操作。
最后一个是,kset是包含在一个子系统下的,所以里边有一个子系统指针
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj; //私有kobject
struct kset_uevent_ops *uevent_ops; //热插拔等处理函数
};
uevent_ops会在后边的热插拔里讨论
4、子系统
子系统相当于kset的一个父类,包含很多个kset,不同的是,子系统生成的目录只在根目录下。
struct subsystem {
struct kset kset; //下一级kset的父节点
struct rw_semaphore rwsem; //串行访问kset的内部链表
}
这里要注意的几点:
1、一个kset不一定只在一个子系统下
2、通过kset可以找到包含kset的每一个子系统,但是无法直接从subsystem结构中找到子系统中包含的多个kset
3、rwsem用于串行访问kset的内部链表
5、属性相关
注意的第一个问题是,调用kobject_add时,kobject的parent指针是NULL,这个kobject就会是kset的私有kobject(书的原话是,它将被设置为嵌入到新kobject的kset中的kobject,我理解应该是一个私有kobject,英文原版找不到,这里翻译的弄不明白,以后找见了翻过来修改),如果kobject的parent和kset都是NULL,则会在最高层创建sysfs目录。
创建kobject时,就会有默认属性,保存在kobj_type中,default_attrs是一个结构数组,一个attribute就是一个属性,反应出来就是一个文件。sysfs_ops是属性的处理方法。当用户空间读取一个属性时,内核会使用指向kobject的指针和正确的结构属性调用show方法,写一个属性时是一样的,调用store方法。
6、热插拔事件
在Linux系统中,当系统配置发生变化时,如: 添加kset到系统;移动kobject, 一个通知会从内核空间发送到用户空间,这就是热插
拔事件。热插拔事件会导致用户空间中相应的处理程序(如udev,mdev)被调用, 这些处理程序 会通过加载驱动程序, 创建设备节点
等来响应热插拔事件。操作集合:
struct kset_uevent_ops {
int (*filter)(struct kset *kset, struct kobject *kobj);
//决定是否将事件传递到用户空间。如果filter 返回0,将不传递事件
const char *(*name)(struct kset *kset, struct kobject *kobj);
//用于将字符串传递给用户空间的热插拔处理程序
int (*uevent)(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env);
//将用户空间需要的参数添加到环境变量中
};