sys文件系统介绍
sysfs
是 Linux 内核中的一种虚拟文件系统,它为用户空间和内核之间提供了一种统一的接口。通过 sysfs
,用户可以查看和修改内核对象的属性,比如设备、驱动程序和内核子系统的配置和状态。sysfs
通常挂载在 /sys
目录下。
设计思想
sysfs
的设计思想主要包括以下几个方面:
-
内核对象的可视化和组织:
sysfs
将内核中的对象(如设备、驱动、类等)以文件和目录的形式展现给用户。这些对象按照层次结构组织,类似于文件系统中的目录结构。这使得内核对象的关系和层次变得清晰易懂。
-
统一的接口和操作方式:
sysfs
提供了一种统一的方式来访问内核对象的属性。用户可以通过标准的文件操作(如read
,write
等)来查看和修改这些属性。每个属性通常对应一个文件,文件的内容即是该属性的值。
-
动态可配置性和可扩展性:
sysfs
支持动态添加和删除节点。这意味着内核模块可以在运行时通过sysfs
创建和删除文件或目录,以反映系统状态的变化。这种动态特性使sysfs
适用于热插拔设备等需要实时更新的场景。
-
轻量级和高效:
sysfs
是一个轻量级的文件系统,它没有存储数据的持久化特性,所有数据都驻留在内存中。这使得sysfs
操作非常高效,适合频繁访问和快速响应的需求。
-
安全性和访问控制:
sysfs
文件系统中的节点可以设置权限,控制不同用户和进程的访问。这有助于保护系统的关键数据和配置,防止未经授权的访问或修改。
应用和功能
sysfs
主要用于以下几个方面:
-
设备管理:
sysfs
通过/sys/class
,/sys/bus
,/sys/devices
等目录组织系统中的所有设备和设备驱动,用户可以查看设备的状态、属性,并可以通过写入相应的文件来控制设备。
-
驱动管理:
- 驱动程序可以通过
sysfs
暴露其支持的设备类型和属性,这样用户和其他系统组件可以通过读取sysfs
节点来获取驱动程序的信息。
- 驱动程序可以通过
-
内核子系统配置:
- 一些内核子系统(如电源管理、内存管理等)提供了
sysfs
接口,允许用户调整相关配置或获取状态信息。例如,通过/sys/power
目录,可以管理系统的电源状态。
- 一些内核子系统(如电源管理、内存管理等)提供了
sysfs
的设计和实现大大增强了内核与用户空间的交互能力,使得系统管理和设备控制变得更加直观和灵活。
udev介绍
udev
是 Linux 系统中的一个设备管理工具和守护进程,负责在用户空间管理设备节点。它是设备管理框架的一部分,用于响应系统中的设备事件,并在 /dev
目录中创建和删除设备节点。udev
是 Linux 系统中处理设备管理的重要组件。它提供了一种灵活而强大的方式来响应和管理设备事件,确保系统中的设备节点和权限设置是动态更新和适当配置的。对于系统管理员和开发者来说,理解和利用 udev
可以大大简化设备管理的工作。
主要功能
-
设备节点管理:
udev
动态地在/dev
目录下创建和删除设备节点,这些节点表示系统中的硬件设备。它根据系统中的设备出现或消失的情况更新设备节点。
-
设备事件处理:
- 当新的硬件设备插入或移除时,内核会生成相应的事件。
udev
监听这些事件,并根据配置文件中的规则执行相应的操作,如创建设备节点、设置设备权限、加载固件等。
- 当新的硬件设备插入或移除时,内核会生成相应的事件。
-
设备命名:
udev
允许管理员通过规则文件对设备节点进行命名。例如,可以根据设备的类型、属性、序列号等为设备节点分配有意义的名称,这样在管理系统设备时更容易识别和区分设备。
-
权限设置:
udev
规则文件可以指定设备节点的权限和所有者。这对于多用户系统非常重要,可以控制哪些用户或组可以访问特定的设备。
-
自动化任务:
udev
可以在设备事件发生时触发脚本或程序。例如,当插入一个USB设备时,可以自动挂载它,或者当插入一个网络接口时,自动配置网络。
工作原理
-
内核事件:
- 当一个设备插入或移除时,内核会通过
netlink
接口通知udev
。这些事件包括设备的添加、移除、变化等。
- 当一个设备插入或移除时,内核会通过
-
规则匹配:
udev
通过配置的规则文件(通常位于/etc/udev/rules.d/
)对事件进行匹配。规则文件指定了当匹配到特定设备时应该采取的操作。
-
执行操作:
- 根据匹配的规则,
udev
执行相应的操作,如创建设备节点、设置权限、运行脚本等。
- 根据匹配的规则,
使用 udevadm
工具
udevadm
是 udev
提供的命令行工具,用于管理和调试 udev
。它可以用于触发设备事件、监视设备事件、查看设备信息等。例如:
-
查看当前的
udev
规则:udevadm info --query=all --name=/dev/sda
-
监视设备事件:
udevadm monitor
设备文件创建流程
驱动程序的注册
-
定义和实现操作函数:
- 驱动程序必须定义一个
file_operations
结构体,包含设备操作函数的指针,如open
、release
、read
、write
等。这些函数定义了如何处理设备的各种操作请求。
static const struct file_operations my_fops = { .open = my_open, .release = my_release, .read = my_read, .write = my_write, };
- 驱动程序必须定义一个
-
注册字符设备:
- 驱动程序使用
register_chrdev
(或类似的函数)注册设备号,并将其与file_operations
结构体相关联。这样,内核知道如何处理对该设备的操作。
int major = register_chrdev(0, "my_device", &my_fops);
这里,
register_chrdev
返回主设备号,my_device
是设备名称,my_fops
是操作函数集合。 - 驱动程序使用
-
创建设备节点:
- 驱动程序可以使用
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的作用