kobject结构
拓扑结构:由点和线组成的结构,描述位置关系。
统一设备模型:提供一个独立的机制专门来表示设备,并描述其在系统中的拓扑结构。
kobject
设备模型的核心部分就是kobject,它由struct kobject结构体表示,定义于<linux/kobject.h>中。kobject类似于面向对象语言中的对象(object)类,提供了诸如引用计数、名称和父指针等字段,可以创建对象的层次结构。
struct kobject {
const char *name; /* 指向此kobject的名称 */
struct list_head entry;
struct kobject *parent; /* 指向kobject的父对象 */
struct kset *kset;
struct kobj_type *ktype;
struct kernfs_node *sd; /* sysfs directory entry */
struct kref kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release;
#endif
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
name指针指向此kobject的名称。parent指针指向kobject的父对象。这样一来,kobject就会在内核中构造一个对象层次结构,并且可以将多个对象间的关系表现出来。就如你锁看到的,这便是sysfs的真正面目:一个用户空间的文件系统,用来表示内核中的kobject对象的层次结构。sd指针指向sysfs_dirent结构体,该结构体在sysfs中表示的就是这个kobject。从sysfs文件系统内部看,这个结构体就是表示kobject的一个inode结构体。kref提供引用计数。kobject通常是嵌入其他结构中的,其单独意义其实并不大,例如struct cdev中就用到了kobject结构。
ktype
kobject对象被关联到一种特殊的类型,即ktype。ktype由kobj_type结构体表示,定义于头文件<linux/kobject.h>。
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid);
};
ktype的存在是为了描述一族kobject所具有的普遍特性。这样不再需要每个kobject都分别定义自己的特性,所有的“同类”的kobject都共享一样的特性。
kset
kset是kobject对象的集合体。把它看成一个容器,可将所有相关的kobject对象,比如“全部的块设备”置于同一位置。kset和ktype的区别在于:kset可把kobject集合到一个集合中,ktype描述相关类型的kobject所公有的特性,具有相同的ktype的kobject可以被分组到不同的kset。定义于头文件<linux/kobject.h>中。
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops;
} __randomize_layout;
相互之间的关系如下图
sysfs文件系统
sysfs文件系统是一个处于内存中的虚拟文件系统,它为我们提供了kobject对象层次结构的视图。帮助用户能以一个简单文件系统的方式来观察系统中的各种设备的拓扑结构。sysfs根目录(/sys)下至少包含如下目录:block、bus、class、dev、devices、firmware、fs、hypervisor、kernel、module、power。block目录下的每个子目录都对应着系统中的一个已经注册的块设备。反过来每个目录下又都包含了该块设备的所有分区。bus目录提供了一个系统总线视图。class目录包含了以高层功能逻辑组织起来的系统设备视图。dev目录是已注册设备节点的视图。devices目录是系统中设备拓扑结构视图,它直接映射出了内核中设备结构体的组织层次。firmware目录包含了一些诸如ACPI、EDD、EFI等低层子系统的特殊树。fs目录是已注册文件系统的视图。kernel目录包含内核配置项和状态信息。module目录包含系统已经加载的模块信息。power目录包含系统范围的电源管理数据。其中比较重要的就是devices,该目录将设备模型导出到用户空间。
如果我们需要想sysfs中添加文件,首先我们需要构建一个描述设备属性的结构体attribute。内核提供了如下的宏用于帮助构建属性信息:
#define DEVICE_ATTR(_name, _mode, _show, _store) /* 设备 */
#define DRIVER_ATTR(_name, _mode, _show, _store) /* 驱动 */
#define BUS_ATTR(_name, _mode, _show, _store) /* 总线 */
#define CLASS_ATTR(_name, _mode, _show, _store) /* class */
四个参数的含义分别是名称、权限位、读函数、写函数。其中权限位有如下选择,定义在<linux/stat.h>。
#define S_IRWXU 00700 /* 用户可读写和执行 */
#define S_IRUSR 00400 /* 用户可读 */
#define S_IWUSR 00200 /* 用户可写 */
#define S_IXUSR 00100 /* 用户可执行 */
#define S_IRWXG 00070 /* 用户组可读写和执行 */
#define S_IRGRP 00040 /* 用户组可读 */
#define S_IWGRP 00020 /* 用户组可写 */
#define S_IXGRP 00010 /* 用户组可执行 */
#define S_IRWXO 00007 /* 其他可读写和执行 */
#define S_IROTH 00004 /* 其他可读 */
#define S_IWOTH 00002 /* 其他可写 */
#define S_IXOTH 00001 /* 其他可执行 */
当信息构建完成之后,添加到系统中使用如下函数:
int sysfs_create_file(struct kobject *kobj, const struct attribute *attr); /* 添加 */
void sysfs_remove_files(struct kobject *kobj, const struct attribute **attr); /* 删除 */
如果有很多信息需要添加,可以使用下面的函数,它会添加一个信息的集合:
int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp); /* 添加 */
void sysfs_remove_group(struct kobject *kobj, const struct attribute_group *grp) /* 删除 */
其他函数:sysfs_notify,可以理解为一种通信机制,用来唤醒在读写属性文件(sysfs节点)时因调用select()或poll()而阻塞的用户进程。
sysfs_notify(struct kobject *k, const char *dir, const char *attr);
/* 使用方法:sysfs_notify(&csdn_device->kobj, NULL, dev_attr_csdn_sysfs_write.attr.name); */
实例
我们在创建一个简单的字符设备代码的基础上添加两个属性csdn_sysfs_read和csdn_sysfs_write。具体代码如下:
/* others code */
static ssize_t csdn_sysfs_read_set(struct device *dev,
struct device_attribute *attr, char *buf)
{
int retval = 0;
char *data = "csdn_sysfs_read_set";
int data_len = strlen("csdn_sysfs_read_set");
pr_info("csdn_sysfs_read_set\n");
sprintf(buf, data);
return data_len;
}
static DEVICE_ATTR(csdn_sysfs_read, S_IRUSR, csdn_sysfs_read_set, NULL); /* 只读 */
static ssize_t csdn_sysfs_write_set(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
if (!strncmp(buf, "enable", strlen("enable"))) {
pr_info("csdn_sysfs_write_set enable\n");
} else if (!strncmp(buf, "disable", strlen("disable"))) {
pr_info("csdn_sysfs_write_set disable\n");
} else {
pr_info("csdn_sysfs_write_set error!\n");
}
return count;
}
static DEVICE_ATTR(csdn_sysfs_write, S_IWUSR, NULL, csdn_sysfs_write_set); /* 只写 */
static struct attribute *csdn_attributes[] = {
&dev_attr_csdn_sysfs_read.attr,
&dev_attr_csdn_sysfs_write.attr,
NULL
};
static const struct attribute_group csdn_attribute_group = {
.attrs = csdn_attributes,
};
static __init int csdn_init(void)
{
/* others code */
result = sysfs_create_group(&csdn_device->kobj, &csdn_attribute_group);
if (result) {
device_destroy(csdn_class, csdn_dev);
class_destroy(csdn_class);
cdev_del(&csdn_cdev);
unregister_chrdev_region(csdn_dev, 1);
return result;
}
return 0;
}
static __exit void csdn_exit(void)
{
sysfs_remove_group(&csdn_device->kobj, &csdn_attribute_group);
/* others code */
}
/* others code */
添加模块之后,可以在/sys/devices/virtual/csdn_dev_class/csdn_dev目录下看到csdn_sysfs_write和csdn_sysfs_read。
用户空间代码:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#define DEVICE_NAME "/sys/devices/virtual/csdn_dev_class/csdn_dev"
int sysfs_node_write(int sysfs_fd, char *opt_data, int opt_len)
{
int fd = openat(sysfs_fd, "csdn_sysfs_write", O_WRONLY);
if (fd < 0) {
printf("openat failed, %s\n", strerror(errno));
return -1;
}
int write_len = write(fd, opt_data, opt_len);
if (write_len != opt_len) {
printf("write failed! write_len: %d\n", write_len);
}
close(fd);
return 0;
}
int sysfs_node_read(int sysfs_fd)
{
char result_data[1024] = {0};
int result_data_len = 1024;
int fd = openat(sysfs_fd, "csdn_sysfs_read", O_RDONLY);
if (fd < 0) {
printf("openat failed, %s\n", strerror(errno));
return -1;
}
int read_len = read(fd, result_data, result_data_len);
printf("read_len: %d, result_data: %s\n", read_len, result_data);
close(fd);
return 0;
}
int main(int argc, char const *argv[])
{
int cmd = 0;
int sysfs_fd = open(DEVICE_NAME, O_RDONLY);
if (sysfs_fd < 0) {
printf("open failed! error:%s\n", strerror(errno));
return 0;
}
while (1) {
printf("input> ");
scanf("%d", &cmd);
switch (cmd) {
case 1:
sysfs_node_write(sysfs_fd, "enable", strlen("enable"));
break;
case 2:
sysfs_node_write(sysfs_fd, "disable", strlen("disable"));
break;
case 3:
sysfs_node_read(sysfs_fd);
break;
}
}
close(sysfs_fd);
return 0;
}
运行结果如下:
内核空间打印:
[ 6044.202793] csdn_sysfs_write_set enable
[ 6046.226716] csdn_sysfs_write_set disable
[ 6047.850789] csdn_sysfs_read_set
用户空间输入和打印:
input> 1
input> 2
input> 3
read_len: 19, result_data: csdn_sysfs_read_set
源码下载:https://gitee.com/zhangshusheng/csdn_blog/tree/master/sysfs