首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

以一个实际例子来学习Linux驱动程序开发之“设备类”的相关知识【利用设备类实现对同一设备类下的多个LED灯实现点亮或关闭】

  • 25-03-05 04:42
  • 4035
  • 6088
blog.csdn.net

前言

对于一个设备的驱动程序来说,其实上层用户主要看到的、用到的就是设备文件和设备类,当然用得最多的是设备文件,虽然设备类用得不多,但也是每一个设备注册实例化时必须要用到的东西,本篇博文就以一个简单的例子说明设备类的功能。

设备类的本质

所谓设备类,本质上就是“物以类聚,人以群分”思想的体现,它允许每个设备有一个自己的所属类,说白了就是所属分组,假如某几个设备的所属类是相同的,那么我们就能对这些设备进行一些统一的操作。

下面以一个实际例子看下设备类在Linux嵌入式驱动开发中是如何被定义和使用的。

例子的问题背景和源码

假设我们有 3 个 LED 灯设备(功能相似),它们共享一个驱动程序,每个设备可以独立地开关操作。设备类可以在以下方面帮助实现分组管理:

  • 在Linux的 /sys/class/ 目录中,将这些设备归类到一个统一的类目录下。
  • 通过类属性实现对所有设备的统一操作,比如一键控制所有 LED 的开关。

我们这里就利用设备类的概念来一键控制所有 LED 的开关。

源码如下:

#include 
#include 
#include 
#include 

#define LED_COUNT 3  // 三个 LED 设备

static struct class *led_class;
static struct cdev led_cdev;
static dev_t dev;
static int led_status[LED_COUNT];  // 每个 LED 的状态(0: 关,1: 开)

// 模拟控制 LED 的硬件操作
static void led_control(int index, int status)
{
    printk(KERN_INFO "LED %d is now %s\n", index, status ? "ON" : "OFF");
    led_status[index] = status;
}

// 打开设备的回调函数
static int led_open(struct inode *inode, struct file *file)
{
    int minor = iminor(inode);  // 获取设备次设备号
    printk(KERN_INFO "LED device %d opened\n", minor);
    return 0;
}

// 写入设备数据的回调函数
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    int minor = iminor(file_inode(file));  // 获取次设备号
    int status;

    // 模拟接收用户的控制命令,'1' 为开,'0' 为关
    if (copy_from_user(&status, buf, sizeof(int)))
        return -EFAULT;

    if (status != 0 && status != 1)
        return -EINVAL;

    // 控制对应的 LED
    led_control(minor, status);
    return sizeof(int);
}

// 文件操作结构体
static const struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
};

// 统一控制所有 LED 的类属性
static ssize_t led_all_control_store(struct class *cls, struct class_attribute *attr, const char *buf, size_t count)
{
    int status, i;

    if (kstrtoint(buf, 10, &status) || (status != 0 && status != 1))
        return -EINVAL;

    for (i = 0; i < LED_COUNT; i++)
        led_control(i, status);

    return count;
}

// 定义类属性
CLASS_ATTR_WO(led_all_control);

// 模块初始化函数
static int __init led_init(void)
{
    int ret, i;

    // 分配主设备号和次设备号范围
    ret = alloc_chrdev_region(&dev, 0, LED_COUNT, "led");
    if (ret < 0) {
        printk(KERN_ERR "Failed to allocate device numbers\n");
        return ret;
    }

    // 初始化 cdev 并注册
    cdev_init(&led_cdev, &led_fops);
    ret = cdev_add(&led_cdev, dev, LED_COUNT);
    if (ret < 0) {
        printk(KERN_ERR "Failed to add cdev\n");
        unregister_chrdev_region(dev, LED_COUNT);
        return ret;
    }

    // 创建设备类
    led_class = class_create(THIS_MODULE, "led_class");
    if (IS_ERR(led_class)) {
        printk(KERN_ERR "Failed to create class\n");
        cdev_del(&led_cdev);
        unregister_chrdev_region(dev, LED_COUNT);
        return PTR_ERR(led_class);
    }

    // 添加类属性
    ret = class_create_file(led_class, &class_attr_led_all_control);
    if (ret) {
        printk(KERN_ERR "Failed to create class attribute\n");
        class_destroy(led_class);
        cdev_del(&led_cdev);
        unregister_chrdev_region(dev, LED_COUNT);
        return ret;
    }

    // 为每个 LED 创建设备文件
    for (i = 0; i < LED_COUNT; i++) {
        device_create(led_class, NULL, MKDEV(MAJOR(dev), MINOR(dev) + i), NULL, "led%d", i);
    }

    printk(KERN_INFO "LED driver loaded\n");
    return 0;
}

// 模块退出函数
static void __exit led_exit(void)
{
    int i;

    // 删除每个 LED 的设备文件
    for (i = 0; i < LED_COUNT; i++) {
        device_destroy(led_class, MKDEV(MAJOR(dev), MINOR(dev) + i));
    }

    // 删除类属性
    class_remove_file(led_class, &class_attr_led_all_control);

    // 销毁设备类
    class_destroy(led_class);

    // 删除 cdev
    cdev_del(&led_cdev);

    // 注销设备号
    unregister_chrdev_region(dev, LED_COUNT);

    printk(KERN_INFO "LED driver unloaded\n");
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");

  • 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

以下我们开始对源码进行分析,源码分析完,那么“设备类”的相关知识就搞懂了。

驱动模块加载代码module_init(led_init);

module_init(led_init);
  • 1

这行代码,将led_init 函数注册为模块的初始化函数。如果你编译出的模块文件名字为led_driver.ko,那么当你运行 insmod led_driver.ko 时,内核会自动调用函数 led_init。

驱动模块加载代码module_exit(led_exit);

如果理解了驱动模块加载代码module_init(led_init);,那么这句代码就没什么好理解了。

模块许可证申明代码MODULE_LICENSE("GPL");

关于这句代码的详细介绍见我的另一篇博文
http://iyenn.com/rec/1709539.html

模块初始化函数led_init()分析

源码

// 模块初始化函数
static int __init led_init(void)
{
    int ret, i;

    // 分配主设备号和次设备号范围
    ret = alloc_chrdev_region(&dev, 0, LED_COUNT, "led");
    if (ret < 0) {
        printk(KERN_ERR "Failed to allocate device numbers\n");
        return ret;
    }

    // 初始化 cdev 并注册
    cdev_init(&led_cdev, &led_fops);
    ret = cdev_add(&led_cdev, dev, LED_COUNT);
    if (ret < 0) {
        printk(KERN_ERR "Failed to add cdev\n");
        unregister_chrdev_region(dev, LED_COUNT);
        return ret;
    }

    // 创建设备类
    led_class = class_create(THIS_MODULE, "led_class");
    if (IS_ERR(led_class)) {
        printk(KERN_ERR "Failed to create class\n");
        cdev_del(&led_cdev);
        unregister_chrdev_region(dev, LED_COUNT);
        return PTR_ERR(led_class);
    }

    // 添加类属性
    ret = class_create_file(led_class, &class_attr_led_all_control);
    if (ret) {
        printk(KERN_ERR "Failed to create class attribute\n");
        class_destroy(led_class);
        cdev_del(&led_cdev);
        unregister_chrdev_region(dev, LED_COUNT);
        return ret;
    }

    // 为每个 LED 创建设备文件
    for (i = 0; i < LED_COUNT; i++) {
        device_create(led_class, NULL, MKDEV(MAJOR(dev), MINOR(dev) + i), NULL, "led%d", i);
    }

    printk(KERN_INFO "LED driver loaded\n");
    return 0;
}
  • 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

函数声明static int __init led_init(void)

这句代码关键是要理解__init是怎么回事儿?
详情见 http://iyenn.com/rec/1709614.html

分配主设备号和次设备号范围的代码

    // 分配主设备号和次设备号范围
    ret = alloc_chrdev_region(&dev, 0, LED_COUNT, "led");
    if (ret < 0) {
        printk(KERN_ERR "Failed to allocate device numbers\n");
        return ret;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这段代码主要是理解函数alloc_chrdev_region(),关于这个函数的理解见博文 http://iyenn.com/rec/1709451.html 【搜索关键字“第一步是调用函数alloc_chrdev_region”】

初始化 cdev 结构体→将cdev 结构体和file_operations结构体绑定的代码,→写入设备号信息到cdev 结构体

  // 初始化 cdev 并注册
    cdev_init(&led_cdev, &led_fops);
    ret = cdev_add(&led_cdev, dev, LED_COUNT);
    if (ret < 0) {
        printk(KERN_ERR "Failed to add cdev\n");
        unregister_chrdev_region(dev, LED_COUNT);
        return ret;
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这段代码主要是理解函数cdev_init和函数cdev_add,关于这两个函数的理解见博文 http://iyenn.com/rec/1709451.html 【搜索关键字“第二步是调用函数cdev_init()”和关键字“第三步是调用函数cdev_add”】

在这里我们需要去看下file_operations结构体的实例led_fops的实现,它里面的成员函数其实才是对设备的具体操作,才是驱动程序的核心。

file_operations结构体的实例led_fops

// 文件操作结构体
static const struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这个结构体成员中对成员open和write的赋值是我们自己定义的两个函数led_open和led_write,很好理解。但是对成员owner赋值为THIS_MODULE就不理解了,所以专门写了篇博文来理解这个问题,详情见 http://iyenn.com/rec/1709537.html

★★创建设备类的代码★★

这里是我们这篇博文重点关注的问题。

    // 创建设备类
    led_class = class_create(THIS_MODULE, "led_class");
    if (IS_ERR(led_class)) {
        printk(KERN_ERR "Failed to create class\n");
        cdev_del(&led_cdev);
        unregister_chrdev_region(dev, LED_COUNT);
        return PTR_ERR(led_class);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这段创建设备类的代码其实在理解第一个参数宏THIS_MODULE的定义、作用、原理后(详情见 http://iyenn.com/rec/1709537.html),就很好理解了,第二个参数led_class就是设备类的名字,注意,这里的类不是面向对象编程中的类的概念,而是分组、分类的意思。

当代码:

led_class = class_create(THIS_MODULE, "led_class");
  • 1

运行完成后,系统目录/sys/class/下会新加一个名叫led_class的目录【注意目录名来自class_create的第二个参数,而不是=左边的变量led_class,这里恰好两个的名字一样了】,即存在了下面这个目录路径:

/sys/class/led_class/
  • 1

★★★添加设备的类属性的代码(class_create_file函数及与设备类属性有关的重要参数class_attr_led_all_control的分析)★★★

    // 添加类属性
    ret = class_create_file(led_class, &class_attr_led_all_control);
    if (ret) {
        printk(KERN_ERR "Failed to create class attribute\n");
        class_destroy(led_class);
        cdev_del(&led_cdev);
        unregister_chrdev_region(dev, LED_COUNT);
        return ret;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这里是设备类这个知识点比较难理解的地方。
显然,重点是理解函数class_create_file()。
函数 class_create_file() 是一个用于在指定的设备类(struct class)中创建属性文件的函数。它为该设备类在 /sys/class/ 下的目录中添加一个用户可访问的文件,即为这个类添加一个属性,这个属性中有相应的操作函数,比如读操作函数、写操作函数。

它的函数原型如下:

int class_create_file(struct class *cls, const struct class_attribute *attr);
  • 1
  • cls: 指向设备类(struct class)的指针,通常由 class_create 创建。
  • attr: 指向 struct class_attribute 的指针,用于定义设备类属性文件的属性和操作。

返回值:

  • 返回 0 表示成功。
  • 返回负数表示失败,例如内存分配失败或文件创建失败。

第一个参数cls已经在上面通过下面的代码得到了:

  led_class = class_create(THIS_MODULE, "led_class");
  • 1

第二个参数const struct class_attribute *attrr = &class_attr_led_all_control的分析是难点,但还是要硬着头皮上…
前方高能,接下来是的内容有如下这些:
前方高能,接下来是的内容有如下这些:
前方高能,接下来是的内容有如下这些:

  • 宏CLASS_ATTR_WO(led_all_control);的初步展开
  • 结构体struct class_attribute 的定义
  • 结构体struct attribute的定义
  • 宏__ATTR_WO(led_all_control)的展开
  • 宏__ATTR(led_all_control, 0200, NULL, led_all_control_store)的展开
  • 宏CLASS_ATTR_WO(led_all_control);的彻底展开

宏CLASS_ATTR_WO(led_all_control);的初步展开

回到问题本身,要理解函数 class_create_file的关键是要理解第二个参数const struct class_attribute *attr,首先我们要看第二个参数*attr 被赋值为 &class_attr_led_all_control,那我们就需要去看下变量class_attr_led_all_control 是在代码中的哪里被定义的?
变量class_attr_led_all_control 实际上在整个代码中你找不到它的显式定义的,实际上是它是在前面的第68行的代码中被定义的:

CLASS_ATTR_WO(led_all_control);
  • 1

这是一个宏定义,CLASS_ATTR_WO这个宏的定义如下:

#define CLASS_ATTR_WO(_name) struct class_attribute class_attr_##_name = __ATTR_WO(_name)
  • 1

关于上面这个宏定义中标记分隔符##的详解见我的另一篇博文 http://iyenn.com/rec/1709615.html

明白标记分隔符##的使用后,可将宏初步展开为:

struct class_attribute class_attr_led_all_control = __ATTR_WO(led_all_control)
  • 1

你看这里面不是有结构体class_attribute的实例class_attr_led_all_control`了吗?然后等号右边又是一个宏定义:

__ATTR_WO(led_all_control)
  • 1

这个宏的定义如下:

#define __ATTR_WO(_name) _ATTR(_name, 0200, NULL, _name##_store)
  • 1

所以进一步展开后为:

struct class_attribute class_attr_led_all_control = __ATTR(led_all_control, 0200, NULL, led_all_control_store);
  • 1

而__ATTR又是一个宏定义,在解读它之前我们先要搞清楚结构体class_attribute的定义:

struct class_attribute {
    struct attribute attr;         // 包含类属性的基本信息
    ssize_t (*show)(struct class *class, struct class_attribute *attr, char *buf);
    ssize_t (*store)(struct class *class, struct class_attribute *attr, const char *buf, size_t count);
};
  • 1
  • 2
  • 3
  • 4
  • 5

结构体class_attribute的第一个成员是一个结构体:struct attribute attr;,它的定义如下:

struct attribute {
    const char *name;  // 类属性的名称
    umode_t mode;      // 类属性的文件权限
};
  • 1
  • 2
  • 3
  • 4

结构体class_attribute的第二个成员show是一个函数指针,对应的函数实际上是这个设备类属性的读取函数,当这个设备类属性要进行读操作时,就调用这个函数,这里可以是用户定义的读取函数,也可以是 NULL(表示不可读)。

结构体class_attribute的第三个成员store是一个函数指针,对应的函数实际上是这个设备类属性的写入函数,当这个设备类属性要进行写操作时,就调用这个函数,这里可以是用户定义的写入函数,也可以是 NULL(表示不可写)。

有了上面两个结构体的定义之后我们再来看__ATTR 宏,它的定义如下:

以下是 __ATTR 的典型定义(可能会因内核版本略有不同):

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

你看它的内容:

{ \
    .attr = { .name = _name, .mode = _mode }, \
    .show = _show, \
    .store = _store, \
}
  • 1
  • 2
  • 3
  • 4
  • 5

不正是结构体class_attribute的主体部分吗?所以它相当于初始化了一个名叫class_attr_led_all_control的结构体。其中的

所以我们把宏CLASS_ATTR_WO(led_all_control);彻底展开后的内容如下:

struct class_attribute class_attr_led_all_control = {
    .attr = {
        .name = "led_all_control",
        .mode = 0200,
    },
    .show = NULL,
    .store = led_all_control_store,
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

到这里,我们就算真正的把代码led_class = class_create(THIS_MODULE, "led_class");中的第二个参数搞清楚了,它的定义和初始化如下:

struct class_attribute class_attr_led_all_control = {
    .attr = {
        .name = "led_all_control",
        .mode = 0200,
    },
    .show = NULL,
    .store = led_all_control_store,
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

我们再来说说各成员的意义:

  • name表示这个类属性的名称,在这里类属性的名称为led_all_control;
  • mode表示这个类属性的读写权限,这里的0200表示权限为只写;
  • show表示这个类属性的读操作函数;
  • store表示这个类属性的写操作函数。

当下面的代码:

ret = class_create_file(led_class, &class_attr_led_all_control);
  • 1

运行完成后,/sys/class/led_class/ 目录中增加了下面这个文件:

led_all_control
  • 1

为每个LED设备创建设备文件

    // 为每个 LED 创建设备文件
    for (i = 0; i < LED_COUNT; i++) {
        device_create(led_class, NULL, MKDEV(MAJOR(dev), MINOR(dev) + i), NULL, "led%d", i);
    }
  • 1
  • 2
  • 3
  • 4

这个就没啥好讲的了,在之前的博文 http://iyenn.com/rec/1709451.html 已经把函数device_create()的使用、主设备号、次设备号讲清楚了。
不过在这里,对第一个参数,即设备类struct class *cls = led_class,有了认识,之前完全不知道设备类是怎么回事儿。

上面这段代码运行完后:
在系统的/dev/ 目录下有了下面这些文件:

/dev/led0
/dev/led1
/dev/led2
  • 1
  • 2
  • 3

在系统的/sys/class/led_class/目录的有下面这4个文件:

/sys/class/led_class/led_all_control
/sys/class/led_class/led0
/sys/class/led_class/led1
/sys/class/led_class/led2
  • 1
  • 2
  • 3
  • 4

模块退出函数led_init()分析

// 模块退出函数
static void __exit led_exit(void)
{
    int i;

    // 删除每个 LED 的设备文件
    for (i = 0; i < LED_COUNT; i++) {
        device_destroy(led_class, MKDEV(MAJOR(dev), MINOR(dev) + i));
    }

    // 删除类属性
    class_remove_file(led_class, &class_attr_led_all_control);

    // 销毁设备类
    class_destroy(led_class);

    // 删除 cdev
    cdev_del(&led_cdev);

    // 注销设备号
    unregister_chrdev_region(dev, LED_COUNT);

    printk(KERN_INFO "LED driver unloaded\n");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

关于这个函数声明行中关键字__exit的理解,可在对关键字“__init”的理解基础上理解(详情见 http://iyenn.com/rec/1709451.html 其实在这篇博文中也讲了对__exit的理解和作用)

关于退出函数,关键是要注意资源的释放顺序,顺序就是谁最后被创建,谁最后被销毁。

底层实现函数(具体操作硬件的函数)

下面这些函数都是具体操作硬件的函数

led_control
led_open
led_write
led_all_control_store
  • 1
  • 2
  • 3
  • 4

这里就不去理它们内部的逻辑了,只说下作用:
led_open就是打开设备的函数;
led_write就是单个LED设备的设备文件的写函数;
led_control是真正控制LED设备的函数,led_write会调用它;
led_all_control_store是设备类的写函数,它实现对这3个LED设备进行统一点亮或关闭。

驱动模块加载之后,怎么样利用设备类将3个LED设备统一关闭或点亮?

驱动模块加载之后,下面这个示例代码即可实现将3个LED设备统一关闭或点亮:

#include 
#include 
#include 
#include 

#define LED_CLASS_ATTR_PATH "/sys/class/led_class/led_all_control"

void control_leds(int status) {
    int fd;
    char buffer[16];

    // 打开类属性文件
    fd = open(LED_CLASS_ATTR_PATH, O_WRONLY);
    if (fd < 0) {
        perror("Failed to open LED class attribute file");
        exit(EXIT_FAILURE);
    }

    // 写入状态(0 或 1)
    snprintf(buffer, sizeof(buffer), "%d", status);
    if (write(fd, buffer, sizeof(buffer)) < 0) {
        perror("Failed to write to LED class attribute file");
        close(fd);
        exit(EXIT_FAILURE);
    }

    printf("Successfully set all LEDs to %s\n", status ? "ON" : "OFF");

    // 关闭文件
    close(fd);
}

int main() {
    // 点亮所有 LED
    control_leds(1);

    // 延时 2 秒
    sleep(2);

    // 关闭所有 LED
    control_leds(0);

    return 0;
}
  • 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

程序说明

  1. 路径定义: LED_CLASS_ATTR_PATH 定义了类属性文件路径,需与驱动生成的路径一致。
  2. 文件操作: 使用 open 打开类属性文件,使用 write 将状态值写入文件。
  3. LED 控制: 程序调用 control_leds 函数,参数为 1 表示点亮,0 表示关闭。

输出结果

运行程序后,可以观察到:

  • 所有 LED 点亮,延时 2 秒后关闭。
  • 控制状态在 /sys/class/led_class/led_all_control 中生效,同时会打印相应的日志信息。

注意事项

  1. 确保 /sys/class/led_class/led_all_control 文件存在。
  2. 如果遇到权限问题,可以手动修改类属性文件权限:
    chmod 666 /sys/class/led_class/led_all_control
    
    • 1
  3. 如果需要更复杂的控制逻辑,可以扩展 control_leds 函数以支持读取状态或处理错误。

从上面这个代码中我们可以看出,驱动模块加载后,咱们通过调用系统函数open()和write()对类属性文件就可以实现对具体设备的操作。

这里要特别注意:代码write(fd, buffer, sizeof(buffer))调用的底层写函数应该是代码中的函数led_all_control_store,而不是函数led_write,详细说明如下:

在示例代码中:

write(fd, buffer, sizeof(buffer));
  • 1

这个调用的确对应驱动中定义的类属性写函数 led_all_control_store,而不是 led_write。

原因分析

  1. 类属性文件:

    • /sys/class/led_class/led_all_control 是通过 class_create_file 创建的类属性文件。
    • 类属性文件操作(如读/写)由相应的 store 和 show 函数处理,在例子中是 led_all_control_store。
  2. 设备文件:

    • 每个 LED 设备对应一个设备文件 /dev/led0, /dev/led1, /dev/led2。
    • 对这些设备文件的读/写操作由 file_operations 中的函数(如 led_write)处理。

类属性文件与设备文件的区别

  • 类属性文件:

    • 作用于设备类级别,可以对同类设备进行统一管理。
    • 操作逻辑由 struct class_attribute 中的 store 和 show 函数实现。
    • 在例子中,对 /sys/class/led_class/led_all_control 的写入调用了 led_all_control_store。
  • 设备文件:

    • 作用于具体的设备实例,可以对单个设备进行操作。
    • 操作逻辑由 struct file_operations 中的函数(如 read、write)实现。
    • 在例子中,对 /dev/led0 的写入调用了 led_write。

代码中的调用关系

对类属性文件的写操作:
  • 调用流程:
    1. 用户态:write(fd, buffer, sizeof(buffer))
    2. 内核态:led_all_control_store
对设备文件的写操作:
  • 调用流程:
    1. 用户态:write(fd, buffer, sizeof(buffer))(设备文件,如 /dev/led0)
    2. 内核态:led_write

总结

  • 写类属性文件 /sys/class/led_class/led_all_control 时,调用的是 led_all_control_store。
  • 写设备文件 /dev/ledX 时,调用的是 led_write。
  • 类属性文件适用于统一管理,设备文件适用于单个设备操作。

类属性文件中存储着什么信息?

类属性文件是 Linux 内核中的一种机制,用于通过 /sys/class// 目录中的属性文件与设备类相关的信息交互。这些文件通常由内核模块定义,用户空间可以通过读写这些文件与内核模块通信。


类属性文件的内容和作用

1. 存储的信息

类属性文件存储的信息取决于驱动开发者定义的逻辑。常见的内容包括:

  • 设备状态(如 LED 是否打开)。
  • 设备的配置信息(如模式、频率等)。
  • 统计数据(如运行次数、错误计数等)。
  • 控制指令(如启动、停止设备)。

类属性文件的存储信息是动态的,由类属性文件的读写回调函数(show 和 store)定义,文件本身并没有固定内容。

2. 文件的读写方式
  • 读取类属性文件:通过 cat /sys/class//,调用 class_attribute 中定义的 show 回调函数获取信息。
  • 写入类属性文件:通过 echo "value" > /sys/class//,调用 store 回调函数处理写入数据。

类属性文件的实现过程

以 LED 驱动为例:

定义类属性文件
// store函数 - 用于写操作
static ssize_t led_all_control_store(struct class *cls, struct class_attribute *attr, const char *buf, size_t count)
{
    int status, i;

    // 解析用户输入
    if (kstrtoint(buf, 10, &status) || (status != 0 && status != 1))
        return -EINVAL;

    // 设置所有 LED 的状态
    for (i = 0; i < LED_COUNT; i++)
        led_control(i, status);

    return count; // 返回写入的字节数
}

// 定义类属性
CLASS_ATTR_WO(led_all_control);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
添加类属性
ret = class_create_file(led_class, &class_attr_led_all_control);
  • 1

此操作会在 /sys/class/led_class/ 目录下创建文件 led_all_control,绑定回调函数 led_all_control_store。


类属性文件的用途

示例 1:通过类属性控制所有 LED
# 关闭所有 LED
echo 0 > /sys/class/led_class/led_all_control

# 打开所有 LED
echo 1 > /sys/class/led_class/led_all_control
  • 1
  • 2
  • 3
  • 4
  • 5
示例 2:通过类属性获取状态信息

如果类属性文件定义了 show 回调函数,例如:

static ssize_t led_all_control_show(struct class *cls, struct class_attribute *attr, char *buf)
{
    int i, status;

    status = led_status[0]; // 假设所有 LED 状态一致
    for (i = 1; i < LED_COUNT; i++) {
        if (led_status[i] != status) {
            return sprintf(buf, "mixed\n");
        }
    }

    return sprintf(buf, "%s\n", status ? "on" : "off");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

可以通过以下命令获取状态:

cat /sys/class/led_class/led_all_control
  • 1

小结

  • 类属性文件的作用:提供一个简单的接口,让用户空间程序可以通过文件系统与设备类交互。
  • 存储内容:动态生成,由开发者在 show 和 store 回调函数中定义。
  • 使用场景:常用于统一控制或查询某类设备的状态和配置。
昊虹嵌入式技术交流群
QQ群名片
注:本文转载自blog.csdn.net的昊虹AI笔记的文章"https://blog.csdn.net/wenhao_ir/article/details/144901797"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top