1. 程式人生 > >【原創】linux裝置模型之kset/kobj/ktype分析

【原創】linux裝置模型之kset/kobj/ktype分析

# 背 景 - `Read the fucking source code!` --By 魯迅 - `A picture is worth a thousand words.` --By 高爾基 說明: 1. Kernel版本:4.14 2. ARM64處理器,Contex-A53,雙核 3. 使用工具:Source Insight 3.5, Visio # 1. 概述 今天來聊一下Linux裝置模型的基石:`kset/kobject/ktype`。 ![](https://img2020.cnblogs.com/blog/1771657/202007/1771657-20200718002252480-648024997.png) - `sysfs`檔案系統提供了一種使用者與核心資料結構進行互動的方式,可以通過`mount -t sysfs sysfs /sys`來進行掛載; - Linux裝置模型中,裝置、驅動、匯流排組織成拓撲結構,通過`sysfs`檔案系統以目錄結構進行展示與管理; - Linux裝置模型中,匯流排負責裝置和驅動的匹配,裝置與驅動都掛在某一個總線上,當它們進行註冊時由匯流排負責去完成匹配,進而回調驅動的`probe`函式; - SoC系統中有`spi`, `i2c`, `pci`等實體匯流排用於外設的連線,而針對整合在SoC中的外設控制器,Linux核心提供一種虛擬匯流排`platform`用於這些外設控制器的連線,此外`platform`匯流排也可用於沒有實體匯流排的外設; - 在`/sys`目錄下,`bus`用於存放各類匯流排,其中匯流排中會存放掛載在該總線上的驅動和裝置,比如`serial8250`,`devices`存放了系統中的裝置資訊,`class`是針對不同的裝置進行分類; 上邊這些功能的實現,離不開`kobject/kset/ktype`機制的支撐,開始旅程吧。 # 2. 資料結構 ![](https://img2020.cnblogs.com/blog/1771657/202007/1771657-20200718002306661-1333590176.png) ## 2.1 kobject - `kobject`代表核心物件,結構體本身不單獨使用,而是巢狀在其他高層結構中,用於組織成拓撲關係; - `sysfs`檔案系統中一個目錄對應一個`kobject`; 看看結構體吧: ```c struct kobject { const char *name; /* 名字,對應sysfs下的一個目錄 */ struct list_head entry; /* kobject中插入的 list_head結構,用於構造雙向連結串列 */ struct kobject *parent; /* 指向當前kobject父物件的指標,體現在sys中就是包含當前kobject物件的目錄物件 */ struct kset *kset; /* 當前kobject物件所屬的集合 */ struct kobj_type *ktype; /* 當前kobject物件的型別 */ struct kernfs_node *sd; /* VFS檔案系統的目錄項,是裝置和檔案之間的橋樑,sysfs中的符號連結是通過kernfs_node內的聯合體實現的 */ struct kref kref; /* kobject的引用計數,當計數為0時,回撥之前註冊的release方法釋放該物件 */ #ifdef CONFIG_DEBUG_KOBJECT_RELEASE struct delayed_work release; #endif unsigned int state_initialized:1; /* 初始化標誌位,初始化時被置位 */ unsigned int state_in_sysfs:1; /* kobject在sysfs中的狀態,在目錄中建立則為1,否則為0 */ unsigned int state_add_uevent_sent:1; /* 新增裝置的uevent事件是否傳送標誌,新增裝置時向用戶空間傳送uevent事件,請求新增裝置 */ unsigned int state_remove_uevent_sent:1; /* 刪除裝置的uevent事件是否傳送標誌,刪除裝置時向用戶空間傳送uevent事件,請求解除安裝裝置 */ unsigned int uevent_suppress:1; /* 是否忽略上報(不上報uevent) */ }; ``` ## 2.2 kset - `kset`是包含多個`kobject`的集合; - 如果需要在`sysfs`的目錄中包含多個子目錄,那需要將它定義成一個`kset`; - `kset`結構體中包含`struct kobject`欄位,可以使用該欄位連結到更上一層的結構,用於構建更復雜的拓撲結構; - `sysfs`中的裝置組織結構很大程度上根據`kset`組織的,`/sys/bus`目錄就是一個`kset`物件,在Linux裝置模型中,註冊裝置或驅動時就將`kobject`新增到對應的`kset`中; ```c struct kset { struct list_head list; /* 包含在kset內的所有kobject構成一個雙向連結串列 */ spinlock_t list_lock; struct kobject kobj; /* 歸屬於該kset的所有的kobject的共有parent */ const struct kset_uevent_ops *uevent_ops; /* kset的uevent操作函式集,當kset中的kobject有狀態變化時,會回撥這個函式集,以便kset新增新的環境變數或過濾某些uevent,如果一個kobject不屬於任何kset時,是不允許傳送uevent的 */ } __randomize_layout; ``` ## 2.3 ktype - `kobj_type`用於表徵`kobject`的型別,指定了刪除`kobject`時要呼叫的函式,`kobject`結構體中有`struct kref`欄位用於對`kobject`進行引用計數,當計數值為0時,就會呼叫`kobj_type`中的`release`函式對`kobject`進行釋放,這個就有點類似於C++中的智慧指標了; - `kobj_type`指定了通過`sysfs`顯示或修改有關`kobject`的資訊時要處理的操作,實際是呼叫`show/store`函式; ```c struct kobj_type { void (*release)(struct kobject *kobj); /* 釋放kobject物件的介面,有點類似面向物件中的析構 */ const struct sysfs_ops *sysfs_ops; /* 操作kobject的方法集 */ struct attribute **default_attrs; const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj); const void *(*namespace)(struct kobject *kobj); }; struct sysfs_ops { /* kobject操作函式集 */ ssize_t (*show)(struct kobject *, struct attribute *, char *); ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t); }; /* 所謂的attribute就是核心空間和使用者空間進行資訊互動的一種方法,例如某個driver定義了一個變數,卻希望使用者空間程式可以修改該變數,以控制driver的行為,那麼可以將該變數以sysfs attribute的形式開放出來 */ struct attribute { const char *name; umode_t mode; #ifdef CONFIG_DEBUG_LOCK_ALLOC bool ignore_lockdep:1; struct lock_class_key *key; struct lock_class_key skey; #endif }; ``` 可以看一下`kobject`建立的時候,與`ktype`的關係,這樣理解起來更順: ![](https://img2020.cnblogs.com/blog/1771657/202007/1771657-20200718002321827-1893938589.png) - `kobject`在建立的時候,預設設定`kobj_type`的值為`dynamic_kobj_ktype`,通常`kobject`會嵌入在其他結構中來使用,因此它的初始化跟特定的結構相關,典型的比如`struct device`和`struct device_driver`; - 在`/sys`檔案系統中,通過`echo/cat`的操作,最終會呼叫到`show/store`函式,而這兩個函式的具體實現可以放置到驅動程式中; ## 2.4 結構關係 為了更形象的說明這幾個結構體的關係,再來一張圖: ![](https://img2020.cnblogs.com/blog/1771657/202007/1771657-20200718002333793-261920011.png) - `kset`既是`kobject`的集合,本身又是一個`kobject`,進而可以新增到其他的集合中,從而就可以構建成複雜的拓撲結構,滿足`/sys`資料夾下的檔案組織需求; 如果只看`kset/kobject`的資料結構組織,可能還是會迷惑,它怎麼跟Linux的裝置模型相關?這時就不得不提到Linux核心中一個很精妙的存在`container_of`,它可以通過成員變數的地址來獲取所在結構的地址資訊。前文提到過`kobject/kset`結構本身不會單獨使用,通常都是會巢狀在其他結構中,既然`kobjcet/kset`能組織成拓撲結構,那麼包含它們的結構同樣可以構建這個關係,因為可以通過`container_of`就可以找到結構體的首地址。 ![](https://img2020.cnblogs.com/blog/1771657/202007/1771657-20200718002343647-1527682403.png) - 結構體A、B、C、D、E同樣可以構建拓撲結構關係; - `struct device`和`struct device_driver`結構體中都包含了`struct kobject`,而`struct bus_type`結構體中包含了`struct kset`結構,這個也就對應到前文提到的裝置和驅動都新增到總線上,由匯流排來負責匹配; # 3. 流程分析 `kobject/kset`的相關程式碼比較簡單,畢竟它只是作為一個結構體嵌入其他high-level的結構中,充當紐帶的作用。不過,我還是簡單的上一張圖吧: ![](https://img2020.cnblogs.com/blog/1771657/202007/1771657-20200718002354687-857277208.png) - 完成的工作基本就是分配結構體,初始化各個結構體欄位,構建拓撲關係(主要是新增到kset的list中,parent的指向等)等,看懂了結構體的組織,這部分的程式碼理解起來就很輕鬆了; # 4. 示例 先上一個原理圖: ![](https://img2020.cnblogs.com/blog/1771657/202007/1771657-20200718002407027-793599697.png) ## 4.1 程式碼 ```c #include #include #include #include //自定義一個結構,包含了struct kobject子結構 struct test_kobj { int value; struct kobject kobj; }; //自定義個屬性結構體,包含了struct attribute結構 struct test_kobj_attribute { struct attribute attr; ssize_t (*show)(struct test_kobj *obj, struct test_kobj_attribute *attr, char *buf); ssize_t (*store)(struct test_kobj *obj, struct test_kobj_attribute *attr, const char *buf, size_t count); }; //宣告一個全域性結構用於測試 struct test_kobj *obj; //用於初始化sysfs_ops中的函式指標 static ssize_t test_kobj_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct test_kobj_attribute *test_kobj_attr; ssize_t ret = -EIO; test_kobj_attr = container_of(attr, struct test_kobj_attribute, attr); //回撥到具體的實現函式 if (test_kobj_attr->
show) ret = test_kobj_attr->show(container_of(kobj, struct test_kobj, kobj), test_kobj_attr, buf); return ret; } //用於初始化sysfs_ops中的函式指標 static ssize_t test_kobj_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count) { struct test_kobj_attribute *test_kobj_attr; ssize_t ret = -EIO; test_kobj_attr = container_of(attr, struct test_kobj_attribute, attr); //回撥到具體的實現函式 if (test_kobj_attr->
store) ret = test_kobj_attr->store(container_of(kobj, struct test_kobj, kobj), test_kobj_attr, buf, count); return ret; } //用於初始化kobj_ktype const struct sysfs_ops test_kobj_sysfs_ops = { .show = test_kobj_attr_show, .store = test_kobj_attr_store, }; //用於初始化kobj_ktype,最終用於釋放kobject void obj_release(struct kobject *kobj) { struct test_kobj *obj = container_of(kobj, struct test_kobj, kobj); printk(KERN_INFO "test kobject release %s\n", kobject_name(&obj->
kobj)); kfree(obj); } //定義kobj_ktype,用於指定kobject的型別,初始化的時候使用 static struct kobj_type test_kobj_ktype = { .release = obj_release, .sysfs_ops = &test_kobj_sysfs_ops, }; //show函式的具體實現 ssize_t name_show(struct test_kobj *obj, struct test_kobj_attribute *attr, char *buffer) { return sprintf(buffer, "%s\n", kobject_name(&obj->kobj)); } //show函式的具體實現 ssize_t value_show(struct test_kobj *obj, struct test_kobj_attribute *attr, char *buffer) { return sprintf(buffer, "%d\n", obj->value); } //store函式的具體實現 ssize_t value_store(struct test_kobj *obj, struct test_kobj_attribute *attr, const char *buffer, size_t size) { sscanf(buffer, "%d", &obj->value); return size; } //定義屬性,最終註冊進sysfs系統 struct test_kobj_attribute name_attribute = __ATTR(name, 0664, name_show, NULL); struct test_kobj_attribute value_attribute = __ATTR(value, 0664, value_show, value_store); struct attribute *test_kobj_attrs[] = { &name_attribute.attr, &value_attribute.attr, NULL, }; //定義組 struct attribute_group test_kobj_group = { .name = "test_kobj_group", .attrs = test_kobj_attrs, }; //模組初始化函式 static int __init test_kobj_init(void) { int retval; printk(KERN_INFO "test_kobj_init\n"); obj = kmalloc(sizeof(struct test_kobj), GFP_KERNEL); if (!obj) { return -ENOMEM; } obj->value = 1; memset(&obj->kobj, 0, sizeof(struct kobject)); //新增進sysfs系統 kobject_init_and_add(&obj->kobj, &test_kobj_ktype, NULL, "test_kobj"); //在sys資料夾下建立檔案 retval = sysfs_create_files(&obj->kobj, (const struct attribute **)test_kobj_attrs); if (retval) { kobject_put(&obj->kobj); return retval; } //在sys資料夾下建立group retval = sysfs_create_group(&obj->kobj, &test_kobj_group); if (retval) { kobject_put(&obj->kobj); return retval; } return 0; } //模組清理函式 static void __exit test_kobj_exit(void) { printk(KERN_INFO "test_kobj_exit\n"); kobject_del(&obj->kobj); kobject_put(&obj->kobj); return; } module_init(test_kobj_init); module_exit(test_kobj_exit); MODULE_AUTHOR("LoyenWang"); MODULE_LICENSE("GPL"); ``` ## 4.2 Makefile ```c ifneq ($(KERNELRELEASE),) obj-m:=test_kobject.o else KERDIR := /lib/modules/$(shell uname -r)/build PWD:=$(shell pwd) all: make -C $(KERDIR) M=$(PWD) modules clean: rm -f *.ko *.o *.symvers *.cmd *.cmd.o modules.* *.mod.c endif ``` - `Makefile`沒有太多好說的,注意`Tab`的使用,否則容易出錯; ## 4.3 測試結果 ![](https://img2020.cnblogs.com/blog/1771657/202007/1771657-20200718002424014-1053202046.png) - 在/sys目錄下建立了test_kobj資料夾,在該資料夾下除了`name`和`value`外,還有一個`test_kobj_group`的子資料夾; - 可以通過`cat/echo`的操作,來操作`name`和`value`,分別會呼叫到底層的`xxx_show`和`xxx_store`函式; - 對著程式碼看這個圖,一目瞭然; 草草收場,洗洗睡了。 # 參考 `https://lwn.net/Articles/263200/` 歡迎關注個人公眾號,不定期更新核心機制文章 。 ![](https://img2020.cnblogs.com/blog/1771657/202007/1771657-20200718002455827-16330839