官术网_书友最值得收藏!

2.4 sysfs文件系統

上面我們反復提及將內核對象添加到sysfs文件系統。其實,這種說法并不十分精確。在那里,已經足夠了。但現在,我們需要更為嚴格的解釋。

sysfs應該從兩個角度來理解:內部表示和外部呈現。從內部表示來看,sysfs是一種表示內核對象、對象屬性,以及對象關系的一種機制。sysfs核心將內核輸出的對象、對象屬性以及對象關系組織成樹狀形式,本書稱為sysfs內部樹。從外部呈現來看,sysfs文件系統是一個類似于proc文件系統的特殊文件系統,用于將系統中的設備組織成層次結構(可以稱為sysfs外部樹),向用戶空間導出內核的設備和驅動信息,并且為內核的設備和驅動提供配置接口。

sysfs核心負責為內核中的內部表示和用戶空間的外部呈現之間建立對應關系,也被稱為sysfs映射:

? 內核對象被映射為用戶空間的目錄;

? 對象屬性被映射為用戶空間的常規文件;

? 對象關系被映射為用戶空間的符號鏈接。

sysfs的代碼放在fs/sysfs/中,它的公共函數原型在include/linux/sysfs.h。sysfs代碼提供了兩種構件,即兩個方面的API:一個是內核編程接口,用于向內核其他模塊提供構建內部樹的API;另一個是文件系統接口,使得用戶空間可以查看并操作對應的內核對象。

2.4.1 構建內核對象、對象屬性和對象關系的內部樹

盡管和用戶空間看到的sysfs文件系統組織不完全相同,sysfs核心確實將內核對象、對象屬性以及對象關系也組織成樹的結構。sysfs內部樹中有四種類型的節點:目錄節點、鏈接節點、屬性節點和二進制屬性節點,分別和內核對象、對象關系、對象屬性相對應。sysfs核心將通過sysfs文件系統呈現到用戶空間。關于sysfs文件系統實現的部分,我們將在下一節闡述。

內部樹的所有節點(如圖2-7所示)都用sysfs_dirent描述符表示,根保存在全局變量sysfs_root中。如果是中間節點,sysfs_dirent的s_parent和s_sibling分別指向其父親節點和下一個兄弟節點。最頂層節點sysfs_root,這兩個域均為NULL。sysfs_dirent結構中的域的情況如表2-7所示。

圖2-7 內部樹的節點

表2-7 sysfs_dirent結構中的域(來自文件fs/sysfs/sysfs.h)

內核代碼其他部分通過sysfs核心提供的API修改sysfs內部樹。對內核代碼可見的sysfs函數被分成三類,基于它們被導出到用戶空間的對象類型(以及它們在文件系統中創建的對象類型)。

? 內核對象(目錄)

? 對象屬性(常規文件)

? 對象關系(符號鏈接)

此外,另外還有兩個種類,被用來導出屬性,除了導出單個ASCII文件之外。對于這兩類,在文件系統中都創建常規文件:屬性組和二進制文件。

1.內核對象

內核對象在內部用一個“目錄節點”表示,由sysfs文件系統作為目錄輸出到用戶空間。由于是目錄節點,它下面將創建其他的節點(鏈接節點、屬性節點和二進制節點,以及作為其后代內核對象的目錄節點和代表屬性組名的目錄節點)。表示目錄節點的數據結構為sysfs_elem_dir,sysfs_elem_dir結構中的域如表2-8所示,其children域指向了它的第一個孩子節點,沿著孩子節點的s_sibling組成一個鏈表。

表2-8 sysfs_elem_dir結構中的域(來自文件fs/sysfs/sysfs.h)

sysfs核心向Linux內核其他模塊提供了兩個對應的API函數。

? int sysfs_create_dir(struct kobject * kobj)

sysfs_create_dir為一個內核對象創建目錄節點,它的步驟可歸納如下:在內存中為目錄節點分配一個sysfs_dirent描述符,節點的名字為內核對象名,標志為目錄;建立內核對象和目錄節點之間的關聯;將目錄節點鏈入sysfs內部樹,如果內核對象的parent域不為NULL,則目錄節點會作為其父內核對象的子節點;否則目錄節點會被作為sysfs_root的子節點。最終,如果成功,函數返回0;否則返回負的錯誤碼。

? void sysfs_remove_dir(struct kobject *kobj)

sysfs_remove_dir將刪除一個內核對象的目錄節點。盡管這個函數的當前實現會負責將目錄節點下面的屬性節點一并刪除,但是這種做法經常是引發競爭的根源,說不定哪一天就被從內核中去除。因此,建議Linux內核開發者在刪除目錄節點之前,自行刪除它創建在該目錄節點下的屬性節點。

2.對象屬性

對象屬性在內部用一個“屬性節點”表示,由sysfs作為常規文件輸出到用戶空間。屬性節點的結構參見文件fs/sysfs/sysfs.h中的sysfs_elem_attr,而它又有一個指向屬性描述符(即attribute結構,見文件include/linux/sysfs.h)的指針域。

? int sysfs_create_file(struct kobject * kobj, const struct attribute * attr)

sysfs_create_file為內核對象的屬性創建一個屬性節點。在內存中為屬性節點分配一個sysfs_dirent描述符,節點的名字為屬性名,標志為屬性;將屬性節點鏈入sysfs內部樹,作為內核對象對應目錄節點的子節點。反映到sysfs文件系統這將在內核對象的目錄下創建一個對應名字的文件,文件的訪問模式取決于屬性的模式。

? void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr)

sysfs_remove_file刪除內核對象和屬性對應的節點,它將導致sysfs文件系統中內核對象的目錄下和屬性對應的文件被刪除。

3.對象鏈接

對象鏈接反映了內核對象之間的關系,在內部用“鏈接節點”來表示。在sysfs文件系統中,它對應一個符號鏈接文件,而在內核中,只是兩個不同內核對象之間的關聯節點,具體來講,是在一個內核對象目錄節點下創建的鏈接節點,其target_sd域指向另一個內核對象目錄節點,如表2-9所示。

表2-9 sysfs_elem_symlink結構中的域(來自文件fs/sysfs/sysfs.h)

int sysfs_create_link(struct kobject *kobj, struct kobject *target, const char *name)

sysfs_create_link在兩個內核對象之間創建用于關聯的鏈接節點。第一個參數kobj是作為鏈接源的內核對象,第二個參數target是作為鏈接目標的內核對象,第三個參數name是鏈接名。在內存中,為鏈接節點分配一個sysfs_dirent描述符,節點的名字為name,標志為鏈接節點,節點的父親為kobj所對應的目錄節點,節點的target_sd域指向target所對應的目錄節點,這個鏈接節點被鏈入sysfs內部樹。反映到sysfs文件系統上,這將在kobj所對應的目錄下創建名字為name的符號鏈接文件,指向target所對應的目錄。

? void sysfs_remove_link(struct kobject * kobj, const char * name)

sysfs_remove_link刪除內核對象的目錄節點下給定名字的鏈接節點,它將導致sysfs文件系統中內核對象的目錄下對應的符號連接文件被刪除。

4.屬性組

屬性組是一個簡化的接口,可以在一次調用中添加或刪除一系列屬性。

? int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp)

屬性組中屬性的創建是原子操作。如果任何一個屬性添加失敗(例如,因為內存不足或者屬性名重復),這個組中已經添加的屬性都將被刪除,并返回錯誤碼給調用者。

屬性組包含一個屬性結構的數組,以及可選的屬性組名字。如果指定名字,則sysfs將在內核對象對應的目錄節點下創建一個相應名字的目錄節點,它將作為所有屬性對應的屬性節點的父親。在組織大量的屬性時,屬性組非常有用。

如果沒有指定屬性組名字,則所有屬性對應的屬性節點都將被創建在和內核對象對應的目錄節點下。

? void sysfs_remove_group(struct kobject * kobj, const struct attribute_group * grp)

在刪除屬性組時,所有屬性對應的屬性節點都被刪除。如果屬性組指定名字,則作為這些屬性節點父親的目錄節點也被刪除。

5.二進制屬性

盡管我們介紹,對象屬性為內核對象提供了獲取和設置信息的一種方法。但考慮到某些信息有已知的標準格式(例如PCI配置空間寄存器)或者嚴格以二進制格式被使用(例如二進制firmware映像),為此引入二進制屬性。

在內部,二進制屬性用“二進制屬性節點”來表示,由sysfs作為常規文件輸出到用戶空間。二進制屬性節點對應的數據結構為sysfs_elem_bin_attr,在文件fs/sysfs/syfs.h中。

? int sysfs_create_bin_file(struct kobject *kobj, const struct bin_attribute *attr)

sysfs_create_file為內核對象的二進制屬性創建二進制屬性節點。在內存中為二進制屬性節點分配一個sysfs_dirent描述符,節點的父親為內核對象所對應的節點,節點的名字為二進制屬性的名字,標志為屬性節點,這個二進制屬性節點被鏈入sysfs內部樹。反映到sysfs文件系統,這將在內核對象所在的目錄下創建一個對應名字的二進制屬性文件,文件的訪問模式取決于二進制屬性的模式。

? void sysfs_remove_bin_file(struct kobject *kobj, const struct bin_attribute *attr)

sysfs_remove_bin_file刪除和內核對象的二進制屬性對應的二進制屬性節點,它將導致sysfs文件系統中內核對象的目錄下對應名字的二進制屬性文件被刪除。

2.4.2 對sysfs文件的讀/寫轉換為對屬性的show和store操作

上一節介紹的API都是用來維護sysfs內部樹的,也就是說,在內部組織好了內核對象、對象屬性以及對象關系的結構。sysfs核心還包括用于將上面的包含內核對象、對象屬性和對象關系的內部樹通過sysfs文件系統導出到用戶空間的代碼。本節闡述對sysfs文件系統中的文件的讀/寫操作,如何最終轉化為對內核對象的屬性的show和store方法,本節內容和文件系統密切相關,請讀者結合后面的“文件系統”一章進行理解。

在Linux內核初始化過程中,將調用sysfs_init函數(見文件fs/sysfs/mount.c),它將注冊文件系統sysfs_fs_type,并在內核中構建了sysfs文件系統的裝載實例。

sysfs在fs/sysfs/mount.c中通過sysfs_init函數進行初始化。這個函數直接被VFS初始化代碼調用。它必須調用得早,因為許多子系統都依賴于sysfs以注冊對象。這個函數負責三件事情。

? 創建sysfs_dir_cache高速緩存,這個高速緩存用于分配sysfs_ dirent描述符。

? 為sysfs文件系統初始化后備設備信息,這是因為sysfs文件系統比較特殊,其中的文件不支持預讀,并且對文件的修改不需要被寫回到磁盤——它本來就是在內存中動態構建的。

? 向VFS注冊,調用register_ filesystem時傳入sysfs_fs_type對象。sysfs文件系統還有一個特殊的地方,就是在內核中只有一個裝載實例,無論在用戶空間被裝載多少次。

在內核中構建sysfs文件系統的內部裝載實例,如圖2-8所示。這樣確保sysfs總是可以被其他內核代碼使用,即使在引導過程早期,也不依賴于用戶通過交互顯式裝載之。

圖2-8 sysfs文件系統的裝載實例

一旦這些動作完成,sysfs功能就到位了,可以被其他內核代碼使用了。但是,用戶空間要使用sysfs文件系統,還需要專門裝載。

sysfs文件系統可以在啟動時通過文件/etc/fstab被自動裝載。大多數支持2.6內核的發行版本在/etc/sysfs都有一項用于裝載sysfs。如下所示:

sysfs /sys sysfs noauto 0 0

Linux系統啟動完成后,如果發現sysfs文件系統還沒有被裝載,用戶可以通過如下命令來裝載sysfs:

# mount -t sysfs sysfs /sys

注意:上面兩個例子中,sysfs被裝載的目錄都是/sys,這并不是說sysfs一定要裝載到這個目錄下。它只是sysfs裝載點事實上的標準位置,被各個主要發行版所采用。本書所有給出的sysfs文件系統目錄或文件實例,也采用這一約定,如圖2-9所示。

圖2-9 sysfs文件系統的目錄項表示

Linux文件系統的編程遵循特定的模式,用于將sysfs內部樹導出到用戶空間的sysfs文件系統也是如此。因此,sysfs文件系統的部分內容需要在讀者讀完本書“文件系統”那一章后,再回過頭自己參照代碼理解。這里我們主要看對sysfs文件的讀操作如何轉換為對屬性的show方法的調用。類似地,對sysfs文件的寫操作被轉換為對屬性的store方法的調用。也就是說,kobject的屬性作為文件系統中的常規文件輸出,sysfs將文件I/O操作轉發給為屬性定義的方法,提供了一種讀/寫內核對象屬性的機制。

在“文件系統”那一章,我們會看到,用戶空間在常規文件上執行read系統調用,將最終作用在這個常規文件的文件操作表中的read方法。sysfs文件系統的實現邏輯確保了屬性文件的文件操作表為sysfs_file_operations(定義在文件fs/sysfs/file.c),read回調函數為sysfs_read_file。也就是說,在讀屬性文件時,sysfs_read_file會被執行,代碼如程序2-10所示。

程序2-10 函數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函數有四個參數:第一個參數file為指向要讀取的file描述符的指針;第二個參數buf為指向用戶空間緩沖區的指針,讀出的屬性數據將保存在這個緩沖區;第三個參數count為要讀取的字節數;第四個參數pos是一個輸入/輸出參數,傳入在文件中的起始偏移位置,傳出更新后的偏移位置。

對sysfs屬性文件的讀/寫過程用到一個緩沖區作為中轉,其結構為sysfs_buffer,該結構中域的功能如表2-10所示。這樣做的一個重要目的是“一次填充,多次使用”。以讀為例,這個結構中指向一個頁面,用來保存屬性數據。第一次讀時,從屬性獲得數據填充整個頁面(當前屬性數據不會超過4096個字節),這次讀操作以及后續讀操作依據頁面的內容來提供,除非特別聲明頁面內容無效。

表2-10 sysfs_buffer結構中的域(來自文件fs/sysfs/file.h)

sysfs_buffer描述符在系統調用open時被分配,并保存在屬性文件的file描述符的private_data域,請參見fs/sysfs.c文件的sysfs_open_file函數代碼。

在read系統調用之前,一定會先執行open系統調用。換句話說,在sysfs_read_file函數執行時,file描述符的private_data必定指向一個有效的sysfs_buffer描述符。

第137行先獲得這個sysfs_buffer描述符,其中有一個頁面指針指向用來保存緩沖區數據的頁面。在本函數被調用的時候,頁面中的數據可能還是無效的(needs_read_fill域為1),或者甚至頁面可能還沒有被分配(第一次讀取,即*ppos為零時)。第141~145行的代碼處理這兩種情況,調用fill_read_buffer讀取內核對象屬性填充這個緩沖區,函數fiee_read_buffer()代碼如程序2-11所示。

程序2-11 函數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}

進入fill_read_buffer函數有兩種可能:

? 頁面已經分配,但頁面中的內容無效。這主要出現在need_read_fill域設置的情況下;

? 頁面還沒有被分配。這一般出現在從第一次開始位置讀屬性數據的情況下。

因此,第82~85行首先檢查頁面是否已經被分配。如果沒有,先為sysfs_buffer分配一個頁面,保存在page域。

在第92行,調用sysfs_ops操作表的show方法將屬性數據填入頁面。而操作表地址也已經由sysfs_open_file函數從內核對象所屬對象類型賦值保存在sysfs_buffer結構中。show函數返回已填充的字節數,我們做如下的處理。

? 如果填充字節數超過一個頁面(含)。這實際是不可能的,因為我們只給了一個頁面的緩沖區,而且最后應該以“\0”字節結尾。碰到這樣的返回值,我們只有重新設定。這是第100~104行的目的。

? 如果返回負的錯誤碼,則將錯誤碼返回到調用者,見代碼第110行。

? 否則表明數據已經成功填充到頁面,清零needs_read_fill域,并更新count域。參見第106~108行代碼。

本函數返回到sysfs_read_file函數的第142行。返回負值表示錯誤。若返回0,表示頁面填充成功,接下來還需要將數據復制到用戶空間,為此在第148和149行調用simple_read_from_buffer函數,其原型如下:

ssize_t simple_read_from_buffer(void __user *to, size_t count, loff_t *ppos, const void *from, size_t
available)

第一個參數to為要讀取數據到其中的用戶空間緩沖區;第二個參數count為要讀取的最大字節數;第三個輸入/輸出參數ppos為在緩沖區中的當前/更新位置;第四個參數from為要從其中讀取數據的內核緩沖區;第五個參數available為內核緩沖區的長度。

simple_read_from_buffer函數的邏輯相當直觀,它從緩沖區from的當前位置*ppos讀取最多count個字節的數據到用戶空間緩沖區to中,實際讀取的字節數還受緩沖區的長度available制約。

若成功,返回讀取的字節數,并且ppos指針向前移動同樣的字節數。若錯誤,返回負的錯誤碼。

至此,對屬性在sysfs文件系統中對應文件的read系統調用,已經從內核對象sysfs操作表的show方法獲得了數據,正確地返回了。

我們只是簡要說明一下對sysfs文件的寫操作被轉換為對屬性的store方法的調用的大致步驟,不準備詳細跟蹤代碼流程。

在通過sysfs文件系統寫屬性文件時,它在文件操作表sysfs_file_operations(文件fs/sysfs/file.c)定義的write回調函數sysfs_write_file(文件fs/sysfs.c)會被執行。

sysfs_write_file的實現邏輯也很直接,調用了同一文件中的輔助函數fill_write_buffer和flush_write_buffer。前者將用戶空間緩沖區中的數據先寫入到中轉緩沖區(sysfs_buff)的頁面(必要時為它分配空間),并設置needs_read_fill域;后者最終調用內核對象sysfs操作表中的store回調方法。

2.4.3 為具體內核對象定義屬性的規范流程

對對象屬性所對應sysfs文件的讀/寫操作被轉發給它所屬內核對象的sysfs_ops操作表中的方法。sysfs_ops結構定義在文件fs/sysfs/file.h中,它只有show和store兩個函數指針域。

? ssize_t (*show)(struct kobject *, struct attribute *,char *)

為內核對象的屬性獲取數據,填入給定的緩沖區。第一個參數為指向內核對象描述符的指針,第二個參數為指向屬性描述符的指針,第三個參數為要保存數據到其中的緩沖區的指針。

? ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t)

根據給定的緩沖區,為內核對象的屬性設置數據。第一個參數為指向內核對象描述符的指針,第二個參數為指向屬性描述符的指針,第三個參數為要從其中獲得數據的緩沖區的指針,第四個參數為緩沖區的長度。

在上述兩個函數中,我們都看到了屬性描述符,即attribute結構,該結構中的域如表2-11所示。它的定義非常簡單,可以看作是一個“裸”的屬性。其中只包含屬性名和屬性模式,不包含讀/寫這個屬性值的方法。

表2-11 attribute結構中的域(來自文件include/linux/sysfs.h)

一個內核對象下有多個屬性,也就是說,show和store方法要負責多個屬性的處理。很容易想到的做法是,在show和store方法中根據屬性名比較傳入屬性和內核對象下的所有可能屬性,如果匹配,則調用該屬性的邏輯代碼。這顯然是一個笨拙的方案,它至少存在兩個問題:

① show和store方法會相當冗長,尤其在內核對象有多個屬性的時候;

② 可擴充性差,如果要添加一個屬性,還需要修改show和store方法。

Linux內核當然不會這么做,如它一貫的風格,這里的設計也很巧妙。我們以圖2-10作為例子進行闡述。先總結其中的關鍵點,后面再給出更詳細的分析。

定義一個具體的內核對象結構(例如foo_obj),以kojbect作為它的一個內嵌域,并為它定義一個具體的對象類型(例如foo_type),實現其中的sysfs_ops方法表(foo_attr_show和foo_attr_store)。

圖2-10 為具體內核對象定義屬性的規范優流程

定義具體內核對象的屬性結構(foo_attribute),以attribute作為它的一個內嵌域,其中還包含與具體內核對象相關的show和store函數指針。如果用這個屬性結構定義新的屬性,則還必須為屬性提供show和store的方法實現。

foo_attr_show和foo_attr_store方法實現必須將read或write系統調用轉為對具體內核對象屬性結構的show和store方法的調用。此外,可以額外實現兩個輔助函數:foo_create_file和foo_remove_file,用于方便為具體內核對象創建和刪除sysfs屬性。

kobject被嵌入到一個foo_obj結構中,它通過對象類型指向一個sysfs操作表,后者的show和store回調方法分別被實例化為foo_attr_show和foo_attr_store。

為了避免上面的問題,對“裸”的屬性結構進行擴充,引入特定的show和store回調方法,封裝成一個新的結構。一般地,具體內核對象結構應該定義自己的屬性結構,例如對于foo_obj,其屬性結構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);
};

從參數來看,內核對象的通用回調函數,已經變成了具體屬性的特定回調函數。

show的第一個參數為指向具體內核對象(foo_obj)描述符的指針,第二個參數為指向具體屬性(foo_attribute)描述符的指針,第三個參數為要保存數據到其中的緩沖區的指針。

store的第一個參數為指向具體內核對象(foo_obj)描述符的指針,第二個參數為指向具體屬性(foo_attribute)描述符的指針,第三個參數為要從其中獲得數據的緩沖區的指針,第四個參數為緩沖區的長度。

大多數情況下,每個屬性實現自己獨有的回調函數。在回調函數被調用時,已經知道自己處理的是哪個屬性了,基于這個原因,上面兩個回調函數的第二個參數都是可以被去掉的。之所以保留它,是為了在多個屬性共用show和store實現代碼的時候,在代碼中區分正在處理的屬性。這似乎就是我們本節要解決的問題,但放在這里,就成為代碼重用的一個例證了。

我們可以用這個結構來定義foo_obj的所有屬性,但實際上還可以通過宏來進一步簡化定義:

#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);

將聲明一個類型為struct foo_attribute的foo_attr_value對象,其名字為value,show方法為value_show,store方法為value_store。

這樣,具體屬性有了自己的回調方法,在文件被讀/寫時,sysfs調用具體內核對象的通用回調方法,在這些方法實現中需要派發到具體屬性的對應回調方法。就foo_obj結構來講,foo_attr_show和foo_attr_store方法需要進一步派發到對應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);
}

兩個方法都將通用的struct kobject和struct attribute指針轉換成指向具體內核對象(foo_obj)和屬性(foo_attribute)的指針,然后調用該屬性的對應方法。

為了便于為具體內核對象foo_obj創建和刪除sysfs屬性,又定義了兩個輔助函數: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);
}
主站蜘蛛池模板: 瑞安市| 临漳县| 汽车| 眉山市| 凤庆县| 凤庆县| 博白县| 当涂县| 延寿县| 沾化县| 松阳县| 区。| 德保县| 苏尼特右旗| 余江县| 曲靖市| 西华县| 长汀县| 阜新| 上虞市| 龙游县| 巴南区| 尚义县| 崇阳县| 汕尾市| 寿阳县| 荥经县| 嘉定区| 阳江市| 增城市| 惠水县| 岳阳市| 富顺县| 锦屏县| 丹巴县| 来宾市| 邹平县| 调兵山市| 新郑市| 马鞍山市| 长寿区|