内核向用户空间传递数据-------sysfs


添加 sysfs 支持
如果你正在开发的设备驱动程序中需要与用户层的接口,一般可选的方法有:
  1. 注册虚拟的字符设备文件,以这个虚拟设备上的 read/write/ioctl 等接口与用户交互;但 read/write 一般只能做一件事情, ioctl 可以根据 cmd 参数做多个功能,但其缺点是很明显的: ioctl 接口无法直接在 Shell 脚本中使用,为了使用 ioctl 的功能,还必须编写配套的 C语言的虚拟设备操作程序, ioctl 的二进制数据接口也是造成大小端问题 (big endian与little endian)、32位/64位不可移植问题的根源;
  2. 注册 proc 接口,接受用户的 read/write/ioctl 操作;同样的,一个 proc 项通常使用其 read/write/ioctl 接口,它所存在的问题与上面的虚拟字符设备的的问题相似;
  3. 注册 sysfs 属性;
最重要的是,添加虚拟字符设备支持和注册 proc 接口支持这两者所需要增加的代码量都并不少,最好的方法还是使用 sysfs 属性支持,一切在用户层是可见的透明,且增加的代码量是最少的,可维护性也最好;方法就是使用 <include/linux/device.h> 头文件提供的这四个宏,分别应用于总线/类别/驱动/设备四种内核数据结构对象上:
#define BUS_ATTR(_name, _mode, _show, _store) \struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)#define CLASS_ATTR(_name, _mode, _show, _store) \struct class_attribute class_attr_##_name = __ATTR(_name, _mode, _show, _store)#define DRIVER_ATTR(_name, _mode, _show, _store) \struct driver_attribute driver_attr_##_name = \ __ATTR(_name, _mode, _show, _store)#define DEVICE_ATTR(_name, _mode, _show, _store) \struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
总线(BUS)和类别(CLASS)属性一般用于新设计的总线和新设计的类别,这两者一般是不用的;因为你的设备一般是以PCI等成熟的常规方式连接到主机,而不会去新发明一种类型;使用驱动属性和设备属性的区别就在于:看你的 sysfs 属性设计是针对整个驱动有效的还是针对这份驱动所可能支持的每个设备分别有效。
从头文件中还可以找到 show/store 函数的原型,注意到它和虚拟字符设备或 proc 项的 read/write 的作用很类似,但有一点不同是 show/store 函数上的 buf/count 参数是在 sysfs 层已作了用户区/内核区的内存复制,虚拟字符设备上常见的 __user 属性在这里并不需要,因而也不需要多一次 copy_from_user/copy_to_user, 在 show/store 函数参数上的 buf/count 参数已经是内核区的地址,可以直接操作。
上面四种都是 Linux 统一设备模型所添加的高级接口,如果使用 sysfs 所提供的底层接口的话,则还有下面两个,定义来自 <include/linux/sysfs.h> :(上面的总线/类别/驱动/设备四个接口都是以这里的__ATTR实现的)
#define __ATTR(_name,_mode,_show,_store) { \ .attr = {.name = __stringify(_name), .mode = _mode }, \ .show = _show, \.store = _store, \}#define __ATTR_RO(_name) { \ .attr = { .name = __stringify(_name), .mode = 0444 }, \ .show = _name##_show, \}
上面这些宏都是在注册总线/类别/驱动/设备时作为缺省属性而使用的,在实际应用中还有一种情况是根据条件动态添加属性,如 PCI 设备上的 resource{0,1,2,...} 属性文件,因为一个 PCI 设备上的可映射资源究竟有多少无法预知,也只能以条件判断的方式动态添加上。
int __must_check sysfs_create_file(struct kobject *kobj, const struct attribute *attr);int __must_check sysfs_create_bin_file(struct kobject *kobj, struct bin_attribute *attr);
这两个函数可以对一个 kobject 动态添加上文本属性或二进制属性,这也是唯一可以添加二进制属性的方法。
二进制属性与普通文本属性的区别在于:
  • 二进制属性 struct bin_attribute 中内嵌一个 struct attribute 结构体对象,因此具有普通属性的所有功能特征;
  • 二进制属性上多一个 size 用来描述此二进制文件的大小,而普通属性文件的大小总是 4096, 准确地说,应该是一个内存页的大小,因为从当前 sysfs 内核实现来说,它分配一个内存页面来作为 (buf/count) 的缓冲区;
  • 二进制属性比普通属性多内存映射(mmap)接口的支持;

Linux 统一设备模型

在 Linux 2.5 内核的开发过程中,人们设计了一套新的设备模型,目的是为了对计算机上的所有设备进行统一地表示和操作,包括设备本身和设备之间的连接关系。这个模型是在分析了 PCI 和 USB 的总线驱动过程中得到的,这两个总线类型能代表当前系统中的大多数设备类型,它们都有完善的热挺拔机制和电源管理的支持,也都有级连机制的支持,以桥接的 PCI/USB 总线控制器的方式可以支持更多的 PCI/USB 设备。为了给所有设备添加统一的电源管理的支持,而不是让每个设备中去独立实现电源管理的支持,人们考虑的是如何尽可能地重用代码;而且在有层次模型的 PCI/USB 总线中,必须以合理形式展示出这个层次关系,这也是电源管理等所要求的必须有层次结构。

如在一个典型的 PC 系统中,中央处理器(CPU)能直接控制的是 PCI 总线设备,而 USB 总线设备是以一个 PCI 设备(PCI-USB桥)的形式接入在 PCI 总线设备上,外部 USB 设备再接入在 USB 总线设备上;当计算机执行挂起(suspend)操作时, Linux 内核应该以 “外部USB设备->USB总线设备->PCI总线设备” 的顺序通知每一个设备将电源挂起;执行恢复(resume)时则以相反的顺序通知;反之如果不按此顺序则将有设备得不到正确的电源状态变迁的通知,将无法正常工作。

sysfs 是在这个 Linux 统一设备模型的开发过程中的一项副产品(见 参考资料 中 Greg K. Hartman 写作的 LinuxJournal 文章)。为了将这些有层次结构的设备以用户程序可见的方式表达出来,人们很自然想到了利用文件系统的目录树结构(这是以 UNIX 方式思考问题的基础,一切都是文件!)在这个模型中,有几种基本类型,它们的对应关系见 表 2. Linux 统一设备模型的基本结构 :
表 2. Linux 统一设备模型的基本结构
类型 所包含的内容 对应内核数据结构 对应/sys项
设备(Devices) 设备是此模型中最基本的类型,以设备本身的连接按层次组织 struct device /sys/devices/*/*/.../
设备驱动(Device Drivers) 在一个系统中安装多个相同设备,只需要一份驱动程序的支持 struct device_driver /sys/bus/pci/drivers/*/
总线类型(Bus Types) 在整个总线级别对此总线上连接的所有设备进行管理 struct bus_type /sys/bus/*/
设备类别(Device Classes) 这是按照功能进行分类组织的设备层次树;如 USB 接口和 PS/2 接口的鼠标都是输入设备,都会出现在 /sys/class/input/ 下 struct class /sys/class/*/

从内核在实现它们时所使用的数据结构来说, Linux 统一设备模型又是以两种基本数据结构进行树型和链表型结构组织的:

kobject: 在 Linux 设备模型中最基本的对象,它的功能是提供引用计数和维持父子(parent)结构、平级(sibling)目录关系,上面的 device, device_driver 等各对象都是以 kobject 基础功能之上实现的;

struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct sysfs_dirent *sd;
struct kref kref;
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
};


其中 struct kref 内含一个 atomic_t 类型用于引用计数, parent 是单个指向父节点的指针, entry 用于父 kset 以链表头结构将 kobject 结构维护成双向链表;
kset: 它用来对同类型对象提供一个包装集合,在内核数据结构上它也是由内嵌一个 kboject 实现,因而它同时也是一个 kobject (面向对象 OOP 概念中的继承关系) ,具有 kobject 的全部功能;

struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
struct kset_uevent_ops *uevent_ops;
};


其中的 struct list_head list 用于将集合中的 kobject 按 struct list_head entry 维护成双向链表;

涉及到文件系统实现来说, sysfs 是一种基于 ramfs 实现的内存文件系统,与其它同样以 ramfs 实现的内存文件系统(configfs,debugfs,tmpfs,...)类似, sysfs 也是直接以 VFS 中的 struct inode 和 struct dentry 等 VFS 层次的结构体直接实现文件系统中的各种对象;同时在每个文件系统的私有数据 (如 dentry->d_fsdata 等位置) 上,使用了称为 struct sysfs_dirent 的结构用于表示 /sys 中的每一个目录项。

struct sysfs_dirent {
atomic_t s_count;
atomic_t s_active;
struct sysfs_dirent *s_parent;
struct sysfs_dirent *s_sibling;
const char *s_name;

union {
struct sysfs_elem_dir s_dir;
struct sysfs_elem_symlink s_symlink;
struct sysfs_elem_attr s_attr;
struct sysfs_elem_bin_attr s_bin_attr;
};

unsigned int s_flags;
ino_t s_ino;
umode_t s_mode;
struct iattr *s_iattr;
};

在上面的 kobject 对象中可以看到有向 sysfs_dirent 的指针,因此在sysfs中是用同一种 struct sysfs_dirent 来统一设备模型中的 kset/kobject/attr/attr_group.

具体在数据结构成员上, sysfs_dirent 上有一个 union 共用体包含四种不同的结构,分别是目录、符号链接文件、属性文件、二进制属性文件;其中目录类型可以对应 kobject,在相应的 s_dir 中也有对 kobject 的指针,因此在内核数据结构, kobject 与 sysfs_dirent 是互相引用的;

有了这些概念,再来回头看 图 1. sysfs 目录层次图 所表达的 /sys 目录结构就是非常清晰明了:

在 /sys 根目录之下的都是 kset,它们组织了 /sys 的顶层目录视图;
在部分 kset 下有二级或更深层次的 kset;
每个 kset 目录下再包含着一个或多个 kobject,这表示一个集合所包含的 kobject 结构体;
在 kobject 下有属性(attrs)文件和属性组(attr_group),属性组就是组织属性的一个目录,它们一起向用户层提供了表示和操作这个 kobject 的属性特征的接口;
在 kobject 下还有一些符号链接文件,指向其它的 kobject,这些符号链接文件用于组织上面所说的 device, driver, bus_type, class, module 之间的关系;
不同类型如设备类型的、设备驱动类型的 kobject 都有不同的属性,不同驱动程序支持的 sysfs 接口也有不同的属性文件;而相同类型的设备上有很多相同的属性文件;

注意,此表内容是按照最新开发中的 2.6.28 内核的更新组织的,在附录资源如 LDD3 等位置中有提到 sysfs 中曾有一种管理对象称为 subsys (子系统对象),在最新的内核中经过重构认为它是不需要的,它的功能完全可以由 kset 代替,也就是说 sysfs 中只需要一种管理结构是 kset,一种代表具体对象的结构是 kobject,在 kobject 下再用属性文件表示这个对象所具有的属性;



例:




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值