字符设备驱动基础—sys文件系统,udev介绍,驱动模块在内核空间注册设备

sys文件系统介绍

sysfs 是 Linux 内核中的一种虚拟文件系统,它为用户空间和内核之间提供了一种统一的接口。通过 sysfs,用户可以查看和修改内核对象的属性,比如设备、驱动程序和内核子系统的配置和状态。sysfs 通常挂载在 /sys 目录下。

设计思想

sysfs 的设计思想主要包括以下几个方面:

  1. 内核对象的可视化和组织

    • sysfs 将内核中的对象(如设备、驱动、类等)以文件和目录的形式展现给用户。这些对象按照层次结构组织,类似于文件系统中的目录结构。这使得内核对象的关系和层次变得清晰易懂。
  2. 统一的接口和操作方式

    • sysfs 提供了一种统一的方式来访问内核对象的属性。用户可以通过标准的文件操作(如 read, write 等)来查看和修改这些属性。每个属性通常对应一个文件,文件的内容即是该属性的值。
  3. 动态可配置性和可扩展性

    • sysfs 支持动态添加和删除节点。这意味着内核模块可以在运行时通过 sysfs 创建和删除文件或目录,以反映系统状态的变化。这种动态特性使 sysfs 适用于热插拔设备等需要实时更新的场景。
  4. 轻量级和高效

    • sysfs 是一个轻量级的文件系统,它没有存储数据的持久化特性,所有数据都驻留在内存中。这使得 sysfs 操作非常高效,适合频繁访问和快速响应的需求。
  5. 安全性和访问控制

    • sysfs 文件系统中的节点可以设置权限,控制不同用户和进程的访问。这有助于保护系统的关键数据和配置,防止未经授权的访问或修改。

应用和功能

sysfs 主要用于以下几个方面:

  1. 设备管理

    • sysfs 通过 /sys/class, /sys/bus, /sys/devices 等目录组织系统中的所有设备和设备驱动,用户可以查看设备的状态、属性,并可以通过写入相应的文件来控制设备。
  2. 驱动管理

    • 驱动程序可以通过 sysfs 暴露其支持的设备类型和属性,这样用户和其他系统组件可以通过读取 sysfs 节点来获取驱动程序的信息。
  3. 内核子系统配置

    • 一些内核子系统(如电源管理、内存管理等)提供了 sysfs 接口,允许用户调整相关配置或获取状态信息。例如,通过 /sys/power 目录,可以管理系统的电源状态。

sysfs 的设计和实现大大增强了内核与用户空间的交互能力,使得系统管理和设备控制变得更加直观和灵活。

udev介绍

udev 是 Linux 系统中的一个设备管理工具和守护进程,负责在用户空间管理设备节点。它是设备管理框架的一部分,用于响应系统中的设备事件,并在 /dev 目录中创建和删除设备节点。udev 是 Linux 系统中处理设备管理的重要组件。它提供了一种灵活而强大的方式来响应和管理设备事件,确保系统中的设备节点和权限设置是动态更新和适当配置的。对于系统管理员和开发者来说,理解和利用 udev 可以大大简化设备管理的工作。

主要功能

  1. 设备节点管理

    • udev 动态地在 /dev 目录下创建和删除设备节点,这些节点表示系统中的硬件设备。它根据系统中的设备出现或消失的情况更新设备节点。
  2. 设备事件处理

    • 当新的硬件设备插入或移除时,内核会生成相应的事件。udev 监听这些事件,并根据配置文件中的规则执行相应的操作,如创建设备节点、设置设备权限、加载固件等。
  3. 设备命名

    • udev 允许管理员通过规则文件对设备节点进行命名。例如,可以根据设备的类型、属性、序列号等为设备节点分配有意义的名称,这样在管理系统设备时更容易识别和区分设备。
  4. 权限设置

    • udev 规则文件可以指定设备节点的权限和所有者。这对于多用户系统非常重要,可以控制哪些用户或组可以访问特定的设备。
  5. 自动化任务

    • udev 可以在设备事件发生时触发脚本或程序。例如,当插入一个USB设备时,可以自动挂载它,或者当插入一个网络接口时,自动配置网络。

工作原理

  1. 内核事件

    • 当一个设备插入或移除时,内核会通过 netlink 接口通知 udev。这些事件包括设备的添加、移除、变化等。
  2. 规则匹配

    • udev 通过配置的规则文件(通常位于 /etc/udev/rules.d/)对事件进行匹配。规则文件指定了当匹配到特定设备时应该采取的操作。
  3. 执行操作

    • 根据匹配的规则,udev 执行相应的操作,如创建设备节点、设置权限、运行脚本等。

使用 udevadm 工具

udevadmudev 提供的命令行工具,用于管理和调试 udev。它可以用于触发设备事件、监视设备事件、查看设备信息等。例如:

  • 查看当前的 udev 规则:

    udevadm info --query=all --name=/dev/sda
    
  • 监视设备事件:

    udevadm monitor
    

设备文件创建流程

驱动程序的注册

  1. 定义和实现操作函数

    • 驱动程序必须定义一个 file_operations 结构体,包含设备操作函数的指针,如 openreleasereadwrite 等。这些函数定义了如何处理设备的各种操作请求。
    static const struct file_operations my_fops = {
        .open = my_open,
        .release = my_release,
        .read = my_read,
        .write = my_write,
    };
    
  2. 注册字符设备

    • 驱动程序使用 register_chrdev(或类似的函数)注册设备号,并将其与 file_operations 结构体相关联。这样,内核知道如何处理对该设备的操作。
    int major = register_chrdev(0, "my_device", &my_fops);
    

    这里,register_chrdev 返回主设备号,my_device 是设备名称,my_fops 是操作函数集合。

  3. 创建设备节点

    • 驱动程序可以使用 device_create 函数创建设备节点,通常在用户空间使用 udev 来自动创建和管理设备节点。
    struct class *my_class;
    struct device *my_device;
    
    my_class = class_create(THIS_MODULE, "my_class");
    my_device = device_create(my_class, NULL, MKDEV(major, 0), NULL, "my_device");
    

device_create函数详解

当驱动程序调用 device_create 时,它实际上是在内核中注册了一个新的设备对象。这个设备对象包含了设备的相关信息,包括设备名称、设备号、设备类等。

struct device *device_create(struct class *cls, struct device *parent, dev_t devt,
                             void *drvdata, const char *fmt, ...);

这里,cls 是设备的类结构体指针,devt 是设备号,fmt 是格式化字符串,用于生成设备名称。

内核在成功创建设备对象后,会生成一个 uevent 事件。这是一个内核通知事件,用于告知用户空间有新的设备注册或现有设备状态发生变化。这个事件包含了设备的属性和相关信息。

udev 守护进程监听这些 uevent 事件。每当内核发出这样的事件时,udev 会根据事件信息和系统中定义的 udev 规则文件,决定如何处理该事件。

udev 规则文件通常位于 /etc/udev/rules.d//lib/udev/rules.d/ 目录下,这些规则定义了如何为不同类型的设备创建设备节点、设置权限、指定设备文件名称等。

根据 udev 规则和 uevent 中的信息,udev/dev 目录下创建相应的设备文件(设备节点)。这些设备文件允许用户空间的应用程序与该设备进行交互。

除了创建设备文件外,udev 还可以根据规则设置设备文件的权限、所有者和其他属性。这确保了设备文件的安全性和可访问性。

示例代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>

#define DEVICE_NAME "my_char_device"

static int major_number;
static struct class *my_class = NULL;
static struct device *my_device = NULL;
static struct cdev mydev;  // 声明 cdev 结构体

static int my_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "my_char_device: open()\n");
    return 0;
}

static int my_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "my_char_device: release()\n");
    return 0;
}

static ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    printk(KERN_INFO "my_char_device: read()\n");
    return 0;
}

static ssize_t my_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    printk(KERN_INFO "my_char_device: write()\n");
    return count;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = my_open,
    .release = my_release,
    .read = my_read,
    .write = my_write,
};

static int __init test_init(void)
{
    int retval;
    dev_t dev;

    printk(KERN_INFO "module init success\n");

    // 1. 动态分配主次设备号
    retval = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);
    if (retval < 0)
    {
        printk(KERN_ERR "Failed to allocate major number\n");
        goto fail_alloc_chrdev_region;
    }

    major_number = MAJOR(dev);
    printk(KERN_INFO "major number is: %d, minor number is: %d\n", major_number, MINOR(dev));

    // 2. 初始化 cdev 结构体并添加到内核
    cdev_init(&mydev, &fops);
    retval = cdev_add(&mydev, dev, 1);
    if (retval < 0)
    {
        printk(KERN_ERR "Failed to add cdev\n");
        goto fail_cdev_add;
    }

    // 3. 创建设备类
    my_class = class_create(THIS_MODULE, "my_class");
    if (IS_ERR(my_class))
    {
        printk(KERN_ERR "Failed to create class\n");
        retval = PTR_ERR(my_class);
        goto fail_class_create;
    }

    // 4.  申请设备,内核空间就会通知用户空间的udev 进行创建设备,驱动程序本身自己是创建不了文件的!
    my_device = device_create(my_class, NULL, dev, NULL, DEVICE_NAME);
    if (IS_ERR(my_device))
    {
        printk(KERN_ERR "Failed to create device\n");
        retval = PTR_ERR(my_device);
        goto fail_device_create;
    }

    printk(KERN_INFO "my_char_device: module loaded\n");
    return 0;



fail_device_create:
    class_destroy(my_class);
fail_class_create:
    cdev_del(&mydev);
fail_cdev_add:
    unregister_chrdev_region(dev, 1);
fail_alloc_chrdev_region:
    return retval;
}

static void __exit test_exit(void)
{
    dev_t dev = MKDEV(major_number, 0);
    if (my_device)
        device_destroy(my_class, dev);
    if (my_class)
        class_destroy(my_class);
    cdev_del(&mydev);
    unregister_chrdev_region(dev, 1);
    printk(KERN_INFO "my_char_device: module unloaded\n");
}

module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");

效果图

当insmod 模块之后,在/dev目录下就可以查看由udev生成的设备文件了

在这里插入图片描述

如果使用cat 来查看设备文件,此时设备的文件操作结构体file_operations也会被触发,例如:

cat /dev/my_char_device 

在这里插入图片描述

cat 本质上就是读操作,相当于读取驱动程序,因此,Open read 会被触发,当读取完毕之后,就触发release操作

如果调用下方这个命令

echo 1 > /dev/my_char_device 

echo本质上就是写操作,相当于往驱动程序写值, 此时 open write会被触发,当写入完毕之后,就会触发release操作

在这里插入图片描述

当然,本次代码没有实现具体的读写逻辑,只是展示了cat 和echo的作用

  • 29
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Trump. yang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值