Linux创建sysfs属性节点 - DEVICE_ATTR宏、device_create_file()、sysfs_create_group()

目录

简介:

一、DEVICE_ATTR介绍

1、DEVICE_ATTR宏

[1.1 参数说明](#1.1 参数说明)

[1.2 调用方法](#1.2 调用方法)

二、sysfs创建属性文件

1、创建一个sysfs属性文件

[1.1 device_create_file()函数](#1.1 device_create_file()函数)

[1.2 device_create_file()实例](#1.2 device_create_file()实例)

2、创建多个sysfs属性文件

[2.1 sysfs_create_group()函数](#2.1 sysfs_create_group()函数)

[2.2 sysfs_create_group()实例](#2.2 sysfs_create_group()实例)


简介:

在Linux驱动调试时,常常需要添加属性文件,sysfs属性节点可以实现用户空间与硬件交互,如:设置GPIO管脚电平、控制驱动等功能。下面介绍如何创建sysfs属性节点。

一、DEVICE_ATTR介绍


1、DEVICE_ATTR宏


DEVICE_ATTR 宏在linux/device.h中有如下定义:

cpp 复制代码
/* 路径:linux/device.h */
#define DEVICE_ATTR(_name, _mode, _show, _store) \
	struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

struct device_attribute {
    struct attribute	attr;
    ssize_t (*show)(struct device *dev, struct device_attribute *attr,
                    char *buf);
    ssize_t (*store)(struct device *dev, struct device_attribute *attr,
                     const char *buf, size_t count);
};

/* 路径:include/linux/sysfs.h */
#define __ATTR(_name, _mode, _show, _store) {				\
	.attr = {.name = __stringify(_name),				\
		 .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },		\
	.show	= _show,						\
	.store	= _store,						\
}

DEVICE_ATTR 宏用来定义一个 struct device_attribute 结构体,并对各成员初始化。

1.1 参数说明

cpp 复制代码
DEVICE_ATTR(_name, _mode, _show, _store)
  • _name:名称,也就是将在sysfs中生成的文件名称;
  • _mode:属性文件的权限mode,与普通文件相同,UGO的格式。只读0444,只写0222,或者读写都行的0666;
  • _show:显示函数,cat该文件时,此函数被调用;
  • _store:写函数,echo内容到该文件时,此函数被调用;

1.2 调用方法

cpp 复制代码
static DEVICE_ATTR(demo, 0664, demo_show, demo_store);

demo_show、demo_store函数由我们自己定义,展开后

cpp 复制代码
struct device_attribute dev_attr_demo = {
	.attr = {.name = __stringify(demo),
	.mode = VERIFY_OCTAL_PERMISSIONS(0664) },
	.show	= demo_show,
	.store	= demo_store,
}

调用 DEVICE_ATTR 后,就将 device_attribute 结构体初始化完成了。还需要使用 device_create_file() 或 sysfs_create_group() 将属性文件加入sysfs文件系统中。

二、sysfs创建属性文件


1、创建一个sysfs属性文件


1.1 device_create_file()函数

DEVICE_ATTR 宏创建 device_attribute 结构体后,调用 device_create_file() 将属性文件加入sysfs文件系统中,会在 /sys/class/xxx 子目录下生成一个属性文件。

cpp 复制代码
/* 路径:drivers/base/core.c */
int device_create_file(struct device *dev,
                       const struct device_attribute *attr)
{
    int error = 0;

    if (dev) {
        WARN(((attr->attr.mode & S_IWUGO) && !attr->store),
             "Attribute %s: write permission without 'store'\n",
             attr->attr.name);
        WARN(((attr->attr.mode & S_IRUGO) && !attr->show),
             "Attribute %s: read permission without 'show'\n",
             attr->attr.name);
        error = sysfs_create_file(&dev->kobj, &attr->attr);
    }

    return error;
}
EXPORT_SYMBOL_GPL(device_create_file);

这种方式一次只能创建一个属性节点。

1.2 device_create_file()实例

使用 device_create_file() 函数时要引用 device_create 创建设备时返回的device*指针,作用是在/sys/class/下创建一个属性文件,从而通过对这个属性文件进行读写就能完成对应的数据操作。

实例代码:

cpp 复制代码
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

typedef struct
{
	dev_t devid; 			/* 设备号 */
	int major;				/* 主设备号 */
	int minor;				/* 次设备号 */
	struct cdev cdev;   	/* cdev */
	struct class *class;	/* 类 */
	struct device *device;  /* 设备 */
}CHARDEV_HANDLE_T;

#define CDEV_CNT		1		  		/* 设备号个数 */
#define CDEV_NAME		"my_leddrv"		/* 名字 */

static CHARDEV_HANDLE_T stMyled = {0};

static int led_drv_open(struct inode *node, struct file *file)
{
	//filp->private_data = &cdev_data;  /* 设置私有数据 */
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

static int led_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

static int led_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return 1;
}

static int led_drv_release(struct inode *node, struct file *file)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

/* 设备操作函数 */
static struct file_operations led_drv_fops =
{
    .owner = THIS_MODULE,
    .open  = led_drv_open,
    .read  = led_drv_read,
    .write = led_drv_write,
    .release = led_drv_release,
};

ssize_t para_show(struct device *dev, struct device_attribute *attr, char *buf)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return sprintf(buf, "hello world!\n");
}

ssize_t para_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    printk(KERN_INFO "%s\n", buf);
    return count;
}

static DEVICE_ATTR(para, 0664, para_show, para_store);

/* 入口函数 */
static int __init led_init(void)
{
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
/* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (stMyled.major) { /* 定义了设备号 */
        stMyled.devid = MKDEV(stMyled.major, 0);
        register_chrdev_region(stMyled.devid, CDEV_CNT, CDEV_NAME);
    } else { /* 没有定义设备号 */
        alloc_chrdev_region(&stMyled.devid, 0, CDEV_CNT, CDEV_NAME); /* 申请设备号 */
        stMyled.major = MAJOR(stMyled.devid); /* 获取分配号的主设备号 */
        stMyled.minor = MINOR(stMyled.devid); /* 获取分配号的次设备号 */
    }
    printk("major=%d, minor=%d\r\n", stMyled.major, stMyled.minor);

    /* 2、初始化 cdev */
    stMyled.cdev.owner = THIS_MODULE;
    cdev_init(&stMyled.cdev, &led_drv_fops);				//file_operations

    /* 3、添加一个 cdev */
    cdev_add(&stMyled.cdev, stMyled.devid, CDEV_CNT);

    /* 4、创建类 */
    stMyled.class = class_create(THIS_MODULE, CDEV_NAME);	///sys/class/目录下会创建一个新的文件夹
    if (IS_ERR(stMyled.class)) {
        return PTR_ERR(stMyled.class);
    }

    /* 5、创建设备 */
    stMyled.device = device_create(stMyled.class, NULL, stMyled.devid, NULL, CDEV_NAME);//dev目录下创建相应的设备节点
    if (IS_ERR(stMyled.device)) {
        return PTR_ERR(stMyled.device);
    }
	
	/* 6、将属性文件加入sysfs文件系统中 */
    device_create_file(stMyled.device, &dev_attr_para);
	
    return 0;
}

/* 出口函数 */
static void __exit led_exit(void)
{
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
	
    /* 注销字符设备驱动 */
    cdev_del(&stMyled.cdev); /* 删除 cdev */
    unregister_chrdev_region(stMyled.devid, CDEV_CNT); /* 注销 */

    device_destroy(stMyled.class, stMyled.devid);
    class_destroy(stMyled.class);
	
	/* 移除sysfs中的device属性节点 */
	//device_remove_file(stMyled.device, &dev_attr_para);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("donga");

Makefile编译,生成 .ko 文件

cpp 复制代码
KERN_DIR = /home/linux-imx-rel_imx_4.1.15_2.1.0

all:
	make -C $(KERN_DIR) M=`pwd` modules

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m   += chardev_drv.o

insmod加载 .ko 驱动后,生成 /sys/class/my_leddrv/my_leddrv/ 下有para属性节点。cat命令会调用 .show 函数,echo 会调用 .store 函数。测试结果如下:

cat /sys/class/my_leddrv/my_leddrv/para

echo "DEVICE_ATTR test" > /sys/class/my_leddrv/my_leddrv/para

2、创建多个sysfs属性文件


2.1 sysfs_create_group()函数

对于多个属性文件的添加,我们可以定义属性组,然后将这个属性组使用 sysfs_create_group() 添加进sysfs文件系统中。

cpp 复制代码
int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp)

2.2 sysfs_create_group()实例

关键步骤:

cpp 复制代码
static DEVICE_ATTR(para1, 0664, para_show, para_store);
static DEVICE_ATTR(para2, 0664, para_show, para_store);
static DEVICE_ATTR(para3, 0664, para_show, para_store);

static struct attribute *para_attribute[] =
{
    &dev_attr_para1.attr,
    &dev_attr_para2.attr,
    &dev_attr_para3.attr,
    NULL,
};

static struct attribute_group para_attribute_group =
{
    .attrs = para_attribute,
};

sysfs_create_group(&myled.device->kobj, &para_attribute_group);

上面关键步骤我为了简单各个属性文件的show函数和store函数都一样,大家可以根据实际情况自行分别定义。

示例如下:

cpp 复制代码
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

typedef struct
{
	dev_t devid; 			/* 设备号 */
	int major;				/* 主设备号 */
	int minor;				/* 次设备号 */
	struct cdev cdev;   	/* cdev */
	struct class *class;	/* 类 */
	struct device *device;  /* 设备 */
}CHARDEV_HANDLE_T;

#define CDEV_CNT		1		  		/* 设备号个数 */
#define CDEV_NAME		"my_leddrv"		/* 名字 */

static CHARDEV_HANDLE_T stMyled = {0};

static int led_drv_open(struct inode *node, struct file *file)
{
	//filp->private_data = &cdev_data;  /* 设置私有数据 */
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

static int led_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

static int led_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return 1;
}

static int led_drv_release(struct inode *node, struct file *file)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

/* 设备操作函数 */
static struct file_operations led_drv_fops =
{
    .owner = THIS_MODULE,
    .open  = led_drv_open,
    .read  = led_drv_read,
    .write = led_drv_write,
    .release = led_drv_release,
};

ssize_t para_show(struct device *dev, struct device_attribute *attr, char *buf)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return sprintf(buf, "hello world!\n");
}

ssize_t para_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    printk(KERN_INFO "%s\n", buf);
    return count;
}

static DEVICE_ATTR(para1, 0664, para_show, para_store);
static DEVICE_ATTR(para2, 0664, para_show, para_store);
static DEVICE_ATTR(para3, 0664, para_show, para_store);

static struct attribute *para_attribute[] =
{
    &dev_attr_para1.attr,
    &dev_attr_para2.attr,
    &dev_attr_para3.attr,
    NULL,
};

static struct attribute_group para_attribute_group =
{
    .attrs = para_attribute,
};

/* 入口函数 */
static int __init led_init(void)
{
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
/* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (stMyled.major) { /* 定义了设备号 */
        stMyled.devid = MKDEV(stMyled.major, 0);
        register_chrdev_region(stMyled.devid, CDEV_CNT, CDEV_NAME);
    } else { /* 没有定义设备号 */
        alloc_chrdev_region(&stMyled.devid, 0, CDEV_CNT, CDEV_NAME); /* 申请设备号 */
        stMyled.major = MAJOR(stMyled.devid); /* 获取分配号的主设备号 */
        stMyled.minor = MINOR(stMyled.devid); /* 获取分配号的次设备号 */
    }
    printk("major=%d, minor=%d\r\n", stMyled.major, stMyled.minor);

    /* 2、初始化 cdev */
    stMyled.cdev.owner = THIS_MODULE;
    cdev_init(&stMyled.cdev, &led_drv_fops);				//file_operations

    /* 3、添加一个 cdev */
    cdev_add(&stMyled.cdev, stMyled.devid, CDEV_CNT);

    /* 4、创建类 */
    stMyled.class = class_create(THIS_MODULE, CDEV_NAME);	///sys/class/目录下会创建一个新的文件夹
    if (IS_ERR(stMyled.class)) {
        return PTR_ERR(stMyled.class);
    }

    /* 5、创建设备 */
    stMyled.device = device_create(stMyled.class, NULL, stMyled.devid, NULL, CDEV_NAME);//dev目录下创建相应的设备节点
    if (IS_ERR(stMyled.device)) {
        return PTR_ERR(stMyled.device);
    }
	
	/* 6、将属性文件加入sysfs文件系统中 */
    sysfs_create_group(&stMyled.device->kobj, &para_attribute_group);
	
    return 0;
}

/* 出口函数 */
static void __exit led_exit(void)
{
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
	
    /* 注销字符设备驱动 */
    cdev_del(&stMyled.cdev); /* 删除 cdev */
    unregister_chrdev_region(stMyled.devid, CDEV_CNT); /* 注销 */

    device_destroy(stMyled.class, stMyled.devid);
    class_destroy(stMyled.class);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("donga");

加载 .ko 文件后,在 /sys/class/my_leddrv/my_leddrv 目录下多了 para1、para2、para3。

使用 cat、echo命令测试:

cat /sys/class/my_leddrv/my_leddrv/para

echo 1 > /sys/class/my_leddrv/my_leddrv/para


相关推荐
Lary_Rock1 小时前
RK3576 LINUX RKNN SDK 测试
linux·运维·服务器
云飞云共享云桌面3 小时前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络
励志成为嵌入式工程师3 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
一坨阿亮4 小时前
Linux 使用中的问题
linux·运维
hikktn5 小时前
如何在 Rust 中实现内存安全:与 C/C++ 的对比分析
c语言·安全·rust
观音山保我别报错5 小时前
C语言扫雷小游戏
c语言·开发语言·算法
dsywws5 小时前
Linux学习笔记之vim入门
linux·笔记·学习
幺零九零零6 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
小林熬夜学编程7 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法