这篇文章定性的分析了设备模型。也许fudan_adb的,还有http://blogold.chinaunix.net/u1/51562/showart_1077877.html都定量的分析了设备模型。我定量的分析了一个礼拜,总感觉有点说不明白。通过这篇定性的分析,先有点轮廓,以后随着驱动开发的深入,也许定量的分析会容易一点啦!
在这也总结一下自己的理解。首先,我们的驱动和设备都是挂在总线上的,所以我们向内核注册设备或者是驱动时,必须有总线的存在。第一步我们做的就是把总线注册进内核。但内核一般把我们常用的总线都已经注册进了内核,一般不需要我们注册;但如果我们自己要进行usb host控制器的开发,那么就需要我们自己注册usb总线了。注册总线要调用bus_register(struct bus_type)函数,此函数会在/sys/bus目录下创建一个指点名字xxx(此名字包含在bus_type结构体中)的文件,另外还会在/sys/bus/xxx目录下创建devices和drivers目录。然后,当我们开发了一个驱动,在驱动中,我们要指定此驱动挂载的总线类型,不让当你insmod驱动模块时,内核不知道该把驱动模块加载到哪个bus目录下的drivers目录中。驱动模块的注册用driver_register(struct device_driver),但这个函数没有分析过。不确定她都干了些什么,但一点是确定的,也是她最后的任务,用此驱动去匹配../devices目录下的设备,看有没有能驱动的设备,如果有标记她可以驱动此设备。最后,如果我们要向内核中注册设备,设备应该也指出自己要挂载的总线类型。驱动模块注册是调用device_register(struct device)函数,此函数前面分析过,但理解的不是很透彻。说一下自己的理解,device_register()是把设备注册到内核,会在/sys/devices目录下创建相应的设备文件,并在/sys/bus/xxx/devices目录下创建与/sys/devices文件下的文件连接,可能还有其他的文件连接但没有注意过。她也要做一件事,就是和/sys/bus/xxx/drivers目录下的driver进行匹配,看有没有能驱动她的driver,如果有做适当的标识。
这只是大概的理解,随后还有很多事情要作,一方面,上面的这些理解是否都正确,在代码级又是如何实现的。另一方面,fs,inode在/sys下有是什么样的呢?
sysfs 与 /sys
sysfs 文件系统总是被挂载在 /sys 挂载点上。虽然在较早期的2.6内核系统上并没有规定 sysfs 的标准挂载位置,可以把 sysfs 挂载在任何位置,但较近的2.6内核修正了这一规则,要求 sysfs 总是挂载在 /sys 目录上;针对以前的 sysfs 挂载位置不固定或没有标准被挂载,有些程序从 /proc/mounts 中解析出 sysfs 是否被挂载以及具体的挂载点,这个步骤现在已经不需要了。请参考附录给出的 sysfs-rules.txt 文件链接.
sysfs 与 proc
sysfs 与 proc 相比有很多优点,最重要的莫过于设计上的清晰。一个 proc 虚拟文件可能有内部格式,如 /proc/scsi/scsi ,它是可读可写的,(其文件权限被错误地标记为了 0444 !,这是内核的一个BUG),并且读写格式不一样,代表不同的操作,应用程序中读到了这个文件的内容一般还需要进行字符串解析,而在写入时需要先用字符串格式化按指定的格式写入字符串进行操作;相比而言, sysfs 的设计原则是一个属性文件只做一件事情, sysfs 属性文件一般只有一个值,直接读取或写入。整个 /proc/scsi 目录在2.6内核中已被标记为过时(LEGACY),它的功能已经被相应的 /sys 属性文件所完全取代。新设计的内核机制应该尽量使用 sysfs 机制,而将 proc 保留给纯净的“进程文件系统”。
初识 /sys
清单 1. 与 /sys 文件系统的一次交互
这是在 Ubuntu 10.01 的 2.6.32-24-generic 的内核上,可以看到在 /sys 目录下有 block, bus, class, dev, devices, firmware, fs, kernel, module, power 这些子目录,本文将分别介绍这些目录存在的含义。(查看内核版本的命令为:uname -rv)
上面的ls 命令展示了在一个 pci 设备目录下的文件, "ls" 命令的 "-F" 命令为所列出的每个文件使用后缀来显示文件的类型,后缀 "/" 表示列出的是目录,后缀 "@" 表示列出的是符号链接文件。可以看到第二个目录下包含有普通文件 (regular file) 和符号链接文件 (symbolic link file) ,本文也将以这个具体的设备为例说明其中每一个普通文件的用途。
/sys 文件系统下的目录结构
/sys 下的目录结构是经过精心设计的:在 /sys/devices 下是所有设备的真实对象,包括如视频卡和以太网卡等真实的设备,也包括 ACPI 等不那么显而易见的真实设备、还有 tty, bonding 等纯粹虚拟的设备;在其它目录如 class, bus 等中则在分类的目录中含有大量对 devices 中真实对象引用的符号链接文件; 清单1 中在 /sys 根目录下顶层目录的意义如下:
表 1. /sys 下的目录结构
/sys 下的子目录 | 所包含的内容 |
/sys/devices | 这是内核对系统中所有设备的分层次表达模型,也是 /sys 文件系统管理设备的最重要的目录结构,下文会对它的内部结构作进一步分析; |
/sys/dev | 这个目录下维护一个按字符设备和块设备的主次号码(major:minor)链接到真实的设备(/sys/devices下)的符号链接文件,它是在内核 2.6.26 首次引入;(可以看到此文件下的都是连接到/sys/devices目录下的文件的文件连接) |
/sys/bus | 这是内核设备按总线类型分层放置的目录结构, devices 中的所有设备都是连接于某种总线之下,在这里的每一种具体总线之下可以找到每一个具体设备的符号链接,它也是构成 Linux 统一设备模型的一部分;(这里的设备也是符号连接,是连接到/sys/devices目录下的文件) |
/sys/class | 这是按照设备功能分类的设备模型,如系统所有输入设备都会出现在 /sys/class/input 之下,而不论它们是以何种总线连接到系统。它也是构成 Linux 统一设备模型的一部分; |
/sys/block | 这里是系统中当前所有的块设备所在,按照功能来说放置在 /sys/class 之下会更合适,但只是由于历史遗留因素而一直存在于 /sys/block, 但从 2.6.22 开始就已标记为过时,只有在打开了 CONFIG_SYSFS_DEPRECATED 配置下编译才会有这个目录的存在,并且在 2.6.26 内核中已正式移到 /sys/class/block, 旧的接口 /sys/block 为了向后兼容保留存在,但其中的内容已经变为指向它们在 /sys/devices/ 中真实设备的符号链接文件; |
/sys/firmware | 这里是系统加载固件机制的对用户空间的接口,关于固件有专用于固件加载的一套API,在附录 LDD3 一书中有关于内核支持固件加载机制的更详细的介绍; |
/sys/fs | 这里按照设计是用于描述系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点,但目前只有 fuse,gfs2 等少数文件系统支持 sysfs 接口,一些传统的虚拟文件系统(VFS)层次控制参数仍然在 sysctl (/proc/sys/fs) 接口中中; |
/sys/kernel | 这里是内核所有可调整参数的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的 slab 分配器等几项较新的设计在使用它,其它内核可调整参数仍然位于 sysctl (/proc/sys/kernel) 接口中 ; |
/sys/module | 这里有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(ko文件),都可能会出现在 /sys/module 中: 编译为外部模块(ko文件)在加载后会出现对应的 /sys/module/<module_name>/, 并且在这个目录下会出现一些属性文件和属性目录来表示此外部模块的一些信息,如版本号、加载状态、所提供的驱动程序等; 编译为内联方式的模块则只在当它有非0属性的模块参数时会出现对应的 /sys/module/<module_name>, 这些模块的可用参数会出现在 /sys/modules/<modname>/parameters/<param_name> 中, 如 /sys/module/printk/parameters/time 这个可读写参数控制着内联模块 printk 在打印内核消息时是否加上时间前缀; 所有内联模块的参数也可以由 "<module_name>.<param_name>=<value>" 的形式写在内核启动参数上,如启动内核时加上参数 "printk.time=1" 与 向 "/sys/module/printk/parameters/time" 写入1的效果相同; 没有非0属性参数的内联模块不会出现于此。 |
/sys/power | 这里是系统中电源选项,这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机、重启等。 |
/sys/slab (对应 2.6.23 内核,在 2.6.24 以后移至 /sys/kernel/slab) | 从2.6.23 开始可以选择 SLAB 内存分配器的实现,并且新的 SLUB(Unqueued Slab Allocator)被设置为缺省值;如果编译了此选项,在 /sys 下就会出现 /sys/slab ,里面有每一个 kmem_cache 结构体的可调整参数。对应于旧的 SLAB 内存分配器下的 /proc/slabinfo 动态调整接口,新式的 /sys/kernel/slab/<slab_name> 接口中的各项信息和可调整项显得更为清晰。 |
接下来对 /sys/devices/ 下的目录结构作进一步探讨:
可以看到,在 /sys/devices/ 目录下是按照设备的基本总线类型分类的目录,再进入进去查看其中的 PCI 类型的设备。
清单 3. 查看 /sys/devices/pci0000:00/ 的目录结构
在 /sys/devices/pci0000:00/ 目录下是按照 PCI 总线接入的设备号分类存放的目录,再查看其中一个,
清单 4. 查看 /sys/devices/pci0000:00/ 的目录结构
可以看到,(其中有一个目录 0000:01:00.0/,我的版本中没有此文件) ,其它都是属性文件和属性组,而如果对 0000:01:00.0/ 子目录中进行再列表查看则会得到 清单1 的目录结构。
继续以上过程可以了解整个目录树的结构,这里把它整理成 图 1. sysfs 目录层次图
图 1. sysfs 目录层次图
其中涉及到 ksets, kobjects, attrs 等很多术语,这就不得不提到 Linux 统一设备模型。
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 统一设备模型的基本结构 :
类型 | 所包含的内容 | 对应内核数据结构 | 对应/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;
};
name指针指向kobject名称的起始位置
parent指针指向kobject的父对象。因此,kobject就会在内核中构造一个对象层次结构,并且可以将对各对象间的关系表现出来,就如你看到的,这便是sysfs的真正面目:一个用户空间的文件系统,用来表示内核中kobject对象的层次结构。
struct kref 内含一个 atomic_t 类型用于引用计数
struct list_head 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 接口也有不同的属性文件;而相同类型的设备上有很多相同的属性文件