尽管
LDD3中说对多数程序员掌握设备驱动模型不是必要的,但对于嵌入式
Linux的底层程序员而言,对设备驱动模型的学习非常重要。
Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统结构的一般性抽象描述。换句话说,
Linux设备模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,而为需要新添加设备或驱动提供一般性的统一接口,这使得驱动程序的开发变得更简单了,而程序员只需要去学习接口就行了。
在正式进入设备驱动模型的学习之前,有必要把
documentation/filesystems/sysfs.txt读一遍(不能偷懒)。
sysfs.txt主要描述
/sys目录的创建及其属性,
sys目录描述了设备驱动模型的层次关系,我们可以简略看一下
/sys目录,
block:所有块设备
devices:系统所有设备(块设备特殊),对应
struct device的层次结构
bus:系统中所有总线类型(指总线类型而不是总线设备,总线设备在
devices下),
bus的每个子目录都包含
--devices:包含到
devices目录中设备的软链接
--drivers:与
bus类型匹配的驱动程序
class:系统中设备类型(如声卡、网卡、显卡等)
fs:一些文件系统,具体可参考
filesystems /fuse.txt中例子
dev:包含
2个子目录
--char:字符设备链接,链接到
devices目录,以
:命名
--block:块设备链接
Linux设备模型学习分为:
Linux设备底层模型,描述设备的底层层次实现(
kobject);
Linux上层容器,包括总线类型(
bus_type)、设备(
device)和驱动(
device_driver)。
==== Linux设备底层模型 ====
谨记:像上面看到的一样,设备模型是层次的结构,层次的每一个节点都是通过
kobject实现的。在文件上则体现在
sysfs文件系统。
kobject结构
内核中存在
struct kobject数据结构,每个加载到系统中的
kobject都唯一对应
/sys或者子目录中的一个文件夹。可以这样说,许多
kobject结构就构成设备模型的层次结构。每个
kobject对应一个或多个
struct
attribute描述属性的结构。
点击(
此处)折叠或打开
-
struct kobject {
-
const char *name; /* 对应sysfs的目录名 */
-
struct list_head entry; /* kobjetct双向链表 */
-
struct kobject *parent; /* 指向kset中的kobject,相当于指向父目录 */
-
struct kset *kset; /*指向所属的kset */
-
struct kobj_type *ktype; /*负责对kobject结构跟踪*/
-
struct sysfs_dirent *sd;
-
struct kref kref; /*kobject引用计数*/
-
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;
-
unsigned int uevent_suppress:1;
-
};
kobject结构是组成设备模型的基本结构,最初
kobject设计只用来跟踪模块引用计数,现已增加支持,
——
sysfs表述:在
sysfs中的每个对象都有对应的
kobject
—— 数据结构关联:通过链接将不同的层次数据关联
—— 热插拔事件处理:
kobject子系统将产生的热插拔事件通知用户空间
kobject一般不单独使用,而是嵌入到上层结构(比如
struct device,
struct device_driver)当中使用。
kobject的创建者需要直接或间接设置的成员有:
ktype、
kset和
parent。
kset我们后面再说,
parent设置为
NULL时,
kobject默认创建到
/sys顶层目录下,否则创建到对应的
kobject目录中。重点来分析
ktype成员的类型,
点击(
此处)折叠或打开
-
#include <kobject.h>
-
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);
-
};
ktype包含了释放设备、默认属性以及属性的实现方法几个重要成员。每个
kobject必须有一个
release方法,并且
kobject在该方法被调用之前必须保持不变(处于稳定状态)。默认属性的结构如下,
点击(
此处)折叠或打开
-
#include <linux/sysfs.h>
-
struct attribute {
-
const char *name; /* 属性名称 */
-
mode_t mode; /* 属性保护:只读设为S_IRUGO,可写设为S_IWUSR */
-
}
kobj_type中的
default_attrs为二级结构指针,可以对每个
kobject使用多个默认属性,最后一个属性使用
NULL填充。
struct
sysfs_ops结构则如下,
点击(
此处)折叠或打开
-
struct sysfs_ops {
-
ssize_t (*show)(struct
kobject *, struct attribute *,char *);
-
ssize_t (*store)(struct
kobject *,struct attribute *,const char *, size_t);
-
};
show方法用于将传入的指定属性编码后放到
char *类型的
buffer中,
store则执行相反功能:将
buffer中的编码信息解码后传递给
struct
attribute类型变量。两者都是返回实际的属性长度。
一个使用
kobject的简单例子如下,
点击(
此处)折叠或打开
-
#include <linux/module.h>
-
#include <linux/init.h>
-
#include <linux/device.h>
-
#include <linux/string.h>
-
#include <linux/sysfs.h>
-
#include <linux/kernel.h>
-
-
MODULE_AUTHOR("xhzuoxin");
-
MODULE_LICENSE("Dual BSD/GPL");
-
-
void my_obj_release(struct kobject *kobj)
-
{
-
printk("release ok.n");
-
}
-
-
ssize_t my_sysfs_show(struct kobject *kobj, struct attribute *attr, char *buf)
-
{
-
printk("my_sysfs_show.n");
-
printk("attrname:%s.n", attr->name);
-
sprintf(buf, "%s", attr->name);
-
return strlen(attr->name) + 1;
-
}
-
-
ssize_t my_sysfs_store(struct kobject *kobj, struct attribute *attr, const char *buf,
-
size_t count)
-
{
-
printk("my_sysfs_store.n");
-
printk("write:%sn", buf);
-
-
return count;
-
}
-
-
struct sysfs_ops my_sysfs_ops = {
-
.show = my_sysfs_show,
-
.store = my_sysfs_store,
-
};
-
-
struct attribute my_attrs = {
-
.name = "zx_kobj",
-
.mode = S_IRWXUGO,
-
};
-
-
struct attribute *my_attrs_def[] = {
-
&my_attrs,
-
NULL,
-
};
-
struct kobj_type my_ktype = {
-
.release = my_obj_release,
-
.sysfs_ops = &my_sysfs_ops,
-
.default_attrs = my_attrs_def,
-
};
-
-
struct kobject my_kobj ;
-
-
int __init kobj_test_init(void)
-
{
-
printk("kobj_test init.n");
-
kobject_init_and_add(&my_kobj, &my_ktype, NULL, "zx");
-
-
return 0;
-
}
-
-
void __exit kobj_test_exit(void)
-
{
-
printk("kobj_test exit.n");
-
kobject_del(&my_kobj);
-
}
-
-
module_init(kobj_test_init);
-
module_exit(kobj_test_exit);
例子中有两个函数,用于初始化添加和删除kobject结构,
点击(
此处)折叠或打开
-
int kobject_init_and_add(struct kobject *kobj, struct
kobj_type *ktype,
-
struct kobject *parent, const char *fmt, ...); /* fmt指定kobject名称 */
-
void kobject_del(struct kobject *kobj);
加载模块后,在
/sys目录下增加了一个叫
zx达到目录,
zx目录下创建了一个属性文件
zx_kobj,使用
tree
/sys/zx查看。
内核提供了许多与
kobject结构相关的函数,如下:
点击(
此处)折叠或打开
-
// kobject初始化函数
-
void kobject_init(struct kobject * kobj);
-
// 设置指定kobject的名称
-
int kobject_set_name(struct kobject *kobj, const char *format, ...);
-
// 将kobj 对象的引用计数加,同时返回该对象的指针
-
struct kobject *kobject_get(struct kobject *kobj);
-
// 将kobj对象的引用计数减,如果引用计数降为,则调用kobject release()释放该kobject对象
-
void kobject_put(struct kobject * kobj);
-
// 将kobj对象加入Linux设备层次。挂接该kobject对象到kset的list链中,增加父目录各级kobject的引// 用计数,在其parent指向的目录下创建文件节点,并启动该类型内核对象的hotplug函数
-
int kobject_add(struct kobject * kobj);
-
// kobject注册函数,调用kobject init()初始化kobj,再调用kobject_add()完成该内核对象的注册
-
int kobject_register(struct kobject * kobj);
-
// 从Linux设备层次(hierarchy)中删除kobj对象
-
void kobject_del(struct kobject * kobj);
-
// kobject注销函数. 与kobject register()相反,它首先调用kobject
del从设备层次中删除该对象,再调// 用kobject put()减少该对象的引用计数,如果引用计数降为,则释放kobject对象
-
void kobject_unregister(struct kobject * kobj);
kset结构
我们先看上图,
kobject通过
kset组织成层次化的结构,
kset将一系列相同类型的
kobject使用(双向)链表连接起来,可以这样 认为,
kset充当链表头作用,
kset内部内嵌了一个
kobject结构。内核中用
kset数据结构表示为:
点击(
此处)折叠或打开
-
#include <linux/kobject.h>
-
struct kset {
-
struct list_head list; /* 用于连接kset中所有kobject的链表头 */
-
spinlock_t lis