Heng30的博客
搜索 分类 关于 订阅

Linux驱动如何创建sysfs class接口?

2025-03-18

Linux的sysfs是一种虚拟文件系统,用于将内核对象、属性和关系导出到用户空间,方便用户和管理员查看和配置内核及硬件信息。sysfs通常挂载在/sys目录下。

sysfs class是sysfs中的一个重要部分,用于按功能分类展示设备信息。每个类对应一种设备类型(如网络设备、输入设备等),类目录下包含该类设备的子目录,每个子目录代表一个具体设备,包含其属性和相关链接。

常见sysfs class

  • net: 网络设备信息。

  • input: 输入设备(如键盘、鼠标)信息。

  • block: 块设备(如硬盘、分区)信息。

  • tty: 串行设备信息。

  • drm: 图形设备信息。

  • sound: 音频设备信息。

驱动代码

// simple.c

#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/slab.h>

#define DEV_NAME "my-dev"
#define CLASS_NAME "my-class"

static dev_t dev_nr;
static struct class *my_class;
static struct device *my_dev;

static int answer = 42;
DEVICE_INT_ATTR(answer, 0600, answer);

static char text[64] = "Hello World!\n";

static ssize_t text_store(struct device *dev, struct device_attribute *attr,
                           const char *buffer, size_t size) {
    pr_info("simple_text_store\n");

    memset(text, 0, sizeof(text));
    size = min(size, sizeof(text));
    strncpy(text, buffer, size);
    return size;
}

static ssize_t text_show(struct device *dev, struct device_attribute *attr,
                          char *buffer) {
    pr_info("simple_text_show\n");
    strcpy(buffer, text);
    return strlen(text);
}

DEVICE_ATTR(text, 0600, text_show, text_store);

static int __init simple_init(void) {
    pr_info("simple_init\n");

    int status = alloc_chrdev_region(&dev_nr, 0, MINORMASK + 1, DEV_NAME);
    if (status < 0) {
        pr_err("alloc_chrdev_region error\n");
        return status;
    }

    my_class = class_create(CLASS_NAME);
    if (IS_ERR(my_class)) {
        pr_err("class_create error\n");
        status = PTR_ERR(my_class);
        goto free_dev_nr;
    }

    my_dev =
        device_create(my_class, NULL, dev_nr, NULL, DEV_NAME"%d", MINOR(dev_nr));
    if (IS_ERR(my_dev)) {
        pr_err("device_create error\n");
        status = PTR_ERR(my_dev);
        goto free_class;
    }

    status = device_create_file(my_dev, &dev_attr_text);
    if (status) {
        pr_err("device_create_file for text error\n");
        goto free_dev;
    }

    status = device_create_file(my_dev, &dev_attr_answer.attr);
    if (status) {
        pr_err("device_create_file for answer error\n");
        goto free_text;
    }

    return 0;

free_text:
    device_remove_file(my_dev, &dev_attr_text);
free_dev:
    device_destroy(my_class, dev_nr);
free_class:
    class_destroy(my_class);
free_dev_nr:
    unregister_chrdev_region(dev_nr, MINORMASK + 1);

    return status;
}

static void __exit simple_exit(void) {
    pr_info("simple_exit\n");

    device_remove_file(my_dev, &dev_attr_answer.attr);
    device_remove_file(my_dev, &dev_attr_text);
    device_destroy(my_class, dev_nr);
    class_destroy(my_class);
    unregister_chrdev_region(dev_nr, MINORMASK + 1);
}

module_init(simple_init);
module_exit(simple_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("heng30");
MODULE_VERSION("v0.0.1");
MODULE_DESCRIPTION("A simple kernel module");

编译脚本

#!/bin/sh

top-dir = $(shell pwd)
kernel-version = $(shell uname -r)
kernel-dir ?= /lib/modules/$(kernel-version)/build

obj-m += simple.o

all:
        make -C $(kernel-dir) modules M=$(top-dir)

clean:
        rm -f *.o *.ko *.mod *.mod.c *.order *.symvers *.dtbo
        make -C $(kernel-dir) clean m=$(top-dir)

测试

  • 编译程序:make

  • 安装驱动:insmod simple.ko

  • 查看文件:ls /sys/class/my-class/my-dev0

answer  dev  power  subsystem  text  uevent
  • 读取数据:cat /sys/class/my-class/my-dev0/text

  • 写入数据:echo "good" > /sys/class/my-class/my-dev0/text

  • 移除驱动:rmmod simple.ko