Linux设备驱动模型

linux设备驱动框架

一、概述

Linux的设备驱动框架是以busdriverdeviceclass为基石,kobject为基本元素所构成的,配合sysfs文件系统对操作系统软硬件进行组织,管理的框架。

每个kobject都对应sysfs文件系统里面的一个目录,出现在该目录中的文件称为该对象的属性。

二、kobject,kset,subsystem

kobject包含在一个层次化的组织当中,它可以有一个父对象,可以包含在一个kset对象中。

基本数据结构

kobject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 设备驱动程序模型的核心数据结构。对应于sysfs文件系统中每一个目录。
* 它通常被放到设备驱动程序的一个大容器中。典型的容器有总线、设备及驱动程序描述符。
*/
struct kobject {
/**
* 指向容器名称,注意,这里是指针,其在注册时进行分配,注销时回收。
*/
char * k_name;
/**
* 如果容器名称不超过20个字符,就存在这里。
*/
char name[KOBJ_NAME_LEN];
/**
* 容器的引用计数。
*/
struct kref kref;
/**
* 用于将kobject插入某个链表。
*/
struct list_head entry;
/**
* 指向父kobject
*/
struct kobject * parent;
/**
* 指向包含的kset,kset是同类型的kobject结构的一个集合体。
*/
struct kset * kset;
/**
* 指向kobject的类型描述符。
*/
struct kobj_type * ktype;
/**
* 指向与kobject对应的sysfs文件的dentry数据结构。
*/
struct dentry * dentry;
};

kset

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 嵌入相同类型结构的kobject集合。
* 当创建一个kobject对象时,通常需要将它们加入到kset中。
*/
struct kset {
/**
* 所属子系统。
*/
struct subsystem * subsys;
/**
* 所包含的kobjec的类型。
*/
struct kobj_type * ktype;
/**
* 第一个kobject节点。
*/
struct list_head list;
/**
* 嵌入的kobject,也就是说,
*/
struct kobject kobj;
/**
* 用于处理kobject结构的过滤和热插拨操作的回调函数表。
*/
struct kset_hotplug_ops * hotplug_ops;
};

kobj_type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 描述包含kobject对象的结构类型。
*/
struct kobj_type {
/**
* kobject类型的release函数。
*/
void (*release)(struct kobject *);
/**
* 实现对象属性的方法。
*/
struct sysfs_ops * sysfs_ops;
/**
* 当创建kobject时,赋予该对象的默认属性。
*/
struct attribute ** default_attrs;
};
  • kset_hotplug_ops
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 控制热插拨事件的结构。由kset的hotplug_ops指向该结构。
* 如果kset不包含一个指定的kobject,则在sysfs分层结构中进行搜索,直到找到一个包含有kset的kobject为止,然后使用这个kset的热插拨操作。
*/
struct kset_hotplug_ops {
/**
* 无论何时,当内核要为指定的kobject产生事件时,都要调用filter函数。如果filter函数返回0,将不产生事件。
* 因此,该函数给kset一个机会,用于决定是否向用户空间传递指定的事件。
* 使用此函数的一个例子是块设备子系统。在block_hotplug_filter中,只为kobject产生磁盘和分区事件,而不会为请求队列kobject产生事件。
*/
int (*filter)(struct kset *kset, struct kobject *kobj);
/**
* 在调用用户空间的热插拨程序时,相关子系统的名字将作为唯一的参数传递给它。
* name方法负责提供此名字。它将返回一个适合传递给用户空间的字符串。
*/
char *(*name)(struct kset *kset, struct kobject *kobj);
/**
* 任何热插拨脚本所需要知道的信息将通过环境变量传递。最后一个hotplug方法会在调用脚本前,提供添加环境变量的机会。
*/
int (*hotplug)(struct kset *kset, struct kobject *kobj, char **envp,
int num_envp, char *buffer, int buffer_size);
};

subsystem

在高版本中,将其废弃,因为其与kset功能重复,读写信号量的功能被klist替代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 子系统。通常显示在sysfs分层结构中的顶层。
* 包含block_subsys、devices_subsys以及各种总线等子系统,对应于sys/block、sys/devices等目录。
*/
struct subsystem {
/**
* 下层对象集合。
*/
struct kset kset;
/**
* 访问子系统所用的读写信号量。
*/
struct rw_semaphore rwsem;
};

subsys_attribute

1
2
3
4
5
6
7
8
struct subsys_attribute {
//属性本身
struct attribute attr;
//sysfs文件的读
ssize_t (*show)(struct subsystem *, char *);
//sysfs文件的写
ssize_t (*store)(struct subsystem *, const char *, size_t);
};

内部API

kobject_name

返回其k_name,这是在这个kobj对象分配后才会指定的,其他情况下为NULL,可用用来判断kobj对象是否合法

1
2
3
4
static inline char * kobject_name(struct kobject * kobj)
{
return kobj->k_name;
}

populate_dir

填充目录(kobject是一个目录,这个API使用默认属性建立该目录下的文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static int populate_dir(struct kobject * kobj)
{
//得到该kobj的类型,里面有该kobj的默认属性
struct kobj_type * t = get_ktype(kobj);
struct attribute * attr;
int error = 0;
int i;
//如果kobj的类型存在,并且其有默认属性
if (t && t->default_attrs) {
//kobj_type的默认属性是一个二级指针,意味着其可以有多个默认的属性,遍历其可用的属性,创建对应的属性文件,也就是这个kobj目录下的文件
for (i = 0; (attr = t->default_attrs[i]) != NULL; i++) {
//如果创建文件出错,就break掉,返回错误码
if ((error = sysfs_create_file(kobj,attr)))
break;
}
}
return error;
}

create_dir

建立kobject对应的目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static int create_dir(struct kobject * kobj)
{
int error = 0;
//注意,这里的kobject_name返回的是k_name,这个成员是分配其kobj对象时才会赋值,所以这个if判断可以检查kobj是否是一个合法的kobj
if (kobject_name(kobj)) {
//建立kobj对象对应的目录
error = sysfs_create_dir(kobj);
if (!error) {
//若没有出错,填充其目录下的文件,也就是这个kobj的属性
if ((error = populate_dir(kobj)))
//如果出错,清理现场
sysfs_remove_dir(kobj);
}
}
return error;
}

to_kobj

从链表节点得到其kobj

1
2
3
4
static inline struct kobject * to_kobj(struct list_head * entry)
{
return container_of(entry,struct kobject,entry);
}

get_kobj_path_length

得到kobj目录的路径长度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static int get_kobj_path_length(struct kobject *kobj)
{
//length初始为一,是因为kobj本身也是一个目录,在其后面也可以有一个'/'
int length = 1;
//从这个kobj本身开始
struct kobject * parent = kobj;

/* walk up the ancestors until we hit the one pointing to the
* root.
* Add 1 to strlen for leading '/' of each level.
*/
//一级一级计算,从kobj本身开始,一直到/sys
do {
length += strlen(kobject_name(parent)) + 1;
parent = parent->parent;
} while (parent);
return length;
}

fill_kobj_path

填充kobject的路径,第二个参数path是申请好的内存空间,用以存放kobject的绝对路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static void fill_kobj_path(struct kobject *kobj, char *path, int length)
{
struct kobject * parent;
//length先减一,为何?
//因为length是长度,但是在下面的使用中,是当作下标来用的,长度和下标相差1,因此将其减掉
--length;
//从kobj开始,向其父节点遍历
for (parent = kobj; parent; parent = parent->parent) {
//当前节点的kobj->k_name,注意,这个名字是没有'/'的
int cur = strlen(kobject_name(parent));
/* back up enough to print this name with '/' */
//length-cur就是这个kobj->k_name应该在path字符串的第一个位置
length -= cur;
//在这个位置,放入当前kobj->k_name
strncpy (path + length, kobject_name(parent), cur);
//在这个name的前一个位置,放一个'/'
*(path + --length) = '/';
}

pr_debug("%s: path = '%s'\n",__FUNCTION__,path);
}

to_kset

由kobj得到其对应的kset

1
2
3
4
static inline struct kset * to_kset(struct kobject * kobj)
{
return kobj ? container_of(kobj,struct kset,kobj) : NULL;
}

kset_get

若kset存在,递增这个kset->kobj的计数,并将其返回,否则返回NULL

1
2
3
4
static inline struct kset * kset_get(struct kset * k)
{
return k ? to_kset(kobject_get(&k->kobj)) : NULL;
}

kset_put

递减kset->kobj的计数

1
2
3
4
static inline void kset_put(struct kset * k)
{
kobject_put(&k->kobj);
}

get_ktype

如果这个kobj属于某个kset,并且这个kset有ktype,那么返回这个ktype,否则返回kobj->ktype

由此可以看到,优先返回kset的ktype,因为这一个kset的kobj类型一致

1
2
3
4
5
6
7
static inline struct kobj_type * get_ktype(struct kobject * k)
{
if (k->kset && k->kset->ktype)
return k->kset->ktype;
else
return k->ktype;
}

将一个kobj从其kset中移除

1
2
3
4
5
6
7
8
9
10
11
static void unlink(struct kobject * kobj)
{
//如果kobj归属一个kset,就将其从kset的链表中删除,并且初始化entry
if (kobj->kset) {
down_write(&kobj->kset->subsys->rwsem);
list_del_init(&kobj->entry);
up_write(&kobj->kset->subsys->rwsem);
}
//无论是否删除,都将这个kobj的计数减一
kobject_put(kobj);
}

to_kset

从kobj得到其kset

1
2
3
4
static inline struct kset * to_kset(struct kobject * kobj)
{
return kobj ? container_of(kobj,struct kset,kobj) : NULL;
}

kset_get

主要是递增kset中的kobj的引用计数

1
2
3
4
5
static inline struct kset * kset_get(struct kset * k)
{
//这里有一个注意的点,kobject_get的参数是k->kobj,代表kset的那个kobj,递增这个计数
return k ? to_kset(kobject_get(&k->kobj)) : NULL;
}

kset_put

递减kset中的kobj的引用计数

1
2
3
4
static inline void kset_put(struct kset * k)
{
kobject_put(&k->kobj);
}

kobj_set_kset_s

为某个对象设置其kset为子系统的kset,这个obj是内嵌有一个kobj对象的结构

1
2
#define kobj_set_kset_s(obj,subsys) \
(obj)->kobj.kset = &(subsys).kset

kset_set_kset_s

子系统下面也是可以有子系统的,这种就是设置子系统的子系统,比如,bus子系统下面有pci子系统,这个宏是为这个子系统内嵌的kset的kobj的上级kset设置为某个子系统的kset

1
2
#define kset_set_kset_s(obj,subsys) \
(obj)->kset.kobj.kset = &(subsys).kset

subsys_set_kset

设置这个对象的子系统的所属kset,一般用于注册总线

1
2
#define subsys_set_kset(obj,_subsys) \
(obj)->subsys.kset.kobj.kset = &(_subsys).kset

subsys_get

递增子系统的计数,其实就是子系统里面的kset的计数

1
2
3
4
static inline struct subsystem * subsys_get(struct subsystem * s)
{
return s ? container_of(kset_get(&s->kset),struct subsystem,kset) : NULL;
}

subsys_put

递减子系统的计数

1
2
3
4
static inline void subsys_put(struct subsystem * s)
{
kset_put(&s->kset);
}

kobject_release

回调函数,用来kobject_put时,如果计数变为0,将这个kobj资源释放

1
2
3
4
static void kobject_release(struct kref *kref)
{
kobject_cleanup(container_of(kref, struct kobject, kref));
}

decl_subsys

定义一个子系统

1
2
3
4
5
6
7
8
#define decl_subsys(_name,_type,_hotplug_ops) \
struct subsystem _name##_subsys = { \
.kset = { \
.kobj = { .name = __stringify(_name) }, \
.ktype = _type, \
.hotplug_ops =_hotplug_ops, \
} \
}

decl_subsys_name

定义一个子系统,但是子系统名字和kobj名字不一样

1
2
3
4
5
6
7
8
#define decl_subsys_name(_varname,_name,_type,_hotplug_ops) \
struct subsystem _varname##_subsys = { \
.kset = { \
.kobj = { .name = __stringify(_name) }, \
.ktype = _type, \
.hotplug_ops =_hotplug_ops, \
} \
}

API

kobject_get_path

得到这个kobj的完整路径名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
char *kobject_get_path(struct kobject *kobj, int gfp_mask)
{
char *path;
int len;

len = get_kobj_path_length(kobj);
path = kmalloc(len, gfp_mask);
if (!path)
return NULL;
memset(path, 0x00, len);
fill_kobj_path(kobj, path, len);

return path;
}

kobject_init

部分初始化kobj

1
2
3
4
5
6
7
//可以看到,这里只初始化了计数器,entry节点,若kobj有kset,那么递增其计数,否则设为NULL
void kobject_init(struct kobject * kobj)
{
kref_init(&kobj->kref);
INIT_LIST_HEAD(&kobj->entry);
kobj->kset = kset_get(kobj->kset);
}

kobject_get

通用的获取kobj对象的方法,会递增其计数

1
2
3
4
5
6
struct kobject * kobject_get(struct kobject * kobj)
{
if (kobj)
kref_get(&kobj->kref);
return kobj;
}

kobject_add

增加一个kobj对象,也就是在sysfs的目录树中增加一个目录,这里很重要的一个动作是,赋值kobj->k_name,标识这个kobj是有效的,本函数也会构建好kobj和kset的层级关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
int kobject_add(struct kobject * kobj)
{
int error = 0;
struct kobject * parent;

//如果kobj为NULL,返回错误
if (!(kobj = kobject_get(kobj)))
return -ENOENT;
//如果k_name没有值,就将kobj->name赋给它,k_name表示这个kobj是否被分配出去了
if (!kobj->k_name)
kobj->k_name = kobj->name;
//得到kobj的parent,这个可以为NULL
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>" );
//如果kobj属于一个kset
if (kobj->kset) {
down_write(&kobj->kset->subsys->rwsem);
//此时如果没有parent,就将kset作为其parent
if (!parent)
parent = kobject_get(&kobj->kset->kobj);

//然后将kobj加入到kset的list中,表明这个kobj归属于这个kset
list_add_tail(&kobj->entry,&kobj->kset->list);
up_write(&kobj->kset->subsys->rwsem);
}
//设置kobj的parent
kobj->parent = parent;

//在sysfs中创建kobj对应的目录(kobj对应目录,而不是文件!)
error = create_dir(kobj);
//如果创建失败,释放这个kobj
if (error) {
/* unlink does the kobject_put() for us */
//注意这里调用unlink的节点,unlink里面有一个if判断,如果这个kobj有kset,那么将其从kset里面删除并重置entry,而kobj的引用计数都是要减一的
unlink(kobj);//这里对应第七行的kobject_get,这里将其递减
//如果parent存在,也将其引用计数递减,注意想一下这里为何没有用unlink来处理parent,而是直接kobject_put,因为只是要清除本kobj,其kset的kset属于另一个kobj了
if (parent)
kobject_put(parent);//对应23行的kobject_get
} else {
//如果没有出错,那么注册kobj的热插拔功能
kobject_hotplug(kobj, KOBJ_ADD);
}

return error;
}

kobject_register

初始化并添加一个kobj对象到kset,并增加其对应的sysfs文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int kobject_register(struct kobject * kobj)
{
int error = 0;
//如果kobj为NULL,那么直接返回一个错误
if (kobj) {
//否则,先初始化这个kobj的一部分,注意,kobject_init只初始化引用计数,entry,和kset
kobject_init(kobj);
//通过kobject_add来确实新增这个kobj,主要是设置kobj->k_name和设置层级关系,增加sysfs中的对应的目录
error = kobject_add(kobj);
if (error) {
printk("kobject_register failed for %s (%d)\n",
kobject_name(kobj),error);
dump_stack();
}
} else
error = -EINVAL;
return error;
}

kobject_set_name

设置kobject对象的名字

这个函数为何要使用变参?

答:这涉及到操作系统中对kobj对象的命名,对应于device结构中的bus_id,它可能是一个设备名本身,也可能是设备名+ID,这个ID取决于该设备所属的总线分配方式,比如该总线上有五个同类型设备,那么这五个设备的名字可能是xxx0,xxx1,…,xxx4。

这意味着,kobj对象的name可能是多个部分构成的,那么一个可变参数的函数就很有必要了,它可以根据fmt的格式,拼凑出想要的字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
int kobject_set_name(struct kobject * kobj, const char * fmt, ...)
{
int error = 0;
int limit = KOBJ_NAME_LEN;
int need;
va_list args;//变参的必要部分
char * name;

/*
* First, try the static array
*/
//看看这个名字能否放入数组kobj->name中
va_start(args,fmt);
need = vsnprintf(kobj->name,limit,fmt,args);
va_end(args);
//如果格式字串的总长度可以放入kobj->name中,那么用局部变量name指向它
if (need < limit)
name = kobj->name;
else {
/*
* Need more space? Allocate it and try again
*/
//否则需要动态申请一段空间,大小为need+1,标识字符串末尾还有'\0'
limit = need + 1;
name = kmalloc(limit,GFP_KERNEL);
if (!name) {
error = -ENOMEM;
goto Done;
}
//再将其放入动态申请的name中
va_start(args,fmt);
need = vsnprintf(name,limit,fmt,args);
va_end(args);

/* Still? Give up. */
//到这里是出了问题,一个兜底措施,如果need还大于等于limit,释放掉这段空间,防止内存泄漏
if (need >= limit) {
kfree(name);
error = -EFAULT;
goto Done;
}
}

/* Free the old name, if necessary. */
//释放旧名字,意味着这个kobj可能要变名称了,只有k_name表示该kobj的真实名字
if (kobj->k_name && kobj->k_name != kobj->name)
kfree(kobj->k_name);

/* Now, set the new name */
//将新名字赋给k_name
kobj->k_name = name;
Done:
return error;
}

kobject_rename

重命名kobj

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int kobject_rename(struct kobject * kobj, char *new_name)
{
int error = 0;

//注意,kobj要用kobject_get来获取,使用结束后用kobject_put来释放
kobj = kobject_get(kobj);
if (!kobj)
return -EINVAL;
//将sysfs中的kobj的目录名进行修改
error = sysfs_rename_dir(kobj, new_name);
//释放kobj
kobject_put(kobj);

return error;
}

kobject_del

删除kobj对象,如果它在一个kset中,也将其从kset的list中删除

1
2
3
4
5
6
void kobject_del(struct kobject * kobj)
{
kobject_hotplug(kobj, KOBJ_REMOVE);//热插拔的处理
sysfs_remove_dir(kobj);//在sysfs中删除这个kobj
unlink(kobj);//将这个kobj本身删除掉
}

kobject_unregister

注销一个kobj对象

1
2
3
4
5
6
void kobject_unregister(struct kobject * kobj)
{
pr_debug("kobject %s: unregistering\n",kobject_name(kobj));
kobject_del(kobj);//删除kobj对象
kobject_put(kobj);//递减其计数
}

kobject_cleanup

释放kobj对象对应的资源,通过其注册时的kobj->ktype->release函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void kobject_cleanup(struct kobject * kobj)
{
struct kobj_type * t = get_ktype(kobj);
struct kset * s = kobj->kset;
struct kobject * parent = kobj->parent;

pr_debug("kobject %s: cleaning up\n",kobject_name(kobj));
//如果k_name != name,直接释放k_name,因为后面要将其置空,避免内存泄漏,那么可以想一想,如果这俩相等呢?
//如果相等,可以看看kobject_add和kobject_set_name这两个函数,此时,kobj->k_name就是指向kobj->name的指针,因此不用kfree
if (kobj->k_name != kobj->name)
kfree(kobj->k_name);
kobj->k_name = NULL;
//如果release存在,就调用其做一些该设备特殊的操作
if (t && t->release)
t->release(kobj);
//如果这个kobj归属于某个kset,那么需要清除该kobj在这个kset中的痕迹
if (s)
kset_put(s);
//如果其parent存在,递减其parent的计数,因为递增一个kobj计数时,其parent的计数也要递增
if (parent)
kobject_put(parent);
}

kobject_put

递减kobj的计数,如果降为0,调用kobject_release

1
2
3
4
5
void kobject_put(struct kobject * kobj)
{
if (kobj)
kref_put(&kobj->kref, kobject_release);
}

kset_init

初始化一个kset

1
2
3
4
5
6
7
void kset_init(struct kset * k)
{
//初始化其内嵌的kobj
kobject_init(&k->kobj);
//初始化其list,此时没有包含的kobj
INIT_LIST_HEAD(&k->list);
}

kset_add

增加一个kset

1
2
3
4
5
6
7
8
int kset_add(struct kset * k)
{
//如果这个kset没有上级节点(parent和kset两重保障),并且这个kset归属于某个子系统,那么将其parent设置为子系统
if (!k->kobj.parent && !k->kobj.kset && k->subsys)
k->kobj.parent = &k->subsys->kset.kobj;
//剩下的就是增加kobj了
return kobject_add(&k->kobj);
}

kset_register

注册kset

1
2
3
4
5
int kset_register(struct kset * k)
{
kset_init(k);
return kset_add(k);
}

kset_unregister

注销kset,其实就是注销kset中的kobj而已

1
2
3
4
void kset_unregister(struct kset * k)
{
kobject_unregister(&k->kobj);
}

kset_find_obj

在kset中搜索一个对象(遍历kset->list)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct kobject * kset_find_obj(struct kset * kset, const char * name)
{
struct list_head * entry;
struct kobject * ret = NULL;

//链表操作,使用信号量保护链表
down_read(&kset->subsys->rwsem);
//遍历kset->list
list_for_each(entry,&kset->list) {
struct kobject * k = to_kobj(entry);
//第一个条件是判定这个kobj是否存在,第二个是看是否是查找的目标
if (kobject_name(k) && !strcmp(kobject_name(k),name)) {
//如果找到了,递增这个kobj计数,然后跳出
ret = kobject_get(k);
break;
}
}
up_read(&kset->subsys->rwsem);
//如果遍历完都没有找到,则这里返回的是NULL
return ret;
}

subsystem_init

初始化一个子系统

1
2
3
4
5
6
//子系统里面其实就一个读写信号量,一个kset,也就是初始化这俩就行
void subsystem_init(struct subsystem * s)
{
init_rwsem(&s->rwsem);
kset_init(&s->kset);
}

subsystem_register

注册一个子系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//根据子系统的数据结构来看,注册一个子系统需要干些什么?
//注册时,需要填写其层级关系
int subsystem_register(struct subsystem * s)
{
int error;

subsystem_init(s);
//上一行的子系统初始化是没有赋值子系统的名字的,因此,这里注册子系统时,子系统的名字是用户提供
pr_debug("subsystem %s: registering\n",s->kset.kobj.name);
//通过kset_add填写子系统的层级关系,如果填写成功
if (!(error = kset_add(&s->kset))) {
//如果这个子系统本身没有归属,就将其归属到其自身
if (!s->kset.subsys)
s->kset.subsys = s;
}
return error;
}

subsystem_unregister

注销子系统

1
2
3
4
5
6
//直接注销kset即可
void subsystem_unregister(struct subsystem * s)
{
pr_debug("subsystem %s: unregistering\n",s->kset.kobj.name);
kset_unregister(&s->kset);
}

subsys_create_file

导出sysfs属性文件

1
2
3
4
5
6
7
8
9
10
int subsys_create_file(struct subsystem * s, struct subsys_attribute * a)
{
int error = 0;
if (subsys_get(s)) {
//创建子系统的属性文件
error = sysfs_create_file(&s->kset.kobj,&a->attr);
subsys_put(s);
}
return error;
}

subsys_remove_file

移除sysfs属性文件

1
2
3
4
5
6
7
void subsys_remove_file(struct subsystem * s, struct subsys_attribute * a)
{
if (subsys_get(s)) {
sysfs_remove_file(&s->kset.kobj,&a->attr);
subsys_put(s);
}
}

三、kobj体系流程梳理

子系统注册流程

在梳理注册流程时,需要谨记所涉及到的数据结构内容。

注册最重要的功能就是为你新增的内容构建其层级关系

subsystem_register的参数是一个子系统结构体指针,其中有一个读写信号量和一个kset,因此在注册时,使用subsystem_init来先初始化这个子系统结构,其内容分别是初始化读写信号量和kset,这个kset是内嵌到子系统结构体中的,而kset中又内嵌了一个kobj表示其自身,因此这个kobj也要初始化,还有kset中的list链表结构,表示这个kset没有包含任何子节点。

kobj的初始化则是初始化其计数器,表示kobj自身的链表结点,最后,重点来了,这个kobj如果有其上级节点,就将其上级节点的kobj计数加一,举个例子,比如一个kset含有5个kobj子节点,那么这个kset 本身的kobj的计数应该是6,包含kset本身。

此时subsystem_init的流程走完,subsystem结构的基础内容已被填充(其实主要是链表,信号量和计数的初始化),接下来,这个新增的子系统需要添加到/sys的树中,通过的就是kset_add,因为subsystem本身的存在是通过kset来表示的,这也是高版本中删除了subsystem的原因。

kset_add中,会进行一个判断,如果你新增的这个kset没有parent,也没有上级kset,但是有所属的子系统,那么将这个ksetparent设置为这个子系统,注意,这里并没有为其上级kset赋值,这种情况一般对应于某个kset直接挂在子系统下,除了顶级子系统,其他节点都应该在树中的某个位置。

在初始化devices子系统时,其parent和上级kset均为NULL,但是,没有其所属子系统,因此顶级子系统是没有parentkset的。

随后调用kobject_add,因为kobj是基石,整个模型是依据kobj构建的,对子系统,kset的操作,最终都会落到kobj上。kobject_add中最重要的操作,就是给k_name赋值,因为kobj->name一般是静态声明时写好的,但是注册是动态的,如何判断这个kobj已经注册了呢?就是在kobject_add中为k_name赋值的操作来声明,这个kobj已经注册了。

还要通过kobject_get来递增kobj的统计计数,如果其parent存在的话,也需要递增其计数。

然后,如果这个kobj归属于某个kset,就将这个kobj加到其所属ksetlist链表中,此时,其所属kset就是这个kobjparent,所以此时,如果之前其parent不存在的话,这里将其parent设置为其ksetkobj

还记得之前说的kobj就是一个目录么?然后为这个kobj/sys中创建对应的目录。

回到subsystem_register,如果创建kset成功,则error返回0,满足if的条件,那么如果这个子系统没有归属的子系统时,就将其子系统设置为其自身,至此,子系统注册流程结束。

以devices子系统为例,在其注册完成后,其s->kset->kobj->parent=NULL,s->kset->kobj->kset=NULL,s->kset->subsys=s。可以看到,顶级子系统没有parent,没有kset,但是有subsys

例子:devices_init

设备子系统初始化,即一级目录devices

1
2
3
4
int __init devices_init(void)
{
return subsystem_register(&devices_subsys);
}

这里初始化一个设备子系统,其中的devices_subsys由宏生成

1
decl_subsys(devices, &ktype_device, &device_hotplug_ops);

它声明了一个子系统结构体,并将其kset进行赋值,分别是kset->kobj->name,kset->ktype,kset->hotplug_ops。

有一个值得注意的小细节,devices子系统是顶层子系统,因此没有上级节点,在注册时,这个子系统的subsys->kset->kobj->parent=NULL,作为对比,当注册system子系统时,由于该子系统归属于devices子系统, 因此在其注册时,这个区别会在kset_add时显示出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//声明system子系统
decl_subsys(system, &ktype_sysdev, NULL);
int __init system_bus_init(void)
{
//这里注意,由于这个子系统存在上级子系统,那么其parent要设置好,而这个子系统归属于devices子系统,其parent就是devices_subsys.kset.kobj
system_subsys.kset.kobj.parent = &devices_subsys.kset.kobj;
return subsystem_register(&system_subsys);
}
int kset_add(struct kset * k)
{
//看这里的区别,如果其有上级节点,就不赋值parent,否则没有,但是有归属子系统,就将其上级节点设为这个子系统
if (!k->kobj.parent && !k->kobj.kset && k->subsys)
k->kobj.parent = &k->subsys->kset.kobj;

return kobject_add(&k->kobj);
}

bus_init

总线子系统初始化,即一级目录bus

devices_init差不多

四、linux驱动模型

driver_init——驱动模型初始化

linux的驱动初始化由driver_init开始,该函数准备好驱动框架,注册好一些/sys目录下的一级目录,比如devicesbusclass,等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void __init driver_init(void)
{
/* These are the core pieces */
devices_init(); //devices目录
buses_init(); //bus目录
classes_init(); //class目录
firmware_init(); //firmware目录

/* These are also core pieces, but must come after the
* core core pieces.
*/
platform_bus_init(); //platform目录
system_bus_init(); //devices下的system目录
cpu_dev_init(); //cpu目录
attribute_container_init();
}

然后在do_initcalls里面进行具体某个驱动的注册。

bus_register——总线注册

向系统注册一个总线,会被增加到/sys/bus目录中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int bus_register(struct bus_type * bus)
{
int retval;
//这里其实就是设置总线内嵌的kobj的名字
retval = kobject_set_name(&bus->subsys.kset.kobj, "%s", bus->name);
if (retval)
goto out;
//这里是设置这个总线所属的kset,即:声明这个总线是属于总线子系统的!
//等价于bus->subsys->kset->kobj->kset = bus_subsys.kset
subsys_set_kset(bus, bus_subsys);
//向系统注册这个总线
retval = subsystem_register(&bus->subsys);
if (retval)
goto out;
//设置总线包含的device的名字(这里的devices是个kset,它还没有设置名字)
kobject_set_name(&bus->devices.kobj, "devices");
//devices这个kset的子系统
bus->devices.subsys = &bus->subsys;
//注册这个kset
retval = kset_register(&bus->devices);
if (retval)
goto bus_devices_fail;
//同理,driver这个kset的设置
kobject_set_name(&bus->drivers.kobj, "drivers");
bus->drivers.subsys = &bus->subsys;
bus->drivers.ktype = &ktype_driver;
retval = kset_register(&bus->drivers);
if (retval)
goto bus_drivers_fail;
//增加这个总线的属性
bus_add_attrs(bus);

pr_debug("bus type '%s' registered\n", bus->name);
return 0;

bus_drivers_fail:
kset_unregister(&bus->devices);
bus_devices_fail:
subsystem_unregister(&bus->subsys);
out:
return retval;
}

device_register——设备注册

向系统注册一个设备

1
2
3
4
5
6
7
int device_register(struct device *dev)
{
//初始化这个设备
device_initialize(dev);
//将设备增加到某个总线中
return device_add(dev);
}

设备的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
void device_initialize(struct device *dev)
{
//首先,设置这个设备所属的kset,设备肯定要属于一个设备子系统
kobj_set_kset_s(dev, devices_subsys);
//初始化设备 内嵌的kobj
kobject_init(&dev->kobj);
//初始化相关链表
INIT_LIST_HEAD(&dev->node);//用来串联兄弟设备
INIT_LIST_HEAD(&dev->children);//子设备挂在这个节点
INIT_LIST_HEAD(&dev->driver_list);//指向驱动链表
INIT_LIST_HEAD(&dev->bus_list);//指向同一总线的其他设备
INIT_LIST_HEAD(&dev->dma_pools);//DMA缓冲池链表的首部
}

增加一个设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
int device_add(struct device *dev)
{
struct device *parent = NULL;
int error = -EINVAL;

//递增计数器
dev = get_device(dev);
//判断bus_id是否存在,这是一个类似于k_name的结构,这个设备被注册后,会分配一个bus_id,这个id表明设备名字+编号,比如这个bus上挂了五个同类串口设备,那么这五个设备的bus_id可能是serial1,serial2,...
if (!dev || !strlen(dev->bus_id))
goto Error;
//递增其父设备的计数
parent = get_device(dev->parent);

pr_debug("DEV: registering device: ID = '%s'\n", dev->bus_id);

/* first, register with generic layer. */
//将bus_id的名字赋给这个设备内嵌的kobj
kobject_set_name(&dev->kobj, "%s", dev->bus_id);
//如果存在父设备,就将其设备的kobj也设置父节点
if (parent)
dev->kobj.parent = &parent->kobj;

//增加这个kobj
if ((error = kobject_add(&dev->kobj)))
goto Error;
//电源管理相关
if ((error = device_pm_add(dev)))
goto PMError;
//让这个总线增加这个设备
if ((error = bus_add_device(dev)))
goto BusError;
down_write(&devices_subsys.rwsem);
//如果存在父设备,就将这个设备加入到父设备的children的链表中
if (parent)
list_add_tail(&dev->node, &parent->children);
up_write(&devices_subsys.rwsem);

/* notify platform of device entry */
//平台设备的通知链
if (platform_notify)
platform_notify(dev);
Done:
put_device(dev);
return error;
BusError:
device_pm_remove(dev);
PMError:
kobject_del(&dev->kobj);
Error:
if (parent)
put_device(parent);
goto Done;
}

总线去增加这个设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int bus_add_device(struct device * dev)
{
//得到这个设备的总线
struct bus_type * bus = get_bus(dev->bus);
int error = 0;

//如果这个总线存在,不存在就没啥操作了
if (bus) {
down_write(&dev->bus->subsys.rwsem);
pr_debug("bus %s: add device %s\n", bus->name, dev->bus_id);
//将这个设备增加到该设备所属的总线的设备链表中
list_add_tail(&dev->bus_list, &dev->bus->devices.list);
//关键点!
//让这个设备去搜寻其对应的设备驱动
device_attach(dev);
up_write(&dev->bus->subsys.rwsem);
//增加这个设备对应的属性文件
device_add_attrs(bus, dev);
//增加符号链接
sysfs_create_link(&bus->devices.kobj, &dev->kobj, dev->bus_id);
}
return error;
}

核心函数——找一个驱动去匹配这个设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int device_attach(struct device * dev)
{
struct bus_type * bus = dev->bus;
struct list_head * entry;
int error;

//如果这个设备本身有驱动,就直接将这个设备和其驱动绑定
if (dev->driver) {
device_bind_driver(dev);
return 1;
}
//走到这里,说明没有绑定的驱动,那么可以通过总线的match函数搜索驱动
if (bus->match) {
//遍历这个总线的所有驱动
list_for_each(entry, &bus->drivers.list) {
//得到这个驱动
struct device_driver * drv = to_drv(entry);
//调用这个驱动去绑定设备
error = driver_probe_device(drv, dev);
//如果绑定成功,return 1
if (!error)
/* success, driver matched */
return 1;
//否则,看是否是没有设备或者IO占用,报错
if (error != -ENODEV && error != -ENXIO)
/* driver matched but the probe failed */
printk(KERN_WARNING
"%s: probe of %s failed with error %d\n",
drv->name, dev->bus_id, error);
}
}
//return 0说明没有匹配成功
return 0;
}

驱动去尝试probe设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int driver_probe_device(struct device_driver * drv, struct device * dev)
{
//如果这个设备所属的总线有match函数,那么调用这个match
if (drv->bus->match && !drv->bus->match(dev, drv))
return -ENODEV;

//给这个设备所属的驱动赋值,说明这个设备现在有对应的驱动了
dev->driver = drv;
//调用驱动的probe
if (drv->probe) {
int error = drv->probe(dev);
//如果出错,就返回错误
if (error) {
dev->driver = NULL;
return error;
}
}
//否则到了这里,说明一级找到了设备对应的驱动,那么进行绑定
device_bind_driver(dev);
return 0;
}

设备绑定驱动

1
2
3
4
5
6
7
8
9
10
void device_bind_driver(struct device * dev)
{
pr_debug("bound device '%s' to driver '%s'\n",
dev->bus_id, dev->driver->name);
//这里做真正的绑定操作,将这个设备的链表节点加入到驱动的devices链表中
list_add_tail(&dev->driver_list, &dev->driver->devices);
sysfs_create_link(&dev->driver->kobj, &dev->kobj,
kobject_name(&dev->kobj));
sysfs_create_link(&dev->kobj, &dev->driver->kobj, "driver");
}

driver_register——驱动注册

向系统注册一个驱动

1
2
3
4
5
6
7
8
9
int driver_register(struct device_driver * drv)
{
//初始化链表,devices是这个驱动所能支持的设备
INIT_LIST_HEAD(&drv->devices);
//初始化锁
init_MUTEX_LOCKED(&drv->unload_sem);
//bus增加一个驱动
return bus_add_driver(drv);
}

向bus增加一个驱动,可以和bus增加一个设备比较一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int bus_add_driver(struct device_driver * drv)
{
struct bus_type * bus = get_bus(drv->bus);
int error = 0;

//如果bus存在
if (bus) {
pr_debug("bus %s: add driver %s\n", bus->name, drv->name);
//设置这个驱动的名字到驱动的kobj中
error = kobject_set_name(&drv->kobj, "%s", drv->name);
if (error) {
put_bus(bus);
return error;
}
//驱动的上级节点是这个总线的driver这个kset
drv->kobj.kset = &bus->drivers;
//向系统注册这个驱动
if ((error = kobject_register(&drv->kobj))) {
put_bus(bus);
return error;
}

down_write(&bus->subsys.rwsem);
//关键函数,驱动去找设备
driver_attach(drv);
up_write(&bus->subsys.rwsem);
//模块相关
module_add_driver(drv->owner, drv);
//驱动增加属性文件
driver_add_attrs(bus, drv);
}
return error;
}

驱动绑定设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void driver_attach(struct device_driver * drv)
{
struct bus_type * bus = drv->bus;
struct list_head * entry;
int error;

//如果这个总线没有match函数,直接返回
if (!bus->match)
return;
//遍历总线下的所有设备
list_for_each(entry, &bus->devices.list) {
struct device * dev = container_of(entry, struct device, bus_list);
//如果这个设备还没有驱动(有设备的驱动就不用管了,人家有主了)
if (!dev->driver) {
//仍旧是驱动probe设备
error = driver_probe_device(drv, dev);
//如果出错,并且错误不是没有设备(有其他令人发愁的错误出现了QAQ)
if (error && (error != -ENODEV))
/* driver matched but the probe failed */
printk(KERN_WARNING
"%s: probe of %s failed with error %d\n",
drv->name, dev->bus_id, error);
}
}
}

Linux设备驱动模型
https://yill-z.github.io/2025/01/06/Linux设备驱动模型/
作者
Yill Zhang
发布于
2025年1月6日
许可协议