sysfs 虛擬文件系統提供了一種比 proc 更爲理想的訪問內核數據的途徑php
sysfs 文件系統老是被掛載在 /sys 掛載點上。雖然在較早期的2.6內核系統上並無規定 sysfs 的標準掛載位置,能夠把 sysfs 掛載在任何位置,但較近的2.6內核修正了這一規則,要求 sysfs 老是掛載在 /sys 目錄上;針對之前的 sysfs 掛載位置不固定或沒有標準被掛載,有些程序從 /proc/mounts 中解析出 sysfs 是否被掛載以及具體的掛載點,這個步驟如今已經不須要了。請參考附錄給出的 sysfs-rules.txt 文件連接。html
sysfs 與 proc 相比有不少優勢,最重要的莫過於設計上的清晰。一個 proc 虛擬文件可能有內部格式,如 /proc/scsi/scsi
,它是可讀可寫的,(其文件權限被錯誤地標記爲了 0444 !,這是內核的一個BUG),而且讀寫格式不同,表明不一樣的操做,應用程序中讀到了這個文件的內容通常還須要進行字符串解析,而在寫入時須要先用字符串格式化按指定的格式寫入字符串進行操做;相比而言, sysfs 的設計原則是一個屬性文件只作一件事情, sysfs 屬性文件通常只有一個值,直接讀取或寫入。整個 /proc/scsi
目錄在2.6內核中已被標記爲過期(LEGACY),它的功能已經被相應的 /sys 屬性文件所徹底取代。新設計的內核機制應該儘可能使用 sysfs 機制,而將 proc 保留給純淨的「進程文件系統」。node
$ ls -F /sys
block/ bus/ class/ dev/ devices/ firmware/ fs/ kernel/ module/ power/
$ ls -F /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/
broken_parity_status enable modalias resource0 rom uevent
class irq msi_bus resource0_wc subsystem@ vendor
config local_cpulist power/ resource1 subsystem_device
device local_cpus resource resource2 subsystem_vendor
這是在 Fedora 10 的 2.6.27.5-117.fc10.i686 的內核上,能夠看到在 /sys 目錄下有 block, bus, class, dev, devices, firmware, fs, kernel, module, power 這些子目錄,本文將分別介紹這些目錄存在的含義。linux
第二個 ls 命令展現了在一個 pci 設備目錄下的文件, "ls" 命令的 "-F" 命令爲所列出的每一個文件使用後綴來顯示文件的類型,後綴 "/" 表示列出的是目錄,後綴 "@" 表示列出的是符號連接文件。能夠看到第二個目錄下包含有普通文件 (regular file) 和符號連接文件 (symbolic link file) ,本文也將以這個具體的設備爲例說明其中每個普通文件的用途。git
/sys 下的目錄結構是通過精心設計的:在 /sys/devices
下是全部設備的真實對象,包括如視頻卡和以太網卡等真實的設備,也包括 ACPI 等不那麼顯而易見的真實設備、還有 tty, bonding 等純粹虛擬的設備;在其它目錄如 class, bus 等中則在分類的目錄中含有大量對 devices 中真實對象引用的符號連接文件; 清單 1 中在 /sys 根目錄下頂層目錄的意義以下:編程
接下來對 /sys/devices/ 下的目錄結構做進一步探討:bash
$ ls -F /sys/devices/
isa/ LNXSYSTM:00/ pci0000:00/ platform/ pnp0/ pnp1/ system/ virtual/
能夠看到,在 /sys/devices/ 目錄下是按照設備的基本總線類型分類的目錄,再進入進去查看其中的 PCI 類型的設備:服務器
$ ls -F /sys/devices/pci0000:00/
0000:00:00.0/ 0000:00:02.5/ 0000:00:03.1/ 0000:00:0e.0/ power/
0000:00:01.0/ 0000:00:02.7/ 0000:00:03.2/ firmware_node@ uevent
0000:00:02.0/ 0000:00:03.0/ 0000:00:03.3/ pci_bus/
在 /sys/devices/pci0000:00/ 目錄下是按照 PCI 總線接入的設備號分類存放的目錄,再查看其中一個,數據結構
$ ls -F /sys/devices/pci0000:00/0000:00:01.0/
0000:01:00.0/ device local_cpus power/ subsystem_vendor
broken_parity_status enable modalias resource uevent
class irq msi_bus subsystem@ vendor
config local_cpulist pci_bus/ subsystem_device
能夠看到,其中有一個目錄 0000:01:00.0/, 其它都是屬性文件和屬性組,而若是對 0000:01:00.0/ 子目錄中進行再列表查看則會獲得 清單 1 的目錄結構。app
繼續以上過程能夠了解整個目錄樹的結構,這裏把它整理成 圖 1. sysfs 目錄層次圖
其中涉及到 ksets, kobjects, attrs 等不少術語,這就不得不提到 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 統一設備模型的基本結構 :
從內核在實現它們時所使用的數據結構來講, Linux 統一設備模型又是以兩種基本數據結構進行樹型和鏈表型結構組織的:
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 kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
struct kset_uevent_ops *uevent_ops;
};
涉及到文件系統實現來講, 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 目錄結構就是很是清晰明瞭:
注意,此表內容是按照最新開發中的 2.6.28 內核的更新組織的,在附錄資源如 LDD3 等位置中有提到 sysfs 中曾有一種管理對象稱爲 subsys (子系統對象),在最新的內核中通過重構認爲它是不須要的,它的功能徹底能夠由 kset 代替,也就是說 sysfs 中只須要一種管理結構是 kset,一種表明具體對象的結構是 kobject,在 kobject 下再用屬性文件表示這個對象所具備的屬性;
使用 sysfs 的關鍵就是掌握這些 sysfs 屬性的用法,下面以一些常見的 sysfs 屬性來展現它的用法;
以一份桌面系統上的視頻卡爲例,列舉它對應的 kobject 上的屬性文件的對應用途;
通常來講,在 Linux 桌面上都有視頻卡以支持 Xorg 軟件包做爲 XWindow 服務器來運行,所以先找到 Xorg 的進程號,查看這個進程所使用的全部文件(注意查看這個進程屬性須要 root 用戶權限);
# 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 出來,以下。(有關 PCI 設備自己的三種地址空間,請參考附錄 LDD3)
# 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
這個空間正好是 256字節大小,熟悉 PCI 的人們還能夠知道,從 PCI 配置空間能夠讀到有關此 PCI 設備的不少有用信息,如廠商代碼,設備代碼,IRQ 號碼等;前四個字節 0x39 0x10 0x30 0x63 就是按小端(little endian)存放的2個短整數,所以其 PCI 廠商號碼和 PCI 設備號碼分別是 0x1039 和 0x6330
# 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 屬性文件;關於純文本屬性和二進制屬性,在下文 編程實踐:添加sysfs支持 一節會進一步說明。
有了 PCI 核心對 sysfs 的完善支持,每一個設備甚至不用單獨的驅動程序,如這裏的 "0000:01:00.0" 不須要一個內核級的驅動程序,有了 PCI 核心對該設備的配置空間發現機制,能夠自動發現它的各個不一樣段落的資源屬性,在 Xorg 應用程序中能夠直接以 "/usr/lib/xorg/modules/drivers/sis_drv.so" 這個用戶空間的驅動程序對其進行映射,就能夠直接操做此視頻卡了;
有了這一個 PCI 設備的示例能夠知道,有了一個 PCI 設備的 /sys/devices/ 設備對象,去訪問它的各項屬性和設置屬性都很是簡單。
在 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
在設備驅動 /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" 驅動:
注意,它要求的寫入的是總線號碼,對應於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 總線鏈接的主機上,與 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
對這些屬性文件解釋以下:
其中的 scan 屬性文件在調試一些 SCSI 硬件驅動時頗有用,它是隻寫的,能夠寫入三個至四個以空格分開的整數,用於分別指定對應的 host, channel, id, lun 進行從新搜索。且這個 scan 屬性支持以"-"做爲通配符,如如下命令能夠執行讓整個 scsi_host 進行從新搜索,這個功能用於調試某些對熱挺拔實現不完善的 SCSI 驅動程序頗有用:
# echo '- - -' >/sys/devices/pci0000:00/0000:00:02.5/host0/scsi_host/host0/scan
以一個 8139too 模塊爲例解釋在這個 kboject 下每個屬性的用途;
# find /sys/module/8139too/ -ls
6408 0 -r--r--r-- 1 root root 4096 12月 13 02:17 \
/sys/module/8139too/version
6412 0 drwxr-xr-x 2 root root 0 12月 13 02:17 \
/sys/module/8139too/sections
6433 0 drwxr-xr-x 2 root root 0 12月 13 02:17 \
/sys/module/8139too/notes
6434 0 -r--r--r-- 1 root root 36 12月 13 02:17 \
/sys/module/8139too/notes/.note.gnu.build-id
6486 0 drwxr-xr-x 2 root root 0 12月 13 02:17 \
/sys/module/8139too/drivers
6487 0 lrwxrwxrwx 1 root root 0 12月 13 02:17 \
/sys/module/8139too/drivers/pci:8139too -> ../../../bus/pci/drivers/8139too
其中的屬性文件都是隻讀的,用於提供信息。從 version, srcversion 上能夠了解到這個模塊所聲明的版本號,源碼版本號, refcnt 是模塊引用計數, sections 屬性組中有一些模塊加載至內存的相應節信息, drivers/ 目錄中是對所提供的驅動的連接;
由於模塊是內核驅動編程的最佳選擇,而一個模塊有可能提供多個驅動程序,於是在未知一個設備在用哪個驅動的狀況下能夠先從 /sys/module/ 查找相應模塊的狀況,再從 drivers/ 發現出真正的驅動程序。或者也能夠徹底反過來利用這些信息,先用 lspci/lshw 等工具找到 /sys/devices/ 下的設備節點,再從其設備的 driver 連接找到 /sys/bus/*/drivers/ 下的 device_driver, 再從 device_driver 下的 module 連接找到 /sys/module/*/,這樣就能夠獲得已加載模塊中空間是哪個模塊在給一個設備提供驅動程序。
以上所舉的例子僅僅是一些常見的 sysfs 屬性用法,實際的系統中還經常有不少其它的從未見過的 sysfs 屬性,所以只有舉例是不夠的,即便維護了一份 sysfs 屬性用法參考大全也不夠,將來的內核版本還會出現新的 sysfs 屬性,所以還必須瞭解 Linux 內核代碼以找到實現這些屬性的代碼位置,以學會在沒有相應屬性文檔的狀況從內核源代碼來分析其 sysfs 屬性功能。
更多的 sysfs 屬性的功能只能靠閱讀源代碼來理解。仍是以上文提到的 scsi_host 的 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
中:
static ssize_t
store_scan(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct Scsi_Host *shost = class_to_shost(dev);
int res;
res = scsi_scan(shost, buf);
if (res == 0)
res = count;
return res;
};
static DEVICE_ATTR(scan, S_IWUSR, NULL, store_scan);
DEVICE_ATTR 宏聲明有四個參數,分別是名稱、權限位、讀函數、寫函數。這裏對應的,名稱是 scan, 權限是隻有屬主可寫(S_IWUSR)、沒有讀函數、只有寫函數。所以讀寫功能與權限位是對應的,由於 DEVICE_ATTR 把權限位聲明與真正的讀寫是否實現放在了一塊兒,減小了出現不一致的可能。(上文提到 /proc/scsi/scsi 接口的權限位聲明與其功能不對應,這與註冊 proc 接口的函數設計中的不一致是有關係的,權限位聲明與功能實現不在代碼中同一個位置,所以易出錯。雖然修復 /proc/scsi/scsi 的權限位錯誤很容易,但內核團隊中多年來一直沒有人發現或未有人去修正這個 BUG,應該是與 /proc/scsi/ 接口的過期有關,過期的功能會在將來某個內核版本中去除。)
上面的 scan 屬性寫入功能是在 store_scan 函數中實現的,這個接口的四個參數中, buf/count 表明用戶寫入過來的字符串,它把 buf 進一步傳給了 scsi_scan 函數;若是進一步分析 scsi_scan 函數實現能夠知道,它指望從 buf 中接受三個或四個整型值(也接受"-"做爲通配符),分別表明 host, channel, id 三個值,(第四個整數在早期內核中曾表明 lun 號碼,但在較新內核中第四個數字被忽略,僅做爲向後兼容保留接受四個整數),而後對具體的 (host, channel, id) 進行從新掃描以發現這個 SCSI 控制器上的設備變更。
若是你正在開發的設備驅動程序中須要與用戶層的接口,通常可選的方法有:
最重要的是,添加虛擬字符設備支持和註冊 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
結構體對象,所以具備普通屬性的全部功能特徵;首先,這個程序自己是針對當時做者寫書的年代的內核(2.6.11)而編寫的,在當前的 Fedora10 系統 (2.6.27.5-117.fc10.i686) 上甚至沒法編譯編譯經過;所以首先須要將它移植過來至少達到可運行狀態;
附件的壓縮包中含有修改過的 lddbus, sculld 的源代碼和修改過程的四個patch:
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);
}
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/qsetsysfs 給應用程序提供了統一訪問設備的接口,但能夠看到, sysfs 僅僅是提供了一個能夠統一訪問設備的框架,但到底是否支持 sysfs 還須要各設備驅動程序的編程支持;在 2.6 內核誕生 5年以來的發展中,不少子系統、設備驅動程序逐漸轉向了 sysfs 做爲與用戶空間友好的接口,但仍然也存在大量的代碼還在使用舊的 proc 或虛擬字符設備的 ioctl 方式;若是僅從最終用戶的角度來講, sysfs 與 proc 都是在提供相同或相似的功能,對於舊的 proc 代碼,沒有絕對的必要去作 proc 至 sysfs 的升級;所以在可預見的未來, sysfs 會與 proc, debugfs, configfs 等共存很長一段時間。