设备驱动模型之sysfs与kobject
1. kobject, kset和ktype
要分析sysfs
,首先就要分析kobject
和kset
,因为驱动设备的层次结构的构成就是由这两个东东来完成的。
sysfs
与kobject
密不可分。
1.1 kobject
kobject
是组成设备模型的基本结构,是所有用来描述设备模型的数据结构的基类。
kobject
是一个对象的抽象,它用于管理对象。
kobject
被用来控制访问一个更大的,具有特定作用域的对象;为了达到这个作用,kobject
将会被嵌入到其他结构体中。
如果你用面向对象的角度来看,kobject
结构可以被看做顶层的抽象类,其他类都是从这个类派生出来的。
在sysfs
中,每个kobject
对应着一个目录。
/*include/linux/kobject.h*/
struct kobject {
/* 对应sysfs的目录名 */
const char *name;
/*用于连接到所属kset的链表中,用于将kobj挂在kset->list中*/
struct list_head entry;
/*指向 父对象,形成层次结构,在sysfs中表现为父子目录的关系*/
struct kobject *parent;
/*
属于哪个kset
表征该kobj所属的kset。
kset可以作为parent的“候补”:当注册时,传入的parent为空时,可以让kset来担当。
*/
struct kset *kset;
/*类型属性,每个kobj或其嵌入的结构对象应该都对应一个kobj_type。 */
const struct kobj_type *ktype;
/*sysfs中与该对象对应的文件节点对象*/
// 在3.14以后的内核中,sysfs基于kernfs来实现。
struct kernfs_node *sd; /* sysfs directory entry */
/*对象的应用计数*/
struct kref kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release;
#endif
/* 记录初始化与否。调用kobject_init()后,会置位。 */
unsigned int state_initialized:1;
/* 记录kobj是否注册到sysfs,在kobject_add_internal()中置位。 */
unsigned int state_in_sysfs:1;
/* 当发送KOBJ_ADD消息时,置位。提示已经向用户空间发送ADD消息。*/
unsigned int state_add_uevent_sent:1;
/* 当发送KOBJ_REMOVE消息时,置位。提示已经向用户空间发送REMOVE消息。*/
unsigned int state_remove_uevent_sent:1;
/* 如果该字段为1,则表示忽略所有上报的uevent事件。 */
unsigned int uevent_suppress:1;
};
/*include/linux/kref.h*/
struct kref {
refcount_t refcount;
};
目前为止,Kobject
主要提供如下功能:
- 构成层次结构:通过
parent
指针,可以将所有Kobject
以层次结构的形式组合起来。 - 生命周期管理:使用引用计数(
reference count
),来记录Kobject
被引用的次数,并在引用次数变为0
时把它释放。 - 和
sysfs
虚拟文件系统配合,将每一个Kobject
及其特性,以文件的形式,开放到用户空间。
没有一个结构会嵌入多于一个kobject
结构,如果这么做了,关于这个对象的引用计数肯定会一团糟,你的code
也会充满bug
,所以千万不要这么做。
实际上,kobject
对自身实现什么功能并不感兴趣,它存在的意义在于把高级的对象链接到设备模型上。因此内核代码很少去创建一个单独的kobject
对象,相反,kobject
用于控制对大型域相关对象的访问(通过container_of
)。
1.2 ktype
每个kobject
对象都内嵌有一个ktype
,该结构定义了kobject
在创建和删除时所采取的行为,可以理解为对象的属性。
实际上,Kobject
的生命周期管理就是由关联的ktype
来实现的。
注意,每个kobject
必须关联一个ktype
。
由于通用性,多个Kobject可能共用同一个属性操作集,因此把Ktype独立出来了。
/*include/linux/kobject.h*/
struct kobj_type {
/* 处理对象终结的回调函数。该接口应该由具体对象负责填充。 */
void (*release)(struct kobject *kobj);
/* 该类型kobj的sysfs操作接口。 */
const struct sysfs_ops *sysfs_ops;
/* 该类型kobj自带的缺省属性(文件),这些属性文件在注册kobj时,直接pop为该目录下的文件。 */
const struct attribute_group **default_groups;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
/*child_ns_type/namespace 是 文件系统命名空间相关)略*/
const void *(*namespace)(struct kobject *kobj);
void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid);
};
/*include/linux/sysfs.h*/
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *, char *);
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};
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
};
当kobject
的引用计数为0
时,通过release
方法来释放相关的资源。
attribute
为属性,每个属性在sysfs
中都有对应的属性文件。
sysfs_op
的两个方法用于实现读取和写入属性文件时应该采取的行为。
1.3 kset
kset
是一些kobject
的集合,这些kobject
可以有相同的ktype
(属性),也可以不同。
同时,kset
自己也包含一个kobject
;因此在sysfs
中,kset
也是对应这一个目录,但是目录下面包含着其他的kojbect
。
也有一种说法,把Kset看是一个特殊的Kobject。
每个Kobject
不一定出现在sys
中,但Kset
中的每个Kobject
会出现在sys
中。
/*include/linux/kobject.h*/
/**
* struct kset - a set of kobjects of a specific type, belonging to a specific subsystem.
*
* A kset defines a group of kobjects. They can be individually
* different "types" but overall these kobjects all want to be grouped
* together and operated on in the same manner. ksets are used to
* define the attribute callbacks and other common events that happen to
* a kobject.
*
* @list: the list of all kobjects for this kset
* @list_lock: a lock for iterating over the kobjects
* @kobj: the embedded kobject for this kset (recursion, isn't it fun...)
* @uevent_ops: the set of uevent operations for this kset. These are
* called whenever a kobject has something happen to it so that the kset
* can add new environment variables, or filter out the uevents if so
* desired.
*/
struct kset {
/*属于该kset的kobject链表,与kobj->entry对应,用来组织本kset管理的kobj*/
struct list_head list;
spinlock_t list_lock;
/*该kset内嵌的kobj*/
struct kobject kobj;
/*
kset用于发送消息的操作函数集。
需要指出的是,kset能够发送它所包含的各种子kobj、孙kobj的消息;
即kobj或其父辈、爷爷辈,都可以发送消息;优先父辈,然后是爷爷辈,以此类推。
*/
const struct kset_uevent_ops *uevent_ops;
} __randomize_layout;
kset
通过标准的内核链表(struct list_head list
)来管理它的所有子节点,kobject
通过kset
成员变量指向它的kset容器。
大多数情况下,kobject
都是它所属kset
(或者严格点,kset
内嵌的kobject
)的子节点。
2. kobject与kset的关系
下面这张图非常经典。最下面的kobj
都属于一个kset
,同时这些kobj
的父对象就是kset
内嵌的kobj
。
通过链表,kset
可以获取所有属于它的kobj
。
从sysfs
角度而言,kset
代表一个文件夹,而下面的kobj
就是这个文件夹里面的内容,而内容有可能是文件也有可能是文件夹。
kobj
和kset
没有严格的层级关系,也并不是完全的父子关系。如何理解这句话?
kobject
之间可以组成一个树形结构:Kobj.parent = kobj
kset
之间也可以组成一个树形结构,但这是基于kobject
实现的:Kset.kobj.parent = Kset'.kobj
正因为
kobj
和kset
并不是完全的父子关系,因此在注册kobj
时,将同时对parent
及其所属的kset
增加引用计数。
若parent
和kset
为同一对象,则会对kset
增加两次引用计数。
kset
算是kobj
的“接盘侠”:
- 当
kobj
没有所属的parent
时,才让kset
来接盘当parent
; - 如果连
kset
也没有,那该kobj
属于顶层对象,其sysfs
目录将位于/sys/
下。
kset内
部本身也包含一个kobj
对象,在sysfs
中也表现为目录;所不同的是,kset
要承担kobj
状态变动消息的发送任务。因此,首先kset
会将所属的kobj
组织在kset.list
下,同时,通过uevent_ops
在合适时候发送消息。
对于kobject_add()
来说,它的输入信息是:kobj-parent
、kobj-name
,kobject_add()
优先使用传入的parent
作为kobj->parent
;其次,使用kset
作为kobj->parent
。
kobj
状态变动后,必须依靠所关联的kset
来向用户空间发送消息;若无关联kset
(该kobj
向上组成的树中,任何成员都无所属的kset
),则kobj
无法发送用户消息。
kobject
结构可能的层次结构如图:
3. 通过kobject创建bus目录
分析如何通过kobject
在/sys
创建bus
目录。
/*drivers/base/bus.c*/
static int bus_uevent_filter(struct kobject *kobj)
{
const struct kobj_type *ktype = get_ktype(kobj);
if (ktype == &bus_ktype)
return 1;
return 0;
}
static const struct kset_uevent_ops bus_uevent_ops = {
.filter = bus_uevent_filter,
};
int __init buses_init(void)
{
// 直接调用kset_create_and_add,第一个参数为要创建的目录的名字,而第三个参数指定父对象。
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
if (!bus_kset)
return -ENOMEM;
system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj);
if (!system_kset)
return -ENOMEM;
return 0;
}
3.1 kset_create_and_add
kset_create_and_add
是kobject_create
和kobject_add
的组合
/*lib/kobject.c*/
/**
* kset_create_and_add() - Create a struct kset dynamically and add it to sysfs.
*
* @name: the name for the kset
* @uevent_ops: a struct kset_uevent_ops for the kset
* @parent_kobj: the parent kobject of this kset, if any.
*
* This function creates a kset structure dynamically and registers it
* with sysfs. When you are finished with this structure, call
* kset_unregister() and the structure will be dynamically freed when it
* is no longer being used.
*
* If the kset was not able to be created, NULL will be returned.
*/
/**
* kobject_create_and_add - 动态创建一个kobject结构并注册到sysfs
*
* @name: kobject的名称
* @parent: kobject的parent kobject of this kobject, 如果有的话
*
* 该方法动态创建一个kobject结构并注册到sysfs。当你完成该结构
* 之后. kobject_del(),这样该结构在不再使用时将会动态的释放。
*
* 如果该kobject无法被创建,将会返回NULL。
*/
struct kset *kset_create_and_add(const char *name,
const struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj)
{
struct kset *kset;
int error;
/*创建kset实例,设置某些字段*/
kset = kset_create(name, uevent_ops, parent_kobj);
if (!kset)
return NULL;
/*添加kset到sysfs*/
error = kset_register(kset);
if (error) {
kfree(kset);
return NULL;
}
return kset;
}
EXPORT_SYMBOL_GPL(kset_create_and_add);
/**
* kobject_add() - The main kobject add function.
* @kobj: the kobject to add
* @parent: pointer to the parent of the kobject.
* @fmt: format to name the kobject with.
*
* The kobject name is set and added to the kobject hierarchy in this
* function.
*
* If @parent is set, then the parent of the @kobj will be set to it.
* If @parent is NULL, then the parent of the @kobj will be set to the
* kobject associated with the kset assigned to this kobject. If no kset
* is assigned to the kobject, then the kobject will be located in the
* root of the sysfs tree.
*
* Note, no "add" uevent will be created with this call, the caller should set
* up all of the necessary sysfs files for the object and then call
* kobject_uevent() with the UEVENT_ADD parameter to ensure that
* userspace is properly notified of this kobject's creation.
*
* Return: If this function returns an error, kobject_put() must be
* called to properly clean up the memory associated with the
* object. Under no instance should the kobject that is passed
* to this function be directly freed with a call to kfree(),
* that can leak memory.
*
* If this function returns success, kobject_put() must also be called
* in order to properly clean up the memory associated with the object.
*
* In short, once this function is called, kobject_put() MUST be called
* when the use of the object is finished in order to properly free
* everything.
*/
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);
这里主要调用了两个函数,接下分别来看下。
3.1.1 kset_create
建立kset
,设置名字与父对象。
路径:lib/kobject.c
/**
* kset_create() - Create a struct kset dynamically.
*
* @name: the name for the kset
* @uevent_ops: a struct kset_uevent_ops for the kset
* @parent_kobj: the parent kobject of this kset, if any.
*
* This function creates a kset structure dynamically. This structure can
* then be registered with the system and show up in sysfs with a call to
* kset_register(). When you are finished with this structure, if
* kset_register() has been called, call kset_unregister() and the
* structure will be dynamically freed when it is no longer being used.
*
* If the kset was not able to be created, NULL will be returned.
*/
static struct kset *kset_create(const char *name,
const struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj)
{
struct kset *kset;
int retval;
/*创建 kset 实例*/
kset = kzalloc(sizeof(*kset), GFP_KERNEL);
if (!kset)
return NULL;
/*设置kobj->name*/
retval = kobject_set_name(&kset->kobj, "%s", name);
if (retval) {
kfree(kset);
return NULL;
}
kset->uevent_ops = uevent_ops;
/*设置父对象*/
kset->kobj.parent = parent_kobj;
/*
* The kobject of this kset will have a type of kset_ktype and belong to
* no kset itself. That way we can properly free it when it is
* finished being used.
*/
kset->kobj.ktype = &kset_ktype;
/*本keset不属于任何kset*/
kset->kobj.kset = NULL;
return kset;
}
这个函数中,动态分配了kset
实例,调用kobject_set_name
设置kset->kobj->name
为bus
,也就是我们要创建的目录bus
。
同时这里kset->kobj.parent
为NULL
,也就是没有父对象。
因为要创建的
bus
目录是在sysfs
所在的根目录创建的,自然没有父对象。
随后简要看下由kobject_set_name
函数调用引发的一系列调用。
/*lib/kobject.c*/
/**
* kobject_set_name_vargs() - Set the name of a kobject.
* @kobj: struct kobject to set the name of
* @fmt: format string used to build the name
* @vargs: vargs to format the string.
*/
int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
va_list vargs)
{
const char *s;
if (kobj->name && !fmt)
return 0;
s = kvasprintf_const(GFP_KERNEL, fmt, vargs);
if (!s)
return -ENOMEM;
/*
* ewww... some of these buggers have '/' in the name ... If
* that's the case, we need to make sure we have an actual
* allocated copy to modify, since kvasprintf_const may have
* returned something from .rodata.
*/
if (strchr(s, '/')) {
char *t;
t = kstrdup(s, GFP_KERNEL);
kfree_const(s);
if (!t)
return -ENOMEM;
strreplace(t, '/', '!');
s = t;
}
kfree_const(kobj->name);
kobj->name = s;
return 0;
}
/*lib/kasprintf.c*/
/* Simplified asprintf. */
char *kvasprintf(gfp_t gfp, const char *fmt, va_list ap)
{
unsigned int first, second;
char *p;
va_list aq;
va_copy(aq, ap);
first = vsnprintf(NULL, 0, fmt, aq);
va_end(aq);
p = kmalloc_track_caller(first+1, gfp);
if (!p)
return NULL;
second = vsnprintf(p, first+1, fmt, ap);
WARN(first != second, "different return values (%u and %u) from vsnprintf(\"%s\", ...)",
first, second, fmt);
return p;
}
EXPORT_SYMBOL(kvasprintf);
3.1.2 kset_register
/*lib/kobject.c*/
/**
* kset_register() - Initialize and add a kset.
* @k: kset.
*/
int kset_register(struct kset *k)
{
int err;
if (!k)
return -EINVAL;
/*初始化kset*/
kset_init(k);
/*在sysfs中建立目录*/
err = kobject_add_internal(&k->kobj);
if (err)
return err;
/* 向用户空间发送 KOBJ_ADD事件。 */
kobject_uevent(&k->kobj, KOBJ_ADD);
return 0;
}
EXPORT_SYMBOL(kset_register);
这里面调用了3个函数。这里先介绍前两个函数。kobject_uevent
实现的是用户空间事件传递,留到以后再说。
3.1.2.1 kset_init
该函数用于初始化kset
。
/*lib/kobject.c*/
/**
* kset_init() - Initialize a kset for use.
* @k: kset
*/
void kset_init(struct kset *k)
{
/*初始化kobject的某些字段*/
kobject_init_internal(&k->kobj);
/*初始化链表头*/
INIT_LIST_HEAD(&k->list);
/*初始化自旋锁*/
spin_lock_init(&k->list_lock);
}
/* 设置初始化状态 */
static void kobject_init_internal(struct kobject *kobj)
{
if (!kobj)
return;
/*初始化引用基计数*/
kref_init(&kobj->kref);
/*初始化链表头*/
INIT_LIST_HEAD(&kobj->entry);
// 记录kobj是否注册到sysfs,在kobject_add_internal()中置位。
kobj->state_in_sysfs = 0;
// 当发送KOBJ_ADD消息时,置位。提示已经向用户空间发送ADD消息。
kobj->state_add_uevent_sent = 0;
// 当发送KOBJ_REMOVE消息时,置位。提示已经向用户空间发送REMOVE消息。
kobj->state_remove_uevent_sent = 0;
// 标记已经初始化了。
kobj->state_initialized = 1;
}
3.1.2.2 kobject_add_internal
该函数将在sysfs
中建立目录。
kobject_add_internal()
会根据kobj.parent
和kobj.kset
来综合决定父对象(相当于/sysfs
中的父级目录):
- 如果有
parent
则默认parent
为父对象; - 如果没有
parent
作为父对象,但是有当前的kobj
位于kset
中,则使用kset
中的kobj
作为父对象 - 如果没有
parent
也没有kset
作为父对象,那该kobj
属于顶层对象:在sysfs
中,我们可以看到kobj
位于/sys/
下。
/*lib/kobject.c*/
static int kobject_add_internal(struct kobject *kobj)
{
int error = 0;
struct kobject *parent;
if (!kobj)
return -ENOENT;
/*检查name字段是否存在*/
if (!kobj->name || !kobj->name[0]) {
return -EINVAL;
}
/*有父对象则增加父对象引用计数*/
parent = kobject_get(kobj->parent);
/* join kset if set, use it as parent if we do not already have one */
/*
由于在kset_create中有kset->kobj.kset = NULL,
因此if (kobj->kset)条件不满足。
*/
if (kobj->kset) {
if (!parent)
/*kobj属于某个kset,但是该kobj没有父对象,则以kset的kobj作为父对象*/
parent = kobject_get(&kobj->kset->kobj);
/*将kojbect添加到kset结构中的链表当中*/
kobj_kset_join(kobj);
/* 根据kobj.parent和kobj.kset来综合决定父对象*/
kobj->parent = parent;
}
pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n",
kobject_name(kobj), kobj, __func__,
parent ? kobject_name(parent) : "<NULL>",
kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>");
/*根据kobj->name在sys中建立目录*/
error = create_dir(kobj);
// 如果出错时,回收资源
if (error) {
/* 把kobj从kobj->kset的链表中去除 */
// 对应于 kobj_kset_join
kobj_kset_leave(kobj);
kobject_put(parent);
kobj->parent = NULL;
/* be noisy on error issues */
if (error == -EEXIST)
WARN(1, "%s failed for %s with "
"-EEXIST, don't try to register things with "
"the same name in the same directory.\n",
__func__, kobject_name(kobj));
else
WARN(1, "%s failed for %s (error: %d parent: %s)\n",
__func__, kobject_name(kobj), error,
parent ? kobject_name(parent) : "'none'");
} else
kobj->state_in_sysfs = 1;
return error;
}
因此在这个函数中,对name
进行了必要的检查之后,调用了create_dir
在sysfs
中创建目录。
在create_dir
执行完成以后会在sysfs
的根目录(/sys/
)建立文件夹bus
。该函数的详细分析将在后面给出。
至此,对bus
目录的建立有了简单而直观的了解。
我们可以看出kset
其实就是表示一个文件夹,而kset
本身也含有一个kobject
,而该kobject
的name
字段即为该目录的名字,本例中为bus
。
4. kobj/kset功能特性
我们先大概提一下这些功能特性,在后面的文章中会详细说明:对象生命周期管理以及用户空间事件投递。
4.1 对象生命周期管理
在创建一个kobj
对象时,kobj
中的引用计数管理成员kref
被初始化为1
;从此kobj
可以使用下面的API函数来进行生命周期管理:
/*lib/kobject.c*/
/**
* kobject_get() - Increment refcount for object.
* @kobj: object.
*/
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);
/**
* kobject_put() - Decrement refcount for object.
* @kobj: object.
*
* Decrement the refcount, and if 0, call kobject_cleanup().
*/
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);
static void dynamic_kobj_release(struct kobject *kobj)
{
pr_debug("kobject: (%p): %s\n", kobj, __func__);
kfree(kobj);
}
对于kobject_get()
,它就是直接使用kref_get()
接口来对引用计数进行加1操作;
而对于kobject_put()
,它不仅要使用kref_put()
接口来对引用计数进行减1
操作,还要对生命终结的对象执行release()
操作:当引用计数减为0
时,回收该对象的资源。
然而kobject
是高度抽象的实体,导致kobject
不会单独使用,而是嵌在具体对象中。反过来也可以这样理解:凡是需要做对象生命周期管理的对象,都可以通过内嵌kobject
来实现需求。
4.1.1 kobject在kobject_put的资源回收是如何实现的?
实际上,kobject_put()
通常被具体对象做一个简单包装,如:bus_put()
,它直接调用kset_put()
,然后调用到kobject_put()
。
那对于这个bus_type
对象而言,仅仅通过kobject_put()
,如何来达到释放整个bus_type
的目的呢?这里就需要kobject
另一个成员struct kobj_type * ktype
来完成。
当引用计数为0
时,kobject
核心会调用kobject_release()
,最后会调用kobj_type->release(kobj)
来完成对象的释放。可是具体对象的释放,最后却通过kobj->kobj_type->release()
来释放,那这个release()
函数,就必须得由具体的对象来指定。
还是拿bus_type
举例:
在通过bus_register(struct bus_type *bus)
进行总线注册时,该API内部会执行priv->subsys.kobj.ktype = &bus_ktype
操作;
// driver/base/bus.c
int bus_register(struct bus_type *bus)
{
// ...
priv->subsys.kobj.ktype = &bus_ktype;
// ...
}
有了该操作,那么前面的bus_put()
在执行bus_type->p-> subsys.kobj->ktype->release()
时,就会执行上面注册的bus_ktype.release = bus_release
函数;
// driver/base/bus.c
static struct kobj_type bus_ktype = {
.sysfs_ops = &bus_sysfs_ops,
.release = bus_release,
};
static void bus_release(struct kobject *kobj)
{
// 获取整个 具体的 bus子系统 对象
struct subsys_private *priv = to_subsys_private(kobj);
struct bus_type *bus = priv->bus;
// 释放资源
kfree(priv);
bus->p = NULL;
}
由于bus_release()
函数由具体的bus
子系统提供,它必定知道如何释放包括kobj
在内的bus_type
对象。
5. sysfs文件系统的层次组织
sysfs
向用户空间展示了驱动设备的层次结构。这一个功能比较简单,先完全贴出来。
我们都知道设备和对应的驱动都是由内核管理的,这些对于用户空间是不可见的。现在通过sysfs
,可以在用户空间直观的了解设备驱动的层次结构。
实际上,sysfs文
件系统的组织功能是基于kobject
实现的:该功能依靠kobj
的parent
、kset
、sd
等成员来完成。sysfs
文件系统能够以友好的界面,将kobj
所刻画的对象层次、所属关系、属性值,展现在用户空间。
我们来看看sysfs
的文件结构:
uos@uos-PC:~$ uname -r
4.19.0-loongson-3-desktop
uos@uos-PC:~$ ls /sys
block bus class dev devices firmware fs kernel module power
目录 | 意义 |
---|---|
block | 块设备 |
bus | 系统中的总线 |
class | 设备类型,比如输入设备 |
dev | 系统中已注册的设备节点的视图,有两个子目录char和block。 |
devices | 系统中所有设备拓扑结构视图 |
fireware | 固件 |
fs | 文件系统 |
kernel | 内核配置选项和状态信息 |
module | 模块 |
power | 系统的电源管理数据 |
6. 用户空间事件投递
实际上,当具体对象有事件发生时,相应的操作函数中(如device_add()
),会调用事件消息接口kobject_uevent()
。在该接口中,首先会添加一些共性的消息(路径、子系统名等),然后会找寻合适的kset->uevent_ops
,并调用该ops->uevent(kset, kobj, env)
来添加该对象特有的事件消息(如device
对象的设备号、设备名、驱动名、DT信息),一切准备完毕,就会通过两种可能的方法向用户空间发送消息:1.netlink
广播;2. uevent_helper
程序。
因此,上面具体对象的事件消息填充函数,应该由特定对象来填充;对于device
对象来说,在初始化的时候,通过下面这行代码:
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
来完成kset->uevent_op
s的指定,今后所有设备注册时,调用device_register()-->device_initialize()
后,都将导致:
dev->kobj.kset = devices_kset;
因此通过device_register()
注册的设备,在调用kobject_uevent()
接口发送事件消息时,就自动会调用devices_kset的device_uevent_ops
。该ops
的uevent()
方法定义如下:
static const struct kset_uevent_ops device_uevent_ops = {
.filter = dev_uevent_filter,
.name = dev_uevent_name,
.uevent = dev_uevent,
}; ||
\/
dev_uevent(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env)
add_uevent_var(env, "xxxxxxx", ....)
add_uevent_var(env, "xxxxxxx", ....)
add_uevent_var(env, "xxxxxxx", ....)
....
if (dev->bus && dev->bus->uevent)
dev->bus->uevent(dev, env) //通过总线的uevent()方法,发送设备状态改变的事件消息
if (dev->class && dev->class->dev_uevent)
dev->class->dev_uevent(dev, env)
if (dev->type && dev->type->uevent)
dev->type->uevent(dev, env)
在该ops
的uevent()
方法中,会分别调用bus
、class
、device type
的uevent
方法来生成事件消息。
7. kobject总结
kobject
可以看成是一个基类,这个基类通过ktype
属性实现了资源回收的功能,至于kset
,负责记录一组kobject
,我们将其看成是一个集合,负责事件管理。
从此,其他对象就可以通过包含kobject
来实现上层的功能。
8. sysfs
sysfs
是一个基于内存的虚拟的文件系统,由kernel
提供,挂载到 /sys
目录下(用 mount
查看得到 sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime))
,负责以设备树的形式向 user space
提供直观的设备和驱动信息。
8.1 对象模型体系
sysfs
(system filesystem
)是一个处于内存中的虚拟文件系统,为我们提供了kobject
对象层次结构的视图。以一个简单的文件系统的方式来观察系统中各种设备的拓扑结构。
sysfs
是用于表现设备驱动模型的文件系统,它基于ramfs
。要学习linux
的设备驱动模型,就要先做好底层工作,总结sysfs
提供给外界的API就是其中之一。
sysfs
文件系统中提供了四类文件的创建与管理,分别是目录、普通文件、软链接文件、二进制文件。
- 目录层次往往代表着设备驱动模型的结构
- 软链接文件则代表着不同部分间的关系。比如某个设备的目录只出现在
/sys/devices
下,其它地方涉及到它时只好用软链接文件链接过去,保持了设备唯一的实例。 - 普通文件和二进制文件往往代表了设备的属性,读写这些文件需要调用相应的属性读写。
sysfs
是表现设备驱动模型的文件系统,它的目录层次实际反映的是对象的层次。为了配合这种目录,linux专门提供了两个结构作为sysfs
的骨架,它们就是struct kobject
和struct kset
。
我们知道,sysfs
是完全虚拟的,它的每个目录其实都对应着一个kobject
,要想知道这个目录下有哪些子目录,就要用到kset
。从面向对象的角度来讲,kset
继承了kobject
的功能,既可以表示sysfs
中的一个目录,还可以包含下层目录。
dentry
结构体表示目录项,通过连接kobject
到指定的目录项上,无疑方便的将kobject
映射到该目录上。
因此,kobjects
其实已经形成了一棵树了——就是对象模型体系。
由于kobject
被映射到目录项,同时对象层次结构也已经在内存中形成了一棵树,因此sysfs
就诞生了。
uos@uos-PC:~$ tree /sys | head -n 300
/sys
├── block
│ └── nvme0n1 -> ../devices/pci0000:00/0000:00:13.0/0000:05:00.0/nvme/nvme0/nvme0n1
├── bus
│ ├── acpi
│ │ ├── devices
│ │ │ ├── ACPI0003:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00/ACPI0003:00
│ │ │ ├── ACPI0007:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0007:00
│ │ │ ├── ACPI0007:01 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0007:01
│ │ │ ├── ACPI0007:02 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0007:02
│ │ │ ├── ACPI0007:03 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0007:03
│ │ │ ├── device:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00
│ │ │ ├── device:01 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:01
│ │ │ ├── device:02 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:01/device:02
│ │ │ ├── device:03 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:03
│ │ │ ├── device:04 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:03/device:04
│ │ │ ├── device:05 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:03/device:04/device:05
│ │ │ ├── device:06 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:03/device:04/device:06
│ │ │ ├── device:07 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:03/device:04/device:07
│ │ │ ├── device:08 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08
│ │ │ ├── device:09 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08/device:09
│ │ │ ├── device:0a -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08/device:09/device:0a
│ │ │ ├── device:0b -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08/device:09/device:0b
│ │ │ ├── device:0c -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08/device:09/device:0c
│ │ │ ├── device:0d -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:0d
│ │ │ ├── device:0e -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:0d/device:0e
│ │ │ ├── device:0f -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:0d/device:0e/device:0f
│ │ │ ├── device:10 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:0d/device:0e/device:10
│ │ │ ├── device:11 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:0d/device:0e/device:11
│ │ │ ├── device:12 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:12
│ │ │ ├── device:13 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:12/device:13
│ │ │ ├── device:14 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:12/device:13/device:14
│ │ │ ├── device:15 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:12/device:13/device:15
│ │ │ ├── device:16 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:12/device:13/device:16
│ │ │ ├── device:17 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:17
│ │ │ ├── device:18 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:18
│ │ │ ├── device:19 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:19
│ │ │ ├── device:1a -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:19/device:1a
│ │ │ ├── device:1b -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1b
│ │ │ ├── device:1c -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1b/device:1c
│ │ │ ├── device:1d -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1d
│ │ │ ├── device:1e -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1d/device:1e
│ │ │ ├── IPI0001:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/IPI0001:00
│ │ │ ├── LEN2020:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/LEN2020:00
│ │ │ ├── LNXPOWER:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00/LNXPOWER:00
│ │ │ ├── LNXPOWER:01 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:01/LNXPOWER:01
│ │ │ ├── LNXPWRBN:00 -> ../../../devices/LNXSYSTM:00/LNXPWRBN:00
│ │ │ ├── LNXSYBUS:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00
│ │ │ ├── LNXSYBUS:01 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:01
│ │ │ ├── LNXSYSTM:00 -> ../../../devices/LNXSYSTM:00
│ │ │ ├── LNXTHERM:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:01/LNXTHERM:00
│ │ │ ├── LOON0000:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00/LOON0000:00
│ │ │ ├── LOON0001:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0001:00
│ │ │ ├── LOON0002:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0002:00
│ │ │ ├── LOON0004:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0004:00
│ │ │ ├── LOON0004:01 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0004:01
│ │ │ ├── LOON0004:02 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0004:02
│ │ │ ├── LOON0004:03 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0004:03
│ │ │ ├── LOON0004:04 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0004:04
│ │ │ ├── LOON0006:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0006:00
│ │ │ ├── LOON0006:01 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0006:01
│ │ │ ├── LOON0006:02 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0006:02
│ │ │ ├── LOON0006:03 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0006:03
│ │ │ ├── PNP0303:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0303:00
│ │ │ ├── PNP0501:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0501:00
│ │ │ ├── PNP0501:01 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0501:01
│ │ │ ├── PNP0501:02 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0501:02
│ │ │ ├── PNP0501:03 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0501:03
│ │ │ ├── PNP0501:04 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0501:04
│ │ │ ├── PNP0A08:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00
│ │ │ ├── PNP0C09:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00
│ │ │ └── PNP0C0A:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00/PNP0C0A:00
│ │ ├── drivers
│ │ │ ├── ac
│ │ │ │ ├── ACPI0003:00 -> ../../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00/ACPI0003:00
│ │ │ │ ├── bind
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ ├── battery
│ │ │ │ ├── bind
│ │ │ │ ├── PNP0C0A:00 -> ../../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00/PNP0C0A:00
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ ├── button
│ │ │ │ ├── bind
│ │ │ │ ├── LNXPWRBN:00 -> ../../../../devices/LNXSYSTM:00/LNXPWRBN:00
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ ├── ec
│ │ │ │ ├── bind
│ │ │ │ ├── PNP0C09:00 -> ../../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ ├── loongson-laptop_EC Event
│ │ │ │ ├── bind
│ │ │ │ ├── LOON0000:00 -> ../../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00/LOON0000:00
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ ├── thermal
│ │ │ │ ├── bind
│ │ │ │ ├── LNXTHERM:00 -> ../../../../devices/LNXSYSTM:00/LNXSYBUS:01/LNXTHERM:00
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ └── tpm_crb
│ │ │ ├── bind
│ │ │ ├── uevent
│ │ │ └── unbind
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
│ ├── clockevents
│ │ ├── devices
│ │ │ ├── clockevent0 -> ../../../devices/system/clockevents/clockevent0
│ │ │ ├── clockevent1 -> ../../../devices/system/clockevents/clockevent1
│ │ │ ├── clockevent2 -> ../../../devices/system/clockevents/clockevent2
│ │ │ └── clockevent3 -> ../../../devices/system/clockevents/clockevent3
│ │ ├── drivers
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
│ ├── clocksource
│ │ ├── devices
│ │ │ └── clocksource0 -> ../../../devices/system/clocksource/clocksource0
│ │ ├── drivers
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
│ ├── container
│ │ ├── devices
│ │ ├── drivers
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
│ ├── cpu
│ │ ├── devices
│ │ │ ├── cpu0 -> ../../../devices/system/cpu/cpu0
│ │ │ ├── cpu1 -> ../../../devices/system/cpu/cpu1
│ │ │ ├── cpu2 -> ../../../devices/system/cpu/cpu2
│ │ │ └── cpu3 -> ../../../devices/system/cpu/cpu3
│ │ ├── drivers
│ │ │ └── processor
│ │ │ ├── bind
│ │ │ ├── cpu0 -> ../../../../devices/system/cpu/cpu0
│ │ │ ├── cpu1 -> ../../../../devices/system/cpu/cpu1
│ │ │ ├── cpu2 -> ../../../../devices/system/cpu/cpu2
│ │ │ ├── cpu3 -> ../../../../devices/system/cpu/cpu3
│ │ │ ├── uevent
│ │ │ └── unbind
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
│ ├── event_source
│ │ ├── devices
│ │ │ ├── cpu -> ../../../devices/cpu
│ │ │ ├── kprobe -> ../../../devices/kprobe
│ │ │ ├── software -> ../../../devices/software
│ │ │ ├── tracepoint -> ../../../devices/tracepoint
│ │ │ └── uprobe -> ../../../devices/uprobe
│ │ ├── drivers
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
│ ├── genpd
│ │ ├── devices
│ │ ├── drivers
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
│ ├── gpio
│ │ ├── devices
│ │ │ └── gpiochip0 -> ../../../devices/platform/LOON0002:00/gpiochip0
│ │ ├── drivers
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
│ ├── hdaudio
│ │ ├── devices
│ │ │ ├── hdaudioC0D0 -> ../../../devices/pci0000:00/0000:00:06.2/hdaudioC0D0
│ │ │ ├── hdaudioC0D1 -> ../../../devices/pci0000:00/0000:00:06.2/hdaudioC0D1
│ │ │ └── hdaudioC1D0 -> ../../../devices/pci0000:00/0000:00:07.0/hdaudioC1D0
│ │ ├── drivers
│ │ │ ├── snd_hda_codec_analog
│ │ │ │ ├── bind
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ ├── snd_hda_codec_ca0110
│ │ │ │ ├── bind
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ ├── snd_hda_codec_ca0132
│ │ │ │ ├── bind
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ ├── snd_hda_codec_cirrus
│ │ │ │ ├── bind
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ ├── snd_hda_codec_cmedia
│ │ │ │ ├── bind
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ ├── snd_hda_codec_conexant
│ │ │ │ ├── bind
│ │ │ │ ├── hdaudioC1D0 -> ../../../../devices/pci0000:00/0000:00:07.0/hdaudioC1D0
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ ├── snd_hda_codec_generic
│ │ │ │ ├── bind
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ ├── snd_hda_codec_hdmi
│ │ │ │ ├── bind
│ │ │ │ ├── hdaudioC0D0 -> ../../../../devices/pci0000:00/0000:00:06.2/hdaudioC0D0
│ │ │ │ ├── hdaudioC0D1 -> ../../../../devices/pci0000:00/0000:00:06.2/hdaudioC0D1
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ ├── snd_hda_codec_idt
│ │ │ │ ├── bind
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ ├── snd_hda_codec_realtek
│ │ │ │ ├── bind
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ ├── snd_hda_codec_si3054
│ │ │ │ ├── bind
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ └── snd_hda_codec_via
│ │ │ ├── bind
│ │ │ ├── uevent
│ │ │ └── unbind
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
│ ├── hid
│ │ ├── devices
│ │ │ └── 0003:1C4F:0048.0001 -> ../../../devices/pci0000:00/0000:00:05.0/usb6/6-1/6-1:1.0/0003:1C4F:0048.0001
│ │ ├── drivers
│ │ │ └── hid-generic
│ │ │ ├── 0003:1C4F:0048.0001 -> ../../../../devices/pci0000:00/0000:00:05.0/usb6/6-1/6-1:1.0/0003:1C4F:0048.0001
│ │ │ ├── bind
│ │ │ ├── module -> ../../../../module/hid_generic
│ │ │ ├── new_id
│ │ │ ├── uevent
│ │ │ └── unbind
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
│ ├── i2c
│ │ ├── devices
│ │ │ ├── 0-0050 -> ../../../devices/pci0000:00/0000:00:06.1/i2c-0/0-0050
│ │ │ ├── 6-0050 -> ../../../devices/pci0000:00/0000:00:06.1/i2c-6/6-0050
│ │ │ ├── i2c-0 -> ../../../devices/pci0000:00/0000:00:06.1/i2c-0
│ │ │ ├── i2c-1 -> ../../../devices/platform/LOON0004:00/i2c-1
│ │ │ ├── i2c-2 -> ../../../devices/platform/LOON0004:01/i2c-2
│ │ │ ├── i2c-3 -> ../../../devices/platform/LOON0004:02/i2c-3
│ │ │ ├── i2c-4 -> ../../../devices/platform/LOON0004:03/i2c-4
│ │ │ ├── i2c-5 -> ../../../devices/platform/LOON0004:04/i2c-5
│ │ │ └── i2c-6 -> ../../../devices/pci0000:00/0000:00:06.1/i2c-6
│ │ ├── drivers
│ │ │ ├── dummy
│ │ │ │ ├── bind
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ ├── ipmi_ssif
│ │ │ │ ├── bind
│ │ │ │ ├── module -> ../../../../module/ipmi_ssif
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ └── pca953x
│ │ │ ├── bind
│ │ │ ├── uevent
│ │ │ └── unbind
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
│ ├── isa
│ │ ├── devices
│ │ ├── drivers
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
│ ├── mdio_bus
│ │ ├── devices
│ │ ├── drivers
│ │ │ ├── Generic 10G PHY
│ │ │ │ ├── bind
│ │ │ │ ├── uevent
│ │ │ │ └── unbind
│ │ │ └── Generic PHY
│ │ │ ├── bind
│ │ │ ├── uevent
│ │ │ └── unbind
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
│ ├── memory
│ │ ├── devices
│ │ │ ├── memory0 -> ../../../devices/system/memory/memory0
sysfs
的根目录下包含了七个目录:block
、bus
、class
、devices
、firmware
、module
和power
。
block
目录下的每个子目录都对应着系统的一个块设备。bus
目录提供了一个系统总线视图。class
目录包含了以高层功能逻辑组织起来的系统设备视图。devices
目录是系统中设备拓扑结构视图,它直接映射除了内核中设备结构体的组织层次。firmware
目录包含了一些诸如ACPI、EDD、EFI等低层子系统的特殊树。power
目录包含了系统范围的电源管理数据。- 最重要的目录是
devices
,该目录将设备模型导出到用户空间。目录结构就是系统中实际的设备拓扑。kernel 包括了一些内核的可调参数等信息,和devices目录关联性没那么强。
注意,其他目录中的很多数据都是将devices
目录下的数据加以转换加工而得;
因此,在class
章节中,我们会看到一些软链接的创建,其实就是为了优雅地归对设备进行归类。
8.2 sysfs中添加和删除kobject
8.2.1 导入kobject到sysfs
仅仅初始化kobjcet
是不能自动将其导出到sysfs
中的,想要把kobject
导入sysfs
,需要用到函数kobject_add()
:
需要注意的是,并不是说每一个
kobject
对象都需要在sysfs
中表示,但是每一个被注册到系统中的kset
都会被添加到sysfs
文件系统中。
一个kset
对象就对应一个/sys
中的一个目录,kset
中的每一个kobject
成员,都对应sysfs
中一个文件或者一个目录。
#include <linux/kobject.h>
#if 0 // 下面两种操作等价
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
int kobject_add(struct kobject *kobj);
#else
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
// 再也没有 kobject_register 这个函数了
#endif
kobject
在sysfs
中的位置取决于kobject
在对象层次结构中的位置。如果kobject
的父指针被设置,那么在sysfs
中的kobject
将被映射为其父目录下的子目录。如果parent
没有设置,那么kobject
将被映射为kset->kobj
中的子目录。
如果给定的kobject
中parent
或kset
字段都没有设置,那么就认为kobject
没有父对象,所以就会被映射成sysfs
下的根级目录。但这往往不是你所需要的,所以在调用kobject_add()
前parent
或kset
字段应该显示的设置。
不管怎样,sysfs
中代表kobject
的目录名字是由kobj->k_name
指定的。
不过不需要调用kobject_init()
和kobject_add()
,因为系统提供了函数kobject_create_and_add()
。
该函数既初始化了给定的kobject
对象,同时又将其加入到对象层次结构中。
8.2.1.1 kobj/kset的引用计数示例
下图展示了一个顶层kobj/kset
,通过不断的添加子节点,导致的引用计数变化情况。
首先在(I)中建立了顶层对象A,其kref
初始化为1
;在第(II)步中,建立了A的子对象B,此时A对象的kref
引用计数加1
变为2
;在第(III)步中,继续在A下建立子对象C,此时A对象的kref
引用计数加1
变为3
;在第(IV)步中,建立B对象的子对象D,此时,只会对D的父对象进行引用计数加1
;而对更上层的父对象,则不进行引用加1
操作。
8.2.1.2 platform_bus_type的注册示例
内核启动时,会在初始化阶段进行platform_bus_type
总线的注册:bus_register(&platform_bus_type)
。在该函数里,会设置platform_bus_type->p->subsys.kobj.kset = bus_kset
、platform_bus_type->p->subsys.kobj.ktype = &bus_ktype
,因此按照前面的总结,由于platform_bus_type
有kset
、无parent
,导致该bus
注册进系统后,其kset
引用计数将被增加两次,同时,在bus_kset
对应的目录(/sys/bus/
)下建立platform_bus_type
对应的子目录(/sys/bus/platform/
)。同理,当该总线的引用计数为0
时,将导致该对象生命的终结,会触发release()
操作,显然该操作将导致bus_kset
的引用计数减少两次。
8.2.2 从sysfs删除kobject
/*lib/kobject.c*/
/**
* kobject_del() - Unlink kobject from hierarchy.
* @kobj: object.
*
* This is the function that should be called to delete an object
* successfully added via kobject_add().
*/
void kobject_del(struct kobject *kobj)
{
struct kobject *parent;
if (!kobj)
return;
parent = kobj->parent;
__kobject_del(kobj);
kobject_put(parent);
}
EXPORT_SYMBOL(kobject_del);
从sysfs
中删除一个kobject
对应文件目录。需使用函数kobject_del()
,内部会自动使用put来让引用减一。
以前的版本需要再调用一次
kobject_put
;但现在不需要了。
8.2.3 向sysfs中添加文件
kobject
已经被映射为文件目录。所有的对象层次一个不少的映射成sys
下的目标结构。
sysfs
仅仅是一个漂亮的树,但是没有提供实际数据的文件。
因此,需要kobj_type
来进行支持,有关sysfs
的成员如下:
/*include/linux/kobject.h*/
struct kobj_type {
void (*release)(struct kobject *kobj);
/* 该类型kobj的sysfs操作接口。 */
const struct sysfs_ops *sysfs_ops;
/* 该类型kobj自带的缺省属性(文件),这些属性文件在注册kobj时,直接pop为该目录下的文件。 */
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);
};
8.2.3.1 默认属性attribute
默认的文件集合是通过Kobject
和kset
的ktype
字段提供的。因此所有具有相同类型的kobject
在它们对应的sysfs
目录下都拥有相同的默认文件集合。
kobj_type
字段含有一个字段——default_attrs
,它是一个attribute
结构体数组。这些属性负责将内核数据映射成sysfs
中的文件。
8.2.3.1.1 attribute原型
/*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 attribute_group - data structure used to declare an attribute group.
* @name: Optional: Attribute group name
* If specified, the attribute group will be created in
* a new subdirectory with this name.
* @is_visible: Optional: Function to return permissions associated with an
* attribute of the group. Will be called repeatedly for each
* non-binary attribute in the group. Only read/write
* permissions as well as SYSFS_PREALLOC are accepted. Must
* return 0 if an attribute is not visible. The returned value
* will replace static permissions defined in struct attribute.
* @is_bin_visible:
* Optional: Function to return permissions associated with a
* binary attribute of the group. Will be called repeatedly
* for each binary attribute in the group. Only read/write
* permissions as well as SYSFS_PREALLOC are accepted. Must
* return 0 if a binary attribute is not visible. The returned
* value will replace static permissions defined in
* struct bin_attribute.
* @attrs: Pointer to NULL terminated list of attributes.
* @bin_attrs: Pointer to NULL terminated list of binary attributes.
* Either attrs or bin_attrs or both must be provided.
*/
struct attribute_group {
/* 属性名称 */
const char *name;
umode_t (*is_visible)(struct kobject *,
struct attribute *, int);
umode_t (*is_bin_visible)(struct kobject *,
struct bin_attribute *, int);
struct attribute **attrs;
struct bin_attribute **bin_attrs;
};
其中,name
:提供了该属性的名称,最终出现在sysfs
中的文件名就是它。
mode
字段类型为mode_t
,它表示了sysfs
中该文件的权限。
- 对于只读属性而言,如果是所有人都可读它,那么该字段设为
S_IRUGO
; - 如果只限于所有者可读,则该字段设置为
S_IRUSR
。 - 同样对于可写属性,可能会设置该字段为
S_IRUGO|S_IWUSR
。 sysfs
中的所有文件和目录的uid
与gid
标志均为零。
8.2.3.1.2 如何理解属性?
uos@uos-PC:~$ ls /sys/class/leds/input0::capslock/
brightness device max_brightness power subsystem trigger uevent
其中,brightness
、trigger
这些文件就是属性,以文件的形式提供。
8.2.3.1.3 属性读写方法sysfs_ops
虽然default_attrs
列出了默认的属性,sysfs_ops
则描述了如何使用它们。
#include <linux/sysfs.h>
struct sysfs_ops {
/* 读取该 sysfs 文件时该方法被调用 */
ssize_t (*show)(struct kobject *, struct attribute *, char *);
/* 写入该 sysfs 文件时该方法被调用 */
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};
struct sysfs_ops
中包含show
和store
两个函数指针,它们分别在sysfs
文件读和文件写时调用。
8.2.3.1.4 创建/删除新属性
一些特别情况下会碰到特殊的kobject
实例,它希望(甚至必须)有自己的属性——也许是通用属性没包含那些需要的数据或者函数。
因此使用sysfs_create_file()
接口在默认集合上添加新属性:
#include <linux/sysfs.h>
int sysfs_create_file(struct kobject *kobj,
const struct attribute *attr);
int sysfs_create_files(struct kobject *kobj,
const struct attribute **attr);
void sysfs_remove_file(struct kobject *kobj,
const struct attribute *attr)
这个接口通过attr
参数指向相应的attribute
结构体,而参数kobj
则指定了属性所在的kobject
对象。
在该函数被调用之前,给定的属性将被赋值,如果成功,该函数返回零。否则返回负的错误码。
kobject
中ktype.sysfs_ops
操作将负责处理新属性。现有的show()
和store()
方法必须能够处理新属性。
删除一个属性通过函数sysfs_remove_file()
完成:一旦调用sysfs_remove_file
,给定的属性将不复存在于给定的kobject
目录中。
8.2.3.1.5 创建/删除软链接
除了添加文件外,可能还要创建符号连接。在sysfs
中创建一个符号连接方式。
int sysfs_create_link(struct kobject *kobj, struct kobject *target,
const char *name);
int sysfs_create_link_nowarn(struct kobject *kobj,
struct kobject *target,
const char *name);
该函数创建的符号连接名由name
指定,连接则由kobj
对应的目录映射到target
指定的目录。如果成功,则返回零,如果失败,返回负的错误码。
而由sysfs_create_link()
创建的符号连接可通过函数sysfs_remove_link()
删除:
void sysfs_remove_link(struct kobject *kobj, const char *name);
// 一旦调用返回,给定的连接将不复存在于给定的kobject目录中。
8.2.3.2 sysfs约定
首先sysfs
属性应该保证每个属性文件只导出一个值,该值应该是文本形式而且被映射为简单C类型。其次,在sysfs
中要以一个清晰的层次组织数据。最后,记住sysfs
提供内核到用户空间的服务,这多少有些用户空间的ABI(应用程序二进制接口)的作用。
9. 附录:kobject创建
9.1 简单创建kobj以及添加
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kobject.h>
struct kobject *g_obj = NULL;
struct kobject *g_parobj = NULL;
static int __init test_init(void)
{
g_parobj = kobject_create_and_add("dog",NULL);
if(NULL == g_parobj)
{
printk("create kobj error\n");
return -1;
}
g_obj = kobject_create_and_add("whitedog",g_parobj);
if(NULL == g_obj)
{
if(g_parobj)
{
kobject_del(g_parobj);
kobject_put(g_parobj);
}
}
return 0;
}
static void __exit test_exit(void)
{
if(g_obj)
{
kobject_del(g_obj);
kobject_put(g_obj);
}
if(g_parobj)
{
kobject_del(g_parobj);
kobject_put(g_parobj);
}
return;
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
9.2 内核kobj例子
代码来自:samples/kobject/kobject-example.c
可能会遇到error: negative width in bit-field ‘’这个错误,需要将__ATTR中的666权限改为664。
// SPDX-License-Identifier: GPL-2.0
/*
* Sample kobject implementation
*
* Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com>
* Copyright (C) 2007 Novell Inc.
*/
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/module.h>
#include <linux/init.h>
/*
* This module shows how to create a simple subdirectory in sysfs called
* /sys/kernel/kobject-example In that directory, 3 files are created:
* "foo", "baz", and "bar". If an integer is written to these files, it can be
* later read out of it.
*/
static int foo;
static int baz;
static int bar;
/*
* The "foo" file where a static variable is read from and written to.
*/
static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
return sysfs_emit(buf, "%d\n", foo);
}
static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
int ret;
ret = kstrtoint(buf, 10, &foo);
if (ret < 0)
return ret;
return count;
}
/* Sysfs attributes cannot be world-writable. */
static struct kobj_attribute foo_attribute =
__ATTR(foo, 0664, foo_show, foo_store);
/*
* More complex function where we determine which variable is being accessed by
* looking at the attribute for the "baz" and "bar" files.
*/
static ssize_t b_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
int var;
if (strcmp(attr->attr.name, "baz") == 0)
var = baz;
else
var = bar;
return sysfs_emit(buf, "%d\n", var);
}
static ssize_t b_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
int var, ret;
ret = kstrtoint(buf, 10, &var);
if (ret < 0)
return ret;
if (strcmp(attr->attr.name, "baz") == 0)
baz = var;
else
bar = var;
return count;
}
static struct kobj_attribute baz_attribute =
__ATTR(baz, 0664, b_show, b_store);
static struct kobj_attribute bar_attribute =
__ATTR(bar, 0664, b_show, b_store);
/*
* Create a group of attributes so that we can create and destroy them all
* at once.
*/
static struct attribute *attrs[] = {
&foo_attribute.attr,
&baz_attribute.attr,
&bar_attribute.attr,
NULL, /* need to NULL terminate the list of attributes */
};
/*
* An unnamed attribute group will put all of the attributes directly in
* the kobject directory. If we specify a name, a subdirectory will be
* created for the attributes with the directory being the name of the
* attribute group.
*/
static struct attribute_group attr_group = {
.attrs = attrs,
};
static struct kobject *example_kobj;
static int __init example_init(void)
{
int retval;
/*
* Create a simple kobject with the name of "kobject_example",
* located under /sys/kernel/
*
* As this is a simple directory, no uevent will be sent to
* userspace. That is why this function should not be used for
* any type of dynamic kobjects, where the name and number are
* not known ahead of time.
*/
example_kobj = kobject_create_and_add("kobject_example", kernel_kobj);
if (!example_kobj)
return -ENOMEM;
/* Create the files associated with this kobject */
retval = sysfs_create_group(example_kobj, &attr_group);
if (retval)
kobject_put(example_kobj);
return retval;
}
static void __exit example_exit(void)
{
kobject_put(example_kobj);
}
module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Greg Kroah-Hartman <greg@kroah.com>");
9.2.1 对应的Makefile
EXTRA_CFLAGS += $(DEBFLAGS)
#EXTRA_CFLAGS += -I$(INCDIR)
########## change your module name here
MODULE = myKobj
########## change your obj file(s) here
$(MODULE)-objs:= kobject-example.o
#CROSS_COMPILE ?= arm-linux-gnueabihf-
#ARCH ?= arm
ifneq ($(KERNELRELEASE), )
obj-m := $(MODULE).o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE_BEGIN)
@echo
@if \
$(MAKE) INCDIR=$(PWD)/configs -C $(KERNELDIR) M=$(PWD) modules; \
then $(MAKE_DONE);\
else \
$(MAKE_ERR);\
exit 1; \
fi
endif
show:
@echo "ARCH : ${ARCH}"
@echo "CC : ${CROSS_COMPILE}gcc"
@echo "KDIR : ${KERNELDIR}"
@echo "$(MODULE): $(ALLOBJS)"
clean:
$(CLEAN_BEGIN)
rm -rf *.cmd *.o *.ko *.mod.c *.symvers *.order *.markers .tmp_versions .*.cmd *~ .*.d
$(CLEAN_END)
.PHONY:all clean show
#xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
### nothing
#OFFSET=\e[21G # 21 col
COLOR1=\e[32m # all --> bule
COLOR2=\e[33m # clean --> brown
COLOR3=\e[31m # error --> red
RESET=\e[0m
CLEAN_BEGIN=@echo -e "$(OFFSET)$(COLOR2)Cleaning up...$(RESET)"
CLEAN_END=@echo -e "$(OFFSET)$(COLOR2)Cleaned.$(RESET)"
MAKE_BEGIN=@echo -ne "$(OFFSET)$(COLOR1)Compiling...$(RESET)"
### I do not forget "@", but it DOES NOT need "@"
MAKE_DONE=echo -e "$(OFFSET)$(COLOR1)Compilied.$(RESET)"
MAKE_ERR=echo -e "$(OFFSET)$(COLOR3)[Oops! Error occurred]$(RESET)"
9.2.2 测试
编译以后,加载模块:
sudo insmod myKobj.ko
发现创建了/sys/kernel/kobject_example/
目录,而且目录中有3个属性文件。
uos@uos-PC:$ ls /sys/kernel/kobject_example/
bar baz foo
读写这些属性
root@uos-PC:/home/uos/Desktop/samples/kobject# cd
root@uos-PC:~#
root@uos-PC:~# ls /sys/kernel/kobject_example/
bar baz foo
root@uos-PC:~# cat /sys/kernel/kobject_example/bar
0
root@uos-PC:~# cat /sys/kernel/kobject_example/baz
0
root@uos-PC:~# cat /sys/kernel/kobject_example/foo
0
root@uos-PC:~# echo "2021" > /sys/kernel/kobject_example/foo
root@uos-PC:~# cat /sys/kernel/kobject_example/foo
2021
root@uos-PC:~#
9.3 内核kset例子
代码来自:samples/kobject/kset-example.c
。
可能会遇到error: negative width in bit-field ‘’这个错误,需要将__ATTR中的666权限改为664。
// SPDX-License-Identifier: GPL-2.0
/*
* Sample kset and ktype implementation
*
* Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com>
* Copyright (C) 2007 Novell Inc.
*/
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
/*
* This module shows how to create a kset in sysfs called
* /sys/kernel/kset-example
* Then tree kobjects are created and assigned to this kset, "foo", "baz",
* and "bar". In those kobjects, attributes of the same name are also
* created and if an integer is written to these files, it can be later
* read out of it.
*/
/*
* This is our "object" that we will create a few of and register them with
* sysfs.
*/
struct foo_obj {
struct kobject kobj;
int foo;
int baz;
int bar;
};
#define to_foo_obj(x) container_of(x, struct foo_obj, kobj)
/* a custom attribute that works just for a struct foo_obj. */
struct foo_attribute {
struct attribute attr;
ssize_t (*show)(struct foo_obj *foo, struct foo_attribute *attr, char *buf);
ssize_t (*store)(struct foo_obj *foo, struct foo_attribute *attr, const char *buf, size_t count);
};
#define to_foo_attr(x) container_of(x, struct foo_attribute, attr)
/*
* The default show function that must be passed to sysfs. This will be
* called by sysfs for whenever a show function is called by the user on a
* sysfs file associated with the kobjects we have registered. We need to
* transpose back from a "default" kobject to our custom struct foo_obj and
* then call the show function for that specific object.
*/
static ssize_t foo_attr_show(struct kobject *kobj,
struct attribute *attr,
char *buf)
{
struct foo_attribute *attribute;
struct foo_obj *foo;
attribute = to_foo_attr(attr);
foo = to_foo_obj(kobj);
if (!attribute->show)
return -EIO;
return attribute->show(foo, attribute, buf);
}
/*
* Just like the default show function above, but this one is for when the
* sysfs "store" is requested (when a value is written to a file.)
*/
static ssize_t foo_attr_store(struct kobject *kobj,
struct attribute *attr,
const char *buf, size_t len)
{
struct foo_attribute *attribute;
struct foo_obj *foo;
attribute = to_foo_attr(attr);
foo = to_foo_obj(kobj);
if (!attribute->store)
return -EIO;
return attribute->store(foo, attribute, buf, len);
}
/* Our custom sysfs_ops that we will associate with our ktype later on */
static const struct sysfs_ops foo_sysfs_ops = {
.show = foo_attr_show,
.store = foo_attr_store,
};
/*
* The release function for our object. This is REQUIRED by the kernel to
* have. We free the memory held in our object here.
*
* NEVER try to get away with just a "blank" release function to try to be
* smarter than the kernel. Turns out, no one ever is...
*/
static void foo_release(struct kobject *kobj)
{
struct foo_obj *foo;
foo = to_foo_obj(kobj);
kfree(foo);
}
/*
* The "foo" file where the .foo variable is read from and written to.
*/
static ssize_t foo_show(struct foo_obj *foo_obj, struct foo_attribute *attr,
char *buf)
{
return sysfs_emit(buf, "%d\n", foo_obj->foo);
}
static ssize_t foo_store(struct foo_obj *foo_obj, struct foo_attribute *attr,
const char *buf, size_t count)
{
int ret;
ret = kstrtoint(buf, 10, &foo_obj->foo);
if (ret < 0)
return ret;
return count;
}
/* Sysfs attributes cannot be world-writable. */
static struct foo_attribute foo_attribute =
__ATTR(foo, 0664, foo_show, foo_store);
/*
* More complex function where we determine which variable is being accessed by
* looking at the attribute for the "baz" and "bar" files.
*/
static ssize_t b_show(struct foo_obj *foo_obj, struct foo_attribute *attr,
char *buf)
{
int var;
if (strcmp(attr->attr.name, "baz") == 0)
var = foo_obj->baz;
else
var = foo_obj->bar;
return sysfs_emit(buf, "%d\n", var);
}
static ssize_t b_store(struct foo_obj *foo_obj, struct foo_attribute *attr,
const char *buf, size_t count)
{
int var, ret;
ret = kstrtoint(buf, 10, &var);
if (ret < 0)
return ret;
if (strcmp(attr->attr.name, "baz") == 0)
foo_obj->baz = var;
else
foo_obj->bar = var;
return count;
}
static struct foo_attribute baz_attribute =
__ATTR(baz, 0664, b_show, b_store);
static struct foo_attribute bar_attribute =
__ATTR(bar, 0664, b_show, b_store);
/*
* Create a group of attributes so that we can create and destroy them all
* at once.
*/
static struct attribute *foo_default_attrs[] = {
&foo_attribute.attr,
&baz_attribute.attr,
&bar_attribute.attr,
NULL, /* need to NULL terminate the list of attributes */
};
ATTRIBUTE_GROUPS(foo_default);
/*
* Our own ktype for our kobjects. Here we specify our sysfs ops, the
* release function, and the set of default attributes we want created
* whenever a kobject of this type is registered with the kernel.
*/
static struct kobj_type foo_ktype = {
.sysfs_ops = &foo_sysfs_ops,
.release = foo_release,
.default_groups = foo_default_groups,
};
static struct kset *example_kset;
static struct foo_obj *foo_obj;
static struct foo_obj *bar_obj;
static struct foo_obj *baz_obj;
static struct foo_obj *create_foo_obj(const char *name)
{
struct foo_obj *foo;
int retval;
/* allocate the memory for the whole object */
foo = kzalloc(sizeof(*foo), GFP_KERNEL);
if (!foo)
return NULL;
/*
* As we have a kset for this kobject, we need to set it before calling
* the kobject core.
*/
foo->kobj.kset = example_kset;
/*
* Initialize and add the kobject to the kernel. All the default files
* will be created here. As we have already specified a kset for this
* kobject, we don't have to set a parent for the kobject, the kobject
* will be placed beneath that kset automatically.
*/
retval = kobject_init_and_add(&foo->kobj, &foo_ktype, NULL, "%s", name);
if (retval) {
kobject_put(&foo->kobj);
return NULL;
}
/*
* We are always responsible for sending the uevent that the kobject
* was added to the system.
*/
kobject_uevent(&foo->kobj, KOBJ_ADD);
return foo;
}
static void destroy_foo_obj(struct foo_obj *foo)
{
kobject_put(&foo->kobj);
}
static int __init example_init(void)
{
/*
* Create a kset with the name of "kset_example",
* located under /sys/kernel/
*/
example_kset = kset_create_and_add("kset_example", NULL, kernel_kobj);
if (!example_kset)
return -ENOMEM;
/*
* Create three objects and register them with our kset
*/
foo_obj = create_foo_obj("foo");
if (!foo_obj)
goto foo_error;
bar_obj = create_foo_obj("bar");
if (!bar_obj)
goto bar_error;
baz_obj = create_foo_obj("baz");
if (!baz_obj)
goto baz_error;
return 0;
baz_error:
destroy_foo_obj(bar_obj);
bar_error:
destroy_foo_obj(foo_obj);
foo_error:
kset_unregister(example_kset);
return -EINVAL;
}
static void __exit example_exit(void)
{
destroy_foo_obj(baz_obj);
destroy_foo_obj(bar_obj);
destroy_foo_obj(foo_obj);
kset_unregister(example_kset);
}
module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Greg Kroah-Hartman <greg@kroah.com>");
9.3.1 对应的Makefile
EXTRA_CFLAGS += $(DEBFLAGS)
#EXTRA_CFLAGS += -I$(INCDIR)
########## change your module name here
MODULE = myKset
########## change your obj file(s) here
#$(MODULE)-objs:= kobject-example.o
$(MODULE)-objs:= kset-example.o
#CROSS_COMPILE ?= arm-linux-gnueabihf-
#ARCH ?= arm
ifneq ($(KERNELRELEASE), )
obj-m := $(MODULE).o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE_BEGIN)
@echo
@if \
$(MAKE) INCDIR=$(PWD)/configs -C $(KERNELDIR) M=$(PWD) modules; \
then $(MAKE_DONE);\
else \
$(MAKE_ERR);\
exit 1; \
fi
endif
show:
@echo "ARCH : ${ARCH}"
@echo "CC : ${CROSS_COMPILE}gcc"
@echo "KDIR : ${KERNELDIR}"
@echo "$(MODULE): $(ALLOBJS)"
clean:
$(CLEAN_BEGIN)
rm -rf *.cmd *.o *.ko *.mod.c *.symvers *.order *.markers .tmp_versions .*.cmd *~ .*.d
$(CLEAN_END)
.PHONY:all clean show
#xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
### nothing
#OFFSET=\e[21G # 21 col
COLOR1=\e[32m # all --> bule
COLOR2=\e[33m # clean --> brown
COLOR3=\e[31m # error --> red
RESET=\e[0m
CLEAN_BEGIN=@echo -e "$(OFFSET)$(COLOR2)Cleaning up...$(RESET)"
CLEAN_END=@echo -e "$(OFFSET)$(COLOR2)Cleaned.$(RESET)"
MAKE_BEGIN=@echo -ne "$(OFFSET)$(COLOR1)Compiling...$(RESET)"
### I do not forget "@", but it DOES NOT need "@"
MAKE_DONE=echo -e "$(OFFSET)$(COLOR1)Compilied.$(RESET)"
MAKE_ERR=echo -e "$(OFFSET)$(COLOR3)[Oops! Error occurred]$(RESET)"
9.3.2 测试
编译以后,加载模块:
sudo insmod myKset.ko
发现创建了/sys/kernel/kset_example/
目录,而且目录中有3个子目录,每一个子目录中都有3个属性文件。
root@uos-PC:~# tree /sys/kernel/kset_example/
/sys/kernel/kset_example/
├── bar
│ ├── bar
│ ├── baz
│ └── foo
├── baz
│ ├── bar
│ ├── baz
│ └── foo
└── foo
├── bar
├── baz
└── foo
3 directories, 9 files
10. 附录:一部分关于kobject的API
10.1 动态创建
struct kobject *kobject_create(void);
动态创建一个kobject
结构,并将其设置为一个带默认释放方法(dynamic_kobj_ktype
)的动态的kobject
。
如果无法创建kobject
,将会返回NULL
。从这里返回的kobject
结构释放必须调用kobject_put()
方法而不是kfree()
,因为kobject_init()
已经被调用过了。
10.2 初始化
创建kobject
当然必须初始化kobject
对象,kobject
的一些内部成员需要(强制)通过kobject_init()
初始化:
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
该方法会正确的初始化一个kobject
来保证它可以被传递给kobject_add()
该功能被调用后,kobject
必须通过调用kobject_put()
来清理,而不是直接调用kfree
,来保证所有的内存都可以被正确的清理。
辅助函数kobject_init_and_add
用来在同时初始化和添加kobject
,它的参数和前面介绍的单独使用kobject_init()
、kobject_add()
这两个函数时一样
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
struct kobject *parent, const char *fmt, ...);
10.3 添加到sysfs
在调用kobject_init
将kobject
注册到sysfs
之后,必须调用kobject_add()
添加:
/**
* kobject_add - kobject添加主方法
* @kobj: 要添加的kobject
* @parent: 指向父kobject的指针
* @fmt: kobject带的名称格式
*
* 本方法中会设置kobject名称并添加到kobject阶层。
*
* 如果设置了@parent, 那么@kobj的parent将会被设置为它.
* 如果 @parent为空, 那么该@kobj的 parent会被设置为与该kobject
* 相关联的. 如果没有kset分配给这个kobject,那么该kobject会放在
* sysfs的根目录。
*
* 如果该方法返回错误,必须调用 kobject_put()来正确的清理该object
* 相关联的内存。
* 在任何情况下都不要直接通过调用to kfree()来直接释放传递给该方法
* 的kobject,那样会导致内存泄露。
*
* 注意,该调用不会创建"add" uevent, 调用方需要为该object设置好所有
* 必要的sysfs文件,然后再调用带UEVENT_ADD 参数的kobject_uevent()
* 来保证用户控件可以正确的收到该kobject创建的通知。
*/
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);
它正确设置了kobject
的名称和父节点,如果这个kobject
和一个特定的kset
关联,则kobj->kset
必须在调用kobject_add
之前被指定。
若kobject
已经跟一个kset
关联,在调用kobject_add
时可以将这个kobject
的父节点设置为NULL,这样它的父节点就会被设置为这个kset
本身。
在将kobject
添加到kernel
时,它的名称就已经被设定好了,代码中不应该直接操作kobject
的名称。
不过不需要调用kobject_init()
和kobject_add()
,因为系统提供了函数kobject_create_and_add()
。
当kobj,无parent、无kset时,将在sysfs的根目录(即/sys/)下创建目录;
当kobj,无parent、有kset时,kobject_add()会设置kobj->parent为kset->kobj;因此会在该kset下创建目录;该kobj会加入kset.list;同时,会对kobj->kset->kobj增加两次引用:1.增加对kobj->parent的引用;2.增加对kobj->kset的引用。
当kobj,有parent、无kset时,kobject_add()会在该parent下创建目录;同时,会对parent增加引用。
当kobj,有parent、有kset时,优先在parent下创建目录;该kobj会加入kset.list;同时,会分别对parent、kset增加引用计数。
#include <linux/kobject.h>
#if 0 // 下面两种操作等价
struct kobject *kobject_create(void);
int kobject_add(struct kobject *kobj);
#else
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
// 再也没有 kobject_register 这个函数了
#endif
10.4 创建简单的kobject
有些时候,开发者希望的只是在sysfs
中创建一个目录,并且不会被kset
、show
、store
函数等细节的复杂概念所迷惑。
这里有一个可以单独创建kobject
的例外,为了创建这样一个入口,可以通过如下的函数来实现:
struct kobject *kobject_create_and_add(char *name, struct kobject *parent);
这个函数会创建一个kobject
,并把它的目录放到指定父节点在sysfs
中目录里面。
10.5 重命名
如果你必须为kobject
改名,则调用kobject_rename()
函数实现:
int kobject_rename(struct kobject *kobj, const char *new_name);
// 如果 kobject 还没被 add 进 sysfs,
// 那么还可以使用下列的函数,否则必须使用kobject_rename
int kobject_set_name(struct kobject *kobj, const char *fmt, ...);
int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
va_list vargs);
kobject_rename
内部并不执行任何锁定操作,也没用何时名称是有效的概念。因此调用者必须自己实现完整性检查和保证序列性。
可以通过kobject_name()
函数来正确获取kobject
的名称:
const char *kobject_name(const struct kobject * kobj);
11. 小结
kobj
对应一个目录;kobj
实现对象的生命周期管理(计数为0即清除);kset
包含一个kobj
,相当于也是一个目录;kset
是一个容器,可以包容不同类型的kobj
(甚至父子关系的两个kobj
都可以属于一个kset
);- 注册
kobj
(如kobj_add()
函数)时,优先以kobj->parent
作为自身的parent
目录;
其次,以kset
作为parent
目录;若都没有,则是sysfs
的顶层目录;
同时,若设置了kset
,还会将kobj
加入kset.list
。举例:
1、无parent
、无kset
,则将在sysfs
的根目录(即/sys/
)下创建目录;
2、无parent
、有kset
,则将在kset
下创建目录;并将kobj
加入kset.list
;
3、有parent
、无kset
,则将在parent
下创建目录;
4、有parent
、有kset
,则将在parent
下创建目录,并将kobj
加入kset.list
; - 注册
kset
时,如果它的kobj->parent
为空,则设置它所属的kset
(kset.kobj->kset.kobj
)为parent
,即优先使用kobj
所属的parent
;然后再使用kset
作为parent
。(如platform_bus_type
的注册,如某些input
设备的注册) - 注册
device
时,dev->kobj.kset = devices_kset
;然而kobj->parent
的选取有优先次序:
refer to