Linux驱动sysfs接口示例源码解析

Linux驱动sysfs接口示例源码解析

1. 概述

本文档提供了一个Linux内核驱动程序中sysfs接口的实现示例解析。这个驱动创建了一个简单的字符串读写接口,可以通过sysfs文件系统进行交互。该实现遵循Linux内核驱动模型,使用class机制在/sys/class/目录下创建节点,展示了如何正确实现和使用sysfs属性。

本文包含Makefile(Ubuntu系统环境下测试通过),用于编译、安装和管理驱动模块。

2. 源码解析

2.1 头文件包含

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/sysfs.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/typecheck.h>

这些头文件提供了内核模块开发所需的基本功能:

  • linux/module.h:模块编程的基本定义
  • linux/kernel.h:内核核心函数和宏定义
  • linux/init.h:模块初始化和退出相关函数
  • linux/device.h:设备模型相关函数和结构体
  • linux/kdev_t.h:设备号相关操作
  • linux/cdev.h:字符设备驱动相关函数
  • linux/sysfs.h:sysfs接口相关函数和结构体
  • linux/err.h:错误处理相关函数
  • linux/slab.h:内核内存分配相关函数
  • linux/uaccess.h:用户空间与内核空间数据交换函数
  • linux/typecheck.h:类型检查相关宏

2.2 宏定义与常量

#define DRIVER_SYS_CLASS "drv_test"
# 定义驱动名称、作者、描述和许可证
#define DRIVER_NAME "sysfs_test"
#define DRIVER_AUTHOR "Embedded Linux Strategist"
#define DRIVER_DESC "A sysfs interface for string and uint32 read/write"
#define DRIVER_LICENSE "GPL"

// 定义字符串缓冲区大小
#define MAX_STRING_SIZE 100

这些宏定义了驱动的基本信息和参数:

  • DRIVER_SYS_CLASS:指定sysfs类的名称,对应/sys/class/下的目录名
  • DRIVER_NAME:驱动程序的名称,用于日志输出和设备创建
  • MAX_STRING_SIZE:定义字符串缓冲区的最大大小,防止缓冲区溢出

2.3 数据结构定义

// 定义一个简单的设备结构体
typedef struct {
    char test_string[MAX_STRING_SIZE];  // 存储字符串属性值
    uint32_t test_uint32;               // 存储uint32属性值
    struct device *dev;
} sysfs_test_dev;

// 创建设备实例
sysfs_test_dev *g_dev;

// 声明类和设备号
static struct class *sysfs_test_class;
static dev_t dev_num;
static struct cdev cdev;

这些结构体和变量定义了驱动的核心组件:

  • sysfs_test_dev:设备结构体,包含要在sysfs中操作的字符串缓冲区和设备指针
  • g_dev:指向设备实例的全局指针
  • sysfs_test_class:设备类结构体,用于在/sys/class/下创建目录
  • dev_num:设备号,由内核分配
  • cdev:字符设备结构体,用于注册字符设备

2.4 sysfs属性操作函数

2.4.1 字符串属性读操作函数
// 定义字符串属性的show函数(读操作)
static ssize_t str_show(struct device *dev, struct device_attribute *attr, 
                       char *buf)
{
    sysfs_test_dev *sys_dev = dev_get_drvdata(dev);
    return sprintf(buf, "%s\n", sys_dev->test_string);
}

str_show函数实现了字符串类型sysfs属性的读操作:

  • 通过dev_get_drvdata获取与设备关联的私有数据结构
  • 使用sprintf将字符串内容复制到提供的缓冲区中,并添加换行符
  • 返回写入缓冲区的字节数
2.4.2 字符串属性写操作函数
// 定义字符串属性的store函数(写操作)
static ssize_t str_store(struct device *dev, struct device_attribute *attr,
                        const char *buf, size_t count)
{
    sysfs_test_dev *sys_dev = dev_get_drvdata(dev);
    
    // 确保不会超出缓冲区大小
    if (count >= MAX_STRING_SIZE) {
        pr_err("String too long! Maximum size is %d\n", MAX_STRING_SIZE - 1);
        return -EINVAL;
    }
    
    // 复制字符串到设备结构体
    strncpy(sys_dev->test_string, buf, count);
    
    // 移除可能的换行符
    if (sys_dev->test_string[count - 1] == '\n')
        sys_dev->test_string[count - 1] = '\0';
    else
        sys_dev->test_string[count] = '\0';
    
    pr_info("Received string: %s\n", sys_dev->test_string);
    return count;
}

str_store函数实现了字符串类型sysfs属性的写操作:

  • 进行边界检查,防止缓冲区溢出
  • 使用strncpy安全地将用户输入复制到设备结构体的字符串缓冲区
  • 处理字符串结尾,确保正确的null终止
  • 记录接收到的字符串到内核日志
  • 返回成功写入的字节数
2.4.3 整数属性读操作函数
// 定义uint32属性的show函数(读操作)
static ssize_t uint32_show(struct device *dev, struct device_attribute *attr,
                          char *buf)
{
    sysfs_test_dev *sys_dev = dev_get_drvdata(dev);
    return sprintf(buf, "%u\n", sys_dev->test_uint32);
}

uint32_show函数实现了32位无符号整数类型sysfs属性的读操作:

  • 通过dev_get_drvdata获取与设备关联的私有数据结构
  • 使用sprintf将整数格式化为字符串并复制到提供的缓冲区
  • 返回写入缓冲区的字节数
2.4.4 整数属性写操作函数
// 定义uint32属性的store函数(写操作)
static ssize_t uint32_store(struct device *dev, struct device_attribute *attr,
                           const char *buf, size_t count)
{
    sysfs_test_dev *sys_dev = dev_get_drvdata(dev);
    unsigned int value;
    
    // 将字符串转换为无符号整数
    if (kstrtouint(buf, 10, &value)) {
        pr_err("Invalid uint32 value\n");
        return -EINVAL;
    }
    
    sys_dev->test_uint32 = (uint32_t)value;
    pr_info("Received uint32: %u\n", sys_dev->test_uint32);
    
    return count;
}

uint32_store函数实现了32位无符号整数类型sysfs属性的写操作:

  • 使用kstrtoul安全地将用户输入的字符串转换为无符号长整型
  • 将结果转换为uint32_t类型并存储到设备结构体中
  • 记录接收到的整数值到内核日志
  • 如有转换错误则返回错误码,否则返回成功写入的字节数

2.5 属性定义和注册

// 使用DEVICE_ATTR宏定义字符串类型sysfs属性
// 参数: 名称, 权限, show函数, store函数
static DEVICE_ATTR(str, 0664, str_show, str_store);

// 使用DEVICE_ATTR宏定义uint32类型sysfs属性
static DEVICE_ATTR(uint32, 0664, uint32_show, uint32_store);

// 定义属性数组,用于一次性注册所有属性
static struct attribute *dev_attrs[] = {
    &dev_attr_str.attr,
    &dev_attr_uint32.attr,
    NULL,  // 必须以NULL结尾
};

// 定义属性组
static struct attribute_group dev_attr_group = {
    .attrs = dev_attrs,
};

这段代码定义了sysfs属性和属性组:

  • DEVICE_ATTR宏创建了两个设备属性:struint32,权限均为0664(所有者和组可读可写,其他用户只读)
  • dev_attrs数组包含所有要注册的属性指针,以NULL结尾
  • dev_attr_group定义了一个属性组,包含上述属性数组

2.6 文件操作结构体

// 定义设备文件操作
static struct file_operations fops = {
    .owner = THIS_MODULE,
};

这个简单的file_operations结构体只设置了.owner字段,将其设置为当前模块指针,确保在模块使用时不会被卸载。由于该驱动主要通过sysfs接口进行交互,文件操作结构体可以非常简单。

2.7 模块初始化函数

// 模块初始化函数
static int __init sysfs_test_init(void)
{
    int ret = 0;
    
    // 分配内存给设备结构体
    g_dev = kzalloc(sizeof(sysfs_test_dev), GFP_KERNEL);
    if (!g_dev) {
        pr_err("Failed to allocate memory for device structure\n");
        return -ENOMEM;
    }
    
    // 初始化默认值
    strcpy(g_dev->test_string, "Hello, sysfs!");
    g_dev->test_uint32 = 100;  // 设置默认uint32值
    
    // 分配设备号
    ret = alloc_chrdev_region(&dev_num, 0, 1, DRIVER_NAME);
    if (ret < 0) {
        pr_err("Failed to allocate device number\n");
        kfree(g_dev);
        return ret;
    }
    
    // 初始化cdev
    cdev_init(&cdev, &fops);
    cdev.owner = THIS_MODULE;
    
    // 添加cdev到内核
    ret = cdev_add(&cdev, dev_num, 1);
    if (ret < 0) {
        pr_err("Failed to add cdev\n");
        unregister_chrdev_region(dev_num, 1);
        kfree(g_dev);
        return ret;
    }
    
    // 创建类,这样会在/sys/class/下创建目录
    sysfs_test_class = class_create(THIS_MODULE, DRIVER_SYS_CLASS);
    if (IS_ERR(sysfs_test_class)) {
        pr_err("Failed to create class\n");
        cdev_del(&cdev);
        unregister_chrdev_region(dev_num, 1);
        kfree(g_dev);
        return PTR_ERR(sysfs_test_class);
    }
    
    // 创建设备,这样会在/sys/class/drv_test/下创建设备目录
    g_dev->dev = device_create(sysfs_test_class, NULL, dev_num, g_dev, DRIVER_NAME);
    if (IS_ERR(g_dev->dev)) {
        pr_err("Failed to create device\n");
        class_destroy(sysfs_test_class);
        cdev_del(&cdev);
        unregister_chrdev_region(dev_num, 1);
        kfree(g_dev);
        return PTR_ERR(g_dev->dev);
    }
    
    // 在设备目录下创建属性文件
    ret = sysfs_create_group(&g_dev->dev->kobj, &dev_attr_group);
    if (ret) {
        pr_err("Failed to create sysfs attributes\n");
        device_destroy(sysfs_test_class, dev_num);
        class_destroy(sysfs_test_class);
        cdev_del(&cdev);
        unregister_chrdev_region(dev_num, 1);
        kfree(g_dev);
        return ret;
    }
    
    pr_info("%s driver initialized. Test string: %s, Test uint32: %u\n", 
            DRIVER_NAME, g_dev->test_string, g_dev->test_uint32);
    pr_info("sysfs files created at: /sys/class/%s/%s/str and /sys/class/%s/%s/uint32\n", 
            DRIVER_SYS_CLASS, DRIVER_NAME, DRIVER_SYS_CLASS, DRIVER_NAME);
    
    return 0;
}

sysfs_test_init函数是模块加载时执行的初始化函数,主要完成以下步骤:

  1. 为设备结构分配内存
  2. 初始化默认字符串
  3. 动态分配字符设备号
  4. 初始化字符设备结构体并注册
  5. 创建sysfs类,在/sys/class/下创建目录
  6. 创建设备,在/sys/class/drv_test/下创建设备目录
  7. 将设备结构体指针作为私有数据关联到设备上
  8. 在设备目录下创建sysfs属性文件
  9. 输出初始化成功的日志信息

每个步骤都包含错误处理,确保在出现问题时能够正确清理已分配的资源,避免资源泄漏。

2.8 模块退出函数

// 模块退出函数
static void __exit sysfs_test_exit(void)
{
    // 移除sysfs属性组
    if (g_dev && g_dev->dev) {
        sysfs_remove_group(&g_dev->dev->kobj, &dev_attr_group);
        
        // 销毁设备
        device_destroy(sysfs_test_class, dev_num);
    }
    
    // 销毁类
    if (sysfs_test_class) {
        class_destroy(sysfs_test_class);
    }
    
    // 删除cdev
    cdev_del(&cdev);
    
    // 注销设备号
    unregister_chrdev_region(dev_num, 1);
    
    // 释放设备结构体内存
    kfree(g_dev);
    
    pr_info("%s driver exited\n", DRIVER_NAME);
}

sysfs_test_exit函数是模块卸载时执行的清理函数,按照与初始化相反的顺序释放资源:

  1. 移除sysfs属性组
  2. 销毁设备
  3. 销毁类
  4. 删除字符设备
  5. 注销设备号
  6. 释放设备结构体内存
  7. 输出退出日志

2.9 模块注册和描述信息

// 注册模块入口和出口函数
module_init(sysfs_test_init);
module_exit(sysfs_test_exit);

// 模块描述信息
MODULE_LICENSE(DRIVER_LICENSE);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_VERSION("1.0");

这段代码完成模块的注册和元数据设置:

  • module_initmodule_exit宏注册模块的入口和出口函数
  • MODULE_LICENSE设置模块许可证,必须为GPL兼容许可证
  • 其他宏设置作者、描述和版本信息

3. Makefile解析

# Makefile for sysfs_test kernel module

# 设置模块名称
MODULE_NAME := sysfs_interface

# 设置源文件
obj-m += $(MODULE_NAME).o

# 设置内核源码目录
# 如果未指定KERNELDIR,则默认使用当前运行内核的源码
KERNELDIR ?= /lib/modules/$(shell uname -r)/build

# 当前目录
PWD := $(shell pwd)

# 编译目标
all:
	@echo "Building $(MODULE_NAME) kernel module..."
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
	@echo "Build completed. Run 'sudo insmod $(MODULE_NAME).ko' to load the module."

# 清理目标
clean:
	@echo "Cleaning up..."
	$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

# 安装模块
install:
	sudo insmod $(MODULE_NAME).ko

# 卸载模块
uninstall:
	sudo rmmod $(MODULE_NAME)

# 查看模块信息
info:
	modinfo $(MODULE_NAME).ko

# 查看内核日志
log:
	sudo dmesg | tail -20

这个Makefile提供了编译和管理内核模块的便捷目标:

  • MODULE_NAME:设置模块名称为sysfs_interface
  • obj-m:告诉内核构建系统要构建一个可加载模块
  • KERNELDIR:指定内核源码目录,默认为当前运行内核的构建目录
  • all:主要构建目标,调用内核Makefile构建模块
  • clean:清理构建生成的文件
  • install:加载模块到内核
  • uninstall:从内核中卸载模块
  • info:显示模块信息,如版本、作者等
  • log:显示最近20行内核日志

4. 编译和使用

4.1 编译驱动

在包含源代码和Makefile的目录中执行以下命令:

make

这将编译生成sysfs_interface.ko文件。

4.2 加载驱动

使用以下命令加载驱动:

sudo insmod sysfs_interface.ko

或者使用Makefile的快捷目标:

make install

4.3 查看sysfs属性

加载驱动后,可以使用以下命令查看和测试sysfs属性:

# 读取字符串属性值
cat /sys/class/drv_test/sysfs_test/str

# 写入新的字符串值
echo "New string value" | sudo tee /sys/class/drv_test/sysfs_test/str

# 再次读取验证字符串值
cat /sys/class/drv_test/sysfs_test/str

# 读取uint32属性值
cat /sys/class/drv_test/sysfs_test/uint32

# 写入新的uint32值
echo "123" | sudo tee /sys/class/drv_test/sysfs_test/uint32

# 再次读取验证uint32值
cat /sys/class/drv_test/sysfs_test/uint32

4.4 技术说明

该驱动程序通过Linux内核的设备模型框架实现sysfs接口:

  1. 使用DEVICE_ATTR宏创建了两个sysfs属性文件:
    • str - 字符串类型属性,用于文本数据交换
    • uint32 - 32位无符号整数类型属性,用于数值数据交换
  2. 实现了对应的show和store函数来处理不同数据类型的读写操作:
    • 字符串:str_showstr_store
    • 整数:uint32_showuint32_store
  3. 使用属性组机制(attribute_group)管理sysfs属性集合
  4. 通过device_create创建设备,然后使用sysfs_create_group添加属性组
  5. 在创建设备时通过device_create的参数在设备结构中存储私有数据

4.5 查看内核日志

使用以下命令查看驱动输出的日志信息:

sudo dmesg | tail

或者使用Makefile的快捷目标:

make log

4.5 卸载驱动

使用以下命令卸载驱动:

sudo rmmod sysfs_interface

或者使用Makefile的快捷目标:

make uninstall

5. 完整源码

5.1 sysfs_interface.c完整源码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/sysfs.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/typecheck.h>

#define DRIVER_SYS_CLASS "drv_test"
#define DRIVER_NAME "sysfs_test"
#define DRIVER_AUTHOR "Embedded Linux Strategist"
#define DRIVER_DESC "A sysfs interface for string and uint32 read/write"
#define DRIVER_LICENSE "GPL"

// 定义字符串缓冲区大小
#define MAX_STRING_SIZE 100

// 定义一个简单的设备结构体
typedef struct {
    char test_string[MAX_STRING_SIZE];
    uint32_t test_uint32;  // 新增uint32类型属性
    struct device *dev;
} sysfs_test_dev;

// 创建设备实例
sysfs_test_dev *g_dev;

// 声明类和设备号
static struct class *sysfs_test_class;
static dev_t dev_num;
static struct cdev cdev;

// 定义字符串属性的show函数(读操作)
static ssize_t str_show(struct device *dev, struct device_attribute *attr, 
                       char *buf)
{
    sysfs_test_dev *sys_dev = dev_get_drvdata(dev);
    return sprintf(buf, "%s\n", sys_dev->test_string);
}

// 定义字符串属性的store函数(写操作)
static ssize_t str_store(struct device *dev, struct device_attribute *attr,
                        const char *buf, size_t count)
{
    sysfs_test_dev *sys_dev = dev_get_drvdata(dev);
    
    // 确保不会超出缓冲区大小
    if (count >= MAX_STRING_SIZE) {
        pr_err("String too long! Maximum size is %d\n", MAX_STRING_SIZE - 1);
        return -EINVAL;
    }
    
    // 复制字符串到设备结构体
    strncpy(sys_dev->test_string, buf, count);
    
    // 移除可能的换行符
    if (sys_dev->test_string[count - 1] == '\n')
        sys_dev->test_string[count - 1] = '\0';
    else
        sys_dev->test_string[count] = '\0';
    
    pr_info("Received string: %s\n", sys_dev->test_string);
    return count;
}

// 定义uint32属性的show函数(读操作)
static ssize_t uint32_show(struct device *dev, struct device_attribute *attr,
                          char *buf)
{
    sysfs_test_dev *sys_dev = dev_get_drvdata(dev);
    return sprintf(buf, "%u\n", sys_dev->test_uint32);
}

// 定义uint32属性的store函数(写操作)
static ssize_t uint32_store(struct device *dev, struct device_attribute *attr,
                           const char *buf, size_t count)
{
    sysfs_test_dev *sys_dev = dev_get_drvdata(dev);
    unsigned int value;
    
    // 将字符串转换为无符号整数
    if (kstrtouint(buf, 10, &value)) {
        pr_err("Invalid uint32 value\n");
        return -EINVAL;
    }
    
    sys_dev->test_uint32 = (uint32_t)value;
    pr_info("Received uint32: %u\n", sys_dev->test_uint32);
    
    return count;
}

// 定义设备属性
static DEVICE_ATTR(str, 0664, str_show, str_store);
static DEVICE_ATTR(uint32, 0664, uint32_show, uint32_store);

// 定义属性数组,用于一次性注册所有属性
static struct attribute *dev_attrs[] = {
    &dev_attr_str.attr,
    &dev_attr_uint32.attr,
    NULL,
};

// 定义属性组
static struct attribute_group dev_attr_group = {
    .attrs = dev_attrs,
};

// 定义设备文件操作
static struct file_operations fops = {
    .owner = THIS_MODULE,
};

// 模块初始化函数
static int __init sysfs_test_init(void)
{
    int ret = 0;
    
    // 分配内存给设备结构体
    g_dev = kzalloc(sizeof(sysfs_test_dev), GFP_KERNEL);
    if (!g_dev) {
        pr_err("Failed to allocate memory for device structure\n");
        return -ENOMEM;
    }
    
    // 初始化默认值
    strcpy(g_dev->test_string, "Hello, sysfs!");
    g_dev->test_uint32 = 100;  // 设置默认uint32值
    
    // 分配设备号
    ret = alloc_chrdev_region(&dev_num, 0, 1, DRIVER_NAME);
    if (ret < 0) {
        pr_err("Failed to allocate device number\n");
        kfree(g_dev);
        return ret;
    }
    
    // 初始化cdev
    cdev_init(&cdev, &fops);
    cdev.owner = THIS_MODULE;
    
    // 添加cdev到内核
    ret = cdev_add(&cdev, dev_num, 1);
    if (ret < 0) {
        pr_err("Failed to add cdev\n");
        unregister_chrdev_region(dev_num, 1);
        kfree(g_dev);
        return ret;
    }
    
    // 创建类,这样会在/sys/class/下创建目录
    sysfs_test_class = class_create(THIS_MODULE, DRIVER_SYS_CLASS);
    if (IS_ERR(sysfs_test_class)) {
        pr_err("Failed to create class\n");
        cdev_del(&cdev);
        unregister_chrdev_region(dev_num, 1);
        kfree(g_dev);
        return PTR_ERR(sysfs_test_class);
    }
    
    // 创建设备,这样会在/sys/class/drv_test/下创建设备目录
    g_dev->dev = device_create(sysfs_test_class, NULL, dev_num, g_dev, DRIVER_NAME);
    if (IS_ERR(g_dev->dev)) {
        pr_err("Failed to create device\n");
        class_destroy(sysfs_test_class);
        cdev_del(&cdev);
        unregister_chrdev_region(dev_num, 1);
        kfree(g_dev);
        return PTR_ERR(g_dev->dev);
    }
    
    // 在设备目录下创建属性文件
    ret = sysfs_create_group(&g_dev->dev->kobj, &dev_attr_group);
    if (ret) {
        pr_err("Failed to create sysfs attributes\n");
        device_destroy(sysfs_test_class, dev_num);
        class_destroy(sysfs_test_class);
        cdev_del(&cdev);
        unregister_chrdev_region(dev_num, 1);
        kfree(g_dev);
        return ret;
    }
    
    pr_info("%s driver initialized. Test string: %s, Test uint32: %u\n", 
            DRIVER_NAME, g_dev->test_string, g_dev->test_uint32);
    pr_info("sysfs files created at: /sys/class/%s/%s/str and /sys/class/%s/%s/uint32\n", 
            DRIVER_SYS_CLASS, DRIVER_NAME, DRIVER_SYS_CLASS, DRIVER_NAME);
    
    return 0;
}

// 模块退出函数
static void __exit sysfs_test_exit(void)
{
    // 移除sysfs属性组
    if (g_dev && g_dev->dev) {
        sysfs_remove_group(&g_dev->dev->kobj, &dev_attr_group);
        
        // 销毁设备
        device_destroy(sysfs_test_class, dev_num);
    }
    
    // 销毁类
    if (sysfs_test_class) {
        class_destroy(sysfs_test_class);
    }
    
    // 删除cdev
    cdev_del(&cdev);
    
    // 注销设备号
    unregister_chrdev_region(dev_num, 1);
    
    // 释放设备结构体内存
    kfree(g_dev);
    
    pr_info("%s driver exited\n", DRIVER_NAME);
}

// 注册模块入口和出口函数
module_init(sysfs_test_init);
module_exit(sysfs_test_exit);

// 模块描述信息
MODULE_LICENSE(DRIVER_LICENSE);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_VERSION("1.0");

5.2 Makefile完整源码

# Makefile for sysfs_test kernel module

# 设置模块名称
MODULE_NAME := sysfs_interface

# 设置源文件
obj-m += $(MODULE_NAME).o

# 设置内核源码目录
# 如果未指定KERNELDIR,则默认使用当前运行内核的源码
KERNELDIR ?= /lib/modules/$(shell uname -r)/build

# 当前目录
PWD := $(shell pwd)

# 编译目标
all:
	@echo "Building $(MODULE_NAME) kernel module..."
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
	@echo "Build completed. Run 'sudo insmod $(MODULE_NAME).ko' to load the module."

# 清理目标
clean:
	@echo "Cleaning up..."
	$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

# 安装模块
install:
	sudo insmod $(MODULE_NAME).ko

# 卸载模块
uninstall:
	sudo rmmod $(MODULE_NAME)

# 查看模块信息
info:
	modinfo $(MODULE_NAME).ko

# 查看内核日志
log:
	sudo dmesg | tail -20
```makefile
# Makefile for sysfs_test kernel module

# 设置模块名称
MODULE_NAME := sysfs_interface

# 设置源文件
obj-m += $(MODULE_NAME).o

# 设置内核源码目录
# 如果未指定KERNELDIR,则默认使用当前运行内核的源码
KERNELDIR ?= /lib/modules/$(shell uname -r)/build

# 当前目录
PWD := $(shell pwd)

# 编译目标
all:
	@echo "Building $(MODULE_NAME) kernel module..."
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
	@echo "Build completed. Run 'sudo insmod $(MODULE_NAME).ko' to load the module."

# 清理目标
clean:
	@echo "Cleaning up..."
	$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

# 安装模块
install:
	sudo insmod $(MODULE_NAME).ko

# 卸载模块
uninstall:
	sudo rmmod $(MODULE_NAME)

# 查看模块信息
info:
	modinfo $(MODULE_NAME).ko

# 查看内核日志
log:
	sudo dmesg | tail -20

6. 总结

本示例展示了如何在Linux内核驱动中实现sysfs接口。通过使用设备模型和class机制,我们创建了两个位于/sys/class/drv_test/sysfs_test/目录下的可读写属性节点:

  • str - 用于字符串读写的属性节点
  • uint32 - 用于32位无符号整数读写的属性节点

这个接口允许用户空间程序与内核驱动进行简单的数据交换,是Linux内核驱动程序开发的基础示例。

此实现遵循了Linux内核驱动开发的最佳实践,包括:

  • 适当的错误处理和资源清理
  • 遵循Linux内核编码风格
  • 使用标准内核API进行内存和设备管理
  • 实现安全的用户空间/内核空间数据交换

这些技术可以作为开发更复杂Linux内核驱动程序的基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值