0%

Linux设备模型

Linux设备模型

本文简要分析了linux设备文件系统sysfs的原理。

kobject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// /src/include/linux/kobject.h
struct kobject {
const char *name; // 目录名称
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype; // !重要
struct kernfs_node *sd; /* sysfs directory entry */
struct kref kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release;
#endif
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对应的就是/sys/底下的一个目录,不一定是/sys/xx/,也可能是/sys/devices/xx,总之对应的就是一个目录

为什么要这个结构体?

把外围设备的共有特性抽象出来,方便驱动程序的开发。

最重要的ktype属性

我们看ktype的结构属性:

  1. ktype属性嵌入到kobject当中,目的是描述kobject的一些共有的特性
  2. attribute存储了这个kobject的特性,attribute中的每一个特定都对应一个文件
  3. release函数的作用是在kobject的引用计数为0的时候,系统自动会调用自定义的release()来释放kobject对象。
  4. 我们可以通过sysfs_ops提供的操作函数改变attribute的值
  5. attribute

从kobject到kset

kset是由一组kobject作为链表节点组成的双向链表。

1
2
3
4
5
6
struct kset {
struct list_head list;
spinlock_t list_lock; // 自旋锁
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops; // 用于处理集合中的kobject对象的热拔插
}
  1. 单一一个kset中的一组kobject是相关的,比如一组块设备
  2. 和ktype相似,都是把一些相关的kobkect联系到一起
    1. 区别在于具有相同ktypekobject可以被分到不同的kset
    2. ktype是少数,kset要多一些
  3. kset中嵌入的kobject作为这一组的基类

管理和操作kobject

初始化

使用kobject的第一步是初始化,函数是kobject_init

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
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
char *err_str;

if (!kobj) {
err_str = "invalid kobject pointer!";
goto error;
}
if (!ktype) {
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 */
pr_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:
pr_err("kobject (%p): %s\n", kobj, err_str);
dump_stack();
}

其中kobject_init_internal

1
2
3
4
5
6
7
8
9
10
11
static void kobject_init_internal(struct kobject *kobj)
{
if (!kobj)
return;
kref_init(&kobj->kref);
INIT_LIST_HEAD(&kobj->entry);
kobj->state_in_sysfs = 0;
kobj->state_add_uevent_sent = 0;
kobj->state_remove_uevent_sent = 0;
kobj->state_initialized = 1;
}

推荐——我们可以直接调用kobject_create来处理:

1
2
3
4
5
6
7
8
9
10
11
static struct kobject *kobject_create(void)
{
struct kobject *kobj;

kobj = kzalloc(sizeof(*kobj), GFP_KERNEL); // 分配存储空间并清零
if (!kobj)
return NULL;

kobject_init(kobj, &dynamic_kobj_ktype);
return kobj;
}

引用计数的操作

1
2
3
4
// 增加引用计数
kobject_get(struct kobject *kobj)
// 减少引用计数
kobject_put(struct kobject *kobj)

sysfs

每一个kobject对应的是/sys/目录或者其子目录底下的一个目录。我们怎么把kobject导入到/sys/中呢?

sysfs中添加和删除kobject

我们使用kobject_add函数来完成添加kobject/sys系统中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...)
{
va_list args;
int retval;

if (!kobj)
return -EINVAL;

if (!kobj->state_initialized) {
pr_err("kobject '%s' (%p): tried to add an uninitialized object, something is seriously wrong.\n",kobject_name(kobj), kobj);
dump_stack();
return -EINVAL;
}
va_start(args, fmt);
retval = kobject_add_varg(kobj, parent, fmt, args);
va_end(args);

return retval;
}
EXPORT_SYMBOL(kobject_add);

删除一个kobject对应的目录:

1
static void __kobject_del(struct kobject *kobj)

Create a struct kobject dynamically and register it with sysfs.

1
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)

/sys/中添加文件

我们已经知道了怎么把kobject映射为一个目录,那么如何为每个目录创建文件呢?

默认属性

1
2
3
4
5
6
7
8
9
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops; /*定义了对属性的操作*/
struct attribute **default_attrs; /*定义默认属性*/
const struct attribute_group **default_groups;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid);
};
  • 默认的文件集合由kobject中的ktype字段提供

  • struct attribute **default_attrs这个属性负责把kobject的内核数据映射成为文件

1
2
3
4
5
6
7
8
9
struct attribute {
const char *name; /*名称*/
umode_t mode; /*权限*/
#ifdef CONFIG_DEBUG_LOCK_ALLOC
bool ignore_lockdep:1;
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};
  • const struct sysfs_ops *sysfs_ops;定了了对属性的操作
1
2
3
4
5
6
struct sysfs_ops {
//读sysfs文件的时候被调用
ssize_t (*show)(struct kobject *, struct attribute *, char *);
//写sysfs文件的时候被调用
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};

创建新属性和删除属性

  1. 一般来说,默认属性是充足的,少部分情况下需要添加属性,使用sysfs_create_file函数
  2. 删除属性使用sysfs_remove_file函数

内核事件层

内核事件层是指内核到用户的消息通知系统。这个系统就是建立在kobject基础之上的。

内核事件层把事件模拟为信号,每一个时间有明确的事件源,就是kobject对象,所以每一个事件源都对应一个sysfs路径。为了从用户空间接收事件,用户空间实现了一个后台服务用于监听套接字。

在内核空间向用户空间发送信号使用函数

1
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
  1. 第一个参数是事件源
  2. 第二个参数是事件的动作