1. sysfs
1.1 前言
在linux系统中,用户空间访问驱动程序一般是以“设备文件”的方式通过“read/write/ioctl”访问,但这种方式有几个明显的缺点。
- read/write接口功能单一
- ioctl虽然可以根据cmd参数实现多重功能,但它们都无法直接在shell/mash脚本中被调用,必须通过C语言方式访问
- ioctl二进制数据接口存在大小端问题,不同平台CPU(32/64)不方便移植
除了“设备文件”方式,驱动程序还可以实现procfs虚拟文件系统接口,提供给用户访问。procfs访问驱动程序,同样使用的是read/open/ioctl接口,因此也存在“设备文件”方式中的类似问题。
因此,linux系统从2.6版本内核开始引入一个独立的抽象接口来描述设备和驱动信息,即是sysfs虚拟文件系统。
1.2 什么是sysfs
sysfs是linux系统下一个基于内存的文件系统,主要功能是将设备(device)和驱动(driver)内容通过文件的方式从内核空间映射到用户空间,方便用户对设备和驱动进行访问和设置。实现了sysfs文件接口,会在指定目录下将驱动读写空间生成一个临时文件,该文件可以直接通过shell命令的“echo”、“cat”访问。例如,在之前“内核态访问EEPROM”文章中,AT24系列的的EEPROM就将存储空间映射为“/sys/busi2c/device/4-0050/eeprom”文件。
1.3 sysfs特性
sysfs 一般是挂在是“/sys”目录下,sysfs把系统的设备的驱动映射成层次分明的目录结构,方便用户管理设备,顶级目录一般会包括block、bus、drivers、class、power、firmware等子目录。
block | 系统的块设备符号链接,符号链接指向/sys/devices下的相应目录 |
bus | 系统总线设备,如i2c、spi、platform等,linux系统总线模型由“device”和“driver”组成,因此bus的子目录包括“device”和“driver”目录 |
class | 包含所有注册的到内核的设备类 |
dev | 包含字符设备(char)和块设备(block)的以主次(majior/minor)编码方式描述链接到实际设备(/sys/device)的链接文件 |
devices | 内核对系统中所有设备的分层次表达模型,也是sysfs文件系统管理设备的最重要的目录结构 |
firmware | 系统加载固件机制的对用户空间的接口 |
fs | 用来描述系统中所有的文件系统,包括文件系统本身和按照文件系统分类存放的已挂载点 |
kernel | 存放内核中所有可调整的参数 |
module | 包含当前系统中已加载的模块,包括编译到内核和编译成模块(.ko)的驱动 |
power | 电源管理描述文件和控制接口 |
sysfs机制提供了两组接口,一组用于内核将设备文件映射到sysfs文件系统中,另一组用于用户访问设备文件。两组接口显式描述内核对象、对象属性和对象关系与用户空间的关系。
sysfs对内核空间 | sysfs对用户空间 |
---|---|
内核对象(kobject) | 目录 |
对象属性(attribute) | 文件 |
对象关系(relationship) | 链接 |
2. sysfs驱动接口实现
以“Linux 字符驱动之platform框架”文章中的字符设备驱动为基础,增加sysfs实现接口。简单回顾下该字符驱动的作用:
- 实现一个“软驱动”,通过内核一片物理内存交换用户多进程数据;
- 支持read/write/ioctl函数访问;
下面实现sysfs接口,可通过脚本命令“echo”、“cat”访问驱动。
2.1 访问接口回调
该部分主要是是“echo”、“cat”在驱动最终调用的函数。
static ssize_t memory_drv_cat(struct device *dev,struct device_attribute *attr, char *buf)
{
char *s = buf;
if(pmemory_dev->mem_buf == NULL)
{
return -1;
}
sprintf(s, "%s", pmemory_dev->mem_buf);
return sizeof(s);
}
static ssize_t memory_drv_echo(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
if(pmemory_dev->mem_buf == NULL)
{
return -1;
}
memcpy(pmemory_dev->mem_buf, buf, count);
return count;
}
2.2 创建与释放目录(kobject)
2.2.1 使用总线目录
sysfs默认挂载在“/sys”目录下,对于内核态是“kobject”,对于用户态,一般映射在总线(bus)下对应的设备(device)目录下,如AT24的EEPROM映射在“/sys/busi2c/device/4-0050/”目录下。对于本次的字符驱动,采用的是platform虚拟总线,因此可以映射在“/sys/platform/device/dev-name/”下。
kobject在设备结构体中的描述:
struct platform_device
—>struct device dev
—>struct kobject kobj
设备驱动在创建时,即会创建该目录,因此,不需再手动手动创建,在创建映射文件时直接指定为该目录。
sysfs_create_file(&pdev->dev.kobj, &dev_attr_mem_bin.attr);/* 在总线device下创建文件 */
2.2.2 自定义目录
如果不使用总线下的目录,可在“/sys”目录下创建自定义目录,创建目录用“kobject_create_and_add”接口,原型位于“kernel/libkobject.c”中。
extern struct kobject * __must_check kobject_create_and_add(const char *name,
struct kobject *parent);
参数 | |
---|---|
参数 | 含义/说明 |
引用 | #include<linux/kobject.h> |
name | 目录名称 |
parent | 父目录,NULL为默认在/sys目录下 |
返回 | 成功返回创建的目录句柄,失败返回NULL |
struct kobject *mem_obj;
mem_obj = kobject_create_and_add("mem_sys", NULL);
2.3 创建与释放文件(attribute)
内核对象(attribute)映射到用户态就是文件,“struct attribute”结构体描述,其原型如下,常用的元素的文件名称(name)和文件属性(mode)。
struct attribute {
const char *name;
umode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
bool ignore_lockdep:1;
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};
注:
属性mode,即是文件访问权限,可读、可写、可执行,可以用已经定义的宏表示(S_IWUSR | S_IRUGO),也可以用数字表示(如0660)。
宏含义:
S_IRUSR:用户读权限
S_IWUSR:用户写权限
S_IRGRP:用户组读权限
S_IWGRP:用户组写权限
S_IROTH:其他组都权限
S_IWOTH:其他组写权限
进一步,驱动设备(device)用“struct device_attribute”描述,对应的“show”和“store”则是需驱动工程师实现的函数实体(如2.1节中)。
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);
};
linux内核定义了一个“DEVICE_ATTR”辅助宏,方便定义device_attribute描述变量,宏原型位于“kernel/include/linux/device.h”中。
/* DEVICE_ATTR */
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
/* __ATTR */
#define __ATTR(_name,_mode,_show,_store) { \
.attr = {.name = __stringify(_name), .mode = _mode }, \
.show = _show, \
.store = _store, \
}
例如,我们用“DEVICE_ATTR”宏定义一个设备描述文件,根据上述宏原型展开后为下面代码。当然直接使用展开后的方式定义,对于刚接触linux编程的人员来说,可读性比较好。
static DEVICE_ATTR(mem_bin, 0660, memory_drv_cat, memory_drv_echo);
/* 展开后 */
static struct device_attribute dev_attr_mem_bin = {
.attr = {
.name = "mem_bin",
.mode = 0660
},
.show = memory_drv_cat,
.store = memory_drv_echo,
};
2.3 目录与文件关联
可以关联到驱动加载时创建的的目录下,可以可以自定义目,后者需先创建目录。“sysfs_create_file”原型位于“kernel/include/linux/sysfs.h”中。
static inline int __must_check sysfs_create_file(struct kobject *kobj,
const struct attribute *attr)
对于本次的例子程序,可以这样实现。
#if 0 /* 自定义目录'/sys/mem_sys' */
pmemory_dev->mem_obj = kobject_create_and_add("mem_sys", kernel_kobj->parent);
if(!pmemory_dev->mem_obj)
{
return -ENOMEM;
}
sysfs_create_file(pmemory_dev->mem_obj, &dev_attr_mem_bin.attr);
#else /* 映射到 '/bus/platform/device'目录下 */
sysfs_create_file(&pdev->dev.kobj, &dev_attr_mem_bin.attr);
#endif
与之对应的,则是在驱动退出时,释放文件节点。
#if 0
sysfs_remove_file(pmemory_dev->mem_obj, &dev_attr_mem_bin.attr);
kobject_del(pmemory_dev->mem_obj);
kobject_put(pmemory_dev->mem_obj);
#else
sysfs_remove_file(&pdev->dev.kobj, &dev_attr_mem_bin.attr);
#endif
3. 源码
3.1 测试
修改Makefile文件,设置编译内核路径为Ubuntu16(64bit),“KERNELDIR = /usr/src/linux-headers-4.15.0-88-generic”,然后编译生成“dev_mem .ko”和“drv_mem.ko”模块,分别insmod到系统中。
查看映射文件,驱动如正常加载,会在“/sys/bus/platfor/device/dev_mem”目上生成“mem_bin”文件。
通过“mem_bin”访问驱动。
3.2 源码
【1】https://github.com/Prry/linux-drivers/tree/master/devmem_platform_sysfs
4. 参考
【2】https://blog.csdn.net/wade_510/article/details/72084006