LV06-04-linux设备模型-05-属性文件

属性文件是什么?有什么用?怎么用?若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
PC端开发环境 Windows Windows11
Ubuntu Ubuntu20.04.2的64位版本
VMware® Workstation 17 Pro 17.6.0 build-24238078
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Win32DiskImager Win32DiskImager v1.0
Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04
linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
nxp-imx/linux-imx/releases/tag/v4.19.71 NXP linux内核仓库tags中的v4.19.71
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
Source Code https://elixir.bootlin.com/linux/latest/source linux kernel源码
kernel/git/stable/linux.git - Linux kernel stable tree linux kernel源码(官网,tag 4.19.71)
https://elixir.bootlin.com/u-boot/latest/source uboot源码

一、属性文件是什么?

1. 属性文件简介

属性(attribute)文件就是就是内核空间和用户空间进行信息交互的一种方法。它是对应 kobject 而言的,指的是kobject的“属性”。

我们知道每一个注册的 kobject 都会在 /sys 中以目录的形式呈现,也就是 bus 等数据结构可以利用嵌入 kobject 可以使它显示在 /sys 中。而 attribute 又会以文件的形式出现在目录中, 即kobject的所有属性,都在它对应的sysfs目录下以文件的形式呈现。通过这些属性文件, 我们就能够获取/修改内核中的变量,字符串等信息。

例如某个 driver 中定义了一个变量,希望用户空间程序可以修改该变量,以控制 driver 的运行行为,那么就可以将该变量以 sysfs attribute 的形式开放出来。

Linux内核中,attribute分为普通的attribute和二进制attribute。

2. 数据结构

会接触到的属性相关的结构体大概有以下几个。

2.1 struct attribute

struct attribute 定义如下:

1
2
3
4
5
6
7
8
9
struct attribute {
const char *name;
umode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
bool ignore_lockdep:1;
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};

其中 name 表示文件名称,mode表示文件模式(也就是权限,比如 0644、0666 等)。其他几个是调试用的。

2.2 struct bin_attribute

struct bin_attribute 定义如下:

1
2
3
4
5
6
7
8
9
10
11
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);
};

2.3 struct attribute_group

struct attribute_group 定义如下:

1
2
3
4
5
6
7
8
9
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;
};

2.4 struct kobj_attribute

struct kobj_attribute 定义如下:

1
2
3
4
5
6
7
struct kobj_attribute {
struct attribute attr;
ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,
char *buf);
ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count);
};

这个结构体包含一个attr成员,它就是前面的属性结构体,后面的show和store函数指针就是为每个属性指定不同的回调函数的。

3. 相关API

3.1 sysfs_create_file()

sysfs_create_file() 函数定义如下:

1
2
3
4
5
static inline int __must_check sysfs_create_file(struct kobject *kobj,
const struct attribute *attr)
{
return sysfs_create_file_ns(kobj, attr, NULL);
}

3.2 sysfs_create_files()

sysfs_create_files() 函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
int sysfs_create_files(struct kobject *kobj, const struct attribute **ptr)
{
int err = 0;
int i;

for (i = 0; ptr[i] && !err; i++)
err = sysfs_create_file(kobj, ptr[i]);
if (err)
while (--i >= 0)
sysfs_remove_file(kobj, ptr[i]);
return err;
}
EXPORT_SYMBOL_GPL(sysfs_create_files);

3.3 sysfs_remove_file()

sysfs_remove_file() 函数定义如下:

1
2
3
4
5
static inline void sysfs_remove_file(struct kobject *kobj,
const struct attribute *attr)
{
sysfs_remove_file_ns(kobj, attr, NULL);
}

3.4 sysfs_remove_files()

sysfs_remove_files() 函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
int sysfs_create_files(struct kobject *kobj, const struct attribute **ptr)
{
int err = 0;
int i;

for (i = 0; ptr[i] && !err; i++)
err = sysfs_create_file(kobj, ptr[i]);
if (err)
while (--i >= 0)
sysfs_remove_file(kobj, ptr[i]);
return err;
}
EXPORT_SYMBOL_GPL(sysfs_create_files);

3.5 sysfs_create_group()

sysfs_create_group()函数定义如下:

1
2
3
4
5
6
int sysfs_create_group(struct kobject *kobj,
const struct attribute_group *grp)
{
return internal_create_group(kobj, 0, grp);
}
EXPORT_SYMBOL_GPL(sysfs_create_group);

用于在 sysfs 中创建一个组(group)。组是一组相关的属性文件的集合,可以将它们放在同一个目录下提供更好的组织性和可读性。此函数有俩个参数,分别为如下所示:

  • kobj: 指向包含目标组的 kobject 的指针。
  • grp: 指向 struct attribute_group 结构体的指针,该结构体定义了组中的属性文件。

struct attribute_group 定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
struct attribute_group {
// 组的名称,将用作 sysfs 目录的名称。
const char *name;
// 可选的回调函数,用于决定每个属性文件是否可见。如果为 NULL,则所有属性文件都可见。
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;
};

3.6 __ATTR()

__ATTR() 是一个宏,定义如下:

1
2
3
4
5
6
#define __ATTR(_name, _mode, _show, _store) {				\
.attr = {.name = __stringify(_name), \
.mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \
.show = _show, \
.store = _store, \
}

该宏可以用于简化属性文件(attribute file)的定义。它用于定义一个 struct attribute 结构体,同时设置该属性的名称、权限以及关联的 showstore 操作函数。

  • _name:属性的名称,将出现在 /sys 文件系统中的文件名,这个名称可以不带" ",里面会自己处理成字符串。
  • _mode:属性文件的权限模式,通常是八进制数,比如 0644、0666 等,表示文件的读写权限。
  • _show:属性文件的读取函数指针,当用户读取该文件时将调用此函数。
  • _store:属性文件的写入函数指针,当用户写入该文件时将调用此函数。

4. 属性文件的典型用例

一个典型的例子是 /sys/class/net/eth0/ 目录下的属性文件,它们允许用户查看或配置网络接口 eth0 的一些属性,比如 speed、mtu、operstate 等。

image-20250107095722933

我们可以用以下命令读取相关信息:

1
2
cat /sys/class/net/eth0/speed       # 读取速度
echo 1500 > /sys/class/net/eth0/mtu # 配置 MTU

二、属性文件的创建与调用逻辑

1. sysfs_create_file()

接下来看一下属性文件怎么被创建的,我们从 sysfs_create_file() 函数入手,从这个函数入手,看一下是怎么个逻辑:

image-20250107104840696

可以看到,我们创建kobject的时候,会调用到 sysfs_create_file() 函数来创建属性文件。

1.1 sysfs_create_file_ns()

sysfs_create_file() 函数内部调用了sysfs_create_file_ns(),我们看一下sysfs_create_file_ns()函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int sysfs_create_file_ns(struct kobject *kobj, const struct attribute *attr,
const void *ns)
{
kuid_t uid;
kgid_t gid;

BUG_ON(!kobj || !kobj->sd || !attr);

kobject_get_ownership(kobj, &uid, &gid);
return sysfs_add_file_mode_ns(kobj->sd, attr, false, attr->mode,
uid, gid, ns);

}
EXPORT_SYMBOL_GPL(sysfs_create_file_ns);

这里一共调用了两个函数,我们主要关注这个 sysfs_add_file_mode_ns() 函数。

1.2 sysfs_add_file_mode_ns()

sysfs_add_file_mode_ns() 函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
int sysfs_add_file_mode_ns(struct kernfs_node *parent,
const struct attribute *attr, bool is_bin,
umode_t mode, kuid_t uid, kgid_t gid, const void *ns)
{
struct lock_class_key *key = NULL;
const struct kernfs_ops *ops;
struct kernfs_node *kn;
loff_t size;

if (!is_bin) {
struct kobject *kobj = parent->priv;
const struct sysfs_ops *sysfs_ops = kobj->ktype->sysfs_ops;

/* every kobject with an attribute needs a ktype assigned */
if (WARN(!sysfs_ops, KERN_ERR
"missing sysfs attribute operations for kobject: %s\n",
kobject_name(kobj)))
return -EINVAL;

if (sysfs_ops->show && sysfs_ops->store) {
if (mode & SYSFS_PREALLOC)
ops = &sysfs_prealloc_kfops_rw;
else
ops = &sysfs_file_kfops_rw;
} else if (sysfs_ops->show) {
//......
} else if (sysfs_ops->store) {
// ......
} else
ops = &sysfs_file_kfops_empty;

size = PAGE_SIZE;
} else {
//......
}

//......
return 0;
}

这里大概得逻辑就是从 kobj->ktype 获取 sysfs_ops ,这个成员是 struct sysfs_ops类型,它的定义如下:

1
2
3
4
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *, char *);
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};

从后面的一堆if中,可以知道,当我们定义了这两个函数的时候,ops指针就会指向 sysfs_prealloc_kfops_rw 或者 sysfs_file_kfops_rw,具体指向哪一个,受到mode的影响,这个mode其实就是创建kobject的时候的属性的mode成员,表示读写权限,我们一般想要读写的话,这里会赋值为 0664,这是一个8进制数,SYSFS_PREALLOC 为010000,这也是八进制,所以这里&运算是不成立的,所以最后指向的是 sysfs_file_kfops_rw,这个变量定义如下:

1
2
3
4
static const struct kernfs_ops sysfs_file_kfops_rw = {
.seq_show = sysfs_kf_seq_show,
.write = sysfs_kf_write,
};

这里后面的逻辑我就没有去详细分析了,但是大概就是,当对属性文件进行读写的时候就会去调用 sysfs_file_kfops_rw 中的这两个函数,这里相当于是告诉读写的时候要用哪两个函数,就像驱动注册的时候我们会提供操作函数集一样。

1.3 总结

上面大概就是属性文件的创建过程,其实并没有分析的很完整,但是其实也没啥必要,大概知道个流程就行了,再分析就涉及很多东西了,这里就不花费时间去分析了。但是足够我们后面去找属性文件的读写逻辑了。

2. 属性文件的读写逻辑

前面呢,分析了一堆,大概可以知道,创建文件的时候,为这个文件的操作注册了对应的读写函数,就是 sysfs_file_kfops_rw 这个变量中的两个函数。但是最终是怎么操作的?我们以读的函数为例来看一下。

2.1 sysfs_kf_seq_show()

sysfs_kf_seq_show() 函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static int sysfs_kf_seq_show(struct seq_file *sf, void *v)
{
struct kernfs_open_file *of = sf->private;
struct kobject *kobj = of->kn->parent->priv;
const struct sysfs_ops *ops = sysfs_file_ops(of->kn);
ssize_t count;
char *buf;
//......
if (ops->show) {
count = ops->show(kobj, of->kn->priv, buf);
if (count < 0)
return count;
}
//......
return 0;
}

这里可以看到定义了一个 struct sysfs_ops 类型指针,指向了 sysfs_file_ops() 的返回值,最后调用了其中的show函数,具体这个私有信息是怎么来的,应该文件系统那边去做的这里暂时就不详细去分析了。我们目标只有一个,就是找到这个show函数。

2.2 sysfs_file_ops()

sysfs_file_ops() 函数定义如下:

1
2
3
4
5
6
7
8
static const struct sysfs_ops *sysfs_file_ops(struct kernfs_node *kn)
{
struct kobject *kobj = kn->parent->priv;

if (kn->flags & KERNFS_LOCKDEP)
lockdep_assert_held(kn);
return kobj->ktype ? kobj->ktype->sysfs_ops : NULL;
}

到这里其实大概就比较清楚了,sysfs_file_ops() 函数返回的其实就是ktype中的 sysfs_ops 操作函数集。

2.3 总结

这里不再深入去分析了,内核太庞大了,以后有机会再说。到这里,我们大概知道了属性文件大概的调用逻辑。处理过程大概就是:

在 cat/echo 属性文件时(读/写属性文件写数据时)

  • (1)先调用 sysfs_file_ops() 获取到kobj->ktype->sysfs_ops 指针
  • (2)调用对应内核的 show/store 函数。

从这里可以得出两条结论:

  • 对于用户空间来讲,只负责把数据丢给内核的 store 以及从内核的 show 函数获取数据,至于 store 的数据用来做什么和 show 获取到数据表示什么意思则由内核决定。

  • 如果从属的 kobject(就是 attribute 文件所在的目录)没有 ktype,或者没有 ktype->sysfs_ops 指针,是不允许它注册任何 attribute 的。

三、属性文件的读写实现与优化

1. 属性文件创建demo

1.1 demo源码

我们实现这样一个功能,自定义一个kobject:

1
2
3
4
5
6
typedef struct __SKOBJ_{
char skobj_name[32];
char name_attr[32];
int data;
struct kobject kobj;
}skobj_t;

创建两个属性文件,对应name_attr和data,可以通过cat命令和echo命令修改和显示这两个的值。demo可以看这里:05_device_model/06_attr_file_demo1 · 苏木/imx6ull-driver-demo - 码云 - 开源中国

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#include <linux/init.h> /* module_init module_exit */
#include <linux/kernel.h>
#include <linux/module.h> /* MODULE_LICENSE */

#include <linux/kobject.h>
#include <linux/slab.h>

#include "./timestamp_autogenerated.h"
#include "./version_autogenerated.h"
#include "./sdrv_common.h"

// #undef PRT
// #undef PRTE
#ifndef PRT
#define PRT printk
#endif
#ifndef PRTE
#define PRTE printk
#endif

typedef struct __SKOBJ_{
char skobj_name[32];
char name_attr[32];
int data;
struct kobject kobj;
}skobj_t;

skobj_t *skobject1 = NULL; // 定义kobject指针变量:skobject1

/**
* @brief dynamic_kobj_release()
* @note 定义kobject的释放函数
* @param [in]
* @param [out]
* @retval
*/
static void dynamic_kobj_release(struct kobject *kobj)
{
skobj_t *pskobj = container_of(kobj, skobj_t, kobj);
PRT("kobject: (%p) \n", kobj);
kfree(pskobj);
}

/**
* @brief skobj_attr_show()
* @note 读属性操作函数, cat 属性文件时调用这个函数,会把对应的值显示到终端
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
ssize_t count = 0;
skobj_t *pskobj = container_of(kobj, skobj_t, kobj);

if(strcmp(attr->name, "skobj_attr_name") == 0) // skobj_attr_name 属性对应 pskobj->name 成员
{
count = sprintf(buf, "%s\n", pskobj->name_attr);
}
else if(strcmp(attr->name, "skobj_attr_data") == 0) // skobj_attr_data 属性对应 pskobj->data 成员
{
count = sprintf(buf, "%d\n", pskobj->data);
}
PRT("attr->name=%s count=%d\n", attr->name, count);
return count;
}

/**
* @brief skobj_attr_store()
* @note 写属性操作文件, echo 属性文件时调用这个函数,用于echo x 中的 x 写入到属性对应的成员变量中
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count)
{
skobj_t *pskobj = container_of(kobj, skobj_t, kobj);

PRT("attr->name=%s count=%d\n", attr->name, count);
if(strcmp(attr->name, "skobj_attr_name") == 0) // skobj_attr_name 属性对应 pskobj->name 成员
{
sscanf(buf, "%s\n", pskobj->name_attr);
}
else if(strcmp(attr->name, "skobj_attr_data") == 0) // skobj_attr_data 属性对应 pskobj->data 成员
{
sscanf(buf, "%d\n", &pskobj->data);
}

return count;
}

// 自定义的 sysfs_ops 结构体,包含 show 和 store 函数指
struct sysfs_ops attr_sysfs_ops = {
.show = skobj_attr_show,
.store = skobj_attr_store,
};

// 自定义的 kobj_type 结构体,包含释放函数、默认属性和 sysfs_ops
struct kobj_type stype = {
.release = dynamic_kobj_release,
.sysfs_ops = &attr_sysfs_ops,
};

// 每一个注册的 attribute 结构都是一个属性文件,这里创建两个属性文件
struct attribute s_attr_name = {
.name = "skobj_attr_name",
.mode = S_IRWXUGO,
};

struct attribute s_attr_data = {
.name = "skobj_attr_data",
.mode = S_IRWXUGO,
};

/**
* @brief sdriver_demo_init()
* @note
* @param [in]
* @param [out]
* @retval
*/
static __init int sdriver_demo_init(void)
{
int ret = 0;
printk("*** [%s:%d]Build Time: %s %s, git version:%s ***\n", __FUNCTION__,
__LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION);
PRT("sdriver_demo module init!\n");


// 创建kobject的第二种方法
// 1.使用kzalloc函数分配了一个kobject对象的内存
skobject1 = kzalloc(sizeof(skobj_t), GFP_KERNEL);
if(!skobject1)
{
ret = -1;
goto err_kzalloc;
}
snprintf(skobject1->skobj_name, sizeof(skobject1->skobj_name), "skobject1");

// 2.初始化并添加到内核中,名为"skobject1"。
ret = kobject_init_and_add(&skobject1->kobj, &stype, NULL, "%s", skobject1->skobj_name);
if(ret < 0)
{
PRTE("kobject_init_and_add fail!ret=%d\n", ret);
goto err_kobject_init_and_add;
}
//创建 s_attr_a 属性文件
ret = sysfs_create_file(&skobject1->kobj, &s_attr_name);
if(ret < 0)
{
PRTE("sysfs_create_file s_attr_name fail!ret=%d\n", ret);
goto err_sysfs_create_file_1;
}
//创建 s_attr_b 属性文件
ret = sysfs_create_file(&skobject1->kobj, &s_attr_data);
if(ret < 0)
{
PRTE("sysfs_create_file s_attr_data fail!ret=%d\n", ret);
goto err_sysfs_create_file_2;
}
return 0;

err_sysfs_create_file_2:
sysfs_remove_file(&skobject1->kobj, &s_attr_name);
err_sysfs_create_file_1:
kobject_put(&skobject1->kobj);
err_kobject_init_and_add:
kfree(skobject1);
err_kzalloc:
return ret;
}

/**
* @brief sdriver_demo_exit()
* @note
* @param [in]
* @param [out]
* @retval
*/
static __exit void sdriver_demo_exit(void)
{
// 删除创建的属性文件,好像不删除的话,在释放kobj的时候会自动删除?
sysfs_remove_file(&skobject1->kobj, &s_attr_name);
sysfs_remove_file(&skobject1->kobj, &s_attr_data);
// 释放之前创建的kobject对象
kobject_put(&skobject1->kobj);
PRT("sdriver_demo module exit!\n");
}

module_init(sdriver_demo_init); // 将__init定义的函数指定为驱动的入口函数
module_exit(sdriver_demo_exit); // 将__exit定义的函数指定为驱动的出口函数

/* 模块信息(通过 modinfo chrdev_led_demo 查看) */
MODULE_LICENSE("GPL v2"); /* 源码的许可证协议 */
MODULE_AUTHOR("sumu"); /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description"); /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */

这个demo是并没有将属性写入到ktype中,所以不会自动创建对应的属性文件,需要我们自己调用 sysfs_create_files() 函数创建。

1.2 开发板验证

我们编译完,会得到一个 sdriver_demo.ko 文件。接下来开始验证我们写的demo。

  • (1)一开始的 /sys目录
image-20250107164518539
  • (2)加载模块
image-20250107164601577

加载模块后,会发现,在/sys目录生成了 skobject1 目录,在/sys/skobject1目录下有两个属性文件skobj_attr_name和skobj_attr_data。

  • (3)查看skobj_attr_name和skobj_attr_data的值
image-20250107164739343
  • (4)修改skobj_attr_name和skobj_attr_data的值
image-20250107165743466
  • (5)卸载模块
image-20250107165920554

可以看到,卸载模块后,资源释放,两个属性文件以及kobject目录全都没了。

2. 将属性放入ktype

其实kobject在创建的时候,是会自动帮我们创建属性的,应该是在 populate_dir() 函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
if (error)
break;
}
}
return error;
}

可以看到我们需要把属性给放到ktype的default_attrs成员中。

2.1 demo源码

05_device_model/07_attr_file_demo2 · 苏木/imx6ull-driver-demo - 码云 - 开源中国

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#include <linux/init.h> /* module_init module_exit */
#include <linux/kernel.h>
#include <linux/module.h> /* MODULE_LICENSE */

#include <linux/kobject.h>
#include <linux/slab.h>

#include "./timestamp_autogenerated.h"
#include "./version_autogenerated.h"
#include "./sdrv_common.h"

// #undef PRT
// #undef PRTE
#ifndef PRT
#define PRT printk
#endif
#ifndef PRTE
#define PRTE printk
#endif

typedef struct __SKOBJ_{
char skobj_name[32];
char name_attr[32];
int data;
struct kobject kobj;
}skobj_t;

skobj_t *skobject1 = NULL; // 定义kobject指针变量:skobject1

/**
* @brief dynamic_kobj_release()
* @note 定义kobject的释放函数
* @param [in]
* @param [out]
* @retval
*/
static void dynamic_kobj_release(struct kobject *kobj)
{
skobj_t *pskobj = container_of(kobj, skobj_t, kobj);
PRT("kobject: (%p) \n", kobj);
kfree(pskobj);
}

/**
* @brief skobj_attr_show()
* @note 读属性操作函数, cat 属性文件时调用这个函数,会把对应的值显示到终端
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
ssize_t count = 0;
skobj_t *pskobj = container_of(kobj, skobj_t, kobj);

if(strcmp(attr->name, "skobj_attr_name") == 0) // skobj_attr_name 属性对应 pskobj->name 成员
{
count = sprintf(buf, "%s\n", pskobj->name_attr);
}
else if(strcmp(attr->name, "skobj_attr_data") == 0) // skobj_attr_data 属性对应 pskobj->data 成员
{
count = sprintf(buf, "%d\n", pskobj->data);
}
PRT("attr->name=%s count=%d\n", attr->name, count);
return count;
}

/**
* @brief skobj_attr_store()
* @note 写属性操作文件, echo 属性文件时调用这个函数,用于echo x 中的 x 写入到属性对应的成员变量中
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count)
{
skobj_t *pskobj = container_of(kobj, skobj_t, kobj);

PRT("attr->name=%s count=%d\n", attr->name, count);
if(strcmp(attr->name, "skobj_attr_name") == 0) // skobj_attr_name 属性对应 pskobj->name 成员
{
sscanf(buf, "%s\n", pskobj->name_attr);
}
else if(strcmp(attr->name, "skobj_attr_data") == 0) // skobj_attr_data 属性对应 pskobj->data 成员
{
sscanf(buf, "%d\n", &pskobj->data);
}

return count;
}

// 自定义的 sysfs_ops 结构体,包含 show 和 store 函数指
struct sysfs_ops attr_sysfs_ops = {
.show = skobj_attr_show,
.store = skobj_attr_store,
};

// 每一个注册的 attribute 结构都是一个属性文件,这里创建两个属性文件
struct attribute s_attr_name = {
.name = "skobj_attr_name",
.mode = S_IRWXUGO,
};

struct attribute s_attr_data = {
.name = "skobj_attr_data",
.mode = S_IRWXUGO,
};

// 定义一个指针数组,将 attribute 对象全部放入数组中,方便后续赋值给 ktype 的 default_attrs 成员
struct attribute *s_attr[] = {
&s_attr_name,
&s_attr_data,
NULL,
};

// 自定义的 kobj_type 结构体,包含释放函数、默认属性和 sysfs_ops
struct kobj_type stype = {
.release = dynamic_kobj_release,
.sysfs_ops = &attr_sysfs_ops,
.default_attrs = s_attr,
};

/**
* @brief sdriver_demo_init()
* @note
* @param [in]
* @param [out]
* @retval
*/
static __init int sdriver_demo_init(void)
{
int ret = 0;
printk("*** [%s:%d]Build Time: %s %s, git version:%s ***\n", __FUNCTION__,
__LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION);
PRT("sdriver_demo module init!\n");


// 创建kobject的第二种方法
// 1.使用kzalloc函数分配了一个kobject对象的内存
skobject1 = kzalloc(sizeof(skobj_t), GFP_KERNEL);
if(!skobject1)
{
ret = -1;
goto err_kzalloc;
}
snprintf(skobject1->skobj_name, sizeof(skobject1->skobj_name), "skobject1");

// 2.初始化并添加到内核中,名为"skobject1"。
ret = kobject_init_and_add(&skobject1->kobj, &stype, NULL, "%s", skobject1->skobj_name);
if(ret < 0)
{
PRTE("kobject_init_and_add fail!ret=%d\n", ret);
goto err_kobject_init_and_add;
}

return 0;

err_kobject_init_and_add:
kfree(skobject1);
err_kzalloc:
return ret;
}

/**
* @brief sdriver_demo_exit()
* @note
* @param [in]
* @param [out]
* @retval
*/
static __exit void sdriver_demo_exit(void)
{
// 释放之前创建的kobject对象
kobject_put(&skobject1->kobj);
PRT("sdriver_demo module exit!\n");
}


module_init(sdriver_demo_init); // 将__init定义的函数指定为驱动的入口函数
module_exit(sdriver_demo_exit); // 将__exit定义的函数指定为驱动的出口函数

/* 模块信息(通过 modinfo chrdev_led_demo 查看) */
MODULE_LICENSE("GPL v2"); /* 源码的许可证协议 */
MODULE_AUTHOR("sumu"); /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description"); /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */

2.2 开发板验证

只是改变了属性文件创建的逻辑,现象和上面完全一样。

3. 通过__ATTR() 宏优化一下

3.1 怎么优化?

看一下上面的show和store函数,这里以show函数为例:

image-20250107172843740

由于操作不同属性的时候,都是调用的这个函数,我们要在这里面通过if或者switch来判断,一两个属性还好,七八个怎么办?是不是可以让一个属性对应一个show和store,这样每个属性都有自己的show/store函数,这样属性再多也没啥问题了。

所以,这个时候我们就可以用的到 struct kobj_attribute 结构体,和 __ATTR() 宏。需要注意以下几点:

  • (1)需要定义 struct sysfs_ops 类型的变量用于操作kobject的所有属性,也就是要实现下面两个函数:
1
2
3
4
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *, char *);
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};
  • (2)需要定义每个属性的操作函数,就是实现 struct kobj_attribute 中的两个函数:
1
2
3
4
5
6
7
struct kobj_attribute {
struct attribute attr;
ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,
char *buf);
ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count);
};

其实就很简单的一个逻辑,之前是靠if来区分不同的属性,现在改成通过container_of()函数在show/store函数中获取不同的属性变量的地址,然后调用他们各自的show/store函数。具体后面可以看demo。

3.2 demo源码

05_device_model/08_attr_file_demo3 · 苏木/imx6ull-driver-demo - 码云 - 开源中国

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#include <linux/init.h> /* module_init module_exit */
#include <linux/kernel.h>
#include <linux/module.h> /* MODULE_LICENSE */

#include <linux/kobject.h>
#include <linux/slab.h>

#include "./timestamp_autogenerated.h"
#include "./version_autogenerated.h"
#include "./sdrv_common.h"

// #undef PRT
// #undef PRTE
#ifndef PRT
#define PRT printk
#endif
#ifndef PRTE
#define PRTE printk
#endif

typedef struct __SKOBJ_{
char skobj_name[32];
char name_attr[32];
int data;
struct kobject kobj;
}skobj_t;

skobj_t *skobject1 = NULL; // 定义kobject指针变量:skobject1

/**
* @brief skobj_attr_name_show()
* @note 读属性操作函数, cat 属性文件时调用这个函数,会把对应的值显示到终端
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
ssize_t count = 0;
skobj_t *pskobj = container_of(kobj, skobj_t, kobj);
count = sprintf(buf, "%s\n", pskobj->name_attr);
return count;
}

/**
* @brief skobj_attr_name_store()
* @note 写属性操作文件, echo 属性文件时调用这个函数,用于echo x 中的 x 写入到属性对应的成员变量中
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_name_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
skobj_t *pskobj = container_of(kobj, skobj_t, kobj);
sscanf(buf, "%s\n", pskobj->name_attr);
return count;
}

/**
* @brief skobj_attr_data_show()
* @note 读属性操作函数, cat 属性文件时调用这个函数,会把对应的值显示到终端
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_data_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
ssize_t count = 0;
skobj_t *pskobj = container_of(kobj, skobj_t, kobj);
count = sprintf(buf, "%d\n", pskobj->data);
return count;
}

/**
* @brief skobj_attr_data_store()
* @note 写属性操作文件, echo 属性文件时调用这个函数,用于echo x 中的 x 写入到属性对应的成员变量中
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_data_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
skobj_t *pskobj = container_of(kobj, skobj_t, kobj);
sscanf(buf, "%d\n", &pskobj->data);
return count;
}

// 定义两个属性,用于管理 name_attr 和 data
// 注意,这里的mode赋值的时候不能用这些代表权限的宏,例如S_IRWXUGO,否则会在展开的时候出现问题
struct kobj_attribute s_attr_name = __ATTR(skobj_attr_name, 0664, skobj_attr_name_show, skobj_attr_name_store);
struct kobj_attribute s_attr_data = __ATTR(skobj_attr_data, 0664, skobj_attr_data_show, skobj_attr_data_store);
// 定义一个指针数组,将 attribute 对象全部放入数组中,方便后续赋值给 ktype 的 default_attrs 成员
struct attribute *s_attr[] = {
&s_attr_name.attr,
&s_attr_data.attr,
NULL,
};

/**
* @brief dynamic_kobj_release()
* @note 定义kobject的释放函数
* @param [in]
* @param [out]
* @retval
*/
static void dynamic_kobj_release(struct kobject *kobj)
{
skobj_t *pskobj = container_of(kobj, skobj_t, kobj);
PRT("kobject: (%p) \n", kobj);
kfree(pskobj);
}

/**
* @brief skobj_attr_show()
* @note 读属性操作函数, cat 属性文件时调用这个函数,会把对应的值显示到终端
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
ssize_t count = 0;
struct kobj_attribute *pskobj_attr = container_of(attr, struct kobj_attribute, attr);
count = pskobj_attr->show(kobj, pskobj_attr, buf);
PRT("attr->name=%s count=%d\n", attr->name, count);
return count;
}

/**
* @brief skobj_attr_store()
* @note 写属性操作文件, echo 属性文件时调用这个函数,用于echo x 中的 x 写入到属性对应的成员变量中
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count)
{
struct kobj_attribute *pskobj_attr = container_of(attr, struct kobj_attribute, attr);
PRT("attr->name=%s count=%d\n", attr->name, count);
return pskobj_attr->store(kobj, pskobj_attr, buf, count);
}

// 自定义的 sysfs_ops 结构体,包含 show 和 store 函数指
struct sysfs_ops attr_sysfs_ops = {
.show = skobj_attr_show,
.store = skobj_attr_store,
};

// 自定义的 kobj_type 结构体,包含释放函数、默认属性和 sysfs_ops
struct kobj_type stype = {
.release = dynamic_kobj_release,
.sysfs_ops = &attr_sysfs_ops,
.default_attrs = s_attr,
};

/**
* @brief sdriver_demo_init()
* @note
* @param [in]
* @param [out]
* @retval
*/
static __init int sdriver_demo_init(void)
{
int ret = 0;
printk("*** [%s:%d]Build Time: %s %s, git version:%s ***\n", __FUNCTION__,
__LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION);
PRT("sdriver_demo module init!\n");


// 创建kobject的第二种方法
// 1.使用kzalloc函数分配了一个kobject对象的内存
skobject1 = kzalloc(sizeof(skobj_t), GFP_KERNEL);
if(!skobject1)
{
ret = -1;
goto err_kzalloc;
}
snprintf(skobject1->skobj_name, sizeof(skobject1->skobj_name), "skobject1");

// 2.初始化并添加到内核中,名为"skobject1"。
ret = kobject_init_and_add(&skobject1->kobj, &stype, NULL, "%s", skobject1->skobj_name);
if(ret < 0)
{
PRTE("kobject_init_and_add fail!ret=%d\n", ret);
goto err_kobject_init_and_add;
}

return 0;

err_kobject_init_and_add:
kfree(skobject1);
err_kzalloc:
return ret;
}

/**
* @brief sdriver_demo_exit()
* @note
* @param [in]
* @param [out]
* @retval
*/
static __exit void sdriver_demo_exit(void)
{
// 释放之前创建的kobject对象
kobject_put(&skobject1->kobj);
PRT("sdriver_demo module exit!\n");
}


module_init(sdriver_demo_init); // 将__init定义的函数指定为驱动的入口函数
module_exit(sdriver_demo_exit); // 将__exit定义的函数指定为驱动的出口函数

/* 模块信息(通过 modinfo chrdev_led_demo 查看) */
MODULE_LICENSE("GPL v2"); /* 源码的许可证协议 */
MODULE_AUTHOR("sumu"); /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description"); /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */

3.3 开发板验证

这里还是只是改了属性创建的写法,现象和验证步骤和前面完全一样。

4. 默认属性?

4.1 有一种创建方式没有指定ktype?

我们知道kobject创建的时候有两种方式,一种是手动申请内存,然后调用kobject_init_and_add() 函数来创建kobject:

1
2
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
struct kobject *parent, const char *fmt, ...);

可以看到,这里可以直接指定ktype,这个时候我们可以很方便的给ktype添加属性。转头看向另一种创建方式,是自动申请内存,调用 kobject_create_and_add() 函数完成kobject的创建:

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

仔细一看,发现这个函数好像没有ktyp参数啊,那它的属性怎么办?通过这种方式创建 kobject 的时候,默认设置 kobj_type 的值为 dynamic_kobj_ktype(在 kobject_create() 函数中设置的):

1
2
3
4
static struct kobj_type dynamic_kobj_ktype = {
.release = dynamic_kobj_release,
.sysfs_ops = &kobj_sysfs_ops,
};

对于属性的操作,主要是 kobj_sysfs_ops

1
2
3
4
5
const struct sysfs_ops kobj_sysfs_ops = {
.show = kobj_attr_show,
.store = kobj_attr_store,
};
EXPORT_SYMBOL_GPL(kobj_sysfs_ops);

可以看到最后调用了 kobj_attr_show()kobj_attr_store(),这两个函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* default kobject attribute operations */
static ssize_t kobj_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct kobj_attribute *kattr;
ssize_t ret = -EIO;

kattr = container_of(attr, struct kobj_attribute, attr);
if (kattr->show)
ret = kattr->show(kobj, kattr, buf);
return ret;
}

static ssize_t kobj_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct kobj_attribute *kattr;
ssize_t ret = -EIO;

kattr = container_of(attr, struct kobj_attribute, attr);
if (kattr->store)
ret = kattr->store(kobj, kattr, buf, count);
return ret;
}

看到这里,大概就知道了,这里其实 调用了 container_of() 来计算 attr成员所在的结构体的地址,然后调用里面的show/store函数,这是不是和前面__ATTR()宏的哪个demo很像?只不过这里我们需要手动创建属性文件,并绑定每个属性对应的show/store函数。

4.2 demo源码

05_device_model/09_attr_file_demo4 · 苏木/imx6ull-driver-demo - 码云 - 开源中国

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include <linux/init.h> /* module_init module_exit */
#include <linux/kernel.h>
#include <linux/module.h> /* MODULE_LICENSE */

#include <linux/kobject.h>
#include <linux/slab.h>

#include "./timestamp_autogenerated.h"
#include "./version_autogenerated.h"
#include "./sdrv_common.h"

// #undef PRT
// #undef PRTE
#ifndef PRT
#define PRT printk
#endif
#ifndef PRTE
#define PRTE printk
#endif

struct kobject *skobject1 = NULL; // 定义kobject指针变量:skobject1

/**
* @brief skobj_attr_name_show()
* @note 读属性操作函数, cat 属性文件时调用这个函数,会把对应的值显示到终端
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
ssize_t count = 0;
char data[32] = "aaa";
count = sprintf(buf, "%s\n", data);
return count;
}

/**
* @brief skobj_attr_name_store()
* @note 写属性操作文件, echo 属性文件时调用这个函数,用于echo x 中的 x 写入到属性对应的成员变量中
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_name_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
char data[32] = {0};
sscanf(buf, "%s\n", data);
PRT("data=%s\n", data);
return count;
}

/**
* @brief skobj_attr_data_show()
* @note 读属性操作函数, cat 属性文件时调用这个函数,会把对应的值显示到终端
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_data_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
ssize_t count = 0;
int a = 8;
count = sprintf(buf, "%d\n", a);
return count;
}

/**
* @brief skobj_attr_data_store()
* @note 写属性操作文件, echo 属性文件时调用这个函数,用于echo x 中的 x 写入到属性对应的成员变量中
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_data_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
int a = 6;
sscanf(buf, "%d\n", &a);
PRT("a = %d\n", a);
return count;
}

// 定义两个属性,用于管理 name_attr 和 data
// 注意,这里的mode赋值的时候不能用这些代表权限的宏,例如S_IRWXUGO,否则会在展开的时候出现问题
struct kobj_attribute s_attr_name = __ATTR(skobj_attr_name, 0664, skobj_attr_name_show, skobj_attr_name_store);
struct kobj_attribute s_attr_data = __ATTR(skobj_attr_data, 0664, skobj_attr_data_show, skobj_attr_data_store);

/**
* @brief sdriver_demo_init()
* @note
* @param [in]
* @param [out]
* @retval
*/
static __init int sdriver_demo_init(void)
{
int ret = 0;
printk("*** [%s:%d]Build Time: %s %s, git version:%s ***\n", __FUNCTION__,
__LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION);
PRT("sdriver_demo module init!\n");

skobject1 = kobject_create_and_add("skobject1", NULL);

ret = sysfs_create_file(skobject1, &s_attr_name.attr);
if(ret < 0)
{
PRTE("sysfs_create_file s_attr_name fail!ret=%d\n", ret);
goto err_sysfs_create_file_1;
}
ret = sysfs_create_file(skobject1, &s_attr_data.attr);
if(ret < 0)
{
PRTE("sysfs_create_file s_attr_data fail!ret=%d\n", ret);
goto err_sysfs_create_file_2;
}

return 0;

err_sysfs_create_file_2:
sysfs_remove_file(skobject1, &s_attr_name.attr);
err_sysfs_create_file_1:
kobject_put(skobject1);
return ret;
}

/**
* @brief sdriver_demo_exit()
* @note
* @param [in]
* @param [out]
* @retval
*/
static __exit void sdriver_demo_exit(void)
{
// 释放之前创建的kobject对象
kobject_put(skobject1);
PRT("sdriver_demo module exit!\n");
}


module_init(sdriver_demo_init); // 将__init定义的函数指定为驱动的入口函数
module_exit(sdriver_demo_exit); // 将__exit定义的函数指定为驱动的出口函数

/* 模块信息(通过 modinfo chrdev_led_demo 查看) */
MODULE_LICENSE("GPL v2"); /* 源码的许可证协议 */
MODULE_AUTHOR("sumu"); /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description"); /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */

4.3 开发板验证

这个demo的现象与前面的不同,后面会分析为什么没有办法保持一致。(也许是我太菜了,没有想到好办法?)

  • (1)一开始的 /sys目录
image-20250108104459756
  • (2)加载模块
image-20250108104653640

加载模块后,会发现,在/sys目录生成了 skobject1 目录,在/sys/skobject1目录下有两个属性文件skobj_attr_name和skobj_attr_data。

  • (3)查看skobj_attr_name和skobj_attr_data的值

由于前面stow中数据是写死的,并且没有与内核中某些数据相关联,所以这里显示的值都是固定的:

image-20250108104908429
  • (4)修改skobj_attr_name和skobj_attr_data的值
image-20250108105043285

会发现这里修改之后,查看的话,内容没变,原因就是上面写死了,并没有与某个变量保持一致,其实还是可以的,定义成全局变量就行了,只是不能像前面的demo一样,把kobject和自己的属性数据封装到一起,后面会去分析为什么没有封装到一起做。

  • (5)卸载模块
image-20250108105249577

可以看到,卸载模块后,资源释放,两个属性文件以及kobject目录全都没了。

4.4 为什么不能封装到一起?

前面我们是这样来封装数据:

1
2
3
4
5
6
typedef struct __SKOBJ_{
char skobj_name[32];
char name_attr[32];
int data;
struct kobject kobj;
}skobj_t;

这样我们可以很方便的为自己创建的kobj添加属性,并且比较好管理,总好过这样:

1
2
3
4
5
6
7
typedef struct __SKOBJ_{
char skobj_name[32];
char name_attr[32];
int data;
}skobj_t;
skobj_t kobject_attr = {0};
struct kobject *kobj;

然后去对应全局变量把,那么为什么 kobject_create_and_add() 自动创建的kobject对象不能像前面几个demo一样把属性和对象封装到一起?接下来就来分析一下为什么不行。

前面封装到一起的前提是我们可以通过内部的kobj成员,在show/store中使用下面的方式获取到这个结构体的地址:

1
2
3
4
5
6
7
ssize_t skobj_attr_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
ssize_t count = 0;
skobj_t *pskobj = container_of(kobj, skobj_t, kobj);
count = sprintf(buf, "%s\n", pskobj->name_attr);
return count;
}

container_of()这个函数,在获取结构体的地址的时候,我们需要知道结构体的类型,还需要知道这个结构体中其中一个成员的地址,这个地址必须是成员的地址,若通过指针成员获取结构体地址的话,必须是指针自己的地址,而不是指针指向的地址。我们可以看一下上一节__ATTR() 宏优化过的那个 demo 中,kobj地址的情况:

image-20250108151909652

Tips:注意打印地址的时候,可以用%px,不然可能会打印出来个 ptrval ,好像是因为内核担心暴露地址啥的。

我们来看一下 kobject_create_and_add() 函数:

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

可以看到,调用它的时候,我们不需要自己申请内存,它的返回值是创建完成的kobject的地址。我们要做的就是定义一个指针指向这个地址,这样就可以把kobj往后传了。我们来看一下这种情况下kobj的地址情况:

image-20250108160633745

接下来对比一下这两种情况:

image-20250108161846238

看一下左边的逻辑,我们到那个dynamic_kobj_release()函数里面,获取到了kobj的地址,但是这里是变量的地址,它不是一个指针,并且这个地址就是是结构体成员 kobj 的地址,也可以这样理解:我们创建的kobject对象和其他的成员在同一个地方:

image-20250108162946044

对于右侧的方式,我们也把kobject的地址传递到了 skobj_attr_name_show()函数,这里没有问题,可以正常使用kobject对象的,但是需要注意的是,这里传递的是kobject的地址,整个地址是存放在一个指针里面,它俩不是同一个东西,按照右侧这种方式,我们封装成结构体,应该是这样的:

1
2
3
4
5
6
typedef struct __SKOBJ_{
char skobj_name[32];
char name_attr[32];
int data;
struct kobject *kobj;
}skobj_t;

就会出现这种情况:

image-20250108163730977

最后,传过去的其实是kobj这个成员指针指向的的地址,毫无疑问,它确实是kobject对象的地址,但是我们为了获取整个结构体的地址,我们需要的知道的是结构体成员的地址,而非指针指向的地址。所以,这里就出现了问题,kobj指针自己的地址是没有继续向下传递的,传递的都是kobj指针指向的kobject对象的地址。这就意味这无法在skobj_attr_name_show()函数中通过kobject的地址使用container_of()从而获取整个结构体的地址。

5. 创建多个属性的简单方式

5.1 sysfs_create_group()

多个属性的创建,我们可以使用 sysfs_create_group() 函数。我们需要创建一个属性文件数组,属性文件数组是一个以 struct attribute_group * 类型为元素的数组,以 NULL 结束。每个 struct attribute_group 结构体表示一个属性文件,可以使用&运算符将属性对象(如 struct kobject_attribute) 的.attr 字段传递给属性文件数组。

下面是一个使用 sysfs_create_group() 创建一个组并添加属性文件的简单demo:

1
2
3
4
5
6
7
8
9
10
11
struct attribute *attrs[] = {
&attr1.attr,
&attr2.attr,
NULL,
};
const struct attribute_group attr_group = {
.name = "my_group",
.attrs = attrs,
};

sysfs_create_group(kobj, &attr_group);

我们创建了一个名叫“my_group”的组,并将属性文件 attr1 和 attr2 添加 到该组中,然后使用 sysfs_create_group() 将该组添加到指定的 kobject kobj 中。

5.2 demo源码

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#include <linux/init.h> /* module_init module_exit */
#include <linux/kernel.h>
#include <linux/module.h> /* MODULE_LICENSE */

#include <linux/kobject.h>
#include <linux/slab.h>

#include "./timestamp_autogenerated.h"
#include "./version_autogenerated.h"
#include "./sdrv_common.h"

// #undef PRT
// #undef PRTE
#ifndef PRT
#define PRT printk
#endif
#ifndef PRTE
#define PRTE printk
#endif

typedef struct __SKOBJ_ATTR_VAR_{
char skobj_name[32];
char name_attr[32];
int data;
}skobj_attr_var_t;

struct kobject *skobject1 = NULL; // 定义kobject指针变量:skobject1
skobj_attr_var_t skobject1_attr = {0}; // skobject1的两个属性

/**
* @brief skobj_attr_name_show()
* @note 读属性操作函数, cat 属性文件时调用这个函数,会把对应的值显示到终端
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
ssize_t count = 0;

if(strcmp(kobj->name, skobject1_attr.skobj_name) == 0)
{
count = sprintf(buf, "%s\n", skobject1_attr.name_attr);
}
PRT("kobj->name=%s attr->name=%s count=%d\n", kobj->name, attr->attr.name, count);
return count;
}

/**
* @brief skobj_attr_name_store()
* @note 写属性操作文件, echo 属性文件时调用这个函数,用于echo x 中的 x 写入到属性对应的成员变量中
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_name_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{

if(strcmp(kobj->name, skobject1_attr.skobj_name) == 0)
{
sscanf(buf, "%s\n", skobject1_attr.name_attr);
}

PRT("kobj->name=%s attr->name=%s count=%d\n", kobj->name, attr->attr.name, count);

return count;
}

/**
* @brief skobj_attr_data_show()
* @note 读属性操作函数, cat 属性文件时调用这个函数,会把对应的值显示到终端
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_data_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
ssize_t count = 0;

if(strcmp(kobj->name, skobject1_attr.skobj_name) == 0)
{
count = sprintf(buf, "%d\n", skobject1_attr.data);
}

PRT("kobj->name=%s attr->name=%s count=%d\n", kobj->name, attr->attr.name, count);

return count;
}

/**
* @brief skobj_attr_data_store()
* @note 写属性操作文件, echo 属性文件时调用这个函数,用于echo x 中的 x 写入到属性对应的成员变量中
* @param [in]
* @param [out]
* @retval
*/
ssize_t skobj_attr_data_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
if(strcmp(kobj->name, skobject1_attr.skobj_name) == 0)
{
sscanf(buf, "%d\n", &skobject1_attr.data);
}

PRT("kobj->name=%s attr->name=%s count=%d\n", kobj->name, attr->attr.name, count);

return count;
}

// 定义两个属性,用于管理 name_attr 和 data
// 注意,这里的mode赋值的时候不能用这些代表权限的宏,例如S_IRWXUGO,否则会在展开的时候出现问题
struct kobj_attribute s_attr_name = __ATTR(skobj_attr_name, 0664, skobj_attr_name_show, skobj_attr_name_store);
struct kobj_attribute s_attr_data = __ATTR(skobj_attr_data, 0664, skobj_attr_data_show, skobj_attr_data_store);

struct attribute *s_attr[] = {
&s_attr_name.attr,
&s_attr_data.attr,
NULL,
};

const struct attribute_group s_attr_group = {
.name = "skobj_attr",
.attrs = s_attr,
};

/**
* @brief sdriver_demo_init()
* @note
* @param [in]
* @param [out]
* @retval
*/
static __init int sdriver_demo_init(void)
{
int ret = 0;
printk("*** [%s:%d]Build Time: %s %s, git version:%s ***\n", __FUNCTION__,
__LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION);
PRT("sdriver_demo module init!\n");

snprintf(skobject1_attr.skobj_name, sizeof(skobject1_attr.skobj_name), "skobject1");
skobject1 = kobject_create_and_add(skobject1_attr.skobj_name, NULL);

// 在 kobject "skobject1" 中创建属性组
ret = sysfs_create_group(skobject1, &s_attr_group);
if(ret < 0)
{
PRTE("sysfs_create_group fail!ret=%d\n", ret);
goto err_sysfs_create_group;
}
return 0;

err_sysfs_create_group:
kobject_put(skobject1);
return ret;
}

/**
* @brief sdriver_demo_exit()
* @note
* @param [in]
* @param [out]
* @retval
*/
static __exit void sdriver_demo_exit(void)
{
// 释放之前创建的kobject对象
kobject_put(skobject1);
PRT("sdriver_demo module exit!\n");
}


module_init(sdriver_demo_init); // 将__init定义的函数指定为驱动的入口函数
module_exit(sdriver_demo_exit); // 将__exit定义的函数指定为驱动的出口函数

/* 模块信息(通过 modinfo chrdev_led_demo 查看) */
MODULE_LICENSE("GPL v2"); /* 源码的许可证协议 */
MODULE_AUTHOR("sumu"); /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description"); /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */

5.3 开发板验证

我们编译完成后,拷贝到开发板,依次进行以下验证:

  • (1)一开始的 /sys目录
image-20250108185243172
  • (2)加载模块
image-20250108185515880

加载模块后,会发现,在/sys目录生成了 skobject1 目录,在/sys/skobject1目录下有一个skobj_attr目录,这个skobj_attr目录里面两个属性文件skobj_attr_name和skobj_attr_data。

  • (3)查看/修改skobj_attr_name和skobj_attr_data的值
image-20250108185904025
  • (5)卸载模块
image-20250108185957935

可以看到,卸载模块后,资源释放,属性文件以及kobject目录全都没了。

参考资料:

【1】linux设备驱动(9)attribute详解 - Action_er - 博客园

【2】linux驱动-设备驱动模型(属性文件 kobject )_linux设备模型 kobject-CSDN博客

【3】Linux驱动开发—设备模型框架 kobject创建属性文件_修改 kobject 创建的文件操作权限-CSDN博客

【4】内核里printk打印指针打印出(ptrval) - 简书

【5】如何获得正确的printk格式占位符 — The Linux Kernel documentation