1. 什么是configfs?
configfs 是一個基於內存的文件系統,它提供了與sysfs相反的功能。sysfs 是一個基於文件系統的內核對象視圖,而configfs 是一個基於文件系統的內核對象管理器(或稱為config_items)。
在 sysfs 中,一個對象在內核中被創建(例如,當內核發現一個設備時),並在 sysfs 中注冊,然后它的屬性會出現在 sysfs 中,允許用戶空間通過 readdir(3)/read(2) 讀取,同時,也允許用戶通過 write(2) 修改一些屬性。很重要的一點,對象是在內核中被創建和銷毀的,內核控制着 sysfs 表示內核對象的生命周期,而 sysfs 不能干預。
configfs 的 config_item 是通過用戶空間顯式操作 mkdir(2) 創建, rmdir(2) 銷毀的。對象的屬性在 mkdir(2) 時出現,並且可以通過 read(2) 和 write(2) 進行讀取或修改。和 sysfs 一樣,readdir(3) 可以查詢 items 和屬性的列表,symlink(2) 可以用來將 items 分組。與 sysfs 不同的是,configfs 表示的生命周期完全由用戶空間控制,支持這些 items 的內核模塊必須對用戶控制做出響應。
sysfs 和 configfs 應該同時存在於一個系統中,任何一個都不能取代另一個。
2. 使用configfs
configfs 可以編譯為一個模塊,也可以編譯到內核中。你可以通過以下命令訪問它:
sudo mount -t configfs none /sys/kernel/config/
除非客戶端模塊也被加載,否則 configfs 樹是空的。這些模塊將它們的 item 類型作為子系統注冊到 configfs 中,一旦客戶端子系統被加載,它將作為一個(或多個)子目錄出現在 /sys/kernel/config/ 下。和 sysfs 一樣,無論是否掛載在 /sys/kernel/config/ 上,configfs樹始終存在。
通過 mkdir(2) 創建一個 item。此時,item 的屬性也會出現,readdir(3) 可以查看有哪些屬性,read(2) 可以查詢它們的默認值,write(2) 可以存儲新的值。
注意:不要在一個屬性文件中存入多個屬性。
configfs 屬性的兩種類型:
-
一般屬性,與 sysfs 屬性類似,是小型 ASCII 文本文件,最大尺寸為一頁( PAGE_SIZE ,在 i386 上為 4096 )。每個文件最好只使用一個值,與 sysfs 的注意事項相同。
configfs 希望 write(2) 能一次性存儲整個緩沖區。在寫入一般的 configfs 屬性時,用戶空間進程應該先讀取整個文件,修改想要修改的部分,然后再把整個緩沖區寫回去。
-
二進制屬性,與 sysfs 二進制屬性有些類似,但在語義上做了一些輕微的改變。不受 PAGE_SIZE 的限制,但整個二進制 item 必須適合於單個內核 vmalloc 分配的buffer。
用戶空間的 write(2) 調用是有緩沖的,屬性的 write_bin_attribute 方法會在其被關閉時調用,因此,用戶空間必須檢查 close(2) 的返回碼,以便驗證操作是否成功完成。
為了避免惡意用戶對內核進行OOMing( "out of memory",溢出攻擊),每個二進制屬性都有一個最大的緩沖區值。
當一個 item 需要被銷毀時,用 rmdir(2) 刪除它。如果一個 item 與(通過symlink(2))其他 item 有鏈接,則不能銷毀該 item。可以通過 unlink(2) 取消鏈接。
3. 配置FakeNBD:一個例子
想象一下,有一個網絡塊設備(NBD)驅動程序,它允許你訪問遠程塊設備,我們稱它為FakeNBD。FakeNBD 使用 configfs 進行配置,顯然,需要提供一個很好用的用戶態程序,讓系統管理員能夠方便地配置 FakeNBD,為了使對 FakeNBD 的配置起作用,這個程序必須將配置的信息告訴驅動,這就是 configfs 的作用。
當加載 FakeNBD 驅動時,它會在 configfs 中注冊自己,用戶能使用 readdir(3) 看到它。
ls /sys/kernel/config
fakenbd
用戶也可以使用 mkdir(2) 創建 fakenbd 連接,名字是任意的。不過,示例中的名字可能已經被其他工具(uuid 或者磁盤名)使用了。
mkdir /sys/kernel/config/fakenbd/disk1
ls /sys/kernel/config/fakenbd/disk1
target device rw
target 屬性包含 FakeNBD 要連接的服務器的IP地址,device 屬性是服務器上的設備,可想而知,rw屬性決定了連接是只讀還是讀寫。
echo 10.0.0.1 > /sys/kernel/config/fakenbd/disk1/target
echo /dev/sda1 > /sys/kernel/config/fakenbd/disk1/device
echo 1 > /sys/kernel/config/fakenbd/disk1/rw
就是這樣,通過 shell 就已經把設備配置好了。
4. 用 configfs 編程
configfs 中的每個對象都是一個 config_item,config_item 就是子系統中的一個對象,它的屬性與該對象上的值相匹配。configfs 處理該對象及其屬性的文件系統表示,允許子系統忽略除基本 show/store 之外的其它所有交互。
items 是在 config_group 里面創建和銷毀的。一個 group 是共享相同屬性和操作的 items 集合。items 由mkdir(2)創建,rmdir(2)刪除,這些 configfs 都會處理, group中 有一組操作來執行這些任務。
子系統是客戶端模塊的頂層。在初始化過程中,客戶端模塊向 configfs 注冊子系統,子系統作為一個目錄出現在 configfs 文件系統的最高層(根)。一個子系統也是一個 config_group,可以做所有 config_group 能做的事情。
4.1 config_item 結構體
struct config_item {
char *ci_name;
char ci_namebuf[UOBJ_NAME_LEN];
struct kref ci_kref;
struct list_head ci_entry;
struct config_item *ci_parent;
struct config_group *ci_group;
struct config_item_type *ci_type;
struct dentry *ci_dentry;
};
void config_item_init(struct config_item *);
void config_item_init_type_name(struct config_item *, const char *name, struct config_item_type *type);
struct config_item *config_item_get(struct config_item *);
void config_item_put(struct config_item *);
一般來說,config_item 結構體被嵌入到一個 container 結構體中,這個 container 結構體實際上代表了子系統正在做的事情,其中的 config_item 部分就是對象與 configfs 的交互方式。
無論是在源文件中靜態定義還是由父 config_group 創建,創建 config_item 都必須調用一個 _init() 函數,這將初始化引用計數器並設置相應的字段。
config_item 的所有用戶都應該通過 config_item_get() 引用它,並在完成后通過 config_item_put() 函數放棄這個引用。
就其本身而言,config_item 只能在 configfs 中出現。通常,一個子系統希望這個 item 能夠顯示和存儲屬性,並完成一些其他事情,為此,還需要一個 type 結構體。
換句話說,config_item_type 結構體主要用來完成除了顯示和存儲屬性之外的其他事情。
4.2 config_item_type 結構體
struct configfs_item_operations {
void (*release)(struct config_item *);
int (*allow_link)(struct config_item *src, struct config_item *target);
void (*drop_link)(struct config_item *src, struct config_item *target);
};
struct config_item_type {
struct module *ct_owner;
struct configfs_item_operations *ct_item_ops;
struct configfs_group_operations *ct_group_ops;
struct configfs_attribute **ct_attrs;
struct configfs_bin_attribute **ct_bin_attrs;
};
config_item_type 最基本的功能是定義可以對 config_item 進行哪些操作。所有被動態分配的 item 都需要提供 ct_item_ops->release() 方法。當 config_item 的引用計數為零時,就會調用這個方法釋放它。
4.3 configfs_attribute結構體
struct configfs_attribute {
char *ca_name;
struct module *ca_owner;
umode_t ca_mode;
ssize_t (*show)(struct config_item *, char *);
ssize_t (*store)(struct config_item *, const char *, size_t);
};
當一個 config_item 希望一個屬性以文件的形式出現在項目的 configfs 目錄中時,它必須定義一個 configfs_attribute 來描述它。然后,它將屬性添加到以 NULL 結尾的數組 config_item_type->ct_attrs 中。當 item 出現在 configfs 中時,屬性文件將以configfs_attribute->ca_name 文件名出現,configfs_attribute->ca_mode 指定文件權限。
如果一個屬性是可讀的,並且提供了一個 ->show 方法,那么每當用戶空間要求對該屬性進行 read(2) 時,該方法( ->show )就會被調用。如果一個屬性是可寫的,並且提供了一個 ->store 方法,那么每當用戶空間要求對該屬性進行 write(2) 時,該方法( ->store )就會被調用。
4.4 configfs_bin_attribute 結構體
struct configfs_bin_attribute {
struct configfs_attribute cb_attr;
void *cb_private;
size_t cb_max_size;
};
當需要使用二進制blob來顯示 item 對應 configfs 目錄中文件的內容時,,會使用二進制屬性。
BLOB:binary large object,二進制大對象,是一個可以存儲二進制文件的容器。
將二進制屬性添加到以 NULL 結尾的數組 config_item_type->ct_bin_attrs 中,item 就會出現在 configfs 中。屬性文件會以 configfs_bin_attribute->cb_attr.ca_name 作為文件名, configfs_bin_attribute->cb_attr.ca_mode 指定文件權限。
cb_private 成員是提供給驅動程序使用的,cb_max_size 成員則指定了 vmalloc 緩沖區的最大可用空間。
如果二進制屬性是可讀的,並且 config_item 提供了 ct_item_ops->read_bin_attribute() 方法,那么每當用戶空間要求對屬性進行 read(2) 時,該方法就會被調用。同理,用戶空間的 write(2) 操作會調用 ct_item_ops->write_bin_attribute() 方法。讀/寫會被緩沖,所以只會執行讀/寫的一個,屬性本身不需要關心。
4.5 config_group 結構體
config_item 不能憑空產生,唯一的方法是通過 mkdir(2) 在 config_group 上創建一個,該操作將觸發子 item 的創建。
struct config_group {
struct config_item cg_item;
struct list_head cg_children;
struct configfs_subsystem *cg_subsys;
struct list_head default_groups;
struct list_head group_entry;
};
void config_group_init(struct config_group *group);
void config_group_init_type_name(struct config_group *group, const char *name, struct config_item_type *type);
config_group 結構包含一個 config_item,正確地配置該 item 意味着一個 group 可以單獨作為一個 item。
此外,group 還可以完成更多工作:創建 item 或 group,這是通過在 group 中 config_item_type 指定的 group 操作來實現的。
struct configfs_group_operations {
struct config_item *(*make_item)(struct config_group *group, const char *name);
struct config_group *(*make_group)(struct config_group *group, const char *name);
int (*commit_item)(struct config_item *item);
void (*disconnect_notify)(struct config_group *group, struct config_item *item);
void (*drop_item)(struct config_group *group, struct config_item *item);
};
一個 group 通過提供 ct_group_ops->make_item() 方法來創建子項目。如果提供了這個方法,當在 group 目錄中使用 mkdir(2) 時,該方法被調用。當 ct_group_ops->make_item() 方法被調用,子系統將分配一個新的 config_item( or 更可能是它的 container 結構體),初始化並將其返回給 configfs,然后,configfs 將填充文件系統樹以反映新的 item。
如果子系統希望子 item 本身是一個 group,子系統提供 ct_group_ops->make_group(),其他的操作都是一樣,使用 group 上的 group _init() 函數初始化。
最后,當用戶空間對 item 或 group 調用 rmdir(2) 時,會調用 ct_group_ops->drop_item() 方法。由於 config_group 也是一個 config_item,所以不需要單獨的 drop_group() 方法。子系統必須調用 config_item_put() 函數釋放 item 分配時初始化的引用,如果除了該操作,子系統不需要要做其它操作,可以省略 ct_group_ops->drop_item() 方法,configfs 將代表子系統對 item 調用 config_item_put() 方法。
重要:drop_item() 的返回值為 void,因此不能失敗。當 rmdir(2) 被調用時,configfs 將會從文件系統樹中刪除該 item(假設沒有子 item 正在使用它),子系統負責對此操作做出響應。如果子系統在其他線程中有對該 item 的引用,那么內存是安全的,該 item 從子系統中真正消失可能還需要一段時間,但它已經從 configfs 中消失了。
當 drop_item() 被調用時,item 的鏈接已經被拆掉了,它在父 item 上不再有引用,在 item 的層次結構中也沒有位置。如果客戶端需要在這個拆分發生之前做一些清理工作,子系統可以實現 ct_group_ops->disconnect_notify() 方法。該方法在 configfs 從文件系統結構中刪除 item 后,item 從父 group 中刪除前被調用,和drop_item()一樣,disconnect_notify() 的返回值也為 void,不能失敗。客戶端子系統不應該在這里刪除任何引用,因為這必須在 drop_item() 中進行。
當一個 config_group 還有子 item 的時候,它是不能被刪除的,這在 configfs 的 rmdir(2) 代碼中有實現。->drop_item() 不會被調用,因為該 item 沒有被刪除,rmdir(2) 也將失敗,因為目錄不是空的。
4.6 configfs_subsystem 結構體
一個子系統必須注冊自己,通常是在 module_init 的時候,該操作告訴 configfs 讓子系統出現在文件樹中。
struct configfs_subsystem {
struct config_group su_group;
struct mutex su_mutex;
};
int configfs_register_subsystem(struct configfs_subsystem *subsys);
void configfs_unregister_subsystem(struct configfs_subsystem *subsys);
一個子系統由一個頂級 config_group 和一個 mutex 組成,這個 group 是創建子 config_items 的地方。對於一個子系統,這個 group 通常是靜態定義的,在調用 configfs_register_subsystem() 之前,子系統必須通過 group _init() 函數來初始化這個 group,並且還必須初始化 mutex。
當調用注冊函數返回后,子系統會一直存在,並且可以在 configfs 中看到。這時,用戶程序可以調用 mkdir(2),子系統必須為此做好准備。
5. 一個例子
理解這些基本概念的最好例子是 samples/configfs/configfs_sample.c 中的 simple_children subsystem/group 和 simple_child item,它們展示了一個顯示和存儲屬性的簡單對象,以及一個創建和銷毀這些子 item 的簡單 group。
configfs_sample.c : https://github.com/torvalds/linux/blob/master/samples/configfs/configfs_sample.c
6. 層次導航和子系統互斥
configfs 還提供了一些額外的功能。由於 config_groups 和 config_items 出現在文件系統中,所以它們被安排在一個層次結構中。一個子系統是絕對不會接觸到文件系統部分的,但是子系統可能會對這個層次結構感興趣。出於這個原因,層次結構是通過 config_group->cg_children 和 config_item->ci_parent 結構體成員表示的。
子系統可以瀏覽 cg_children 列表和 ci_parent 指針來查看子系統創建的樹。這可能會與 configfs 對層次結構的管理發生沖突,所以 configfs 使用子系統的 mutex 來保護修改。無論何時子系統要瀏覽層次結構,都必須在子系統 mutex 的保護下進行。
當一個新分配的 item 還沒有被鏈接到這個層次結構中時,子系統將無法獲得 mutex, 同樣,當一個正在被刪除的 item 還沒有解除鏈接時,子系統也無法獲取mutex。這意味着,當一個 item 在 configfs 中時,項目的 ci_parent 指針永遠不會是 NULL,而且,同一時刻,item 只會存在一個父 item 的 cg_children 列表中,這允許子系統在持有 mutex 時信任 ci_parent 和 cg_children。
7. 通過symlink(2)進行item匯總
configfs 通過 group->item 為父/子關系提供了一個簡單的 group,但是,通常情況下,在更大的環境中需要在父/子關系之外進行聚合,這是通過 symlink(2) 實現的。
一個 config_item 可以提供 ct_item_ops->allow_link() 和 ct_item_ops->drop_link() 方法。如果 ->allow_link() 方法存在,就可以調用 symlink(2),將 config_item 作為鏈接的來源。這些鏈接只允許在 configfs 的 config_items 之間進行,任何在 configfs 文件系統之外的 symlink(2) 調用都會被拒絕。
當 symlink(2) 被調用時,源 config_item 的 ->allow_link() 方法會被自己和一個目標 item 調用,如果源 item 允許鏈接到目標 item,則返回0,如果源 item 只想鏈接到某一類型的對象(例如,在它自己子系統中的對象),它可以拒絕該鏈接。
當對符號鏈接調用 unlink(2) 時,通過 ->drop_link() 方法通知源 item,和 ->drop_item() 方法一樣,這也是一個返回值為 void 的函數,不能失敗,子系統負責響應因該函數執行導致的變化。
當一個 config_item 鏈接到任何其它 item 時,它不能被刪除,當一個 item 鏈接到它時,也不能被刪除。在 configfs 中不允許使用軟鏈接。
8. 自動創建分組
一個新的 config_group 可能希望有兩種類型的子 config_items,雖然這可以通過在 ->make_item() 中的 magic names 來編寫,但更顯式的方法是讓用戶空間能夠看到這種不同。
configfs 提供了一種方法,即在創建父 group 時,在其內部自動創建一個或多個子 group,而不是把行為互不相同的 item 放在同一個 group 中。因此,mkdir("parent") 的結果是 "parent","parent/subgroup1",直到 "parent/subgroupN"。現在,type 1 的 item 可以在目錄 "parent/subgroup1" 中創建,type N 的 item 可以在目錄 "parent/subgroupN" 中創建。
這些自動創建的子 group,或者說默認 group,並不影響父 group 的其他子 group,如果 ct_group_ops->make_group() 存在,其他子 group 也可以直接在父 group 上創建。
configfs 子系統通過 configfs_add_default_group() 函數將默認 group 添加到父 config_group 結構體中來指定它們,每個添加的 group 與父 group 同時被填充到 configfs 樹中。同樣地,它們也會與父 group 同時被刪除,不會另外通知,當一個 ->drop_item() 方法調用通知子系統其父 group 即將消失時,意味着與該父 group 關聯的每個默認子 group 也即將消失。
因此,不能直接通過 rmdir(2) 來刪除默認 group,當父 group 的 rmdir(2) 檢查子 group 時,也不會考慮它們(默認 group)。
9. 附屬子系統
有時,某些驅動程序依賴於特定的 configfs item,例如,掛載 ocfs2 依賴於心跳區域 item,如果使用 rmdir(2) 刪除該區域 item,則 ocfs2 掛載會出錯 或轉為 readonly 模式。
configfs 提供了兩個額外的 API 調用:configfs_depend_item() 和 configfs_undepend_item(),客戶端驅動程序可以在一個現有的 item 上調用 configfs_depend_item() 來告訴 configfs 它是被依賴的。如果其他程序 rmdir(2) 該 item,configfs 將返回 -EBUSY,當這個 item 不再被依賴時,客戶端驅動會調用 configfs_undepend_item() 取消依賴。
這些 API 不能在任何的 configfs 回調下調用,因為它們會沖突,不過,它們可以阻塞和重分配。客戶端驅動不能憑自己的直覺調用它們,它應該提供一個外部子系統調用的 API。
這是如何工作的呢?想象一下 ocfs2 的掛載過程。當它掛載時,它會要求一個心跳區域 item,這是通過對心跳代碼的調用來完成的。在心跳代碼中,區域 item 被查找出來,同時,心跳代碼會調用 configfs_depend_item(),如果成功了,那么心跳代碼就知道這個區域是安全的,可以交給 ocfs2,如果失敗了,ocfs2 將被卸載,心跳代碼優雅地傳遞出一個錯誤。
10. 可提交 item
注:可提交的 item 目前尚未使用。
有些 config_item 不能有一個有效的初始狀態,也就是說,不能為 item 的屬性指定默認值(指定了默認值, item 才能起作用),用戶空間必須配置一個或多個屬性后,子系統才可以啟動這個 item 所代表的實體。
考慮一下上面的 FakeNBD 設備,如果沒有目標地址和目標設備,子系統就不知道要導入什么塊設備。這個例子假設子系統只是簡單地等待,直到所有屬性都配置好了,再開始連接。每次屬性存儲操作都檢查屬性是否被初始化的方法確實可行,但這會導致在滿足條件(屬性都已經初始化)的情況下,每次屬性存儲操作必定觸發連接。
更好的做法是用一個顯式的操作來通知子系統 config_item 已經准備好了。更重要的是,顯式操作允許子系統提供反饋,說明屬性是否以合理的方式被初始化,configfs 以可提交 item (commitable item)的形式提供了這種反饋。
configfs 仍然只使用正常的文件系統操作,通過 rename(2) 提交的一個item,會從一個可修改的目錄移動到一個不能修改的目錄。
任何提供 ct_group_ops->commit_item() 方法的 group 都有可提交 item,當這個 group 出現在 configfs 中時,mkdir(2) 將不會直接在該 group 中工作,相反,該 group 將有兩個子目錄 "live" 和 "pending",live" 目錄不支持 mkdir(2) 或 rmdir(2) ,它只允許 rename(2),"pending" 目錄允許使用 mkdir(2) 和 rmdir(2)。如果在 "pending" 目錄中創建了一個 item,它的屬性可以隨意修改,用戶空間通過將 item 重命名到 "live" 目錄中來提交,此時,子系統接收到 ->commit_item() 回調。如果所有所需的屬性都被填充,該方法返回0,item 被移到 "live" 目錄下。
由於 rmdir(2) 在 "live" 目錄中不起作用,所以必須關閉一個 item,或者說使其 "uncommitted",同樣,這也是通過 rename(2) 來完成的,這次是從 "live" 目錄回到 "uncommitted" 目錄,並通過 ct_group_ops->uncommit_object() 方法通知子系統。
參考:https://www.kernel.org/doc/Documentation/filesystems/configfs/configfs.txt