Linux 内核:设备驱动模型(1)sysfs与kobject基类
Linux 内核:设备驱动模型(1)sysfs与kobject基类
背景
学习Linux 设备驱动模型时,对 kobject 不太理解。因此,学习了一下。
现在我知道了:kobj/kset
是如何作为统一设备模型的基础,以及到底提供了哪些功能。
以后我们就知道,在具体应用过程中,如device、bus甚至platform_device等是如何使用kobj/kset的。
参考:
- https://blog.csdn.net/yj4231/article/details/7799245
- http://www.wowotech.net/device_model/kobject.html
- http://www.wowotech.net/device_model/421.html
- https://blog.csdn.net/qb_2008/article/details/6846412
- Documentation\kobject.txt
内核版本:3.14
kobject, kset和ktype
要分析sysfs,首先就要分析kobject和kset,因为驱动设备的层次结构的构成就是由这两个东东来完成的。
sysfs与kobject密不可分。
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。 */
struct kobj_type *ktype;
/*sysfs中与该对象对应的文件节点对象*/
// 在3.14以后的内核中,sysfs基于kernfs来实现。
struct kernfs_node *sd;
/*对象的应用计数*/
struct kref kref;
/* 记录初始化与否。调用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 {
atomic_t refcount;
};
目前为止,Kobject主要提供如下功能:
- 构成层次结构:通过
parent
指针,可以将所有Kobject
以层次结构的形式组合起来。 - 生命周期管理:使用引用计数(reference count),来记录Kobject被引用的次数,并在引用次数变为0时把它释放。
- 和sysfs虚拟文件系统配合,将每一个Kobject及其特性,以文件的形式,开放到用户空间。
没有一个结构会嵌入多于一个kobject结构,如果这么做了,关于这个对象的引用计数肯定会一团糟,你的code也会充满bug,所以千万不要这么做。
实际上,kobject对自身实现什么功能并不感兴趣,它存在的意义在于把高级的对象链接到设备模型上。因此内核代码很少去创建一个单独的kobject对象,相反,kobject用于控制对大型域相关对象的访问(通过container_of)。
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为该目录下的文件。 */
struct attribute **default_attrs;
/*child_ns_type/namespace 是 文件系统命名空间相关)略*/
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
// 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;
};
当kobject的引用计数为0时,通过release
方法来释放相关的资源。
attribute
为属性,每个属性在sysfs中都有对应的属性文件。
sysfs_op
的两个方法用于实现读取和写入属性文件时应该采取的行为。
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;
};
kset通过标准的内核链表(struct list_head list
)来管理它的所有子节点,kobject通过kset成员变量指向它的kset容器。
大多数情况下,kobject都是它所属kset(或者严格点,kset内嵌的kobject)的子节点。
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/下。
待会我们看
kobject_add_internal
中是如何做的。
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 结构可能的层次结构如图:
举例:通过kobject
创建bus
目录
分析如何通过kobject
在/sys
创建bus
目录。
// drivers/base/bus.c
static int bus_uevent_filter(struct kset *kset, struct kobject *kobj)
{
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);
// ...
return 0;
}
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.
*
* 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.
*
* 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.
*/
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);
这里主要调用了两个函数,接下分别来看下。
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);
/*设置kobj->name*/
retval = kobject_set_name(&kset->kobj, "%s", name);
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 an 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 *old_name = kobj->name;
char *s;
if (kobj->name && !fmt)
return 0;
kobj->name = kvasprintf(GFP_KERNEL, fmt, vargs);
if (!kobj->name) {
kobj->name = old_name;
return -ENOMEM;
}
/* ewww... some of these buggers have '/' in the name ... */
while ((s = strchr(kobj->name, '/')))
s[0] = '!';
kfree(old_name);
return 0;
}
// lib/kasprintf.c
/* Simplified asprintf. */
char *kvasprintf(gfp_t gfp, const char *fmt, va_list ap)
{
unsigned int len;
char *p;
va_list aq;
va_copy(aq, ap);
len = vsnprintf(NULL, 0, fmt, aq);
va_end(aq);
p = kmalloc_track_caller(len+1, gfp);
if (!p)
return NULL;
vsnprintf(p, len+1, fmt, ap);
return p;
}
EXPORT_SYMBOL(kvasprintf);
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;
}
这里面调用了3个函数。这里先介绍前两个函数。kobject_uevent
实现的是用户空间事件传递,留到以后再说。
kset_init
该函数用于初始化kset。
/**
* 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;
}
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/
下。
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。
kobj/kset功能特性
我们先大概提一下这些功能特性,在后面的文章中会详细说明:对象生命周期管理
以及用户空间事件投递
。
对象生命周期管理
在创建一个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)
kref_get(&kobj->kref);
return kobj;
}
/**
* 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) {
kref_put(&kobj->kref, kobject_release);
}
}
static void kobject_release(struct kref *kref)
{
struct kobject *kobj = container_of(kref, struct kobject, kref);
kobject_cleanup(kobj);
}
对于kobject_get()
,它就是直接使用kref_get()
接口来对引用计数进行加1操作;
而对于kobject_put()
,它不仅要使用kref_put()
接口来对引用计数进行减1操作,还要对生命终结的对象执行release()
操作:当引用计数减为0时,回收该对象的资源。
然而kobject是高度抽象的实体,导致kobject不会单独使用,而是嵌在具体对象中。反过来也可以这样理解:凡是需要做对象生命周期管理的对象,都可以通过内嵌kobject来实现需求。
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_releas
e函数;
// 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 =
container_of(kobj, typeof(*priv), subsys.kobj);
struct bus_type *bus = priv->bus;
// 释放资源
kfree(priv);
bus->p = NULL;
}
由于bus_release()
函数由具体的bus子系统提供,它必定知道如何释放包括kobj在内的bus_type对象。
sysfs文件系统的层次组织
sysfs向用户空间展示了驱动设备的层次结构。这一个功能比较简单,先完全贴出来。
我们都知道设备和对应的驱动都是由内核管理的,这些对于用户空间是不可见的。现在通过sysfs,可以在用户空间直观的了解设备驱动的层次结构。
实际上,sysfs文件系统的组织功能是基于kobject实现的:该功能依靠kobj的parent、kset、sd等成员来完成。sysfs文件系统能够以友好的界面,将kobj所刻画的对象层次、所属关系、属性值,展现在用户空间。
我们来看看sysfs的文件结构:
$ uname -r
4.15.0-142-generic
$ ls /sys
block bus class dev devices firmware fs hypervisor kernel module power
目录 | 意义 |
---|---|
block | 块设备 |
bus | 系统中的总线 |
class | 设备类型,比如输入设备 |
dev | 系统中已注册的设备节点的视图,有两个子目录char和block。 |
devices | 系统中所有设备拓扑结构视图 |
fireware | 固件 |
fs | 文件系统 |
kernel | 内核配置选项和状态信息 |
module | 模块 |
power | 系统的电源管理数据 |
用户空间事件投递
请参考:uevent与热插拔
实际上,当具体对象有事件发生时,相应的操作函数中(如device_add()
),会调用事件消息接口kobject_uevent()
。
在该接口中,首先会添加一些共性的消息(路径、子系统名等),然后会找寻合适的kset->uevent_ops
,并调用该ops->uevent(kset, kobj, env)
来添加该对象特有的事件消息(如device对象的设备号、设备名、驱动名、DT信息);
kobject总结
kobject
可以看成是一个基类,这个基类通过ktype
属性实现了资源回收的功能,至于kset
,负责记录一组kobject
,我们将其看成是一个集合,负责事件管理。
从此,其他对象就可以通过包含kobject
来实现上层的功能。
sysfs
对象模型体系
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就诞生了。
$ tree /sys | head -n 300
/sys
├── block
...
├── bus
│ ├── cpu
│ │ ├── devices
│ │ │ └── cpu0 -> ../../../devices/system/cpu/cpu0
│ │ ├── drivers
│ │ │ └── processor
│ │ │ ├── bind
│ │ │ ├── cpu0 -> ../../../../devices/system/cpu/cpu0
│ │ │ ├── uevent
│ │ │ └── unbind
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
...
$ ls /sys
block bus class dev devices firmware fs kernel module power
sysfs的根目录下包含了七个目录:block、bus、class、devices、firmware、module和power。
- block目录下的每个子目录都对应着系统的一个块设备。
- bus目录提供了一个系统总线视图。
- class目录包含了以高层功能逻辑组织起来的系统设备视图。
- devices目录是系统中设备拓扑结构视图,它直接映射除了内核中设备结构体的组织层次。
- firmware目录包含了一些诸如ACPI、EDD、EFI等低层子系统的特殊树。
- power目录包含了系统范围的电源管理数据。
- 最重要的目录是devices,该目录将设备模型导出到用户空间。目录结构就是系统中实际的设备拓扑。
kernel 包括了一些内核的可调参数等信息,和devices目录关联性没那么强。
注意,其他目录中的很多数据都是将devices
目录下的数据加以转换加工而得;
因此,在class章节中,我们会看到一些软链接的创建,其实就是为了优雅地归对设备进行归类。
sysfs中添加和删除kobject
导入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对象,同时又将其加入到对象层次结构中。
从sysfs删除kobject
void kobject_del(struct kobject *kobj);
从sysfs中删除一个kobject对应文件目录。需使用函数kobject_del(),内部会自动使用put来让引用减一。
以前的版本需要再调用一次
kobject_put
;但现在不需要了。
向sysfs中添加文件
kobject已经被映射为文件目录。所有的对象层次一个不少的映射成sys下的目标结构。
sysfs仅仅是一个漂亮的树,但是没有提供实际数据的文件。
因此,需要kobj_type
来进行支持,有关sysfs的成员如下:
// include/linux/kobject.h
struct kobj_type {
// ...
/* 该类型kobj的sysfs操作接口。 */
const struct sysfs_ops *sysfs_ops;
/* 该类型kobj自带的缺省属性(文件),这些属性文件在注册kobj时,直接pop为该目录下的文件。 */
struct attribute **default_attrs;
};
默认属性attribute
默认的文件集合是通过Kobject和kset的ktype字段提供的。因此所有具有相同类型的kobject在它们对应的sysfs目录下都拥有相同的默认文件集合。
kobj_type字段含有一个字段——default_attrs,它是一个attribute结构体数组。这些属性负责将内核数据映射成sysfs中的文件。
attribute原型
// include/linux/sysfs.h
#include <linux/sysfs.h>
struct attribute {
/* 属性名称 */
const char *name;
/* 访问权限 */
umode_t mode;
// ...
};
struct attribute_group {
const char *name;
umode_t (*is_visible)(struct kobject *,
struct attribute *, int);
struct attribute **attrs;
struct bin_attribute **bin_attrs;
};
struct bin_attribute {
struct attribute attr;
size_t size;
void *private;
ssize_t (*read)(struct file *, struct kobject *, struct bin_attribute *,
char *, loff_t, size_t);
ssize_t (*write)(struct file *, struct kobject *, struct bin_attribute *,
char *, loff_t, size_t);
int (*mmap)(struct file *, struct kobject *, struct bin_attribute *attr,
struct vm_area_struct *vma);
};
其中,name:提供了该属性的名称,最终出现在sysfs中的文件名就是它。
owner(todo):在存在所属模块的情况下指向其所属module结构体。如果一个模块没有该属性,那么该字段为NULL。
mode字段类型为mode_t,它表示了sysfs中该文件的权限。
- 对于只读属性而言,如果是所有人都可读它,那么该字段设为S_IRUGO;
- 如果只限于所有者可读,则该字段设置为S_IRUSR。
- 同样对于可写属性,可能会设置该字段为S_IRUGO|S_IWUSR。
- sysfs中的所有文件和目录的uid与gid标志均为零。
如何理解属性?
$ ls /sys/class/leds/input1::capslock
brightness device max_brightness power subsystem trigger uevent
其中,brightness
、trigger
这些文件就是属性,以文件的形式提供。
属性读写方法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文件读和文件写时调用。
创建/删除新属性
一些特别情况下会碰到特殊的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目录中。
创建/删除软链接
除了添加文件外,可能还要创建符号连接。在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目录中。
sysfs约定
首先sysfs属性应该保证每个属性文件只导出一个值,该值应该是文本形式而且被映射为简单C类型。其次,在sysfs中要以一个清晰的层次组织数据。最后,记住sysfs提供内核到用户空间的服务,这多少有些用户空间的ABI(应用程序二进制接口)的作用。
附录:kobject创建
简单创建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");
内核kobj例子
kobject-example.c
代码来自:samples/kobject/kobject-example.c
。
可能会遇到
error: negative width in bit-field ‘<anonymous>’
这个错误,需要将__ATTR
中的666
权限改为664
。
/*
* Sample kobject implementation
*
* Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com>
* Copyright (C) 2007 Novell Inc.
*
* Released under the GPL version 2 only.
*
*/
#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 sprintf(buf, "%d\n", foo);
}
static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
sscanf(buf, "%du", &foo);
return count;
}
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 sprintf(buf, "%d\n", var);
}
static ssize_t b_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
int var;
sscanf(buf, "%du", &var);
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");
MODULE_AUTHOR("Greg Kroah-Hartman <greg@kroah.com>");
对应的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)"
测试
编译以后,加载模块:
$ sudo insmod myKobj.ko
发现创建了/sys/kernel/kobject_example/
目录,而且目录中有3个属性文件。
$ ls /sys/kernel/kobject_example/
bar baz foo
读写这些属性
$ cat /sys/kernel/kobject_example/bar
0
$ cat /sys/kernel/kobject_example/baz
0
$ cat /sys/kernel/kobject_example/foo
0
$ sudo su
# echo "2021" > /sys/kernel/kobject_example/foo
# cat /sys/kernel/kobject_example/foo
2021
内核kset例子
kset-example.c
代码来自:samples/kobject/kset-example.c
。
可能会遇到
error: negative width in bit-field ‘<anonymous>’
这个错误,需要将__ATTR
中的666
权限改为664
。
/*
* Sample kset and ktype implementation
*
* Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com>
* Copyright (C) 2007 Novell Inc.
*
* Released under the GPL version 2 only.
*
*/
#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 sprintf(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)
{
sscanf(buf, "%du", &foo_obj->foo);
return count;
}
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 sprintf(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;
sscanf(buf, "%du", &var);
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 */
};
/*
* 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_attrs = foo_default_attrs,
};
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");
MODULE_AUTHOR("Greg Kroah-Hartman <greg@kroah.com>");
对应的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)"
测试
编译以后,加载模块:
$ sudo insmod myKset.ko
发现创建了/sys/kernel/kset_example/
目录,而且目录中有3个子目录,每一个子目录中都有3个属性文件。
$ tree /sys/kernel/kset_example/
/sys/kernel/kset_example/
├── bar
│ ├── bar
│ ├── baz
│ └── foo
├── baz
│ ├── bar
│ ├── baz
│ └── foo
└── foo
├── bar
├── baz
└── foo
读写就不再啰嗦了,是一样的。
附录:一部分关于kobject的API
由于我们现在关心的是设备驱动模型,因此暂不了解。等到具体的代码中,我们再进行解析。
动态创建
struct kobject *kobject_create(void);
动态创建一个kobject结构,并将其设置为一个带默认释放方法(dynamic_kobj_ktype)的动态的kobject。
如果无法创建kobject,将会返回NULL。从这里返回的kobject 结构释放必须调用kobject_put() 方法而不是kfree(),因为kobject_init()已经被调用过了。
初始化
创建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, ...);
添加到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()
。
#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
创建简单的kobject
有些时候,开发者希望的只是在sysfs中创建一个目录,并且不会被kset、show、store函数等细节的复杂概念所迷惑。
这里有一个可以单独创建kobject的例外,为了创建这样一个入口,可以通过如下的函数来实现:
struct kobject *kobject_create_and_add(char *name, struct kobject *parent);
这个函数会创建一个kobject,并把它的目录放到指定父节点在sysfs中目录里面。
重命名
如果你必须为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);
附录:sysfs API
若在页首无特别声明,本篇文章由 Schips 经过整理后发布。
博客地址:https://www.cnblogs.com/schips/