本文摘自网络

2.6内核要求 sysfs 总是挂载在 /sys 目录上.

sysfs 与 proc

sysfs 与 proc 相比最重要的莫过于设计上的清晰。一个 proc 虚拟文件可能有内部格式,如 /proc/scsi/scsi ,它是可读可写的,(其文件权限被错误地标记为了 0444 !,这是内核的一个BUG),并且读写格式不一样,代表不同的操作,应用程序中读到了这个文件的内容一般还需要进行字符串解析,而在写入时需要先用字符串格式化按指定的格式写入字符串进行操作;相比而言, sysfs 的设计原则是一个属性文件只做一件事情, sysfs 属性文件一般只有一个值,直接读取或写入。整个 /proc/scsi 目录在2.6内核中已被标记为过时(LEGACY),它的功能已经被相应的 /sys 属性文件所完全取代。新设计的内核机制应该尽量使用 sysfs 机制。

初识 /sys

"ls" 命令的 "-F" 命令为所列出的每个文件使用后缀来显示文件的类型,后缀 "/" 表示列出的是目录,后缀 "@" 表示列出的是符号链接文件。可以看到第二个目录下包含有普通文件 (regular file) 和符号链接文件 (symbolic link file) 。

在 /sys/devices 下是所有设备的真实对象,包括如视频卡和以太网卡等真实的设备,也包括 ACPI 等不那么显而易见的真实设备、还有 tty, bonding 等纯粹虚拟的设备;在其它目录如 class, bus 等中则在分类的目录中含有大量对 devices 中真实对象引用的符号链接文件;

/sys/dev

这个目录下维护一个按字符设备和块设备的主次号码(major:minor)链接到真实的设备(/sys/devices下)的符号链接文件,它是在内核 2.6.26 首次引入;

/sys/bus

这是内核设备按总线类型分层放置的目录结构, devices 中的所有设备都是连接于某种总线之下,在这里的每一种具体总线之下可以找到每一个具体设备的符号链接

/sys/class

按照设备功能分类的设备模型,如系统所有输入设备都会出现在 /sys/class/input 之下,而不论它们是以何种总线连接到系统。

/sys/block

在 2.6.26 内核中已正式移到 /sys/class/block, 旧的接口 /sys/block 为了向后兼容保留存在,但其中的内容已经变为指向它们在 /sys/devices/ 中真实设备的符号链接文件;

/sys/firmware

系统加载固件机制的对用户空间的接口,关于固件有专用于固件加载的一套API

/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/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> 接口中的各项信息和可调整项显得更为清晰。

图 1. sysfs 目录层次图

新的设备模型是为了对计算机上的所有设备进行统一地表示和操作,包括设备本身和设备之间的连接关系。

为了给所有设备添加统一的电源管理的支持,而不是让每个设备中去独立实现电源管理的支持。当计算机执行挂起(suspend)操作时, Linux 内核应该以 “外部USB设备->USB总线设备->PCI总线设备” 的顺序通知每一个设备将电源挂起;执行恢复(resume)时则以相反的顺序通知。

统一设备模型的基本结构

类型

所包含的内容

对应内核数据结构

对应/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: 提供引用计数和维持父子(parent)结构、平级(sibling)目录关系,上面的 device, device_driver 等各对象都是以 kobject 基础功能之上实现的;其中 struct kref 内含一个 atomic_t 类型用于引用计数, parent 是单个指向父节点的指针, entry 用于父 kset 以链表头结构将 kobject 结构维护成双向链表;

kset: 它用来对同类型对象提供一个包装集合,在内核数据结构上它也是由内嵌一个 kboject 实现。其中的 struct list_head list 用于将集合中的 kobject 按 struct list_head entry 维护成双向链表;

sysfs直接以 VFS 中的 struct inode 和 struct dentry 等 VFS 层次的结构体直接实现文件系统中的各种对象;同时在每个文件系统的私有数据 (如 dentry->d_fsdata 等位置) 上,使用了称为 struct sysfs_dirent 的结构用于表示 /sys 中的每一个目录项。 kobject 对象中可以看到sysfs_dirent 的指针,因此在sysfs中是用同一种 struct sysfs_dirent 来统一设备模型中的 kset/kobject/attr/attr_group。sysfs_dirent 上有一个 union 共用体包含四种不同的结构,分别是目录、符号链接文件、属性文件、二进制属性文件;其中目录类型可以对应 kobject,在相应的 s_dir 中也有对 kobject 的指针,因此kobject 与 sysfs_dirent 是互相引用的。

在 /sys 根目录之下的都是 kset,它们组织了 /sys 的顶层目录视图;

在部分 kset 下有二级或更深层次的 kset;

每个 kset 目录下再包含着一个或多个 kobject,这表示一个集合所包含的 kobject 结构体;

在 kobject 下有属性(attrs)文件和属性组(attr_group),属性组就是组织属性的一个目录,它们一起向用户层提供了表示和操作这个 kobject 的属性特征的接口;

在 kobject 下还有一些符号链接文件,指向其它的 kobject,这些符号链接文件用于组织上面所说的 device, driver, bus_type, class, module 之间的关系;

使用 sysfs 的关键就是掌握这些 sysfs 属性的用法,下面以一些常见的 sysfs 属性来展示它的用法;

以一份桌面系统上的视频卡为例,列举它对应的 kobject 上的属性文件的对应用途;先找到 Xorg 的进程号,查看这个进程所使用的所有文件;

# ps xfa |grep Xorg

2001 tty1     Ss+    2:24      \_ /usr/bin/Xorg :0 -nr -verbose -auth \

/var/run/gdm/auth-for-gdm-NPrkZK/database -nolisten tcp vt1

# lsof -nP -p 2001

Xorg    2001 root  mem    REG        8,3    617732     231033 \

/usr/lib/xorg/modules/drivers/sis_drv.so

[...]

Xorg    2001 root  mem    REG        0,0 134217728       5529 \

/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/resource0

Xorg    2001 root  mem    REG        0,0    131072       5531 \

/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/resource1

[...]

Xorg    2001 root    7u   REG        0,0       256       5504 \

/sys/devices/pci0000:00/0000:00:00.0/config

Xorg    2001 root    8u  unix 0xdbe66000       0t0       8756 socket

Xorg    2001 root    9u   REG        0,0       256       5528 \

/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/config

注意到此 Xorg 服务器是以内存映射 (mem) 的形式打开了 "/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/resource0" 和 "/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/resource1" ,同时以文件读写形式 (7u,9u打开了 "/sys/devices/pci0000:00/0000:00:00.0/config" 和 "/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/config"

事实上, PCI 设备对应的 kobject 目录下的 config 正是代表PCI设备的“配置空间”,对于普通 PCI (非PCI-E)设备而言,其配置空间大小一般是 256字节,这个空间可以使用十六进制工具 dump 出来,如下。

# hexdump -C /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/config

00000000  39 10 30 63 03 00 30 02  00 00 00 03 00 00 00 80  |9.0c..0.........|

00000010  08 00 00 d8 00 00 00 e1  01 d0 00 00 00 00 00 00  |................|

00000020  00 00 00 00 00 00 00 00  00 00 00 00 19 10 30 1b  |..............0.|

00000030  00 00 00 00 40 00 00 00  00 00 00 00 00 00 00 00  |....@...........|

00000040  01 50 02 06 00 00 00 00  00 00 00 00 00 00 00 00  |.P..............|

00000050  02 00 30 00 0b 02 00 ff  00 00 00 00 00 00 00 00  |..0.............|

00000060  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

*

00000100

# lspci -v -d 1039:6330

01:00.0 VGA compatible controller: Silicon Integrated Systems [SiS] 661/741/760 PCI/AGP \

or 662/761Gx PCIE VGA Display Adapter (prog-if 00 [VGA controller])

Subsystem: Elitegroup Computer Systems Device 1b30

Flags: 66MHz, medium devsel

BIST result: 00

Memory at d8000000 (32-bit, prefetchable) [size=128M]

Memory at e1000000 (32-bit, non-prefetchable) [size=128K]

I/O ports at d000 [size=128]

Capabilities: [40] Power Management version 2

Capabilities: [50] AGP version 3.0

在 PCI 设备上除了有 config 是配置空间对用户的接口以外,还有 resource{0,1,2,...} 是资源空间,对应着 PCI 设备的可映射内存空间;此外 PCI 设备还提供了很多接口,全部列表如下:

# ls -lU /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/

总计 0

-rw-r--r-- 1 root root      4096 12-09 00:28 uevent

-r--r--r-- 1 root root      4096 12-09 00:27 resource

-r--r--r-- 1 root root      4096 12-09 00:27 vendor

-r--r--r-- 1 root root      4096 12-09 00:27 device

-r--r--r-- 1 root root      4096 12-09 00:28 subsystem_vendor

-r--r--r-- 1 root root      4096 12-09 00:28 subsystem_device

-r--r--r-- 1 root root      4096 12-09 00:27 class

-r--r--r-- 1 root root      4096 12-09 00:27 irq

-r--r--r-- 1 root root      4096 12-09 00:28 local_cpus

-r--r--r-- 1 root root      4096 12-09 00:28 local_cpulist

-r--r--r-- 1 root root      4096 12-09 00:28 modalias

-rw------- 1 root root      4096 12-09 00:28 enable

-rw-r--r-- 1 root root      4096 12-09 00:28 broken_parity_status

-rw-r--r-- 1 root root      4096 12-09 00:28 msi_bus

lrwxrwxrwx 1 root root         0 12-09 00:28 subsystem -> ../../../../bus/pci

drwxr-xr-x 2 root root         0 12-09 00:28 power

-rw-r--r-- 1 root root       256 12-08 23:03 config

-rw------- 1 root root 134217728 12-08 23:03 resource0

-rw------- 1 root root 134217728 12-09 00:28 resource0_wc

-rw------- 1 root root    131072 12-08 23:03 resource1

-rw------- 1 root root       128 12-09 00:28 resource2

-r-------- 1 root root         0 12-09 00:28 rom

可以看到很多其它属性文件,这些属性文件的权限位也都是正确的,有 w 权限位的才是可以写入。其中大小为 4096字节的属性一般是纯文本描述的属性,可以直接 cat 读出和用 echo 字符串的方法写入;其它非 4096字节大小的一般是二进制属性,类似于上面的 config 属性文件。

从 vendor, device, subsystem_vendor, subsystem_device, class, resource 这些只读属性上分别可以读到此 PCI 设备的厂商号、设备号、子系统厂商号、子系统设备号、PCI类别、资源表等,这些都是相应 PCI 设备的属性,其实就是直接从 config 二进制文件读出来,按照配置空间的格式读出这些号码;

使用 enable 这个可写属性可以禁用或启用这个 PCI 设备,设备的过程很直观,写入1代表启用,写入0则代表禁用;

subsystem 和 driver 符号链接文件分别指向对应的 sysfs 位置;(这里缺少 driver 符号链接说明这个设备当前未使用内核级的驱动程序)

resource0, resource0_wc, resource1, resource2 等是从"PCI 配置空间"解析出来的资源定义段落分别生成的,它们是 PCI 总线驱动在 PCI 设备初始化阶段加上去的,都是二进制属性,但没有实现读写接口,只支持 mmap 内存映射接口,尝试进行读写会提示 IO 错误,其中 _wc 后缀表示 "合并式写入(write combined)" ,它们用于作应用程序的内存映射,就可以访问对应的 PCI 设备上相应的内存资源段落。

使用 uevent

在 sysfs 下的很多 kobject 下都有 uevent 属性,它主要用于内核与 udev (自动设备发现程序)之间的一个通信接口;从 udev 本身与内核的通信接口 netlink 协议套接字来说,它并不需要知道设备的 uevent 属性文件,但多了 uevent 这样一个接口,可用于 udevmonitor 通过内核向 udevd (udev 后台程序)发送消息,也可用于检查设备本身所支持的 netlink 消息上的环境变量,这个特性一般用于开发人员调试 udev 规则文件, udevtrigger 这个调试工具本身就是以写各设备的 uevent 属性文件实现的。

这些 uevent 属性文件一般都是可写的,其中 /sys/devices/ 树下的很多 uevent 属性在较新内核下还支持可读:

# find /sys/ -type f -name uevent -ls

11    0 -rw-r--r--   1 root     root         4096 12月 12 21:10 \

/sys/devices/platform/uevent

1471    0 -rw-r--r--   1 root     root         4096 12月 12 21:10 \

/sys/devices/platform/pcspkr/uevent

3075    0 -rw-r--r--   1 root     root         4096 12月 12 21:10 \

/sys/devices/platform/vesafb.0/uevent

3915    0 -rw-r--r--   1 root     root         4096 12月 12 21:10 \

/sys/devices/platform/serial8250/uevent

3941    0 -rw-r--r--   1 root     root         4096 12月 12 21:10 \

/sys/devices/platform/serial8250/tty/ttyS2/uevent

3950    0 -rw-r--r--   1 root     root         4096 12月 12 21:10 \

/sys/devices/platform/serial8250/tty/ttyS3/uevent

5204    0 -rw-r--r--   1 root     root         4096 12月 12 21:10 \

/sys/devices/platform/i8042/uevent

[...]

912    0 -rw-r--r--   1 root     root         4096 12月 12 21:17 \

/sys/devices/pci0000:00/0000:00:02.5/uevent

[...]

上面截取的最后一个是 SCSI 硬盘控制器设备的 uevent 属性文件,这些 /devices/ 属性文件都支持写入,当前支持写入的参数有 "add","remove","change","move","online","offline"。如,写入 "add",这样可以向 udevd 发送一条 netlink 消息,让它再重新一遍相关的 udev 规则文件;这个功能对开发人员调试 udev 规则文件很有用。

# echo add > /sys/devices/pci0000:00/0000:00:02.5/uevent

使用驱动(PCI)的 sysfs 属性文件, bind, unbind 和 new_id

在设备驱动 /sys/bus/*/driver/... 下可以看到很多驱动都有 bind, unbind, new_id 这三个属性,

# find /sys/bus/*/drivers/ -name bind -ls

...

每一个设备驱动程序在程序内以某种方式注明了可用于哪些硬件,如所有的 PCI 驱动都使用 MODULE_DEVICE_TABLE 声明了所能驱动的 PCI 硬件的 PCI 设备号。但驱动程序不能预知未来,未来生产的新的硬件有可能兼容现有硬件的工作方式,就还可以使用现有硬件驱动程序来工作。在 bind 和 unbind 发明以前,这种情况除了修改 PCI 设备驱动程序的 DEVICE_TABLE 段落,重新编译驱动程序,以外别无他法,在 2.6 内核上添加了 bind 和 unbind 之后可以在不重新编译的情况下对设备和驱动之间进行手工方式地绑定。

而且对于有些硬件设备可以有多份驱动可用,但任何具体时刻只能有一个驱动程序来驱动这个硬件,这时可以使用 bind/unbind 来强制使用和不使用哪一个驱动程序;(注意关于多种驱动程序的选择,更好的管理方法是使用 modprobe.conf 配置文件,需要重启才生效,而 bind/unbind 提供的是一种临时的无需重启立即生效的途径;)

使用它们可以强制绑定某个设备使用或强制不使用某个驱动程序,操作方法就是通过 bind 和 unbind 接口。

# find /sys/ -type f \( -name bind -or -name unbind -or -name new_id \) -ls

69    0 -rw-r--r--   1 root     root         4096 12月 12 22:12 \

/sys/devices/virtual/vtconsole/vtcon0/bind

3072    0 --w-------   1 root     root         4096 12月 12 22:15 \

/sys/bus/platform/drivers/vesafb/unbind

[...]

6489    0 --w-------   1 root     root         4096 12月 12 22:09 \

/sys/bus/pci/drivers/8139too/unbind

6490    0 --w-------   1 root     root         4096 12月 12 22:09 \

/sys/bus/pci/drivers/8139too/bind

6491    0 --w-------   1 root     root         4096 12月 12 22:15 \

/sys/bus/pci/drivers/8139too/new_id

这个结果中特别提到了 8139too 这份驱动程序的这三个属性文件,

# find /sys/bus/pci/drivers/8139too/ -ls

6435    0 drwxr-xr-x   2 root     root            0 12月 12 22:08 \

/sys/bus/pci/drivers/8139too/

6436    0 lrwxrwxrwx   1 root     root            0 12月 12 22:08 \

/sys/bus/pci/drivers/8139too/0000:00:0e.0 -> ../../../../devices/pci0000:00/0000:00:0e.0

6485    0 lrwxrwxrwx   1 root     root            0 12月 12 22:08 \

/sys/bus/pci/drivers/8139too/module -> ../../../../module/8139too

6488    0 --w-------   1 root     root         4096 12月 12 22:08 \

/sys/bus/pci/drivers/8139too/uevent

6489    0 --w-------   1 root     root         4096 12月 12 22:08 \

/sys/bus/pci/drivers/8139too/unbind

6490    0 --w-------   1 root     root         4096 12月 12 22:08 \

/sys/bus/pci/drivers/8139too/bind

6491    0 --w-------   1 root     root         4096 12月 12 22:08 \

/sys/bus/pci/drivers/8139too/new_id

# echo 0000:00:0e.0 > /sys/bus/pci/drivers/8139too/unbind

-bash: echo: write error: 没有那个设备

# ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN

link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

inet 127.0.0.1/8 scope host lo

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state \

UNKNOWN qlen 1000

link/ether 00:14:2a:d1:16:72 brd ff:ff:ff:ff:ff:ff

inet 192.168.1.102/24 brd 192.168.1.255 scope global eth0

3: bond0: <BROADCAST,MULTICAST,MASTER> mtu 1500 qdisc noop state DOWN

link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff

# echo -n 0000:00:0e.0 > /sys/bus/pci/drivers/8139too/unbind

# ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN

link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

inet 127.0.0.1/8 scope host lo

3: bond0: <BROADCAST,MULTICAST,MASTER> mtu 1500 qdisc noop state DOWN

link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff

# echo -n 0000:00:0e.0 > /sys/bus/pci/drivers/8139too/bind

# ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN

link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

inet 127.0.0.1/8 scope host lo

3: bond0: <BROADCAST,MULTICAST,MASTER> mtu 1500 qdisc noop state DOWN

link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff

4: eth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000

link/ether 00:14:2a:d1:16:72 brd ff:ff:ff:ff:ff:ff

这一段操作过程演示了如何对 PCI 设备 "0000:00:0e.0" 强制取消绑定 "8139too" 驱动和强制绑定 "8139too" 驱动:

对 unbind 属性写入总线号码(bus_id)即是强制取消绑定;

对 bind 属性写入总线号码(bus_id)即是强制绑定;

注意,它要求的写入的是总线号码,对应于PCI设备的总线号码是按照 "domain(4位):bus(2位):slot(2位):function号(不限)" 的方式组织,是可以从其设备 kobject 节点上找到,而其它类型的总线有各自不同的规则;

请特别注意: 在这一个例子中, "echo 0000:00:0e.0 > /sys/bus/pci/drivers/8139too/unbind" 这第一个写入命令以 "No such device" 为错误退出,而后续的 "echo -n" 命令则可以成功。这是因为内核在对总线号码进行匹配时过于严格了,通常的 "echo" 命令写入一个字符串会以一个换行符结束输出,内核所接收到的是带有这个换行符的 bus_id 字符串,将它与内核数据结构中的真正的 bus_id 字符串相比较,当然不能找到;所幸的是,这个问题在最新的 2.6.28 开发中的内核上已已经解决,它将这个比较函数改为一个特殊实现的字符串比较,自动忽略结尾处的换行符,在 2.6.28-rc6 内核上测试,不带"-n"参数的 echo 命令已经可以写入成功。

而 new_id 属性文件也可以以另一种途径解决新的设备号问题:它是一个只写的驱动属性,可用于向其中写新的设备号。它支持写入 2至7个十六进制整形参数,分别代表 vendor, device, subvendor, subdevice, class, class_mask, driver_data 最少为 2个是因为一个 PCI设备主要以厂商号(vendor)和设备号(device)所唯一标定,其它 5个参数如果不输入则缺省值为 PCI_ANY_ID(0xffff)。

5441    0 --w-------   1 root     root         4096 12月 14 18:15 \

/sys/bus/pci/drivers/8139too/new_id

从 8139too 驱动上可以看到它当前所静态支持的设备号码列表,其中包括当前系统中的设备 10ec:8139, 假设未来有一款 8140 设备也满足 8139 设备的硬件通讯协议,于是可以使用 8139too 驱动程序来驱动它,操作如下

# echo '10ec 8140' > /sys/bus/pci/drivers/8139too/new_id

这在不更新驱动程序的情况下调试设备很有用处。

使用 scsi_host 的 scan 属性

在具有使用 SCSI 总线连接的主机上,与 PCI类似的是也采用四个号码作为一组来描述一个设备,其中位于最顶层的是 scsi_host。

我们从设备类别 /class/为起点来探索:

# ls -lU /sys/class/scsi_host

总计 0

lrwxrwxrwx 1 root root 0 12-13 01:59 host0 -> \

../../devices/pci0000:00/0000:00:02.5/host0/scsi_host/host0

lrwxrwxrwx 1 root root 0 12-13 01:59 host1 -> \

../../devices/pci0000:00/0000:00:02.5/host1/scsi_host/host1

注意这是 2.6.27 内核的最新变化,在 /sys/class/ 下的都改为符号链接,真实的 kobject 都存在于 /sys/devices/ 中;我们这里探索其中的 host0 这个 SCSI 控制器:

# readlink -f /sys/class/scsi_host/host0

/sys/devices/pci0000:00/0000:00:02.5/host0/scsi_host/host0

# ls -lU /sys/devices/pci0000:00/0000:00:02.5/host0/scsi_host/host0

总计 0

-rw-r--r-- 1 root root 4096 12-13 02:02 uevent

lrwxrwxrwx 1 root root    0 12-13 02:02 subsystem -> ../../../../../../class/scsi_host

lrwxrwxrwx 1 root root    0 12-13 02:02 device -> ../../../host0

-r--r--r-- 1 root root 4096 12-13 02:02 unique_id

-r--r--r-- 1 root root 4096 12-13 02:02 host_busy

-r--r--r-- 1 root root 4096 12-13 02:02 cmd_per_lun

-r--r--r-- 1 root root 4096 12-13 02:02 can_queue

-r--r--r-- 1 root root 4096 12-13 02:02 sg_tablesize

-r--r--r-- 1 root root 4096 12-13 02:02 unchecked_isa_dma

-r--r--r-- 1 root root 4096 12-13 02:02 proc_name

--w------- 1 root root 4096 12-13 02:02 scan

-rw-r--r-- 1 root root 4096 12-13 02:02 state

-rw-r--r-- 1 root root 4096 12-13 02:02 supported_mode

-rw-r--r-- 1 root root 4096 12-13 02:02 active_mode

-r--r--r-- 1 root root 4096 12-13 02:02 prot_capabilities

-r--r--r-- 1 root root 4096 12-13 02:02 prot_guard_type

drwxr-xr-x 2 root root    0 12-13 02:02 power

对这些属性文件解释如下:

有四个 SCSI 特有的可写参数: scan,state,supported_mode,active_mode;可以向其中写入不同的参数来控制此 SCSI 控制器的各种状态;

其它一些可读属性用于读取这个 SCSI 控制器的一些当前值;

其中的 scan 属性文件在调试一些 SCSI 硬件驱动时很有用,它是只写的,可以写入三个至四个以空格分开的整数,用于分别指定对应的 host, channel, id, lun 进行重新搜索。且这个 scan 属性支持以"-"作为通配符,如以下命令可以执行让整个 scsi_host 进行重新搜索,这个功能用于调试某些对热挺拔实现不完善的 SCSI 驱动程序很有用:

# echo '- - -' >/sys/devices/pci0000:00/0000:00:02.5/host0/scsi_host/host0/scan

在内核中, sysfs 属性一般是由 __ATTR 系列的宏来声明的,如对设备的使用 DEVICE_ATTR ,对总线使用 BUS_ATTR ,对驱动使用 DRIVER_ATTR ,对类别(class)使用 CLASS_ATTR, 这四个高级的宏来自于 <include/linux/device.h>, 都是以更低层的来自 <include/linux/sysfs.h> 中的 __ATTR/__ATRR_RO 宏实现; 因此我们在内核源码树中相应位置 drivers/scsi/ 找到这几个宏的使用情况,可以得到在 drivers/scsi/scsi_sysfs.c 。DEVICE_ATTR 宏声明有四个参数,分别是名称、权限位、读函数、写函数。这里对应的,名称是 scan, 权限是只有属主可写(S_IWUSR)、没有读函数、只有写函数。因此读写功能与权限位是对应的,因为 DEVICE_ATTR 把权限位声明与真正的读写是否实现放在了一起,减少了出现不一致的可能。scan 属性写入功能是在 store_scan 函数中实现的,这个接口的四个参数中, buf/count 代表用户写入过来的字符串,它把 buf 进一步传给了 scsi_scan 函数;如果进一步分析 scsi_scan 函数实现可以知道,它期望从 buf 中接受三个或四个整型值(也接受"-"作为通配符),分别代表 host, channel, id 三个值,然后对具体的 (host, channel, id) 进行重新扫描以发现这个 SCSI 控制器上的设备变动。

注册 sysfs 属性;

使用 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)接口的支持;

编程示例,对 LDD3 一书中的 lddbus 驱动程序的 sysfs 改进

首先,这个程序本身是针对当时作者写书的年代的内核(2.6.11)而编写的,在当前的 Fedora10 系统 (2.6.27.5-117.fc10.i686) 上甚至无法编译编译通过;因此首先需要将它移植过来至少达到可运行状态;

附件的压缩包中含有修改过的 lddbus, sculld 的源代码和修改过程的四个patch:

第一个 0001-ldd3-examples-build-on-fedora-10-2.6.27.5-117.fc10.i.patch 是将 lddbus 和 sculld 移植到 Fedora10 内核上可运行,这其中主要是一此内核 API 的变化;

第二个 0002-port-dmem-proc-entry-to-use-sysfs-entry.patch 演示了怎样将原有的 proc 接口改进成为 sysfs 属性接口的,从这个 patch 中可以看到删除的代码多而新增加的代码少,这说明对于相同的功能,使用 sysfs 编程接口的代码量更少,而且 sysfs 代码看起来也比 proc 更为整洁:打印每个设备的调试信息可以做成每个设备上分别有自己的接口,而不是统一的一个 proc 接口;设备属性文件最终出现的位置如 "/sys/devices/ldd0/sculld0/dmem";

static ssize_t sculld_show_dmem(struct device *ddev,

struct device_attribute *attr, char *buf)

{

/* 其中打印每个设备调试信息的代码复制自原proc接口 */

}

static DEVICE_ATTR(dmem, S_IRUGO, sculld_show_dmem, NULL);

static int __init sculld_register_dev(struct sculld_dev *dev, int index)

{

/* 创建此device属性文件 */

ret |= device_create_file(&dev->ldev.dev, &dev_attr_dmem);

}

第三个 0003-add-.gitignore.patch 是增加了 .gitignore 文件,屏蔽一些编译生成的临时文件;

第四个 0004-port-qset-get-set-ioctl-to-use-sysfs-entry.patch 演示了怎样把基于 ioctl 的操作接口改进成为基于 sysfs 接口,由于原来的 ioctl 接口设置和获取 qset 信息是表示整个驱动模块级的变量,它用来控制整个驱动程序而非驱动所支持的单个的设备,因此这个 qset 属性使用 DRIVER_ATTR 来添加更为合适;

ssize_t sculld_show_qset(struct device_driver *driver, char *buf)

{

return snprintf(buf, PAGE_SIZE, "%d\n", sculld_qset);

}

ssize_t sculld_store_qset(struct device_driver *driver, const char *buf,

size_t count)

{

sculld_qset = simple_strtol(buf, NULL, 0);

return count;

}

/* 声明一个权限为0644的可同时读写的driver属性 */

static DRIVER_ATTR(qset, S_IRUGO | S_IWUSR, sculld_show_qset, sculld_store_qset);

/* 创建此driver属性文件 */

result = driver_create_file(&sculld_driver.driver, &driver_attr_qset);

驱动属性最终出现如 "/sys/bus/ldd/drivers/sculld/qset" ,这里声明的是同时可读写的,权限位 0644 与其保持一致。

6446 0 -rw-r--r-- 1 root root 4096 12月 14 07:44 /sys/bus/ldd/drivers/sculld/qset

sys fs的原理和使用相关推荐

  1. k8s集群节点无法创建pod解决:错误提示(Docker error : “/sys/fs/cgroup/memory/xxxx“ “no space left on device“)

    前言 最近部门的k8s 集群为了扩展,增加了两个节点,结果用了一段时间后莫名出现了以下问题,新增的这两个节点上无法创建pod 从rancher发现事件报错信息如下: 问题排查步骤 第一步: 报错信息说 ...

  2. linux进程fd数量,linux – 文件描述符的数量:/ proc / sys / fs / file-nr和/ proc / $pid / fd之间有什么不同?...

    lsof仅列出进程ID.要获取有关线程的信息,您应该使用ps -eLf.根据 man proc: /proc/[pid]/task (since Linux 2.6.0-test6) This is ...

  3. (OK) using persistant storage in linux kernel - /sys/fs/pstore/console-ramoops .

    http://blog.csdn.net/ztguang/article/details/52416910

  4. 5分钟了解Docker原理(2),最简单的cgroups介绍!

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 很多接触Docker的同学,都接触过cgroup这个名词. ...

  5. ext文件系统机制原理剖析

    将磁盘进行分区,分区是将磁盘按柱面进行物理上的划分.划分好分区后还要进行格式化,然后再挂载才能使用(不考虑其他方法).格式化分区的过程其实就是创建文件系统. 文件系统的类型有很多种,如CentOS 5 ...

  6. RAID磁盘阵列的原理与搭建

    RAID(Redundant Array of Independent Disks,独立磁盘冗余阵列)可以提供较普通磁盘更高的速度.安全性,所以服务器在安装时都会选择创建RAID. RAID的创建有两 ...

  7. Docker核心原理之cgroups

    cgroups资源限制 上一篇文章中,我们了解了Docker的资源隔离技术namespace,通过系统调用构建了一个相对隔离的shell环境.也可以称之为一个简单的容器.接下来将讲解另一个强大的内核工 ...

  8. IO多路复用原理剖析

    (最近笔试遇到笔试题:select,poll,epoll都是IO多路复用的机制). I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应 ...

  9. NFS服务器原理、搭建、配置

    目录 NFS服务简介 什么是NFS? NFS挂载原理: RPC与NFS通讯原理: NFS客户端和NFS服务器通讯过程: Linux下NFS服务器部署 NFS服务所需软件及主要配置文件: 服务端安装NF ...

  10. 面试官留步!听我跟你侃会儿Docker原理

    1 Docker 简介 1.1 Docker 由来 Docker 是基于 Go 语言开发的一个容器引擎,Docker是应用程序与系统之间的隔离层.通常应用程序对安装的系统环境会有各种严格要求,当服务器 ...

最新文章

  1. cvSaveImage保存图像
  2. pycharm安装pytorch报错 提示系列问题 torch 包找不到因为pip版本低,结果升级了pip从19.3到20.2 4又提示高版不支持torch安装
  3. 曼昆《经济学原理》(第五版)习题解答 第三章 相互依存性与贸易的好处
  4. Html中的次方符号怎么写
  5. REST设计模式简介
  6. 任务调度之Quartz1
  7. CNN反向传播卷积核翻转
  8. think php 3.2.3 环境,ThinkPHP 3.2.3 入口文件配置
  9. ERROR 1045 (28000): Access denied for user 'root'@'localhost' 的解决方法
  10. 2025年单车平均搭载7-8颗!车载摄像头进入「前装」红利期
  11. 《区块链开源技术需求调研报告》拍了拍你
  12. 合肥有哪些不错的 IT 公司?
  13. 全国首个数字产权区块链平台上线,共享购模式悄然上市
  14. 刚子扯谈:源于Chanel的图片描述
  15. Status Code 304
  16. RUFUS刷UBUNTU启动盘
  17. 数据结构第二版(朱昌杰版)树
  18. 【万字综述】NLP语言模型发展史
  19. 十分钟读好书:《思维力:高效的系统思维》笔记 王世民著
  20. Element的Container布局容器布满全屏

热门文章

  1. UIDocumentPickerViewController docx xlsx
  2. GIS开源软件大全(备用)
  3. 那时候的漫画【怀旧贴】
  4. 应用排行榜第一名脸萌仅仅是刹那的烟火
  5. C++ 使用Poco库实现XML的读取和写入
  6. golang 实现延迟执行
  7. 微信更新版本下载链接汇总【持续更新】
  8. 这是我见过最秀的代码 。。。
  9. 基于RTP/RTCP的无线视频传输自适应带宽控制
  10. 【LLS-Player】webrtc m94 修改