11. Linux内核Sysfs文件系统¶
在上一章PWM子系统实验中,我们通过字符设备节点(/dev/pwm_subsystem)实现了应用层对PWM通道的配置与控制,完成了PWM信号的周期、占空比设置及使能/禁用操作。 但字符设备驱动在实际应用中存在明显弊端,严重影响开发效率和可维护性,具体如下:
接口不统一:每个字符设备驱动都需要自定义read、write等文件操作接口的交互协议,应用层必须严格匹配驱动定义的协议才能实现交互,不同驱动的接口无法复用,开发成本高。
交互不直观:无法通过命令行直接查看或修改PWM的核心参数,必须编写专用应用程序发送指令,调试和测试效率低下。
扩展性差:若新增PWM的配置项,不仅需要修改驱动层的文件操作逻辑,还需同步修改应用层程序,适配成本高。
资源管理混乱:字符设备无法直观展示PWM设备的硬件关联关系,难以通过系统层面统一管理PWM资源。
为解决上述问题,Linux内核引入了Sysfs文件系统。 Sysfs是一种基于内存的虚拟文件系统,它以文件和目录的形式,将内核中的设备、驱动、资源等信息暴露给用户层,提供了统一、直观、可扩展的交互接口。 在PWM子系统中,通过Sysfs可以直接通过命令行查看PWM通道的当前状态、修改配置参数,无需编写专用应用程序,同时简化了驱动开发流程,实现了设备资源的统一管理。
11.1. Sysfs概念和作用¶
11.1.1. Sysfs核心定义¶
Sysfs是Linux内核2.6版本引入的一种虚拟文件系统(与proc、Sysfs同属虚拟文件系统,不占用磁盘空间,仅存在于内存中), 其核心作用是将内核中的设备、驱动、总线、资源等对象的属性以“文件”的形式暴露给用户层,实现用户层与内核层的双向交互,用户层可读取文件获取内核信息,也可写入文件修改内核配置。
Sysfs的设计理念是“一切皆文件”,通过标准化的目录结构和文件接口,屏蔽内核底层实现细节,为用户层和驱动开发者提供统一的交互方式。
11.1.2. Sysfs核心作用¶
Sysfs的核心作用如下:
设备与资源展示:以目录树的形式,清晰展示系统中所有设备、驱动、总线的关联关系,便于用户直观了解系统资源分布。
属性交互:将设备/驱动的属性(如PWM周期、占空比)封装为文件,用户层通过读写文件实现与内核层的交互,无需自定义交互协议。
统一接口规范:为所有设备和驱动提供标准化的接口,不同驱动的属性文件操作方式一致,提升开发复用性和可维护性。
热插拔支持:配合udev机制,当设备热插拔时,sysfs会自动更新目录结构和属性文件,确保用户层能实时感知设备状态变化。
11.1.3. 与其他虚拟文件系统的区别¶
Linux内核中还有proc、debugfs等虚拟文件系统,与sysfs相比,三者的定位和用途存在差异,具体对比如下:
proc:主要用于暴露内核进程、系统状态等信息(如/proc/cpuinfo、/proc/meminfo),以文本形式展示,交互性较弱,不适合频繁修改内核配置。
debugfs:主要用于内核调试,仅在开启CONFIG_DEBUG_FS配置时生效,暴露的信息多为调试相关(如寄存器值、函数调用日志),不适合用户层常规交互。
sysfs:专注于设备、驱动、资源的属性展示与交互,接口标准化、交互直观,支持读写操作,是用户层与内核层设备交互的首选方式,也是PWM子系统推荐的交互接口。
11.2. Sysfs的挂载与目录结构¶
11.2.1. 挂载方式¶
Sysfs默认挂载在/sys目录下,内核启动时会自动完成挂载,也可通过手动命令挂载:
1 2 3 4 5 6 7 8 | #查看挂载情况
mount | grep sysfs
#信息输出如下,默认已经挂载
sysfs on /sys type sysfs (rw,relatime)
#若未挂载,可手动挂载
sudo mount -t sysfs sysfs /sys
|
11.2.2. 目录结构¶
sysfs的目录结构以“对象”为核心,每个目录对应一个内核对象(如设备、驱动、总线),目录下的文件对应对象的属性,核心目录如下:
/sys/devices:系统中所有设备的根目录,每个设备对应一个子目录,PWM控制器、PWM通道的设备目录均位于此,可通过
ls /sys/devices/platform/*.pwm查看。/sys/class:按设备类型分类的目录,PWM子系统在该目录下创建pwm子目录(/sys/class/pwm/),每个PWM通道对应一个子目录(如pwmchip0 pwmchip1),目录下包含该通道的所有属性文件。
/sys/bus:系统中所有总线的目录(如i2c、spi、platform),PWM控制器通常挂载在platform总线下,可通过该目录查看总线与设备的关联关系。
/sys/drivers:系统中所有驱动的目录,每个驱动对应一个子目录,可查看驱动的绑定状态、支持的设备等信息。
11.3. Sysfs核心结构体¶
Sysfs的核心是“对象-属性”模型,内核通过一系列结构体描述对象、属性及它们之间的关联,以下讲解驱动开发中最常用的结构体。
11.3.1. 内核对象结构体¶
内核对象结构体(struct kobject)是sysfs的最核心结构体,代表内核中的一个“对象”,每个sysfs目录都对应一个kobject实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | struct kobject {
const char *name; /* 内核对象的名称,对应sysfs中的目录名称 */
struct list_head entry; /* 链表节点,用于将当前kobject加入父对象的子链表,实现对象层级管理 */
struct kobject *parent; /* 指向父内核对象,对应sysfs中的父目录 */
struct kset *kset; /* 指向该对象所属的kset(对象集合),用于批量管理同类对象 */
struct kobj_type *ktype; /* 指向该对象的类型描述结构体,包含对象的属性操作方法 */
struct kernfs_node *sd; /* sysfs directory entry,描述该对象在sysfs中的目录信息 */
struct kref kref; /* 引用计数,用于内核对象的生命周期管理,避免资源泄漏 */
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release;
#endif
unsigned int state_initialized:1; /* 标记内核对象是否已初始化 */
unsigned int state_in_sysfs:1; /* 标记内核对象是否已注册到sysfs中 */
unsigned int state_add_uevent_sent:1;/* 标记设备添加的uevent事件是否已发送 */
unsigned int state_remove_uevent_sent:1;/* 标记设备移除的uevent事件是否已发送 */
unsigned int uevent_suppress:1; /* 标记是否抑制uevent事件发送 */
/* 省略部分成员*/
};
|
kobject通过parent指针实现层级关系,通过ktype指定属性操作方法,通过kref管理生命周期,是连接内核对象与sysfs目录的关键载体。
11.3.2. 内核对象类型结构体¶
内核对象类型结构体(struct kobj_type)用于描述kobject的“类型”,定义了该类型对象的通用操作方法和默认属性。
1 2 3 4 5 6 7 8 9 10 | struct kobj_type {
void (*release)(struct kobject *kobj); /* 对象释放回调函数,当kobject的引用计数为0时调用,用于释放资源 */
const struct sysfs_ops *sysfs_ops; /* 指向sysfs操作集,定义了属性文件的读写方法,是sysfs交互的核心 */
struct attribute **default_attrs; /* 默认属性数组,对象创建时会自动在sysfs中创建这些属性文件 */
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj); /* 子对象命名空间类型操作,驱动开发中较少用到 */
const void *(*namespace)(struct kobject *kobj); /* 获取对象命名空间,一般用于多命名空间场景,非必需 */
void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid); /* 获取对象所有权(uid/gid),常规驱动可省略 */
/* 省略部分成员*/
};
|
其中,sysfs_ops是核心成员,指定了属性文件的read和write操作函数;default_attrs和groups用于批量管理属性,避免重复创建属性文件。
11.3.3. 属性结构体¶
属性结构体(struct attribute)描述sysfs中的一个“属性文件”,每个属性文件对应一个该结构体实例。
1 2 3 4 5 6 7 8 9 | struct attribute {
const char *name; /* 属性名称,对应sysfs中的文件名 */
umode_t mode; /* 属性文件的访问权限,如0644 */
#ifdef CONFIG_DEBUG_LOCK_ALLOC
bool ignore_lockdep:1; /* 调试相关:标记是否忽略lockdep检查,驱动开发中常规场景较少用到 */
struct lock_class_key *key; /* 调试相关:锁类键值,用于锁调试 */
struct lock_class_key skey; /* 调试相关:二级锁类键值,辅助锁调试 */
#endif
};
|
name成员指定文件名,mode成员指定文件权限,与Linux文件权限规则一致,如0644表示用户可读、根用户可写,0444表示所有用户只读。
11.3.4. 设备属性结构体¶
属性结构体(struct attribute)是sysfs属性的基础结构体,而设备属性结构体(struct device_attribute)是基于struct attribute封装的设备专属属性结构体, 专门用于设备驱动中暴露设备相关属性,内核定义了相关接口用于导出设备属性,其核心作用是关联设备对象与属性的读写逻辑,简化设备属性的sysfs开发。
1 2 3 4 5 6 7 | struct device_attribute {
struct attribute attr; /* 继承基础attribute结构体,包含属性名称和访问权限 */
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf); /* 设备属性的读操作函数,用户层cat属性文件时调用,关联具体设备 */
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count); /* 设备属性的写操作函数,用户层echo属性文件时调用,关联具体设备 */
};
|
struct device_attribute内嵌struct attribute,继承了属性的名称(name)和权限(mode),同时新增了与设备绑定的show/store函数,解决了struct attribute无法直接关联设备对象的问题。
show/store函数的第一个参数为struct device *dev,可直接获取当前属性所属的设备对象,便于在读写操作中访问设备相关资源。
11.3.4.1. 设备属性初始化宏¶
设备属性初始化宏(DEVICE_ATTR_RW)是内核提供的宏,用于快速初始化struct device_attribute结构体实例,自动关联属性的show和store函数,简化设备属性的定义流程,是设备驱动中导出可读写属性的常用宏。
1 2 | #define DEVICE_ATTR_RW(_name) \
struct device_attribute dev_attr_##_name = __ATTR_RW(_name)
|
核心功能:
快速创建struct device_attribute实例,名称格式为dev_attr_xxx(xxx为传入的属性名),自动填充attr成员的name和mode(默认可读写权限0644)。
自动关联属性的show和store函数,要求用户提前实现名为xxx_show和xxx_store的读写函数(如属性名为period,需实现period_show和period_store函数)。
11.3.5. sysfs操作集结构体¶
sysfs操作集结构体(struct sysfs_ops)是内核sysfs框架的统一调用接口。
1 2 3 4 5 6 7 | struct sysfs_ops {
/* 属性文件的读操作函数,用户层cat文件时调用 */
ssize_t (*show)(struct kobject *, struct attribute *, char *);
/* 属性文件的写操作函数,用户层echo文件时调用 */
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};
|
用户执行cat /sys/class/xxx属性时,用户层请求内核sysfs框架:
第一步调用:sysfs_ops的show函数(总调度),内核只认识sysfs_ops,这是固定入口;
调度器找到目标:device_attribute,内核通过attribute指针,用container_of宏找回完整的device_attribut;
最终执行:device_attribute自己的show函数,写操作echo同理。
11.3.6. sysfs属性组结构体¶
在实际驱动开发中,一个对象通常包含多个属性(如PWM通道包含period、duty_cycle、enable等属性), 若逐个创建属性文件,代码冗余且效率低。sysfs提供了属性组(attribute_group)机制,用于批量创建、管理多个属性,核心结构体定义如下:
1 2 3 4 5 6 7 8 9 | struct attribute_group {
const char *name; /* 属性组名称,若不为NULL,会在对象目录下创建该名称的子目录,属性文件位于该子目录下 */
umode_t (*is_visible)(struct kobject *,
struct attribute *, int); /* 控制普通属性是否可见,返回属性实际访问权限 */
umode_t (*is_bin_visible)(struct kobject *,
struct bin_attribute *, int); /* 控制二进制属性是否可见,多用于二进制数据交互场景 */
struct attribute **attrs; /* 该属性组包含的普通属性数组 */
struct bin_attribute **bin_attrs; /* 该属性组包含的二进制属性数组,用于存储二进制格式的属性数据 */
};
|
属性组机制可批量创建多个属性文件,简化代码开发,如在PWM子系统中,PWM通道的所有属性(period、duty_cycle等)会被组织成一个属性组, 当PWM通道的kobject创建时,会自动批量创建所有属性文件,提升开发效率。
11.4. Sysfs驱动核心函数¶
Linux内核为sysfs驱动开发提供了一系列通用核心函数,涵盖kobject创建/注销、属性文件创建/删除、属性组管理等功能。
11.4.1. kobject管理函数¶
11.4.1.1. 创建并注册kobject(kobject_create_and_add)¶
kobject_create_and_add函数用于创建一个kobject实例,设置其名称和父对象,然后将该kobject注册到sysfs中,自动在父对象的目录下创建一个以name命名的子目录。
函数原型:
1 | struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
|
参数说明:
name:kobject的名称,对应sysfs中的目录名称。
parent:指向父kobject的指针,若为NULL,则kobject会被注册到sysfs的根目录(/sys)。
返回值:
成功:返回创建的struct kobject结构体指针。
失败:返回NULL。
11.4.1.2. 注销kobject(kobject_put)¶
kobject_put函数用于减少kobject的引用计数,当引用计数减少到0时,会自动调用该kobject对应的kobj_type->release函数,释放kobject占用的资源,并从sysfs中删除对应的目录。 该函数是注销kobject的核心函数,无需手动删除目录,简化资源释放流程。
函数原型:
1 | void kobject_put(struct kobject *kobj);
|
参数说明:
kobj:指向要注销的struct kobject结构体指针,必须是已注册的kobject。
11.4.2. 属性文件管理函数¶
11.4.2.1. 创建单个属性文件(sysfs_create_file)¶
sysfs_create_file函数用于在指定kobject对应的sysfs目录下,创建一个单个属性文件,属性的名称、权限由struct attribute结构体指定, 属性的读写逻辑由kobject对应的kobj_type->sysfs_ops指定(或属性自身关联的show/store函数)。
函数原型:
1 | int sysfs_create_file(struct kobject *kobj, const struct attribute *attr);
|
参数说明:
kobj:指向kobject结构体指针,属性文件会创建在该kobject对应的sysfs目录下。
attr:指向struct attribute结构体指针,描述要创建的属性文件。
返回值:
0:创建成功。
负数:创建失败。
11.4.2.2. 删除单个属性文件(sysfs_remove_file)¶
sysfs_remove_file函数用于删除指定kobject对应的sysfs目录下的单个属性文件,与sysfs_create_file函数对应,用于驱动注销时释放属性文件资源。
函数原型:
1 | void sysfs_remove_file(struct kobject *kobj, const struct attribute *attr);
|
参数说明:
kobj:指向kobject结构体指针,属性文件所在的目录对应的kobject。
attr:指向struct attribute结构体指针,描述要删除的属性文件,需与创建时的属性一致。
11.4.3. 属性组管理函数¶
11.4.3.1. 创建属性组(sysfs_create_group)¶
sysfs_create_group函数用于在指定kobject对应的sysfs目录下,批量创建属性组中的所有属性文件,若属性组的name成员不为NULL, 则会先创建一个以name命名的子目录,再将所有属性文件创建在该子目录下;若name为NULL,则直接在kobject目录下创建属性文件。
函数原型:
1 | int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp);
|
参数说明:
kobj:指向kobject结构体指针,属性组会创建在该kobject对应的sysfs目录下。
grp:指向struct attribute_group结构体指针,描述要创建的属性组(包含的属性、操作集等)。
返回值:
0:创建成功。
负数:创建失败。
11.4.3.2. 删除属性组(sysfs_remove_group)¶
sysfs_remove_group函数用于批量删除指定kobject对应的sysfs目录下的属性组中的所有属性文件,若属性组有独立子目录,会同时删除该子目录, 与sysfs_create_group函数对应,用于驱动注销时批量释放属性资源。
函数原型:
1 | void sysfs_remove_group(struct kobject *kobj, const struct attribute_group *grp);
|
参数说明:
kobj:指向kobject结构体指针,属性组所在的目录对应的kobject。
grp:指向struct attribute_group结构体指针,描述要删除的属性组,需与创建时的属性组一致。
11.5. Sysfs文件系统实验¶
本实验在PWM子系统实验基础上进行修改,设备树插件完全一致, 驱动修改为“Sysfs+PWM子系统+互斥锁”机制,实现带参数校验和并发保护的Sysfs读写接口来控制PWM通道。
本章的示例代码目录为: linux_driver/29_pwm_sysfs
11.5.1. 驱动代码详解¶
核心定义与数据结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /* 类名称 */
#define CLASS_NAME "pwm-sysfs"
/* 设备名称 */
#define DEV_NAME "pwm-test"
/* PWM控制参数结构体 */
struct pwm_param {
unsigned int period; /* PWM周期,单位:ns */
unsigned int duty_cycle; /* PWM占空比,单位:ns */
unsigned int polarity; /* PWM极性:0-正常 1-反相 */
unsigned int enable; /* PWM使能:0-关闭 1-开启 */
};
/* PWM私有设备结构体 */
struct pwm_dev {
struct class *class; /* 设备类 */
struct device *device; /* 设备节点 */
struct pwm_device *pwm; /* PWM设备句柄 */
struct pwm_param param; /* PWM控制参数 */
struct mutex lock; /* 互斥锁 */
};
|
关键解析:
将PWM参数单独封装为pwm_param结构体,私有设备结构体pwm_dev整合设备类、设备节点、PWM句柄、参数和互斥锁,便于管理。
互斥锁用于解决多个用户同时读写PWM参数的并发冲突问题,确保参数配置的原子性。
platform驱动实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | /* PWM驱动 probe 实现 */
static int pwm_probe(struct platform_device *pdev)
{
/* 定义返回值变量 */
int ret = 0;
/* 定义PWM私有设备结构体指针 */
struct pwm_dev *pwm_priv;
printk(KERN_INFO "pwm driver probe\n");
/* 分配 PWM 私有设备内存 */
pwm_priv = devm_kzalloc(&pdev->dev, sizeof(*pwm_priv), GFP_KERNEL);
if (!pwm_priv)
return -ENOMEM;
/* 初始化互斥锁 */
mutex_init(&pwm_priv->lock);
/* 从设备树获取 PWM 硬件资源 */
pwm_priv->pwm = devm_pwm_get(&pdev->dev, NULL);
if (IS_ERR(pwm_priv->pwm)) {
ret = PTR_ERR(pwm_priv->pwm);
printk(KERN_ERR "pwm get resource failed\n");
return ret;
}
/* PWM 参数初始化 */
pwm_priv->param.period = 10000;
pwm_priv->param.duty_cycle = 5000;
pwm_priv->param.polarity = PWM_POLARITY_NORMAL;
pwm_priv->param.enable = 0;
/* 配置默认周期占空比 */
ret = pwm_config(pwm_priv->pwm, pwm_priv->param.duty_cycle, pwm_priv->param.period);
if (ret) {
printk(KERN_ERR "pwm default config failed\n");
return ret;
}
/* 配置默认极性 */
ret = pwm_set_polarity(pwm_priv->pwm, pwm_priv->param.polarity);
if (ret) {
printk(KERN_ERR "pwm default polarity failed\n");
return ret;
}
/* 创建设备类 */
pwm_priv->class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(pwm_priv->class)) {
ret = PTR_ERR(pwm_priv->class);
printk(KERN_ERR "pwm class create failed\n");
return ret;
}
/* 创建设备节点 */
pwm_priv->device = device_create(pwm_priv->class, &pdev->dev,
MKDEV(0, 0), NULL, DEV_NAME);
if (IS_ERR(pwm_priv->device)) {
ret = PTR_ERR(pwm_priv->device);
printk(KERN_ERR "pwm device create failed\n");
goto destroy_class;
}
/* 绑定私有数据到设备 */
dev_set_drvdata(pwm_priv->device, pwm_priv);
/* 绑定私有数据到平台设备 */
dev_set_drvdata(&pdev->dev, pwm_priv);
/* 创建sysfs接口 */
ret = sysfs_create_group(&pwm_priv->device->kobj, &pwm_sysfs_attr_group);
if (ret) {
printk(KERN_ERR "sysfs create group failed\n");
goto destroy_device;
}
return 0;
destroy_device:
/* 销毁设备节点 */
device_destroy(pwm_priv->class, MKDEV(0, 0));
destroy_class:
/* 销毁设备类 */
class_destroy(pwm_priv->class);
return ret;
}
/* PWM驱动 remove 实现 */
static int pwm_remove(struct platform_device *pdev)
{
/* 获取私有数据 */
struct pwm_dev *pwm_priv = dev_get_drvdata(&pdev->dev);
/* 关闭PWM输出 */
pwm_disable(pwm_priv->pwm);
/* 删除属性组*/
sysfs_remove_group(&pwm_priv->device->kobj, &pwm_sysfs_attr_group);
/* 销毁设备节点 */
device_destroy(pwm_priv->class, MKDEV(0, 0));
/* 销毁设备类 */
class_destroy(pwm_priv->class);
/* 清空PWM设备句柄 */
pwm_priv->pwm = NULL;
printk(KERN_INFO "pwm driver remove\n");
return 0;
}
/* 定义设备树匹配表 */
static const struct of_device_id pwm_of_match[] = {
{ .compatible = "fire,pwm_subsystem" },
{ }
};
/* 导出设备树匹配表 */
MODULE_DEVICE_TABLE(of, pwm_of_match);
/* 平台驱动结构体 */
static struct platform_driver pwm_driver = {
.probe = pwm_probe,
.remove = pwm_remove,
.driver = {
.name = "pwm_sysfs",
.owner = THIS_MODULE,
.of_match_table = pwm_of_match,
},
};
module_platform_driver(pwm_driver);
|
关键解析:
第18行:初始化互斥锁,用于保护PWM参数的并发访问。
第49行:class_create创建设备类(/sys/class/pwm-sysfs)。
第57-58行:device_create创建设备节点,纯sysfs驱动不使用字符设备,设备号使用MKDEV(0, 0),即空设备号,是Linux内核标准用法。
第66-68行:通过dev_set_drvdata将pwm_priv绑定到设备节点和平台设备,后续在show/store函数中通过dev_get_drvdata获取,实现资源共享。
第71行:sysfs_create_group创建sysfs属性组(/sys/class/pwm-sysfs/pwm-test)。
第99行:卸载驱动时调用sysfs_remove_group删除属性组。
第134行:module_platform_driver宏等价于module_init(pwm_init)和module_exit(pwm_exit),简化驱动注册流程。
sysfs属性组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /* 定义sysfs设备属性 */
static DEVICE_ATTR_RW(enable);
static DEVICE_ATTR_RW(period);
static DEVICE_ATTR_RW(duty_cycle);
static DEVICE_ATTR_RW(polarity);
/* 注册属性到属性组 */
static struct attribute *pwm_sysfs_attrs[] = {
&dev_attr_enable.attr,
&dev_attr_period.attr,
&dev_attr_duty_cycle.attr,
&dev_attr_polarity.attr,
NULL,
};
/* 定义属性组 */
static struct attribute_group pwm_sysfs_attr_group = {
.attrs = pwm_sysfs_attrs,
};
|
关键解析:
宏说明:DEVICE_ATTR_RW(name) 等价于创建一个名为name的sysfs属性,自动关联show(读)和store(写)函数,函数名固定为name_show/name_store。
注册属性到属性组:定义一个attribute类型的指针数组,存储所有需要注册的sysfs属性地址,数组末尾必须以NULL结尾,标识属性列表结束。
定义属性组:attribute_group结构体核心成员为attrs,指向上面定义的属性数组,通过属性组,可以一次性创建/删除所有关联的sysfs属性,避免重复操作。
sysfs节点读写函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | /* enable节点show函数:读取使能状态 */
static ssize_t enable_show(struct device *dev, struct device_attribute *attr, char *buf)
{
ssize_t ret;
/* 获取私有数据 */
struct pwm_dev *pwm_priv = dev_get_drvdata(dev);
/* 上锁 */
mutex_lock(&pwm_priv->lock);
/* 数字转字符串,让用户能看懂内核的参数 */
ret = sprintf(buf, "%u\n", pwm_priv->param.enable);
/* 解锁 */
mutex_unlock(&pwm_priv->lock);
return ret;
}
/* enable节点store函数:设置使能/关闭 */
static ssize_t enable_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
unsigned int val;
/* 获取私有数据 */
struct pwm_dev *pwm_priv = dev_get_drvdata(dev);
/* 上锁 */
mutex_lock(&pwm_priv->lock);
/* 按10进制转换,把用户写入的字符串转换成内核能用的数字 */
ret = kstrtouint(buf, 10, &val);
if (ret || (val != 0 && val != 1)) {
dev_err(dev, "enable must be 0 or 1\n");
mutex_unlock(&pwm_priv->lock);
return -EINVAL;
}
pwm_priv->param.enable = val;
/* 配置PWM使能/关闭 */
if (pwm_priv->param.enable)
pwm_enable(pwm_priv->pwm);
else
pwm_disable(pwm_priv->pwm);
/* 解锁 */
mutex_unlock(&pwm_priv->lock);
return count;
}
/* period节点show函数:读取周期 */
static ssize_t period_show(struct device *dev, struct device_attribute *attr, char *buf)
{
ssize_t ret;
/* 获取私有数据 */
struct pwm_dev *pwm_priv = dev_get_drvdata(dev);
/* 上锁 */
mutex_lock(&pwm_priv->lock);
/* 数字转字符串,让用户能看懂内核的参数 */
ret = sprintf(buf, "%u\n", pwm_priv->param.period);
/* 解锁 */
mutex_unlock(&pwm_priv->lock);
return ret;
}
/* period节点store函数:设置周期 */
static ssize_t period_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
unsigned int val;
/* 获取私有数据 */
struct pwm_dev *pwm_priv = dev_get_drvdata(dev);
/* 上锁 */
mutex_lock(&pwm_priv->lock);
/* 按10进制转换,把用户写入的字符串转换成内核能用的数字 */
ret = kstrtouint(buf, 10, &val);
if (ret || val == 0) {
dev_err(dev, "invalid period value\n");
mutex_unlock(&pwm_priv->lock);
return -EINVAL;
}
pwm_priv->param.period = val;
/* 重新配置PWM */
ret = pwm_config(pwm_priv->pwm, pwm_priv->param.duty_cycle, pwm_priv->param.period);
/* 解锁 */
mutex_unlock(&pwm_priv->lock);
return ret ? ret : count;
}
/* duty_cycle节点show函数:读取占空比 */
static ssize_t duty_cycle_show(struct device *dev, struct device_attribute *attr, char *buf)
{
ssize_t ret;
/* 获取私有数据 */
struct pwm_dev *pwm_priv = dev_get_drvdata(dev);
/* 上锁 */
mutex_lock(&pwm_priv->lock);
/* 数字转字符串,让用户能看懂内核的参数 */
ret = sprintf(buf, "%u\n", pwm_priv->param.duty_cycle);
/* 解锁 */
mutex_unlock(&pwm_priv->lock);
return ret;
}
/* duty_cycle节点store函数:设置占空比 */
static ssize_t duty_cycle_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
unsigned int val;
/* 获取私有数据 */
struct pwm_dev *pwm_priv = dev_get_drvdata(dev);
/* 上锁 */
mutex_lock(&pwm_priv->lock);
/* 按10进制转换,把用户写入的字符串转换成内核能用的数字 */
ret = kstrtouint(buf, 10, &val);
if (ret || val > pwm_priv->param.period) {
dev_err(dev, "invalid duty_cycle value\n");
mutex_unlock(&pwm_priv->lock);
return -EINVAL;
}
pwm_priv->param.duty_cycle = val;
/* 重新配置PWM */
ret = pwm_config(pwm_priv->pwm, pwm_priv->param.duty_cycle, pwm_priv->param.period);
/* 解锁 */
mutex_unlock(&pwm_priv->lock);
return ret ? ret : count;
}
/* polarity节点show函数:读取极性 */
static ssize_t polarity_show(struct device *dev, struct device_attribute *attr, char *buf)
{
ssize_t ret;
/* 获取私有数据 */
struct pwm_dev *pwm_priv = dev_get_drvdata(dev);
/* 上锁 */
mutex_lock(&pwm_priv->lock);
/* 数字转字符串,让用户能看懂内核的参数 */
ret = sprintf(buf, "%u\n", pwm_priv->param.polarity);
/* 解锁 */
mutex_unlock(&pwm_priv->lock);
return ret;
}
/* polarity节点store函数:设置极性 */
static ssize_t polarity_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
unsigned int val;
/* 获取私有数据 */
struct pwm_dev *pwm_priv = dev_get_drvdata(dev);
/* 上锁 */
mutex_lock(&pwm_priv->lock);
/* 按10进制转换,把用户写入的字符串转换成内核能用的数字 */
ret = kstrtouint(buf, 10, &val);
if (ret || (val != 0 && val != 1)) {
dev_err(dev, "polarity must be 0 or 1\n");
mutex_unlock(&pwm_priv->lock);
return -EINVAL;
}
pwm_priv->param.polarity = val;
/* 配置PWM极性 */
ret = pwm_set_polarity(pwm_priv->pwm, pwm_priv->param.polarity);
/* 解锁 */
mutex_unlock(&pwm_priv->lock);
return ret ? ret : count;
}
|
关键解析:
show函数是属性文件的读操作函数,用户层cat文件时调用;
store函数是写操作函数,用户层echo文件时调用;
互斥锁保护:每个读写函数都遵循“上锁->操作->解锁”的流程,确保同一时间只有一个进程能修改或读取PWM参数,避免并发冲突。
参数合法性校验:
enable:只能写入0(关闭)或1(开启),其他值均返回错误。
period:不能为0,否则返回错误,避免周期无效。
duty_cycle:不能大于当前周期,否则返回错误,避免占空比无效。
polarity:只能写入0(正常)或1(反相),其他值返回错误。
私有数据获取:通过dev_get_drvdata(dev)获取绑定在设备上的私有数据(pwm_dev结构体),实现各函数间的资源共享。
11.5.3. 程序运行结果¶
如出现 Permission denied 或类似字样,请注意用户权限,大部分操作硬件外设的功能,几乎都需要root用户权限,简单的解决方案是在执行语句前加入sudo或以root用户运行程序。
11.5.3.1. 实验操作¶
使用以下命令加载驱动和运行测试程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #加载驱动
sudo insmod pwm_sysfs.ko
#信息输出如下
[ 284.955874] pwm driver probe
#进入sysfs节点目录
cd /sys/class/pwm-sysfs/pwm-test/
#查看所有sysfs节点
ls -l
#信息输出如下
total 0
lrwxrwxrwx 1 root root 0 Apr 18 09:29 device -> ../../../pwm-test
-rw-r--r-- 1 root root 4096 Apr 18 09:29 duty_cycle
-rw-r--r-- 1 root root 4096 Apr 18 09:29 enable
-rw-r--r-- 1 root root 4096 Apr 18 09:29 period
-rw-r--r-- 1 root root 4096 Apr 18 09:29 polarity
drwxr-xr-x 2 root root 0 Apr 18 09:29 power
lrwxrwxrwx 1 root root 0 Apr 18 09:29 subsystem -> ../../../../../class/pwm-sysfs
-rw-r--r-- 1 root root 4096 Apr 18 09:28 uevent
|
可以看到各属性目录创建成功,默认权限为0644,执行以下命令读取PWM默认参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #读取使能状态
cat enable
#信息输出如下
0
#读取周期
cat period
#信息输出如下
10000
#读取占空比
cat duty_cycle
#信息输出如下
5000
#读取极性(默认0,正常)
cat polarity
#信息输出如下
0
#查看pwm注册情况
sudo cat /sys/kernel/debug/pwm
#信息输出如下
platform/fe6f0010.pwm, 1 PWM device
pwm-0 (pwm-test ): requested period: 10000 ns duty: 5000 ns polarity: normal
platform/fe6e0010.pwm, 1 PWM device
pwm-0 (backlight1 ): requested period: 50000 ns duty: 0 ns polarity: normal
platform/fe6e0000.pwm, 1 PWM device
pwm-0 (backlight ): requested period: 50000 ns duty: 0 ns polarity: normal
platform/fdd70000.pwm, 1 PWM device
pwm-0 ((null) ): period: 0 ns duty: 0 ns polarity: inverse
|
可以看到名字为pwm-test的PWM接口,当前周期为10000 ns,占空比5000 ns,极性为normal,和sysfs节点下的属性文件查看到的值一致。
执行以下命令配置PWM参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #配置需要root执行,普通用户没有写权限
#配置周期
sudo sh -c "echo 20000 > period"
#配置占空比
sudo sh -c "echo 10000 > duty_cycle"
#配置极性
sudo sh -c "echo 1 > polarity"
#配置使能
sudo sh -c "echo 1 > enable"
#查看pwm注册情况
sudo cat /sys/kernel/debug/pwm
#信息输出如下
platform/fe6f0010.pwm, 1 PWM device
pwm-0 (pwm-test ): requested enabled period: 20000 ns duty: 10000 ns polarity: inverse
platform/fe6e0010.pwm, 1 PWM device
pwm-0 (backlight1 ): requested period: 50000 ns duty: 0 ns polarity: normal
platform/fe6e0000.pwm, 1 PWM device
pwm-0 (backlight ): requested period: 50000 ns duty: 0 ns polarity: normal
platform/fdd70000.pwm, 1 PWM device
pwm-0 ((null) ): period: 0 ns duty: 0 ns polarity: inverse
|
可以看到名字为pwm-test的PWM接口,当前周期为20000 ns,占空比10000 ns,极性为inverse,和修改sysfs节点下的属性文件的值一致。
使用示波器量对应引脚的波形如下图所示:
测量到波形为方波,频率为50.0030khz(频率=1/周期),占空比49.99%,和修改sysfs节点下的属性文件的值一致。
11.5.4. 实验注意事项¶
硬件连接:需确认开发板PWM对应的物理引脚,避免接错引脚导致无波形输出;示波器探头连接时,接地夹需可靠接地,避免干扰导致波形失真,影响实验验证。
若无sysfs节点:驱动未加载成功或probe函数执行失败,重点排查设备树匹配、PWM资源获取、设备类/节点创建、sysfs属性组注册步骤。
若无法写入参数:检查权限是否为root、写入的参数是否合法,查看dmesg日志获取错误信息,针对性排查。
若波形异常:如果波形周期、占空比与配置不符,需检查传入的参数是否正确,驱动中pwm_config函数的参数是否正确。