Linux 内核大量使用面向对象的设计思想,通过追踪源码,我们甚至可以使用面向对象语言常用的 UML 类图来分析 Linux 设备管理的"类"之间的关系。这里以 4.14 内核为例从 kobject,kset,ktype 的分析入手,进而一探内核对于设备的管理方式。
这个宏几乎是 linux 数据结构的基础,Linux 中的链表与传统的链表不同,其链表的节点本身并不包含任何数据,任何想要插入到链表的数据只需要包含一个事先写好的节点。
// include/linux/types.h
struct list_head {
struct list_head *next, *prev;
};
但是,使用这种通用的链表的第一个问题就是如何根据一个 list_head 成员来找到相应的数据,Linux 社区的大神们早就找到了相应的方法,就是利用下面这个 container_of 宏,只需要输入成员指针 ptr,包含该成员的结构体类型 type,以及该成员在结构体中名字 name 就可以返回包含 ptr 的 type 类型的结构首地址,这个宏充分利用了 C 语言直接操作内存的特性。需要注意的是,如果单纯为了得到地址只需要 ptr-&((type* 0)->member),内核的写法其实还利用了编译器的类型检查机制做了一份校验工作,即如果传入的 ptr 类型和 type->member 的类型不匹配,会报错。
// include/linux/kernel.h
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \
!__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)(__mptr - offsetof(type, member))); })
Linux 内核中有大量的驱动,而这些驱动往往具有类似的结构,根据面向对象的思想,我们就可以将这些共同的部分提取为父类,这个父类就是 kobject,也就是驱动编程中使用的".ko"文件的由来,下面这张图是我根据内核源码的 kobject 绘制的简单的 UML 图,从中可以看出,kobject 包含了大量的设备必须的信息,而三大类设备驱动都需要包含这个 kobject 结构,也就是"继承"自kobject。一个 kobject 对象就对应 sys 目录中的一个设备。
内核源码中的 kobject 结构定义如下:
// 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 这个结构中,
① name: 表示 kobject 对象的名字,对应 sysfs 下的一个目录。
② entry: 是 kobject 中插入的 head_list 结构。
③ parent: 是指向当前 kobject 父对象的指针,体现在 sysfs 结构中就是包含当前 kobject 对象的目录对象(即当前 kobject 目录的父目录)。
④ kset: 表示当前 kobject 对象所属的集合。
⑤ ktype: 表示当前 kobject 的类型。
⑥ sd: 用于表示 VFS 文件系统的目录项,是设备与文件之间的桥梁,sysfs 中的符号链接就是通过 kernfs_node 内的联合体实现的。
⑦ kref: 是对 kobject 的引用计数,当引用计数为 0 时,就回调之前注册的 release 方法释放该对象。
⑧ state_initialized: 初始化标志位,在对象初始化时被置位,表示对象是否已经被初始化。
⑨ state_in_sysfs: 表示 kobject 对象在 sysfs 中的状态,在对应目录中被创建则置1,否则为0。
⑩ state_add_uevent_sent: 是添加设备的 uevent 事件是否发送标志,添加设备时会向用户空间发送 uevent 事件,请求新增设备。
⑪ state_remove_uevent_sent: 是删除设备的 uevent 事件是否发送标志,删除设备时会向用户空间发送 uevent 事件,请求卸载设备。
4.14 的内核在 lib/koject.c 等源码中定义了一系列对 kobject 操作的函数,这里只列出最简单的几个:
(1)初始化 kobject
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;
}
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 */
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();
}
EXPORT_SYMBOL(kobject_init);
(2)注册 kobject
// 添加 kobject 到内核
/* add the kobject to its kset's list */
static void kobj_kset_join(struct kobject *kobj)
{
if (!kobj->kset)
return;
kset_get(kobj->kset);
spin_lock(&kobj->kset->list_lock);
list_add_tail(&kobj->entry, &kobj->kset->list);
spin_unlock(&kobj->kset->list_lock);
}
static int kobject_add_internal(struct kobject *kobj)
{
int error = 0;
struct kobject *parent;
...
parent = kobject_get(kobj->parent);
/* join kset if set, use it as parent if we do not already have one */
if (kobj->kset) {
if (!parent)
parent = kobject_get(&kobj->kset->kobj);
kobj_kset_join(kobj);
kobj->parent = parent;
}
pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n",
kobject_name(kobj), kobj, __func__,
parent ? kobject_name(parent) : "",
kobj->kset ? kobject_name(&kobj->kset->kobj) : "");
error = create_dir(kobj);
...
kobj->state_in_sysfs = 1;
return error;
}
static __printf(3, 0) int kobject_add_varg(struct kobject *kobj,
struct kobject *parent,
const char *fmt, va_list vargs)
{
int retval;
retval = kobject_set_name_vargs(kobj, fmt, vargs);
if (retval) {
printk(KERN_ERR "kobject: can not set name properly!\n");
return retval;
}
kobj->parent = parent;
return kobject_add_internal(kobj);
}
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) {
printk(KERN_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);
(3)初始化并注册 kobject
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
struct kobject *parent, const char *fmt, ...)
{
va_list args;
int retval;
kobject_init(kobj, ktype);
va_start(args, fmt);
retval = kobject_add_varg(kobj, parent, fmt, args);
va_end(args);
return retval;
}
EXPORT_SYMBOL_GPL(kobject_init_and_add);
(4)注销 kobject
void kobject_del(struct kobject *kobj)
{
struct kernfs_node *sd;
if (!kobj)
return;
sd = kobj->sd;
sysfs_remove_dir(kobj);
sysfs_put(sd);
kobj->state_in_sysfs = 0;
kobj_kset_leave(kobj);
kobject_put(kobj->parent);
kobj->parent = NULL;
}
EXPORT_SYMBOL(kobject_del);
(5)kobject 计数加一
// 将 kobject 对象的引用计数加 1,同时返回该对象指针。
// include/linux/refcount.h
static inline void refcount_inc(refcount_t *r)
{
atomic_inc(&r->refs);
}
// include/linux/kref.h
static inline void kref_get(struct kref *kref)
{
refcount_inc(&kref->refcount);
}
// lib/kobject.c
struct kobject *kobject_get(struct kobject *kobj)
{
if (kobj) {
if (!kobj->state_initialized)
WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
"initialized, yet kobject_get() is being "
"called.\n", kobject_name(kobj), kobj);
kref_get(&kobj->kref);
}
return kobj;
}
EXPORT_SYMBOL(kobject_get);
(6)kobject 计数减一
// 将 kobject 对象的引用计数减 1,如果减为零就释放
// include/linux/refcount.h
static inline __must_check bool refcount_dec_and_test(refcount_t *r)
{
return atomic_dec_and_test(&r->refs);
}
// include/linux/kref.h
static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
if (refcount_dec_and_test(&kref->refcount)) {
release(kref);
return 1;
}
return 0;
}
// lib/kobject.c
void kobject_put(struct kobject *kobj)
{
if (kobj) {
if (!kobj->state_initialized)
WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
"initialized, yet kobject_put() is being "
"called.\n", kobject_name(kobj), kobj);
kref_put(&kobj->kref, kobject_release);
}
}
EXPORT_SYMBOL(kobject_put);
kset 表示一组 kobject 的集合,kobject 通过 kset 组织成层次化的结构,所有属于该 kset 的 kobject 结构的 parent 指针指向 kset 包含的 kobject 对象,构成一个父子层次关系这些 kobject 可以是不同或相同的类型 (kobj_type)。sysfs 中的设备组织结构很大程度上都是根据 kset 进行组织的,比如 "/sys/drivers" 目录就是一个 kset 对象,包含系统中的驱动程序对应的目录,驱动程序的目录由 kobject 表示。比如在平台设备模型中,当我们注册一个设备或驱动到平台总线,其实是将对应的 kobject 挂接到 platform 总线的 kset 上,每种总线都是维护两条链表 (两个kset),一条用于链接挂接在上面的驱动 (驱动kset),一条用于链接挂接在上面的设备 (设备kset)。
// include/linux/kobject.h
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops;
} __randomize_layout;
下面简单分析一下其中的成员,
① list: list_head 还是那个用来挂在链表上的结构,包含在一个 kset 中的所有 kobject 构成了一个双向循环链表,list 就是这个链表的头部,这个链表用来连接第一个和最后一个 kobject 对象,第一个 kobjetc 使用 entry 连接 kset 集合以及第二个 kobject 对象,第二个 kobject 对象使用 entry 连接第一个 kobject 对象和第三个 kobject 对象,依次类推,最终形成一个 kobject 对象的链表。
② kobj: 是归属于该 kset 的所有 kobject 共同的 parent,这个 parent 就是体现内核设备组织结构的关键。同时,kset 的引用计数就是内嵌的 kobject 对象的引用次数。
下面是几个关于 kset 的基础操作方法
(1)初始化 kset
// lib/kobject.c
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;
}
void kset_init(struct kset *k)
{
kobject_init_internal(&k->kobj);
INIT_LIST_HEAD(&k->list);
spin_lock_init(&k->list_lock);
}
(2)注册 kset
// lib/kobject.c
int kset_register(struct kset *k)
{
int err;
if (!k)
return -EINVAL;
kset_init(k);
err = kobject_add_internal(&k->kobj);
if (err)
return err;
kobject_uevent(&k->kobj, KOBJ_ADD);
return 0;
}
EXPORT_SYMBOL(kset_register);
(3)注销 kset
// lib/kobject.c
void kset_unregister(struct kset *k)
{
if (!k)
return;
kobject_del(&k->kobj);
kobject_put(&k->kobj);
}
EXPORT_SYMBOL(kset_unregister);
(4)kset 计数加一
// include/linux/kobject.h
static inline struct kset *kset_get(struct kset *k)
{
return k ? to_kset(kobject_get(&k->kobj)) : NULL;
}
(5)kset 计数减一
// include/linux/kobject.h
static inline void kset_put(struct kset *k)
{
kobject_put(&k->kobj);
}
// include/linux/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);
};
这个结构主要是表征 kobject 的类型,
① release: 是一个释放 kobject 对象的接口,有点像面向对象中的析构。
② sysfs_ops: 是操作 kobject 的方法集。
// include/linux/sysfs.h
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
};
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *, char *);
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};
我们在使用 cat、echo 等工具 (read/write) 系统调用进行读写 sysfs 中相应驱动的属性时,其实就是回调驱动的 show() 和 store() 函数。由此可见,对同一类型的 kobject 操作会回调同一个kobj_type 的方法。
// include/linux/kobject.h
static inline struct kobj_type *get_ktype(struct kobject *kobj)
{
return kobj->ktype;
}
从这个函数中可以看出,4.14 提取 kobject 的 kobj_type 的时候直接提取 kobject 的,我还测试过3.14版本的,也是这种写法,不过网上还有下面的这种 get_ktype 的实现,还没找到具体是哪个版本,显然,这个版本中 kset 中的 ktype 这个类型优先于 kobject 自身中的 ktype。因此在典型的应用中, 在 struct kobject 中的 ktype 成员被设为 NULL, 而 kset 中的 ktype 是实际被使用的。
static inline struct kobj_type * get_ktype(struct kobject * k)
{
if (k->kset && k->kset->ktype)
return k->kset->ktype;
else
return k->ktype;
}
kobject,kset 是 Linux 设备管理中的基本结构体,但在实际操作中我们几乎不会实际操作这些结构,因为他们本身并不具有针对某一个具体设备或驱动的信息,在 Linux 内核中,这两个结构都是被包含具体的设备结构中,比如 cdev,gendisk 等,从面向对象的角度考虑,就是每一类设备都可以看作这两个结构的子类。
通过上面的分析,我们可以看出这三者之间的关系,并画出下面的结构框图,sysfs 中的上目录结构就是根据 kset 之间的数据组织方式进行呈现的。