个人网站 ------------目前网站正在备案,可能暂时无法访问,一段时间后可以正常访问。
Linux设备模型的核心是使用Bus、Class、Device、Driver四个核心数据结构,将大量的、不同功能的硬件设备(以及驱动该硬件设备的方法),以树状结构的形式,进行归纳、抽象,从而方便Kernel统一管理。
一、kobject
kobject 它是构建设备模型的基础,使设备模型能够在设备模型下工作 /sys/ 以下目录层次的形式呈现, 动态管理对象的生命周期,并提供与用户空间信息交互的属性文件(attribute).
目录文件是两个不同的概念,别搞混了。
1.相关数据结构
1) kobject
核心结构kobject,牢牢记住每一个注册到核心的人kobject是目录
/* Kobject: include/linux/kobject.h line 60 */ struct kobject {
const char *name; //该Kobject同时也是名字sys/目录名称。 struct list_head entry; ///用于这个Kobject加入到Kset中的链表头 struct kobject *parent; //kobject形成层次目录结构 struct kset *kset; //该kobject所属的Kset,可以为NULL,若存在且未指定parent,则会把Kset.kobj做为parent。 struct kobj_type *ktype; //该Kobject属于的kobj_type,只有拥有ktye的kobj创建属性文件 struct sysfs_dirent *sd; //该Kobject在sysfs中的表示 struct kref kref; ///引用计数用于原子操作(在include/linux/kref.h中定义) unsigned int state_initialized:1; //指示该Kobject是否已初始化,在Kobject的Init,Put,Add异常校验等。 unsigned int state_in_sysfs:1; //指示该Kobject是否已经在sysfs在自动注销时从中呈现sysfs中移除 /* * 记录是否已发送到用户空间add uevent,若有,且未发送remove uevent, * 自动注销时,补发remove uevent,正确处理用户空间。 */ unsigned int state_add_uevent_sent:1; unsigned int state_remove_uevent_sent:1; unsigned int uevent_suppress:1; //如果字段为1,则表示忽略了所有报告uevent事件。 };
2) kobj_type
如何与用户空间的核心交互是通过该结构中包含的属性链表、指向属性文件和属性操作函数进行交互。
/* include/linux/kobject.h, line 108 */ struct kobj_type { void (*release)(struct kobject *kobj); //当引用计数为0时自动调用,该类型将包含在内kobject释放数据结构的内存空间 const struct sysfs_ops *sysfs_ops; //kobject属性操作函数指针,上层open当获得该指针的属性文件时 struct attribte **default_attrs; //属性链表指针,指向attribute *数组
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
3) attribute
牢牢记住,每一个注册到内核的 attribute 都是它所属的 kobj 目录下的一个属性文件。
--include/linux/sysfs.h
/* FIXME
* The *owner field is no longer used.
* x86 tree has been cleaned up. The owner
* attribute is still left for other arches.
*/
struct attribute {
const char *name; /* 属性的名字,即sys目录中的属性文件名称 */
struct module *owner; /* 属性的拥有者,已不再使用 */
mode_t mode; /* 属性的读写权限,定义在include/linux/stat.h*/
};
// include/uapi/linux/stat.h
/*
* "0" 表示没有权限
* "1" 表示可执行权限
* "2" 表示可写权限
* "4" 表示可读权限
*/
#define S_IRWXU 00700 //用户所有者拥有执行、写、读权限
#define S_IRUSR 00400 //用户所有者拥有读权限
#define S_IWUSR 00200 //用户所有者拥有写权限
#define S_IXUSR 00100 //用户所有者拥有执行权限
#define S_IRWXG 00070 //用户组拥有xxx
#define S_IRGRP 00040
#define S_IWGRP 00020
#define S_IXGRP 00010
#define S_IRWXO 00007 //其他人拥有xxx
#define S_IROTH 00004
#define S_IWOTH 00002
#define S_IXOTH 00001
// include/linux/stat.h
#define S_IRWXUGO (S_IRWXU|S_IRWXG|S_IRWXO)
#define S_IALLUGO (S_ISUID|S_ISGID|S_ISVTX|S_IRWXUGO)
#define S_IRUGO (S_IRUSR|S_IRGRP|S_IROTH)
#define S_IWUGO (S_IWUSR|S_IWGRP|S_IWOTH)
#define S_IXUGO (S_IXUSR|S_IXGRP|S_IXOTH)
4) sysfs_ops
属性文件操作函数,当 cat 属性文件时,会调用 kobj->ktype->sysfs_ops->show,当 echo 属性文件时调用 kobj->ktype->sysfs_ops->store 函数
--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); /* 写属性操作函数 */
};
2、kobject 的创建与初始化
1) kobject_create
该函数动态申请一个kobject结构,然后调用kobject_init对内部成员进行初始化,并且使用 dynamic_kobj_ktype 作为默认的 ktype
kernel-4.9/lib/kobject.c
struct kobject *kobject_create(void)
{
struct kobject *kobj;
kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
if (!kobj)
return NULL;
kobject_init(kobj, &dynamic_kobj_ktype);
return kobj;
}
2) kobject_init
该函数初始化kobj的ktype以及内部成员
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
char *err_str;
if (!kobj) {
/* 检测kobj是否为NULL */
err_str = "invalid kobject pointer!";
goto error;
}
if (!ktype) {
/* 检测ktype是否为NULL */
err_str = "must have a ktype to be initialized properly!\n";
goto error;
}
if (kobj->state_initialized) {
/* 判断kobject是否已经被初始化过,如果初始化过给出警告 */
/* do not error out as sometimes we can recover */
printk(KERN_ERR "kobject (%p): tried to init an initialized "
"object, something is seriously wrong.\n", kobj);
dump_stack();
}
kobject_init_internal(kobj); /* 初始化kobject内部成员 */
kobj->ktype = ktype; /* 设置ktype */
return;
error:
printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
dump_stack();
}
3) kobject_init_internal
该函数初始化引用计数,entry链表以及状态位。
static void kobject_init_internal(struct kobject *kobj)
{
if (!kobj) /* 参数检测,确保kobj不为空 */
return;
kref_init(&kobj->kref); /* 引用计数初始化,初始化为1 */
INIT_LIST_HEAD(&kobj->entry); /* 初始化kobject链表 */
kobj->state_in_sysfs = 0; /* 状态位设置:未导出到sys中 */
kobj->state_add_uevent_sent = 0; /* 状态位设置:未添加uevent */
kobj->state_remove_uevent_sent = 0; /* 状态位设置:未移除uevent */
kobj->state_initialized = 1; /* 状态位设置:已完成初始化 */
}
kobject的创建与初始化基本也就反复用这三个接口了。
3、kobject 的注册
1) kobject_add
设置 kobj 的 name 以及 parent 并将 kobject 注册进入内核
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, va_list vargs)
{
int retval;
retval = kobject_set_name_vargs(kobj, fmt, vargs); //设置kobject的name
if (retval) {
printk(KERN_ERR "kobject: can not set name properly!\n");
return retval;
}
kobj->parent = parent; //设置kobject的parent
return kobject_add_internal(kobj); //在sys/中添加kobject的信息
}
2) kobject_add_internal
将 kobject 注册进入内核
static int kobject_add_internal(struct kobject *kobj)
{
int error = 0;
struct kobject *parent;
if (!kobj)
return -ENOENT;
if (!kobj->name || !kobj->name[0]) {
WARN(1, "kobject: (%p): attempted to be registered with empty "
"name!\n", kobj);
return -EINVAL;
}
parent = kobject_get(kobj->parent); //如果父节点存在,则增加父节点引用计数
/* join kset if set, use it as parent if we do not already have one */
if (kobj->kset) {
//判断是否存在 kset
if (!parent)
parent = kobject_get(&kobj->kset->kobj); //如果父节点不存在则使用Kset->kobj作为父节点,并增加引用计数
kobj_kset_kobj_kset_leavejoin(kobj); //将kobject中的entry链接进入kset中的list链表。
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>");
error = create_dir(kobj); //使用kobj创建目录和属性文件
if (error) {
//如果创建失败减少引用计数
kobj_kset_leave(kobj);
kobject_put(parent);
kobj->parent = NULL;
/* be noisy on error issues */
if (error == -EEXIST)
pr_err("%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
pr_err("%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; //如果创建成功。将state_in_sysfs建为1。表示该object已经在sysfs中了
return error;
}
为了方便理解我在这里附上 create_dir 、 sysfs_create_dir_ns 以及 populate_dir 的源码
static int create_dir(struct kobject *kobj)
{
const struct kobj_ns_type_operations *ops;
int error;
error = sysfs_create_dir_ns(kobj, kobject_namespace(kobj)); //创建kobj目录
if (error)
return error;
error = populate_dir(kobj); //创建kobj默认属性文件
if (error) {
sysfs_remove_dir(kobj);
return error;
}
/***省略部分代码***/
return 0;
}
int sysfs_create_dir_ns(struct kobject *kobj, const void *ns)
{
struct kernfs_node *parent, *kn;
BUG_ON(!kobj);
if (kobj->parent) //判断parent是否存在,如果不存在则在sys/下创建目录
parent = kobj->parent->sd;
else
parent = sysfs_root_kn; // sys/ 所在目录
if (!parent)
return -ENOENT;
kn = kernfs_create_dir_ns(parent, kobject_name(kobj),
S_IRWXU | S_IRUGO | S_IXUGO, kobj, ns); //创建目录
if (IS_ERR(kn)) {
if (PTR_ERR(kn) == -EEXIST)
sysfs_warn_dup(parent, kobject_name(kobj));
return PTR_ERR(kn);
}
kobj->sd = kn;
return 0;
}
static int populate_dir(struct kobject *kobj)
{
struct kobj_type *t = get_ktype(kobj);
struct attribute *attr;
int error = 0;
int i;
if (t && t->default_attrs) {
for (i = 0; (attr = t->default_attrs[i]) != NULL; i++) {
error = sysfs_create_file(kobj, attr); //遍历default_attrs,创建存在的属性文件
if (error)
break;
}
}
return error;
}
kobjcet的注册主要完成了下面三件事情
- 判断父节点是否存在,如果存在增加父节点引用计数,判断是否存在 kset 如果存在则链接进 kset ,如果 kset 存在且父节点不存在则使用 Kset->kobj 作为父节点,增加 kset 点引用计数
- 调用 create_dir 为kobj创建目录和属性文件,在 create_dir 中调用 sysfs_create_dir_ns 为 kobject 创建目录,创建时会判断如果父节点为NULL则使用 sysfs_root_kn 作为父节点,即直接在 sys/ 目录下创建当前目录,在 create_dir 中调用 populate_dir 遍历属性文件链表创建默认属性文件
- 创建成功则设置 state_in_sysfs 为 1
内核也提供了一些组合API
//就是将kobject_creat 函数和 kobject_add 函数组合在一起的函数,创建并注册一个 kobject 到内核。
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
//就是将 kobject_init 函数和 kobject_add 函数组合在一起的函数
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...)
》上面的api这么多,可以根据需要灵活选择来创建并注册kobj,我也总结了一条很简单的原则:
为什么这么选择呢这涉及到后文提到的对对象生命周期管理的内容,这里只需记住这两条规则就行了。
3) 编程实验 1
光说不练假把式,这个实验很简单,我们只需要再内核中创建一个名为 my_kobject 的目录,并不需要将 kobject 嵌入到其他数据结构因此选择使用 kobject_create_and_add
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>
MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");
static struct kobject *my_kobj;
static int my_kobject_init(void)
{
my_kobj = kobject_create_and_add("my_kobject", NULL);
return 0;
}
static void my_kobject_exit(void)
{
kobject_del(my_kobj);
kfree(my_kobj);
}
module_init(my_kobject_init);
module_exit(my_kobject_exit);
验证结果
k85v1_64:/cache # ls /sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu power
k85v1_64:/cache #
k85v1_64:/cache # insmod my_kobject.ko
k85v1_64:/cache # ls /sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu my_kobject power //加载后生成 my_kobject 目录
k85v1_64:/cache #
k85v1_64:/cache # rmmod my_kobject.ko
k85v1_64:/cache # ls /sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu power //卸载后移除 my_kobject 目录
上面这种方式内核也用的挺多的,例如我们熟悉的 /sys/dev 、/sys/dev/char 、/sys/dev/block 等都是用这个方式创建的。
4) 编程实验 2
在sys/下创建一个叫做 my_dir 的目录,这里我们将 kobject 嵌入到我们自己创建的结构中,于是选择 kobject_init_and_add 来创建目录。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>
MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");
struct my_dir
{
char* name;
int data;
struct kobject kobj;
};
/*
* 动态创建一个 struct my_dir, 并初始化 name 后返回该指针
*/
struct my_dir* my_dir_create(char* name)
{
struct my_dir* my_dirp;
my_dirp = kzalloc(sizeof(*my_dirp), GFP_KERNEL);
if (!my_dirp)
return NULL;
my_dirp->name = name;
return my_dirp;
}
static struct my_dir* my_dirp;
static int my_dir_init(void)
{
my_dirp = my_dir_create("my_dir");
kobject_init_and_add(&my_dirp->kobj, NULL, NULL, "%s", my_dirp->name);
return 0;
}
static void my_dir_exit(void)
{
kobject_del(&my_dirp->kobj);
kfree(my_dirp);
}
module_init(my_dir_init);
module_exit(my_dir_exit);
验证结果
k85v1_64:/ # ls sys/
block/ bus/ dev/ firmware/ kernel/ mtk_rgu/
bootinfo/ class/ devices/ fs/ module/ power/
k85v1_64:/ # ls sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu power
k85v1_64:/ #
k85v1_64:/ # cd cache/
k85v1_64:/cache # insmod my_kobject.ko
k85v1_64:/cache # ls /sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu my_dir power //加载后生成 my_dir 目录
k85v1_64:/cache #
2|k85v1_64:/cache # cd /sys/my_dir/
k85v1_64:/sys/my_dir # ls
k85v1_64:/sys/my_dir #
k85v1_64:/sys/my_dir # cd ..
k85v1_64:/sys # rmmod my_kobject.ko
k85v1_64:/sys # ls
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu power //卸载后移除 my_dir 目录
看起来这种方式更加复杂,但实际上我们的 bus、 device、 device_driver 等都是使用这个方式,使用这个方式的优点见后文 “对象生命周期管理"以及"用户空间与内核信息交互”。
4、在 sys/ 下组织出目录层次
object的核心功能之一,利用 kobject.parent 组织出文件的目录层次,前面 kobject 的注册已经分析的很清楚了这里就不再赘述了,内核还提供了链接文件的创建接口。
// 在kobj目录下创建指向target目录的软链接,name 为软链接文件名称
int __must_check sysfs_create_link(struct kobject *kobj, struct kobject *target, const char *name);
这个实验很简单,在/sys/目录下创建一个目录 father 然后在这个目录下创建两个子文件 son1 和 son2,再在 son1 下创建一个链接到 son2 的链接文件 link_to_son2。 只是单纯的展示层次目录关系,因此无需将kobject嵌入到更大的数据结构,采用 kobject_create_and_add 来注册
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>
MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");
static struct kobject* father;
static struct kobject* son1;
static struct kobject* son2;
static int my_kobject_init(void)
{
//在 /sys/ 目录下创建一个目录 father
father = kobject_create_and_add("father", NULL);
//在 father 目录下创建两个子文件 son1 和 son2
son1 = kobject_create_and_add("son1", father);
son2 = kobject_create_and_add("son2", father);
//在 son1 下创建一个链接到son2的链接文件 link_to_son2
sysfs_create_link(son1, son2, "link_to_son2");
return 0;
}
static void my_kobject_exit(void)
{
kobject_del(father);
kfree(father);
kobject_del(son1);
kfree(son1);
kobject_del(son2);
kfree(son2);
}
module_init(my_kobject_init);
module_exit(my_kobject_exit);
验证结果
k85v1_64:/cache # insmod my_kobject.ko
k85v1_64:/cache #
k85v1_64:/cache # cd /sys/
k85v1_64:/sys # ls
block bootinfo bus class dev devices father firmware fs kernel module mtk_rgu power //创建出的 father
k85v1_64:/sys # cd father/
k85v1_64:/sys/father # ls
son1 son2 //创建出的 son1 son2
k85v1_64:/sys/father # cd son1/
k85v1_64:/sys/father/son1 # ls
link_to_son2
k85v1_64:/sys/father/son1 # ls -la
total 0
drwxr-xr-x 2 root root 0 2021-01-11 06:40 .
drwxr-xr-x 4 root root 0 2021-01-11 06:40 ..
lrwxrwxrwx 1 root root 0 2021-01-11 06:41 link_to_son2 -> ../son2 //创建出的链接文件
k85v1_64:/sys/father/son1 #
5、kobj 对象生命周期管理
kobject 还有一个非常强大的功能就是管理所嵌入的对象的生命周期,而引用计数 kref 则是它管理所嵌入对象生命周期的核心。对于kerf内核提供了两个下面函数来进行操作。
1) kobject_get
增加kobj引用计数
/** * 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;
}
2) kobject_put
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); //调用kref_put减少引用计数,同时传入回调函数
}
}
调用 kref_put 减少引用计数,同时传入回调函数 kobject_release,该回调函数在引用计数为0时调用。
int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
WARN_ON(release == NULL);
WARN_ON(release == (void (*)(struct kref *))kfree);
if (atomic_dec_and_test(&kref->refcount)) {
/* 当引用计数为0时,调用 release 函数进行资源的释放 */
release(kref);
return 1;
}
return 0;
}
减少引用计数,当 kref 为 0 时调用传入的 release 回调函数,即前面的 kobject_release 函数
static void kobject_release(struct kref *kref)
{
kobject_cleanup(container_of(kref, struct kobject, kref));
}
kobject_put 传入的回调函数,使用 container_of 函数获取到包含 kref 的 kobjec 结构地址,并传入 kobject_cleanup
/* * kobject_cleanup - free kobject resources. * @kobj: object to cleanup */ static void kobject_cleanup(struct kobject *kobj) { struct kobj_type *t = get_ktype(kobj); const char *name = kobj->name; pr_debug("kobject: '%s' (%p): %s, parent %p\n", kobject_name(kobj), kobj, __func__, kobj->parent); if (t && !t->release) // 判断 release 函数是否存在 pr_debug("kobject: '%s' (%p): does not have a release() " "function, it is broken and must be fixed.\n", kobject_name(kobj), kobj); /* send "remove" if the caller did not do it but sent "add" */ if (kobj->state_add_uevent_sent && !kobj->state_remove_uevent_sent) { pr_debug("kobject: '%s' (%p): auto cleanup 'remove' event\n", kobject_name(kobj), kobj); kobject_uevent(kobj, KOBJ_REMOVE); //发送 uevent 事件 KOBJ_REMOVE } /* remove from sysfs if the caller did not do it */ if (kobj->state_in_sysfs) { // 如果在sys中存在kobj则调用kobject_del删除kobj pr_debug("kobject: '%s' (%p): auto cleanup kobject_del\n", kobject_name(kobj), kobj); kobject_del(kobj); } if