- 存儲(chǔ)技術(shù)原理分析
- 敖青云著
- 7558字
- 2018-12-27 02:38:19
2.4 sysfs文件系統(tǒng)
上面我們反復(fù)提及將內(nèi)核對象添加到sysfs文件系統(tǒng)。其實(shí),這種說法并不十分精確。在那里,已經(jīng)足夠了。但現(xiàn)在,我們需要更為嚴(yán)格的解釋。
sysfs應(yīng)該從兩個(gè)角度來理解:內(nèi)部表示和外部呈現(xiàn)。從內(nèi)部表示來看,sysfs是一種表示內(nèi)核對象、對象屬性,以及對象關(guān)系的一種機(jī)制。sysfs核心將內(nèi)核輸出的對象、對象屬性以及對象關(guān)系組織成樹狀形式,本書稱為sysfs內(nèi)部樹。從外部呈現(xiàn)來看,sysfs文件系統(tǒng)是一個(gè)類似于proc文件系統(tǒng)的特殊文件系統(tǒng),用于將系統(tǒng)中的設(shè)備組織成層次結(jié)構(gòu)(可以稱為sysfs外部樹),向用戶空間導(dǎo)出內(nèi)核的設(shè)備和驅(qū)動(dòng)信息,并且為內(nèi)核的設(shè)備和驅(qū)動(dòng)提供配置接口。
sysfs核心負(fù)責(zé)為內(nèi)核中的內(nèi)部表示和用戶空間的外部呈現(xiàn)之間建立對應(yīng)關(guān)系,也被稱為sysfs映射:
? 內(nèi)核對象被映射為用戶空間的目錄;
? 對象屬性被映射為用戶空間的常規(guī)文件;
? 對象關(guān)系被映射為用戶空間的符號(hào)鏈接。
sysfs的代碼放在fs/sysfs/中,它的公共函數(shù)原型在include/linux/sysfs.h。sysfs代碼提供了兩種構(gòu)件,即兩個(gè)方面的API:一個(gè)是內(nèi)核編程接口,用于向內(nèi)核其他模塊提供構(gòu)建內(nèi)部樹的API;另一個(gè)是文件系統(tǒng)接口,使得用戶空間可以查看并操作對應(yīng)的內(nèi)核對象。
2.4.1 構(gòu)建內(nèi)核對象、對象屬性和對象關(guān)系的內(nèi)部樹
盡管和用戶空間看到的sysfs文件系統(tǒng)組織不完全相同,sysfs核心確實(shí)將內(nèi)核對象、對象屬性以及對象關(guān)系也組織成樹的結(jié)構(gòu)。sysfs內(nèi)部樹中有四種類型的節(jié)點(diǎn):目錄節(jié)點(diǎn)、鏈接節(jié)點(diǎn)、屬性節(jié)點(diǎn)和二進(jìn)制屬性節(jié)點(diǎn),分別和內(nèi)核對象、對象關(guān)系、對象屬性相對應(yīng)。sysfs核心將通過sysfs文件系統(tǒng)呈現(xiàn)到用戶空間。關(guān)于sysfs文件系統(tǒng)實(shí)現(xiàn)的部分,我們將在下一節(jié)闡述。
內(nèi)部樹的所有節(jié)點(diǎn)(如圖2-7所示)都用sysfs_dirent描述符表示,根保存在全局變量sysfs_root中。如果是中間節(jié)點(diǎn),sysfs_dirent的s_parent和s_sibling分別指向其父親節(jié)點(diǎn)和下一個(gè)兄弟節(jié)點(diǎn)。最頂層節(jié)點(diǎn)sysfs_root,這兩個(gè)域均為NULL。sysfs_dirent結(jié)構(gòu)中的域的情況如表2-7所示。

圖2-7 內(nèi)部樹的節(jié)點(diǎn)
表2-7 sysfs_dirent結(jié)構(gòu)中的域(來自文件fs/sysfs/sysfs.h)
內(nèi)核代碼其他部分通過sysfs核心提供的API修改sysfs內(nèi)部樹。對內(nèi)核代碼可見的sysfs函數(shù)被分成三類,基于它們被導(dǎo)出到用戶空間的對象類型(以及它們在文件系統(tǒng)中創(chuàng)建的對象類型)。
? 內(nèi)核對象(目錄)
? 對象屬性(常規(guī)文件)
? 對象關(guān)系(符號(hào)鏈接)
此外,另外還有兩個(gè)種類,被用來導(dǎo)出屬性,除了導(dǎo)出單個(gè)ASCII文件之外。對于這兩類,在文件系統(tǒng)中都創(chuàng)建常規(guī)文件:屬性組和二進(jìn)制文件。
1.內(nèi)核對象
內(nèi)核對象在內(nèi)部用一個(gè)“目錄節(jié)點(diǎn)”表示,由sysfs文件系統(tǒng)作為目錄輸出到用戶空間。由于是目錄節(jié)點(diǎn),它下面將創(chuàng)建其他的節(jié)點(diǎn)(鏈接節(jié)點(diǎn)、屬性節(jié)點(diǎn)和二進(jìn)制節(jié)點(diǎn),以及作為其后代內(nèi)核對象的目錄節(jié)點(diǎn)和代表屬性組名的目錄節(jié)點(diǎn))。表示目錄節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)為sysfs_elem_dir,sysfs_elem_dir結(jié)構(gòu)中的域如表2-8所示,其children域指向了它的第一個(gè)孩子節(jié)點(diǎn),沿著孩子節(jié)點(diǎn)的s_sibling組成一個(gè)鏈表。
表2-8 sysfs_elem_dir結(jié)構(gòu)中的域(來自文件fs/sysfs/sysfs.h)
sysfs核心向Linux內(nèi)核其他模塊提供了兩個(gè)對應(yīng)的API函數(shù)。
? int sysfs_create_dir(struct kobject * kobj)
sysfs_create_dir為一個(gè)內(nèi)核對象創(chuàng)建目錄節(jié)點(diǎn),它的步驟可歸納如下:在內(nèi)存中為目錄節(jié)點(diǎn)分配一個(gè)sysfs_dirent描述符,節(jié)點(diǎn)的名字為內(nèi)核對象名,標(biāo)志為目錄;建立內(nèi)核對象和目錄節(jié)點(diǎn)之間的關(guān)聯(lián);將目錄節(jié)點(diǎn)鏈入sysfs內(nèi)部樹,如果內(nèi)核對象的parent域不為NULL,則目錄節(jié)點(diǎn)會(huì)作為其父內(nèi)核對象的子節(jié)點(diǎn);否則目錄節(jié)點(diǎn)會(huì)被作為sysfs_root的子節(jié)點(diǎn)。最終,如果成功,函數(shù)返回0;否則返回負(fù)的錯(cuò)誤碼。
? void sysfs_remove_dir(struct kobject *kobj)
sysfs_remove_dir將刪除一個(gè)內(nèi)核對象的目錄節(jié)點(diǎn)。盡管這個(gè)函數(shù)的當(dāng)前實(shí)現(xiàn)會(huì)負(fù)責(zé)將目錄節(jié)點(diǎn)下面的屬性節(jié)點(diǎn)一并刪除,但是這種做法經(jīng)常是引發(fā)競爭的根源,說不定哪一天就被從內(nèi)核中去除。因此,建議Linux內(nèi)核開發(fā)者在刪除目錄節(jié)點(diǎn)之前,自行刪除它創(chuàng)建在該目錄節(jié)點(diǎn)下的屬性節(jié)點(diǎn)。
2.對象屬性
對象屬性在內(nèi)部用一個(gè)“屬性節(jié)點(diǎn)”表示,由sysfs作為常規(guī)文件輸出到用戶空間。屬性節(jié)點(diǎn)的結(jié)構(gòu)參見文件fs/sysfs/sysfs.h中的sysfs_elem_attr,而它又有一個(gè)指向?qū)傩悦枋龇碼ttribute結(jié)構(gòu),見文件include/linux/sysfs.h)的指針域。
? int sysfs_create_file(struct kobject * kobj, const struct attribute * attr)
sysfs_create_file為內(nèi)核對象的屬性創(chuàng)建一個(gè)屬性節(jié)點(diǎn)。在內(nèi)存中為屬性節(jié)點(diǎn)分配一個(gè)sysfs_dirent描述符,節(jié)點(diǎn)的名字為屬性名,標(biāo)志為屬性;將屬性節(jié)點(diǎn)鏈入sysfs內(nèi)部樹,作為內(nèi)核對象對應(yīng)目錄節(jié)點(diǎn)的子節(jié)點(diǎn)。反映到sysfs文件系統(tǒng)這將在內(nèi)核對象的目錄下創(chuàng)建一個(gè)對應(yīng)名字的文件,文件的訪問模式取決于屬性的模式。
? void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr)
sysfs_remove_file刪除內(nèi)核對象和屬性對應(yīng)的節(jié)點(diǎn),它將導(dǎo)致sysfs文件系統(tǒng)中內(nèi)核對象的目錄下和屬性對應(yīng)的文件被刪除。
3.對象鏈接
對象鏈接反映了內(nèi)核對象之間的關(guān)系,在內(nèi)部用“鏈接節(jié)點(diǎn)”來表示。在sysfs文件系統(tǒng)中,它對應(yīng)一個(gè)符號(hào)鏈接文件,而在內(nèi)核中,只是兩個(gè)不同內(nèi)核對象之間的關(guān)聯(lián)節(jié)點(diǎn),具體來講,是在一個(gè)內(nèi)核對象目錄節(jié)點(diǎn)下創(chuàng)建的鏈接節(jié)點(diǎn),其target_sd域指向另一個(gè)內(nèi)核對象目錄節(jié)點(diǎn),如表2-9所示。
表2-9 sysfs_elem_symlink結(jié)構(gòu)中的域(來自文件fs/sysfs/sysfs.h)
int sysfs_create_link(struct kobject *kobj, struct kobject *target, const char *name)
sysfs_create_link在兩個(gè)內(nèi)核對象之間創(chuàng)建用于關(guān)聯(lián)的鏈接節(jié)點(diǎn)。第一個(gè)參數(shù)kobj是作為鏈接源的內(nèi)核對象,第二個(gè)參數(shù)target是作為鏈接目標(biāo)的內(nèi)核對象,第三個(gè)參數(shù)name是鏈接名。在內(nèi)存中,為鏈接節(jié)點(diǎn)分配一個(gè)sysfs_dirent描述符,節(jié)點(diǎn)的名字為name,標(biāo)志為鏈接節(jié)點(diǎn),節(jié)點(diǎn)的父親為kobj所對應(yīng)的目錄節(jié)點(diǎn),節(jié)點(diǎn)的target_sd域指向target所對應(yīng)的目錄節(jié)點(diǎn),這個(gè)鏈接節(jié)點(diǎn)被鏈入sysfs內(nèi)部樹。反映到sysfs文件系統(tǒng)上,這將在kobj所對應(yīng)的目錄下創(chuàng)建名字為name的符號(hào)鏈接文件,指向target所對應(yīng)的目錄。
? void sysfs_remove_link(struct kobject * kobj, const char * name)
sysfs_remove_link刪除內(nèi)核對象的目錄節(jié)點(diǎn)下給定名字的鏈接節(jié)點(diǎn),它將導(dǎo)致sysfs文件系統(tǒng)中內(nèi)核對象的目錄下對應(yīng)的符號(hào)連接文件被刪除。
4.屬性組
屬性組是一個(gè)簡化的接口,可以在一次調(diào)用中添加或刪除一系列屬性。
? int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp)
屬性組中屬性的創(chuàng)建是原子操作。如果任何一個(gè)屬性添加失敗(例如,因?yàn)閮?nèi)存不足或者屬性名重復(fù)),這個(gè)組中已經(jīng)添加的屬性都將被刪除,并返回錯(cuò)誤碼給調(diào)用者。
屬性組包含一個(gè)屬性結(jié)構(gòu)的數(shù)組,以及可選的屬性組名字。如果指定名字,則sysfs將在內(nèi)核對象對應(yīng)的目錄節(jié)點(diǎn)下創(chuàng)建一個(gè)相應(yīng)名字的目錄節(jié)點(diǎn),它將作為所有屬性對應(yīng)的屬性節(jié)點(diǎn)的父親。在組織大量的屬性時(shí),屬性組非常有用。
如果沒有指定屬性組名字,則所有屬性對應(yīng)的屬性節(jié)點(diǎn)都將被創(chuàng)建在和內(nèi)核對象對應(yīng)的目錄節(jié)點(diǎn)下。
? void sysfs_remove_group(struct kobject * kobj, const struct attribute_group * grp)
在刪除屬性組時(shí),所有屬性對應(yīng)的屬性節(jié)點(diǎn)都被刪除。如果屬性組指定名字,則作為這些屬性節(jié)點(diǎn)父親的目錄節(jié)點(diǎn)也被刪除。
5.二進(jìn)制屬性
盡管我們介紹,對象屬性為內(nèi)核對象提供了獲取和設(shè)置信息的一種方法。但考慮到某些信息有已知的標(biāo)準(zhǔn)格式(例如PCI配置空間寄存器)或者嚴(yán)格以二進(jìn)制格式被使用(例如二進(jìn)制firmware映像),為此引入二進(jìn)制屬性。
在內(nèi)部,二進(jìn)制屬性用“二進(jìn)制屬性節(jié)點(diǎn)”來表示,由sysfs作為常規(guī)文件輸出到用戶空間。二進(jìn)制屬性節(jié)點(diǎn)對應(yīng)的數(shù)據(jù)結(jié)構(gòu)為sysfs_elem_bin_attr,在文件fs/sysfs/syfs.h中。
? int sysfs_create_bin_file(struct kobject *kobj, const struct bin_attribute *attr)
sysfs_create_file為內(nèi)核對象的二進(jìn)制屬性創(chuàng)建二進(jìn)制屬性節(jié)點(diǎn)。在內(nèi)存中為二進(jìn)制屬性節(jié)點(diǎn)分配一個(gè)sysfs_dirent描述符,節(jié)點(diǎn)的父親為內(nèi)核對象所對應(yīng)的節(jié)點(diǎn),節(jié)點(diǎn)的名字為二進(jìn)制屬性的名字,標(biāo)志為屬性節(jié)點(diǎn),這個(gè)二進(jìn)制屬性節(jié)點(diǎn)被鏈入sysfs內(nèi)部樹。反映到sysfs文件系統(tǒng),這將在內(nèi)核對象所在的目錄下創(chuàng)建一個(gè)對應(yīng)名字的二進(jìn)制屬性文件,文件的訪問模式取決于二進(jìn)制屬性的模式。
? void sysfs_remove_bin_file(struct kobject *kobj, const struct bin_attribute *attr)
sysfs_remove_bin_file刪除和內(nèi)核對象的二進(jìn)制屬性對應(yīng)的二進(jìn)制屬性節(jié)點(diǎn),它將導(dǎo)致sysfs文件系統(tǒng)中內(nèi)核對象的目錄下對應(yīng)名字的二進(jìn)制屬性文件被刪除。
2.4.2 對sysfs文件的讀/寫轉(zhuǎn)換為對屬性的show和store操作
上一節(jié)介紹的API都是用來維護(hù)sysfs內(nèi)部樹的,也就是說,在內(nèi)部組織好了內(nèi)核對象、對象屬性以及對象關(guān)系的結(jié)構(gòu)。sysfs核心還包括用于將上面的包含內(nèi)核對象、對象屬性和對象關(guān)系的內(nèi)部樹通過sysfs文件系統(tǒng)導(dǎo)出到用戶空間的代碼。本節(jié)闡述對sysfs文件系統(tǒng)中的文件的讀/寫操作,如何最終轉(zhuǎn)化為對內(nèi)核對象的屬性的show和store方法,本節(jié)內(nèi)容和文件系統(tǒng)密切相關(guān),請讀者結(jié)合后面的“文件系統(tǒng)”一章進(jìn)行理解。
在Linux內(nèi)核初始化過程中,將調(diào)用sysfs_init函數(shù)(見文件fs/sysfs/mount.c),它將注冊文件系統(tǒng)sysfs_fs_type,并在內(nèi)核中構(gòu)建了sysfs文件系統(tǒng)的裝載實(shí)例。
sysfs在fs/sysfs/mount.c中通過sysfs_init函數(shù)進(jìn)行初始化。這個(gè)函數(shù)直接被VFS初始化代碼調(diào)用。它必須調(diào)用得早,因?yàn)樵S多子系統(tǒng)都依賴于sysfs以注冊對象。這個(gè)函數(shù)負(fù)責(zé)三件事情。
? 創(chuàng)建sysfs_dir_cache高速緩存,這個(gè)高速緩存用于分配sysfs_ dirent描述符。
? 為sysfs文件系統(tǒng)初始化后備設(shè)備信息,這是因?yàn)閟ysfs文件系統(tǒng)比較特殊,其中的文件不支持預(yù)讀,并且對文件的修改不需要被寫回到磁盤——它本來就是在內(nèi)存中動(dòng)態(tài)構(gòu)建的。
? 向VFS注冊,調(diào)用register_ filesystem時(shí)傳入sysfs_fs_type對象。sysfs文件系統(tǒng)還有一個(gè)特殊的地方,就是在內(nèi)核中只有一個(gè)裝載實(shí)例,無論在用戶空間被裝載多少次。
在內(nèi)核中構(gòu)建sysfs文件系統(tǒng)的內(nèi)部裝載實(shí)例,如圖2-8所示。這樣確保sysfs總是可以被其他內(nèi)核代碼使用,即使在引導(dǎo)過程早期,也不依賴于用戶通過交互顯式裝載之。

圖2-8 sysfs文件系統(tǒng)的裝載實(shí)例
一旦這些動(dòng)作完成,sysfs功能就到位了,可以被其他內(nèi)核代碼使用了。但是,用戶空間要使用sysfs文件系統(tǒng),還需要專門裝載。
sysfs文件系統(tǒng)可以在啟動(dòng)時(shí)通過文件/etc/fstab被自動(dòng)裝載。大多數(shù)支持2.6內(nèi)核的發(fā)行版本在/etc/sysfs都有一項(xiàng)用于裝載sysfs。如下所示:
sysfs /sys sysfs noauto 0 0
Linux系統(tǒng)啟動(dòng)完成后,如果發(fā)現(xiàn)sysfs文件系統(tǒng)還沒有被裝載,用戶可以通過如下命令來裝載sysfs:
# mount -t sysfs sysfs /sys
注意:上面兩個(gè)例子中,sysfs被裝載的目錄都是/sys,這并不是說sysfs一定要裝載到這個(gè)目錄下。它只是sysfs裝載點(diǎn)事實(shí)上的標(biāo)準(zhǔn)位置,被各個(gè)主要發(fā)行版所采用。本書所有給出的sysfs文件系統(tǒng)目錄或文件實(shí)例,也采用這一約定,如圖2-9所示。

圖2-9 sysfs文件系統(tǒng)的目錄項(xiàng)表示
Linux文件系統(tǒng)的編程遵循特定的模式,用于將sysfs內(nèi)部樹導(dǎo)出到用戶空間的sysfs文件系統(tǒng)也是如此。因此,sysfs文件系統(tǒng)的部分內(nèi)容需要在讀者讀完本書“文件系統(tǒng)”那一章后,再回過頭自己參照代碼理解。這里我們主要看對sysfs文件的讀操作如何轉(zhuǎn)換為對屬性的show方法的調(diào)用。類似地,對sysfs文件的寫操作被轉(zhuǎn)換為對屬性的store方法的調(diào)用。也就是說,kobject的屬性作為文件系統(tǒng)中的常規(guī)文件輸出,sysfs將文件I/O操作轉(zhuǎn)發(fā)給為屬性定義的方法,提供了一種讀/寫內(nèi)核對象屬性的機(jī)制。
在“文件系統(tǒng)”那一章,我們會(huì)看到,用戶空間在常規(guī)文件上執(zhí)行read系統(tǒng)調(diào)用,將最終作用在這個(gè)常規(guī)文件的文件操作表中的read方法。sysfs文件系統(tǒng)的實(shí)現(xiàn)邏輯確保了屬性文件的文件操作表為sysfs_file_operations(定義在文件fs/sysfs/file.c),read回調(diào)函數(shù)為sysfs_read_file。也就是說,在讀屬性文件時(shí),sysfs_read_file會(huì)被執(zhí)行,代碼如程序2-10所示。
程序2-10 函數(shù)sysfs_read_file()代碼(摘自文件fs/sysfs.c)
sysfs_read_file() 134static ssize_t 135sysfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *ppos) 136{ 137 struct sysfs_buffer * buffer = file->private_data; 138 ssize_t retval = 0; 139 140 mutex_lock(&buffer->mutex); 141 if (buffer->needs_read_fill || *ppos == 0) { 142 retval = fill_read_buffer(file->f_path.dentry,buffer); 143 if (retval) 144 goto out; 145 } 146 pr_debug("%s: count = %zd, ppos = %lld, buf = %s\n", 147 __func__, count, *ppos, buffer->page); 148 retval = simple_read_from_buffer(buf, count, ppos, buffer->page, 149 buffer->count); 150out: 151 mutex_unlock(&buffer->mutex); 152 return retval; 153}
sysfs_read_file函數(shù)有四個(gè)參數(shù):第一個(gè)參數(shù)file為指向要讀取的file描述符的指針;第二個(gè)參數(shù)buf為指向用戶空間緩沖區(qū)的指針,讀出的屬性數(shù)據(jù)將保存在這個(gè)緩沖區(qū);第三個(gè)參數(shù)count為要讀取的字節(jié)數(shù);第四個(gè)參數(shù)pos是一個(gè)輸入/輸出參數(shù),傳入在文件中的起始偏移位置,傳出更新后的偏移位置。
對sysfs屬性文件的讀/寫過程用到一個(gè)緩沖區(qū)作為中轉(zhuǎn),其結(jié)構(gòu)為sysfs_buffer,該結(jié)構(gòu)中域的功能如表2-10所示。這樣做的一個(gè)重要目的是“一次填充,多次使用”。以讀為例,這個(gè)結(jié)構(gòu)中指向一個(gè)頁面,用來保存屬性數(shù)據(jù)。第一次讀時(shí),從屬性獲得數(shù)據(jù)填充整個(gè)頁面(當(dāng)前屬性數(shù)據(jù)不會(huì)超過4096個(gè)字節(jié)),這次讀操作以及后續(xù)讀操作依據(jù)頁面的內(nèi)容來提供,除非特別聲明頁面內(nèi)容無效。
表2-10 sysfs_buffer結(jié)構(gòu)中的域(來自文件fs/sysfs/file.h)
sysfs_buffer描述符在系統(tǒng)調(diào)用open時(shí)被分配,并保存在屬性文件的file描述符的private_data域,請參見fs/sysfs.c文件的sysfs_open_file函數(shù)代碼。
在read系統(tǒng)調(diào)用之前,一定會(huì)先執(zhí)行open系統(tǒng)調(diào)用。換句話說,在sysfs_read_file函數(shù)執(zhí)行時(shí),file描述符的private_data必定指向一個(gè)有效的sysfs_buffer描述符。
第137行先獲得這個(gè)sysfs_buffer描述符,其中有一個(gè)頁面指針指向用來保存緩沖區(qū)數(shù)據(jù)的頁面。在本函數(shù)被調(diào)用的時(shí)候,頁面中的數(shù)據(jù)可能還是無效的(needs_read_fill域?yàn)?),或者甚至頁面可能還沒有被分配(第一次讀取,即*ppos為零時(shí))。第141~145行的代碼處理這兩種情況,調(diào)用fill_read_buffer讀取內(nèi)核對象屬性填充這個(gè)緩沖區(qū),函數(shù)fiee_read_buffer()代碼如程序2-11所示。
程序2-11 函數(shù)fill_read_buffer()代碼(摘自文件fs/sysfs.c)
sysfs_read_file()→fill_read_buffer() 74static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer) 75{ 76 struct sysfs_dirent *attr_sd = dentry->d_fsdata; 77 struct kobject *kobj = attr_sd->s_parent->s_dir.kobj; 78 const struct sysfs_ops * ops = buffer->ops; 79 int ret = 0; 80 ssize_t count; 81 82 if (!buffer->page) 83 buffer->page = (char *) get_zeroed_page(GFP_KERNEL); 84 if (!buffer->page) 85 return -ENOMEM; 86 87 /* need attr_sd for attr and ops, its parent for kobj */ 88 if (!sysfs_get_active(attr_sd)) 89 return -ENODEV; 90 91 buffer->event = atomic_read(&attr_sd->s_attr.open->event); 92 count = ops->show(kobj, attr_sd->s_attr.attr, buffer->page); 93 94 sysfs_put_active(attr_sd); 95 96 /* 97 * The code works fine with PAGE_SIZE return but it's likely to 98 * indicate truncated result or overflow in normal use cases. 99 */ 100 if (count >= (ssize_t)PAGE_SIZE) { 101 print_symbol("fill_read_buffer: %s returned bad count\n", 102 (unsigned long)ops->show); 103 /* Try to struggle along */ 104 count = PAGE_SIZE - 1; 105 } 106 if (count >= 0) { 107 buffer->needs_read_fill = 0; 108 buffer->count = count; 109 } else { 110 ret = count; 111 } 112 return ret; 113}
進(jìn)入fill_read_buffer函數(shù)有兩種可能:
? 頁面已經(jīng)分配,但頁面中的內(nèi)容無效。這主要出現(xiàn)在need_read_fill域設(shè)置的情況下;
? 頁面還沒有被分配。這一般出現(xiàn)在從第一次開始位置讀屬性數(shù)據(jù)的情況下。
因此,第82~85行首先檢查頁面是否已經(jīng)被分配。如果沒有,先為sysfs_buffer分配一個(gè)頁面,保存在page域。
在第92行,調(diào)用sysfs_ops操作表的show方法將屬性數(shù)據(jù)填入頁面。而操作表地址也已經(jīng)由sysfs_open_file函數(shù)從內(nèi)核對象所屬對象類型賦值保存在sysfs_buffer結(jié)構(gòu)中。show函數(shù)返回已填充的字節(jié)數(shù),我們做如下的處理。
? 如果填充字節(jié)數(shù)超過一個(gè)頁面(含)。這實(shí)際是不可能的,因?yàn)槲覀冎唤o了一個(gè)頁面的緩沖區(qū),而且最后應(yīng)該以“\0”字節(jié)結(jié)尾。碰到這樣的返回值,我們只有重新設(shè)定。這是第100~104行的目的。
? 如果返回負(fù)的錯(cuò)誤碼,則將錯(cuò)誤碼返回到調(diào)用者,見代碼第110行。
? 否則表明數(shù)據(jù)已經(jīng)成功填充到頁面,清零needs_read_fill域,并更新count域。參見第106~108行代碼。
本函數(shù)返回到sysfs_read_file函數(shù)的第142行。返回負(fù)值表示錯(cuò)誤。若返回0,表示頁面填充成功,接下來還需要將數(shù)據(jù)復(fù)制到用戶空間,為此在第148和149行調(diào)用simple_read_from_buffer函數(shù),其原型如下:
ssize_t simple_read_from_buffer(void __user *to, size_t count, loff_t *ppos, const void *from, size_t available)
第一個(gè)參數(shù)to為要讀取數(shù)據(jù)到其中的用戶空間緩沖區(qū);第二個(gè)參數(shù)count為要讀取的最大字節(jié)數(shù);第三個(gè)輸入/輸出參數(shù)ppos為在緩沖區(qū)中的當(dāng)前/更新位置;第四個(gè)參數(shù)from為要從其中讀取數(shù)據(jù)的內(nèi)核緩沖區(qū);第五個(gè)參數(shù)available為內(nèi)核緩沖區(qū)的長度。
simple_read_from_buffer函數(shù)的邏輯相當(dāng)直觀,它從緩沖區(qū)from的當(dāng)前位置*ppos讀取最多count個(gè)字節(jié)的數(shù)據(jù)到用戶空間緩沖區(qū)to中,實(shí)際讀取的字節(jié)數(shù)還受緩沖區(qū)的長度available制約。
若成功,返回讀取的字節(jié)數(shù),并且ppos指針向前移動(dòng)同樣的字節(jié)數(shù)。若錯(cuò)誤,返回負(fù)的錯(cuò)誤碼。
至此,對屬性在sysfs文件系統(tǒng)中對應(yīng)文件的read系統(tǒng)調(diào)用,已經(jīng)從內(nèi)核對象sysfs操作表的show方法獲得了數(shù)據(jù),正確地返回了。
我們只是簡要說明一下對sysfs文件的寫操作被轉(zhuǎn)換為對屬性的store方法的調(diào)用的大致步驟,不準(zhǔn)備詳細(xì)跟蹤代碼流程。
在通過sysfs文件系統(tǒng)寫屬性文件時(shí),它在文件操作表sysfs_file_operations(文件fs/sysfs/file.c)定義的write回調(diào)函數(shù)sysfs_write_file(文件fs/sysfs.c)會(huì)被執(zhí)行。
sysfs_write_file的實(shí)現(xiàn)邏輯也很直接,調(diào)用了同一文件中的輔助函數(shù)fill_write_buffer和flush_write_buffer。前者將用戶空間緩沖區(qū)中的數(shù)據(jù)先寫入到中轉(zhuǎn)緩沖區(qū)(sysfs_buff)的頁面(必要時(shí)為它分配空間),并設(shè)置needs_read_fill域;后者最終調(diào)用內(nèi)核對象sysfs操作表中的store回調(diào)方法。
2.4.3 為具體內(nèi)核對象定義屬性的規(guī)范流程
對對象屬性所對應(yīng)sysfs文件的讀/寫操作被轉(zhuǎn)發(fā)給它所屬內(nèi)核對象的sysfs_ops操作表中的方法。sysfs_ops結(jié)構(gòu)定義在文件fs/sysfs/file.h中,它只有show和store兩個(gè)函數(shù)指針域。
? ssize_t (*show)(struct kobject *, struct attribute *,char *)
為內(nèi)核對象的屬性獲取數(shù)據(jù),填入給定的緩沖區(qū)。第一個(gè)參數(shù)為指向內(nèi)核對象描述符的指針,第二個(gè)參數(shù)為指向?qū)傩悦枋龇闹羔槪谌齻€(gè)參數(shù)為要保存數(shù)據(jù)到其中的緩沖區(qū)的指針。
? ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t)
根據(jù)給定的緩沖區(qū),為內(nèi)核對象的屬性設(shè)置數(shù)據(jù)。第一個(gè)參數(shù)為指向內(nèi)核對象描述符的指針,第二個(gè)參數(shù)為指向?qū)傩悦枋龇闹羔槪谌齻€(gè)參數(shù)為要從其中獲得數(shù)據(jù)的緩沖區(qū)的指針,第四個(gè)參數(shù)為緩沖區(qū)的長度。
在上述兩個(gè)函數(shù)中,我們都看到了屬性描述符,即attribute結(jié)構(gòu),該結(jié)構(gòu)中的域如表2-11所示。它的定義非常簡單,可以看作是一個(gè)“裸”的屬性。其中只包含屬性名和屬性模式,不包含讀/寫這個(gè)屬性值的方法。
表2-11 attribute結(jié)構(gòu)中的域(來自文件include/linux/sysfs.h)
一個(gè)內(nèi)核對象下有多個(gè)屬性,也就是說,show和store方法要負(fù)責(zé)多個(gè)屬性的處理。很容易想到的做法是,在show和store方法中根據(jù)屬性名比較傳入屬性和內(nèi)核對象下的所有可能屬性,如果匹配,則調(diào)用該屬性的邏輯代碼。這顯然是一個(gè)笨拙的方案,它至少存在兩個(gè)問題:
① show和store方法會(huì)相當(dāng)冗長,尤其在內(nèi)核對象有多個(gè)屬性的時(shí)候;
② 可擴(kuò)充性差,如果要添加一個(gè)屬性,還需要修改show和store方法。
Linux內(nèi)核當(dāng)然不會(huì)這么做,如它一貫的風(fēng)格,這里的設(shè)計(jì)也很巧妙。我們以圖2-10作為例子進(jìn)行闡述。先總結(jié)其中的關(guān)鍵點(diǎn),后面再給出更詳細(xì)的分析。
定義一個(gè)具體的內(nèi)核對象結(jié)構(gòu)(例如foo_obj),以kojbect作為它的一個(gè)內(nèi)嵌域,并為它定義一個(gè)具體的對象類型(例如foo_type),實(shí)現(xiàn)其中的sysfs_ops方法表(foo_attr_show和foo_attr_store)。

圖2-10 為具體內(nèi)核對象定義屬性的規(guī)范優(yōu)流程
定義具體內(nèi)核對象的屬性結(jié)構(gòu)(foo_attribute),以attribute作為它的一個(gè)內(nèi)嵌域,其中還包含與具體內(nèi)核對象相關(guān)的show和store函數(shù)指針。如果用這個(gè)屬性結(jié)構(gòu)定義新的屬性,則還必須為屬性提供show和store的方法實(shí)現(xiàn)。
foo_attr_show和foo_attr_store方法實(shí)現(xiàn)必須將read或write系統(tǒng)調(diào)用轉(zhuǎn)為對具體內(nèi)核對象屬性結(jié)構(gòu)的show和store方法的調(diào)用。此外,可以額外實(shí)現(xiàn)兩個(gè)輔助函數(shù):foo_create_file和foo_remove_file,用于方便為具體內(nèi)核對象創(chuàng)建和刪除sysfs屬性。
kobject被嵌入到一個(gè)foo_obj結(jié)構(gòu)中,它通過對象類型指向一個(gè)sysfs操作表,后者的show和store回調(diào)方法分別被實(shí)例化為foo_attr_show和foo_attr_store。
為了避免上面的問題,對“裸”的屬性結(jié)構(gòu)進(jìn)行擴(kuò)充,引入特定的show和store回調(diào)方法,封裝成一個(gè)新的結(jié)構(gòu)。一般地,具體內(nèi)核對象結(jié)構(gòu)應(yīng)該定義自己的屬性結(jié)構(gòu),例如對于foo_obj,其屬性結(jié)構(gòu)foo_attribute定義如下:
struct foo_attribute { struct attribute attr; ssize_t (*show)(struct foo_obj *foo, struct foo_attribute *attr, char *buf); ssize_t (*store)(struct foo_obj *foo, struct foo_attribute *attr, const char *buf, size_t count); };
從參數(shù)來看,內(nèi)核對象的通用回調(diào)函數(shù),已經(jīng)變成了具體屬性的特定回調(diào)函數(shù)。
show的第一個(gè)參數(shù)為指向具體內(nèi)核對象(foo_obj)描述符的指針,第二個(gè)參數(shù)為指向具體屬性(foo_attribute)描述符的指針,第三個(gè)參數(shù)為要保存數(shù)據(jù)到其中的緩沖區(qū)的指針。
store的第一個(gè)參數(shù)為指向具體內(nèi)核對象(foo_obj)描述符的指針,第二個(gè)參數(shù)為指向具體屬性(foo_attribute)描述符的指針,第三個(gè)參數(shù)為要從其中獲得數(shù)據(jù)的緩沖區(qū)的指針,第四個(gè)參數(shù)為緩沖區(qū)的長度。
大多數(shù)情況下,每個(gè)屬性實(shí)現(xiàn)自己獨(dú)有的回調(diào)函數(shù)。在回調(diào)函數(shù)被調(diào)用時(shí),已經(jīng)知道自己處理的是哪個(gè)屬性了,基于這個(gè)原因,上面兩個(gè)回調(diào)函數(shù)的第二個(gè)參數(shù)都是可以被去掉的。之所以保留它,是為了在多個(gè)屬性共用show和store實(shí)現(xiàn)代碼的時(shí)候,在代碼中區(qū)分正在處理的屬性。這似乎就是我們本節(jié)要解決的問題,但放在這里,就成為代碼重用的一個(gè)例證了。
我們可以用這個(gè)結(jié)構(gòu)來定義foo_obj的所有屬性,但實(shí)際上還可以通過宏來進(jìn)一步簡化定義:
#define FOO_ATTR(_name, _mode, _show, _store) \ struct foo_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store) #define __ATTR(_name,_mode,_show,_store) { \ .attr = {.name = __stringify(_name), .mode = _mode}, \ .show = _show, \ .store = _store, \ }
這樣,為foo_obj定義屬性變得非常簡單,例如:
FOO_ATTR(value,0644, value_show, value_store);
將聲明一個(gè)類型為struct foo_attribute的foo_attr_value對象,其名字為value,show方法為value_show,store方法為value_store。
這樣,具體屬性有了自己的回調(diào)方法,在文件被讀/寫時(shí),sysfs調(diào)用具體內(nèi)核對象的通用回調(diào)方法,在這些方法實(shí)現(xiàn)中需要派發(fā)到具體屬性的對應(yīng)回調(diào)方法。就foo_obj結(jié)構(gòu)來講,foo_attr_show和foo_attr_store方法需要進(jìn)一步派發(fā)到對應(yīng)foo_attribute的show和store方法上,過程如下。
#define to_foo_obj(x) container_of(x, struct foo_obj, kobj) #define to_foo_attr(x) container_of(x, struct foo_attribute, attr) static ssize_t foo_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct foo_attribute *attribute; struct foo_obj *foo; attribute = to_foo_attr(attr); foo = to_foo_obj(kobj); if (!attribute->show) return -EIO; return attribute->show(foo, attribute, buf); } static ssize_t foo_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t len) { struct foo_attribute *attribute; struct foo_obj *foo; attribute = to_foo_attr(attr); foo = to_foo_obj(kobj); if (!attribute->store) return -EIO; return attribute->store(foo, attribute, buf, len); }
兩個(gè)方法都將通用的struct kobject和struct attribute指針轉(zhuǎn)換成指向具體內(nèi)核對象(foo_obj)和屬性(foo_attribute)的指針,然后調(diào)用該屬性的對應(yīng)方法。
為了便于為具體內(nèi)核對象foo_obj創(chuàng)建和刪除sysfs屬性,又定義了兩個(gè)輔助函數(shù):foo_create_file和foo_remove_file。
int foo_create_file(struct foo_obj *foo, const struct foo_attribute *attr) { int error = 0; if (foo) error = sysfs_create_file(&foo->kobj, &attr->attr); return error; } void foo_remove_file(struct foo_obj *foo, const struct foo_attribute *attr) { if (foo) sysfs_remove_file(&foo->kobj, &attr->attr); }
- 腦動(dòng)力:Linux指令速查效率手冊
- 計(jì)算機(jī)應(yīng)用基礎(chǔ)·基礎(chǔ)模塊
- 自動(dòng)控制原理
- Julia 1.0 Programming
- Google App Inventor
- SharePoint 2010開發(fā)最佳實(shí)踐
- 可編程序控制器應(yīng)用實(shí)訓(xùn)(三菱機(jī)型)
- TensorFlow Reinforcement Learning Quick Start Guide
- 基于神經(jīng)網(wǎng)絡(luò)的監(jiān)督和半監(jiān)督學(xué)習(xí)方法與遙感圖像智能解譯
- LMMS:A Complete Guide to Dance Music Production Beginner's Guide
- 人工智能:智能人機(jī)交互
- PVCBOT零基礎(chǔ)機(jī)器人制作(第2版)
- 工業(yè)機(jī)器人與自控系統(tǒng)的集成應(yīng)用
- Ubuntu 9 Linux應(yīng)用基礎(chǔ)
- 局域網(wǎng)組建與使用完全自學(xué)手冊