Linux用户空间与内核空间通信 | 虚拟文件系统(sysfs读写)

Linux用户空间与内核空间通信主要有以下几种方式:虚拟文件系统(proc、sysfs、debugfs等)、系统调用(read、write、open等)、信号(SIGSEGV、SIGPIPE)、Netlink、内存映像(mmap)、文件等。本文将重点介绍虚拟文件系统中的sysfs

一、sysfs概述

sysfs是一种基于内存的虚拟文件系统(类似的还有proc、configfs、debugfs等),由 kernel 提供,挂载到 /sys 目录下( 可通过mount -l查看),提供了一种用于向用户空间导出内核对象的方法, 它将系统中的设备、驱动程序和其他内核对象表示为文件和目录。其中每个设备和驱动程序都具有唯一的目录。每个目录中包含多个文件,用于控制设备的行为或提供有关设备状态的信息。

sysfs的主要目的是为了方便内核开发人员和设备驱动程序编写者进行交互。sysfs中的每个文件都有一个相关的内核对象,例如一个设备、一个总线或一个驱动程序。当用户或程序读取或写入sysfs文件时,内核会相应地更改相关的内核对象。

从驱动开发的角度,sysfs为用户提供了除了设备文件/dev/proc之外的另一种通过用户空间访问内核数据的方式。使用sysfs编译内核的时候需要定义 CONFIG_SYSFS ,可以 通过 mount -t sysfs sysfs /sys 命令来挂载sysfs/sys目录。

二、sysfs的目录结构

/sys 子目录 用途
block 这个子目录在2.6.26 内核中被 /sys/class/block取代, 旧的接口 /sys/block为了向后兼容仍然保留,二者的内容完全一样,包括所有块设备(通常是硬盘)的符号链接(symbolic link)。链接指向的是/sys/devices中对应的目录。
bus 现代计算机都是总线结构,系统中的所有设备都是连接到某种类型的总线之上(例如:pci总线、usb总线)。每种类型的总线在/sys/bus下有一个对应的子目录。
class 设备按照类型(比如块设备(block)、输入设备(input)、网络设备(net))组织在这个目录中,每个设备放到它的类型对应的子目录里,比如系统中的所有网络设备都可以在/sys/class/net目录下找到。
dev 包含两个子目录blockchar,分别代表系统中的块设备(例如:磁盘)和字节设备(例如:usb、tty)。在这两个子目录中,是指向/sys/devices中的设备目录的符号链接,链接名的格式为major-ID:minor-ID,对应于代表设备的major IDminor ID
devices 系统中的所有设备信息在内核中以一个树形结构组织和存储,/sys/devices就是这个树形结构的文件系统表示。系统中的所有设备信息都会存储在/sys/devices中,且每个设备只会存储一份。/sys/class/sys/bus中的设备都是指向/sys/devices中的子目录的符号链接。
firmware 该目录下包含对固件对象(firmware object)和属性进行操作和观察的接口,即这里是系统加载固件机制的对用户空间的接口。(关于固件有专用于固件加载的一套API)
fs 这里按照设计是用于描述系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点,但目前只有 fuse,ext4 等少数文件系统支持 sysfs 接口,一些传统的虚拟文件系统(VFS)层次控制参数仍然在 sysctl(/proc/sys/fs)接口中。
hypervisor 该目录是与虚拟化Xen相关的装置。(Xen是一个开放源代码的虚拟机监视器)。
kernel 这个目录下存放的是内核中所有可调整的参数。
module 该目录下有系统中所有的模块信息,不论这些模块是以内联(inlined)方式编译到内核映像文件中还是编译为外模块(.ko文件),都可能出现在/sys/module中。即module目录下包含了所有的被载入kernel的模块。
power 该目录是系统中的电源选项,对正在使用的power子系统的描述。这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机/重启等等。

三、sysfs的数据结构和API

sysfs 虚拟文件系统的核心是kobject。把kobject对象与目录项紧密连接在一起,将kobject映射到目录上。 由struct kobject表示,并在<linux/kobject.h>中定义。struct kobject表示一个内核对象,是基本数据类型,每个kobject都会在/sys/文件系统中以目录的形式出现。

1、kobject相关数据结构

1)kobject

首先,简要介绍下数据结构kobject:

struct kobject {
    const char        *name;  // 名称,将在sysfs中作为目录名
    struct list_head    entry;  // 加入kset链表的结构
    struct kobject        *parent;  // 父节点指针,构成树状结构
    struct kset        *kset;  // 指向所属 kset
    const struct kobj_type    *ktype;  // 类型
    struct kernfs_node    *sd;  // 指向所属 (sysfs) 目录项
    struct kref        kref; // 引用计数
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
    struct delayed_work    release;
#endif
    unsigned int state_initialized:1// 是否已经初始化
    unsigned int state_in_sysfs:1// 是否已在sysfs中显示
    unsigned int state_add_uevent_sent:1// 是否已经向user space发送ADD uevent
    unsigned int state_remove_uevent_sent:1// 是否已经向user space发送REMOVE uevent
    unsigned int uevent_suppress:1// 是否忽略上报(不上报uevent)
};

注意

  • name:kobject的名字,在/sys下的目录名使用这个创建;

  • parent:父kobject节点,在/sys下创建目录会在该父目录下;

  • kset: 对kobject进行分类,具有相同属性的kobject在一个集合等;

  • ktype: kobject相关属性,对象的操作方法等,该结构体详细参考内核include/linux/kobject.h;

  • kref:引用计数,为零时调用析构函数;

  • sd:使用kobject_create_and_add函数时,会关联到sysfs_dirent,实现映射。

2)ktype

ktype指针指向了一个kobj_type的数据结构,该数据结构如下代码段所示:

struct kobj_type {
    void (*release)(struct kobject *kobj); //析构函数指针,指向析构函数,该函数在refcount为0时,清理所有”同类“的kobject并释放内存
    const struct sysfs_ops *sysfs_ops; //指针指向”sysfs读写操作函数结构体“
    const struct attribute_group **default_groups; //指针指向attribute结构体数组,描述了属于该ktype的kobject的默认属性
    const struct kobj_ns_type_operations *(*child_ns_type)(const struct kobject *kobj);
    const void *(*namespace)(const struct kobject *kobj);
    void (*get_ownership)(const struct kobject *kobj, kuid_t *uid, kgid_t *gid);
};

kobj_type对kobject的特性描述ktype数据结构初始化将会在随后的kobject_init流程中看到。

3)kset

kset指针指向一个kset的数据结构,该数据结构如下代码段所示:

struct kset {
    struct list_head list; //kset链表
    spinlock_t list_lock;
    struct kobject kobj; //该kset的基类(就是说该kset中的kobject都长这样)
    const struct kset_uevent_ops *uevent_ops; //指针指向kset_uevent_ops结构体,用于处理kset中的kobject对象的热插拔操作。
} __randomize_layout;

kset是很多kobject的集合。一般来说,如果一个kobject属于某个kset,即kset->kobj是存在的,那么这个kset->kobj将作为该kobject的父对象。这一点在随后的kobject_add流程中体现。

4)kref

kref指针指向一个kref的数据结构,该数据结构如下代码段所示:

struct kref {
    refcount_t refcount;
};

kref引用计数来标示kobject的生命周期,在对象初始化时,调用kref_init()来使kref->refcount被原子置1(这一步将会在随后的kobject_init流程中看到)。引用了该kobject的代码都要进行引用计数加一。只要这个refcount不为0,该kobject就会继续被保留在内存中,否则对象将被撤销、内存被释放。

2、kobject相关函数

1、kobject_create_and_add

该函数的作用主要是动态创建一个kobject结构并注册到sysfs,即在/sys/文件夹下创建一个目录。

函数原型:

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

函数参数:

  • name:创建kobj的名字;

  • parent:指定父kobject,实际就是在那个目录下创建一个目录。比如为kernel_kobj,将在/sys/kernel目录下创建目录,如果为NULL,将在/sys下创建。

返回值:

  • 成功:返回kobject结构体指针;

  • 失败:返回NULL

2、sysfs_create_file

该函数的作用主要是通过kobject创建sysfs的节点,即在/sys/目录下创建文件。

函数原型:

int sysfs_create_file ( struct kobject *  kobj, const struct attribute * attr);

函数参数:

  • kobj:我们创建的kobject;
  • attr:属性描述。

返回值:

  • 成功:返回非零值;

  • 失败:返回0。

四、sysfs实战演练

1、在sys目录下创建一个文件夹my_dir

源代码如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/init.h>

static struct kobject *kobj;

static int kobject_test_init(void)
{
    kobj = kobject_create_and_add("my_dir"NULL);
    return 0;
}

static void kobject_test_exit(void)
{
    kobject_del(kobj);
}

module_init(kobject_test_init);
module_exit(kobject_test_exit);
MODULE_LICENSE("GPL v2");

Makefile如下:

obj-m += mydir.o
KDIR := /lib/modules/$(shell uname -r)/build

all:
        make -C $(KDIR) M=$(PWD) modules

clean:
        make -C $(KDIR) M=$(PWD) clean

make编译生成ko程序,如下:

[root@localhost mydir]# make
make -C /lib/modules/4.18.0-394.el8.x86_64/build M=/tmp/mydir modules
make[1]: Entering directory '/usr/src/kernels/4.18.0-394.el8.x86_64'
CC [M] /tmp/mydir/mydir.o
Building modules, stage 2.
MODPOST 1 modules
CC /tmp/mydir/mydir.mod.o
LD [M] /tmp/mydir/mydir.ko
make[1]: Leaving directory '/usr/src/kernels/4.18.0-394.el8.x86_64'

使用insmod加载生成的mydir.ko文件到内核,查看加载后的/sys/文件夹,如下:

[root@localhost mydir]# ls -l
total 480
-rw-r--r-- 1 root root 143 Nov 1 10:50 Makefile
-rw-r--r-- 1 root root 27 Nov 1 10:50 modules.order
-rw-r--r-- 1 root root 0 Nov 1 10:50 Module.symvers
-rw-r--r-- 1 root root 531 Nov 1 10:49 mydir.c
-rw-r--r-- 1 root root 234056 Nov 1 10:50 mydir.ko
-rw-r--r-- 1 root root 874 Nov 1 10:50 mydir.mod.c
-rw-r--r-- 1 root root 117912 Nov 1 10:50 mydir.mod.o
-rw-r--r-- 1 root root 117712 Nov 1 10:50 mydir.o
[root@localhost mydir]# ls -l /sys/
total 0
drwxr-xr-x 2 root root 0 Jun 8 17:34 block
drwxr-xr-x 34 root root 0 Jun 8 17:34 bus
drwxr-xr-x 61 root root 0 Jun 8 17:34 class
drwxr-xr-x 4 root root 0 Jun 8 17:34 dev
drwxr-xr-x 35 root root 0 Jun 8 17:34 devices
drwxr-xr-x 7 root root 0 Jun 8 17:34 firmware
drwxr-xr-x 8 root root 0 Jun 8 17:34 fs
drwxr-xr-x 2 root root 0 Jun 8 17:34 hypervisor
drwxr-xr-x 16 root root 0 Jun 8 17:34 kernel
drwxr-xr-x 169 root root 0 Jun 8 17:34 module
drwxr-xr-x 2 root root 0 Jun 8 17:34 power
[root@localhost mydir]# insmod ./mydir.ko
[root@localhost mydir]# ls -l /sys/ | grep my_dir
drwxr-xr-x 2 root root 0 Nov 1 10:50 my_dir
[root@localhost mydir]# ls -l /sys/my_dir/
total 0

因为,本示例只在/sys/下创建了目录,并未在创建的目录中生成文件,因此,创建的/sys/my_dir目录下为空。

卸载mydir模块后,/sys/下的my_dir目录则会消失,如下:

[root@localhost mydir]# rmmod mydir
[root@localhost mydir]# ls -l /sys/ | grep mydir
[root@localhost mydir]# ls -l /sys/
total 0
drwxr-xr-x 2 root root 0 Jun 8 17:34 block
drwxr-xr-x 34 root root 0 Jun 8 17:34 bus
drwxr-xr-x 61 root root 0 Jun 8 17:34 class
drwxr-xr-x 4 root root 0 Jun 8 17:34 dev
drwxr-xr-x 35 root root 0 Jun 8 17:34 devices
drwxr-xr-x 7 root root 0 Jun 8 17:34 firmware
drwxr-xr-x 8 root root 0 Jun 8 17:34 fs
drwxr-xr-x 2 root root 0 Jun 8 17:34 hypervisor
drwxr-xr-x 16 root root 0 Jun 8 17:34 kernel
drwxr-xr-x 169 root root 0 Jun 8 17:34 module
drwxr-xr-x 2 root root 0 Jun 8 17:34 power

2、在sys目录下创建嵌套文件夹

源代码如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/init.h>

static struct kobject *kobj;
static struct kobject *ckobj;

static int kobject_test_init(void)
{

    kobj = kobject_create_and_add("first"NULL);
    ckobj = kobject_create_and_add("second", kobj);

    return 0;
}

static void kobject_test_exit(void)
{
    kobject_del(ckobj);
    kobject_del(kobj);
}

module_init(kobject_test_init);
module_exit(kobject_test_exit);
MODULE_LICENSE("GPL v2");

Makefile如下:

obj-m += nested_dir.o
KDIR := /lib/modules/$(shell uname -r)/build

all:
        make -C $(KDIR) M=$(PWD) modules

clean:
        make -C $(KDIR) M=$(PWD) clean

make编译生成ko程序,如下:

[root@localhost mydir]# make
make -C /lib/modules/4.18.0-394.el8.x86_64/build M=/tmp/mydir modules
make[1]: Entering directory '/usr/src/kernels/4.18.0-394.el8.x86_64'
CC [M] /tmp/mydir/nested_dir.o
Building modules, stage 2.
MODPOST 1 modules
CC /tmp/mydir/nested_dir.mod.o
LD [M] /tmp/mydir/nested_dir.ko
make[1]: Leaving directory '/usr/src/kernels/4.18.0-394.el8.x86_64'

使用insmod加载生成的nested_dir.ko文件到内核,查看加载后的/sys/文件夹,如下:

[root@localhost mydir]# insmod nested_dir.ko
[root@localhost mydir]# ls -l /sys/
total 0
drwxr-xr-x 2 root root 0 Jun 8 17:34 block
drwxr-xr-x 34 root root 0 Jun 8 17:34 bus
drwxr-xr-x 61 root root 0 Jun 8 17:34 class
drwxr-xr-x 4 root root 0 Jun 8 17:34 dev
drwxr-xr-x 35 root root 0 Jun 8 17:34 devices
drwxr-xr-x 7 root root 0 Jun 8 17:34 firmware
drwxr-xr-x 3 root root 0 Nov 1 11:08 first
drwxr-xr-x 8 root root 0 Jun 8 17:34 fs
drwxr-xr-x 2 root root 0 Jun 8 17:34 hypervisor
drwxr-xr-x 16 root root 0 Jun 8 17:34 kernel
drwxr-xr-x 170 root root 0 Jun 8 17:34 module
drwxr-xr-x 2 root root 0 Jun 8 17:34 power
[root@localhost mydir]# tree /sys/first/
/sys/first/
└── second

1 directory, 0 files

因为,本示例在/sys/下创建了嵌套的目录,即firstsecond目录,但并未在创建的目录中生成文件,因此,创建的/sys/first/second目录下为空。

卸载nested_dir模块后,/sys/下的first目录则会消失,如下:

[root@localhost mydir]# rmmod nested_dir
[root@localhost mydir]# ls -l /sys/
total 0
drwxr-xr-x 2 root root 0 Jun 8 17:34 block
drwxr-xr-x 34 root root 0 Jun 8 17:34 bus
drwxr-xr-x 61 root root 0 Jun 8 17:34 class
drwxr-xr-x 4 root root 0 Jun 8 17:34 dev
drwxr-xr-x 35 root root 0 Jun 8 17:34 devices
drwxr-xr-x 7 root root 0 Jun 8 17:34 firmware
drwxr-xr-x 8 root root 0 Jun 8 17:34 fs
drwxr-xr-x 2 root root 0 Jun 8 17:34 hypervisor
drwxr-xr-x 16 root root 0 Jun 8 17:34 kernel
drwxr-xr-x 169 root root 0 Jun 8 17:34 module
drwxr-xr-x 2 root root 0 Jun 8 17:34 power

3、在sys目录下创建文件夹和文件

源代码如下:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include<linux/sysfs.h>
#include<linux/kobject.h>
#include <linux/err.h>

volatile int test_value = 0;
struct kobject *kobj_test;

/*该函数被调用在sysfs文件被读时*/
static ssize_t sysfs_show(struct kobject *kobj,
                struct kobj_attribute *attr, char *buf)

{
    pr_info("读sysfs!n");
    return sprintf(buf, "test_value = %dn", test_value);
}

/* 该函数被调用在sysfs文件被写时*/
static ssize_t sysfs_store(struct kobject *kobj,
                struct kobj_attribute *attr,const char *buf, size_t count)

{
    pr_info("写sysfs!n");
    sscanf(buf,"%d",&test_value);
    return count;
}

/*使用__ATTR宏初始化sysfs_test_attr结构体,该宏定义在include/linux/sysfs.h*/
struct kobj_attribute sysfs_test_attr = __ATTR(test_value, 0644, sysfs_showsysfs_store);

/*模块初始化函数*/
static int __init sysfs_test_driver_init(void)
{
    /*创建一个目录在/sys下 */
    kobj_test = kobject_create_and_add("sysfs_test",NULL);

    /*在sysfs_test目录下创建一个文件*/
    if(sysfs_create_file(kobj_test,&sysfs_test_attr.attr)){
            pr_err("创建sysfs文件失败.....n");
            goto error_sysfs;
    }

    pr_info("驱动模块初始化完成!n");
    return 0;

error_sysfs:
        kobject_put(kobj_test);
        sysfs_remove_file(kernel_kobj, &sysfs_test_attr.attr);
        return -1;

}

/*模块退出函数*/
static void __exit sysfs_test_driver_exit(void)
{
    kobject_put(kobj_test);
    sysfs_remove_file(kernel_kobj, &sysfs_test_attr.attr);
    pr_info("设备驱动模块移除!n");
}

module_init(sysfs_test_driver_init);
module_exit(sysfs_test_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embedfire");
MODULE_DESCRIPTION("一个简单的使用sysfs的驱动程序");

Makefile如下:

obj-m += sysfs_test.o
KDIR := /lib/modules/$(shell uname -r)/build

all:
        make -C $(KDIR) M=$(PWD) modules

clean:
        make -C $(KDIR) M=$(PWD) clean

make编译生成ko程序,如下:

[root@localhost sysfs_test]# make
make -C /lib/modules/4.18.0-394.el8.x86_64/build M=/tmp/sysfs_test modules
make[1]: Entering directory '/usr/src/kernels/4.18.0-394.el8.x86_64'
CC [M] /tmp/sysfs_test/sysfs_test.o
Building modules, stage 2.
MODPOST 1 modules
CC /tmp/sysfs_test/sysfs_test.mod.o
LD [M] /tmp/sysfs_test/sysfs_test.ko
make[1]: Leaving directory '/usr/src/kernels/4.18.0-394.el8.x86_64'

使用insmod加载生成的sysfs_test.ko文件到内核,查看加载后的/sys/文件夹,读写在/sys/sysfs_test下创建的文件,如下:

[root@localhost sysfs_test]# insmod ./sysfs_test.ko
[root@localhost sysfs_test]# ls -l /sys/
total 0
drwxr-xr-x 2 root root 0 Jun 8 17:34 block
drwxr-xr-x 34 root root 0 Jun 8 17:34 bus
drwxr-xr-x 61 root root 0 Jun 8 17:34 class
drwxr-xr-x 4 root root 0 Jun 8 17:34 dev
drwxr-xr-x 35 root root 0 Jun 8 17:34 devices
drwxr-xr-x 7 root root 0 Jun 8 17:34 firmware
drwxr-xr-x 8 root root 0 Jun 8 17:34 fs
drwxr-xr-x 2 root root 0 Jun 8 17:34 hypervisor
drwxr-xr-x 16 root root 0 Jun 8 17:34 kernel
drwxr-xr-x 170 root root 0 Jun 8 17:34 module
drwxr-xr-x 2 root root 0 Jun 8 17:34 power
drwxr-xr-x 2 root root 0 Nov 1 13:59 sysfs_test
[root@localhost sysfs_test]#
[root@localhost sysfs_test]# ls -l /sys/sysfs_test/
total 0
-rw-r--r-- 1 root root 4096 Nov 1 13:59 test_value
[root@localhost sysfs_test]# cat /sys/sysfs_test/test_value
test_value = 0
[root@localhost sysfs_test]# echo 10 > /sys/sysfs_test/test_value
[root@localhost sysfs_test]# cat /sys/sysfs_test/test_value
test_value = 10
[root@localhost sysfs_test]# dmesg -T | tail -n 10
[Wed Nov 1 13:55:13 2023] 驱动模块初始化完成!
[Wed Nov 1 13:55:37 2023] 读sysfs!
[Wed Nov 1 13:56:05 2023] 写sysfs!
[Wed Nov 1 13:56:07 2023] 读sysfs!


原文始发于微信公众号(Linux二进制):Linux用户空间与内核空间通信 | 虚拟文件系统(sysfs读写)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/302128.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!