本章主要討論與linux的設備驅動和設備管理的相關的4個內核成分,設備類型,模塊,內核對象,sysfs。html
主要內容:node
linux中主要由3種類型的設備,分別是:linux
設備類型shell |
表明設備數組 |
特色網絡 |
訪問方式數據結構 |
塊設備 | 硬盤,光盤 | 隨機訪問設備中的內容 | 通常都是把設備掛載爲文件系統後再訪問 |
字符設備 | 鍵盤,打印機 | 只能順序訪問(一個一個字符或者一個一個字節) | 通常不掛載,直接和設備交互 |
網絡設備 | 網卡 | 打破了Unix "全部東西都是文件" 的設計原則 | 經過套接字API來訪問 |
除了以上3種典型的設備以外,其實Linux中還有一些其餘的設備類型,其中見的較多的應該算是"僞設備"。框架
所謂"僞設備",其實就是一些虛擬的設備,僅提供訪問內核功能而已,沒有物理設備與之關聯。dom
典型的"僞設備"就是 /dev/random(內核隨機數發生器), /dev/null(空設備), /dev/zero(零設備), /dev/full(滿設備)異步
Linux內核是模塊化組成的,內核中的模塊能夠按需加載,從而保證內核啓動時不用加載全部的模塊,即減小了內核的大小,也提升了效率。
經過編寫內核模塊來給內核增長功能或者接口是個很好的方式(既不用從新編譯內核,也方便調試和刪除)。
內核模塊能夠帶參數也能夠不帶參數,不帶參數的內核模塊比較簡單。
我以前的幾篇隨筆中用於測試的例子都是用不帶參數的內核模塊來實驗的。
2.1.1. 無參數的內核模塊
參考:
《Linux內核設計與實現》讀書筆記(八)- 中斷下半部的處理
《Linux內核設計與實現》讀書筆記(十一)- 定時器和時間管理
2.1.2. 帶參數的內核模塊
構造帶參數的內核模塊其實也不難,內核中已經提供了簡單的框架來給咱們聲明參數。
1. module_param(name, type, perm) : 定義一個模塊參數
+ 參數 name :: 既是用戶可見的參數名,也是模塊中存放模塊參數的變量名
+ 參數 type :: 參數的類型(byte, short, int, uint, long, ulong, charp, bool...) byte型存放在char變量中,bool型存放在int變量中
+ 參數 perm :: 指定模塊在 sysfs 文件系統中對應的文件權限(關於 sysfs 的內容後面介紹)
static int stu_id = 0; // 默認id module_param(stu_id, int, 0644);
2. module_param_named(name, variable, type, perm) : 定義一個模塊參數,而且參數對內對外的名稱不同
+ 參數 name :: 用戶可見的參數名
+ 參數 variable :: 模塊中存放模塊參數的變量名
+ 參數 type和perm :: 同 module_param 中的 type 和 perm
static char* stu_name_in = "default name"; // 默認名字 module_param_named(stu_name_out, stu_name_in ,charp, 0644); /* stu_name_out 是對用戶開放的名稱 * stu_name_in 是內核模塊內部使用的名稱 */
3. module_param_string(name, string, len, perm) : 拷貝字符串到指定的字符數組
+ 參數 name :: 用戶可見的參數名
+ 參數 string :: 模塊中存放模塊參數的變量名
+ 參數 len :: string 參數的緩衝區長度
+ 參數 perm :: 同 module_param 中的 perm
static char str_in[BUF_LEN]; module_param_string(str_out, str_in, BUF_LEN, 0); /* perm=0 表示徹底禁止 sysfs 項 */
4. module_param_array(name, type, nump, perm) : 定義數組類型的模塊參數
+ 參數 name :: 同 module_param 中的 name
+ 參數 type :: 同 module_param 中的 type
+ 參數 nump :: 整型指針,存放數組的長度
+ 參數 perm :: 同 module_param 中的 perm
#define MAX_ARR_LEN 5 static int arr_len; static int arr_in[MAX_ARR_LEN]; module_param_array(arr_in, int, &arr_len, 0644);
5. module_param_array_named(name, array, type, nump, perm) : 定義數組類型的模塊參數,而且數組參數對內對外的名稱不同
+ 參數 name :: 數組參數對外的名稱
+ 參數 array :: 數組參數對內的名稱
+ 參數 type,nump,perm :: 同 module_param_array 中的 type,nump,perm
#define MAX_ARR_LEN 5 static int arr_len; static int arr_in[MAX_ARR_LEN]; module_param_array_named(arr_out, arr_in, int, &arr_len, 0644);
6. 參數描述宏
能夠經過 MODULE_PARM_DESC() 來給內核模塊的參數添加一些描述信息。
這些描述信息在編譯完內核模塊後,能夠經過 modinfo 命令查看。
static int stu_id = 0; // 默認id module_param(stu_id, int, 0644); MODULE_PARM_DESC(stu_id, "學生ID,默認爲 0"); // 這句就是描述內核模塊參數 stu_id 的語句
7. 帶參數的內核模塊的示例
示例代碼:test_paramed_km.c
定義了3個內核模塊參數,分別是 int型,char*型,數組型的。
#include<linux/init.h> #include<linux/module.h> #include<linux/kernel.h> MODULE_LICENSE("Dual BSD/GPL"); struct student { int id; char* name; }; static void print_student(struct student*); static int stu_id = 0; // 默認id module_param(stu_id, int, 0644); MODULE_PARM_DESC(stu_id, "學生ID,默認爲 0"); static char* stu_name_in = "default name"; // 默認名字 module_param_named(stu_name_out, stu_name_in ,charp, 0644); MODULE_PARM_DESC(stu_name, "學生姓名,默認爲 default name"); #define MAX_ARR_LEN 5 static int arr_len; static int arr_in[MAX_ARR_LEN]; module_param_array_named(arr_out, arr_in, int, &arr_len, 0644); MODULE_PARM_DESC(arr_in, "數組參數,默認爲空"); static int test_paramed_km_init(void) { struct student* stu1; int i; /* 進入內核模塊 */ printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "test_paramed_km is inited!\n"); printk(KERN_ALERT "*************************\n"); // 根據參數生成 struct student 信息 // 若是沒有參數就用默認參數 printk(KERN_ALERT "alloc one student....\n"); stu1 = kmalloc(sizeof(*stu1), GFP_KERNEL); stu1->id = stu_id; stu1->name = stu_name_in; print_student(stu1); // 模塊數組 for (i = 0; i < arr_len; ++i) { printk(KERN_ALERT "arr_value[%d]: %d\n", i, arr_in[i]); } return 0; } static void test_paramed_km_exit(void) { /* 退出內核模塊 */ printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "test_paramed_km is exited!\n"); printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "\n\n\n\n\n"); } static void print_student(struct student *stu) { if (stu != NULL) { printk(KERN_ALERT "**********student info***********\n"); printk(KERN_ALERT "student id is: %d\n", stu->id); printk(KERN_ALERT "student name is: %s\n", stu->name); printk(KERN_ALERT "*********************************\n"); } else printk(KERN_ALERT "the student info is null!!\n"); } module_init(test_paramed_km_init); module_exit(test_paramed_km_exit);
上面的示例對應的 Makefile 以下:
# must complile on customize kernel obj-m += paramed_km.o paramed_km-objs := test_paramed_km.o #generate the path CURRENT_PATH:=$(shell pwd) #the current kernel version number LINUX_KERNEL:=$(shell uname -r) #the absolute path LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL) #complie object all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned #clean clean: rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned
內核模塊運行方法:(個人運行環境是 CentOS 6.3 x86_64)
[root@vbox chap17]# uname -r 2.6.32-279.el6.x86_64 [root@vbox chap17]# ll total 8 -rw-r--r-- 1 root root 538 Dec 1 19:37 Makefile -rw-r--r-- 1 root root 2155 Dec 1 19:37 test_paramed_km.c [root@vbox chap17]# make <-- 編譯內核 make -C /usr/src/kernels/2.6.32-279.el6.x86_64 M=/root/chap17 modules make[1]: Entering directory `/usr/src/kernels/2.6.32-279.el6.x86_64' CC [M] /root/chap17/test_paramed_km.o LD [M] /root/chap17/paramed_km.o Building modules, stage 2. MODPOST 1 modules CC /root/chap17/paramed_km.mod.o LD [M] /root/chap17/paramed_km.ko.unsigned NO SIGN [M] /root/chap17/paramed_km.ko make[1]: Leaving directory `/usr/src/kernels/2.6.32-279.el6.x86_64' rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned [root@vbox chap17]# ll <-- 編譯內核後,多了 paramed_km.ko 文件 total 124 -rw-r--r-- 1 root root 538 Dec 1 19:37 Makefile -rw-r--r-- 1 root root 118352 Dec 1 19:37 paramed_km.ko -rw-r--r-- 1 root root 2155 Dec 1 19:37 test_paramed_km.c <-- 經過 modinfo 命令能夠查看對內核模塊參數的註釋 [root@vbox chap17]# modinfo paramed_km.ko filename: paramed_km.ko license: Dual BSD/GPL srcversion: C52F97687B033738742800D depends: vermagic: 2.6.32-279.el6.x86_64 SMP mod_unload modversions parm: stu_id:學生ID,默認爲 0 (int) parm: stu_name_out:charp parm: stu_name_in:學生姓名,默認爲 default name parm: arr_out:array of int parm: arr_in:數組參數,默認爲空 <-- 3 個參數都是默認的 [root@vbox chap17]# insmod paramed_km.ko [root@vbox chap17]# rmmod paramed_km.ko [root@vbox chap17]# dmesg | tail -16 <-- 結果中顯示2個默認參數,第3個數組參數默認爲空,因此不顯示 ************************* test_paramed_km is inited! ************************* alloc one student.... **********student info*********** student id is: 0 student name is: default name ********************************* ************************* test_paramed_km is exited! ************************* <-- 3 個參數都被設置 [root@vbox chap17]# insmod paramed_km.ko stu_id=100 stu_name_out=myname arr_out=1,2,3,4,5 [root@vbox chap17]# rmmod paramed_km.ko [root@vbox chap17]# dmesg | tail -21 ************************* test_paramed_km is inited! ************************* alloc one student.... **********student info*********** student id is: 100 student name is: myname ********************************* arr_value[0]: 1 arr_value[1]: 2 arr_value[2]: 3 arr_value[3]: 4 arr_value[4]: 5 ************************* test_paramed_km is exited! *************************
2.2.1. 內核代碼外
上面的例子,以及以前博客中內核模塊的例子都是把模塊代碼放在內核以外來運行的。
2.2.2. 內核代碼中
內核模塊的代碼也能夠直接放到內核代碼樹中。
若是你開發了一種驅動,而且但願被加入到內核中,那麼,能夠在編寫驅動的時候就將完成此驅動功能的內核模塊加到內核代碼樹中 driver 的相應位置。
將內核模塊加入內核代碼樹中以後,不須要另外寫 Makefile,修改內核代碼樹中的已有的 Makefile 就行。
好比,寫了一個某種字符設備相關的驅動,能夠把它加到內核代碼的 /drivers/char 下,
同時修改 /drivers/char下的Makefie,仿照裏面已有的內容,增長新驅動的編譯相關內容便可。
以後,在編譯內核的時候會將新的驅動之內核模塊的方式編譯出來。
2.3.1. 模塊安裝
make modules_install <-- 把隨內核編譯出來的模塊安裝到合適的目錄中( /lib/modules/version/kernel )
2.3.2. 模塊依賴性
linux中自動生產模塊依賴性的命令:
depmod <-- 產生內核依賴關係信息
depmod -A <-- 只爲新模塊生成依賴信息(速度更快)
2.3.3. 模塊的載入
內核模塊實驗時已經用過:
insmod module.ko <-- 推薦使用如下的命令, 自動加載依賴的模塊 modprobe module [module parameters]
2.3.4. 模塊的卸載
內核模塊實驗時已經用過:
rmmod module.ko <-- 推薦使用如下的命令, 自動卸載依賴的模塊 modprobe -r module
2.3.5. 模塊導出符號表
內核模塊被載入後,就動態的加載到內核中,爲了能讓其餘內核模塊使用其功能,須要將其中函數導出。
內核模塊中導出函數的方法:
EXPORT_SYMBOL(函數名) <-- 接在要導出的函數後面便可
EXPORT_SYMBOL_GPL(函數名) <-- 和EXPORT_SYMBOL同樣,區別在於只對標記爲GPL協議的模塊可見
內核模塊導出符號表 示例:
+ 首先編寫一個導出函數的模塊 module_A: test_module_A.c
#include<linux/init.h> #include<linux/module.h> #include<linux/kernel.h> MODULE_LICENSE("Dual BSD/GPL"); static int test_export_A_init(void) { /* 進入內核模塊 */ printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "ENTRY test_export_A!\n"); printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "\n\n\n\n\n"); return 0; } static void test_export_A_exit(void) { /* 退出內核模塊 */ printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "EXIT test_export_A!\n"); printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "\n\n\n\n\n"); } /* 要導出的函數 */ int export_add10(int param) { printk(KERN_ALERT "param from other module is : %d\n", param); return param + 10; } EXPORT_SYMBOL(export_add10); module_init(test_export_A_init); module_exit(test_export_A_exit);
test_module_A.c 的 Makefile
# must complile on customize kernel obj-m += export_A.o export_A-objs := test_export_A.o #generate the path CURRENT_PATH:=$(shell pwd) #the current kernel version number LINUX_KERNEL:=$(shell uname -r) #the absolute path LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL) #complie object all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules rm -rf modules.order .*.cmd *.o *.mod.c .tmp_versions *.unsigned #clean clean: rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned
+ 再編寫一個內核模塊 module_B,使用 module_A 導出的函數 : test_module_B.c
#include<linux/init.h> #include<linux/module.h> #include<linux/kernel.h> MODULE_LICENSE("Dual BSD/GPL"); extern int export_add10(int); // 這個函數是 module_A 中實現的 static int test_export_B_init(void) { /* 進入內核模塊 */ printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "ENTRY test_export_B!\n"); printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "\n\n\n\n\n"); /* 調用 module_A 導出的函數 */ printk(KERN_ALERT "result from test_export_A: %d\n", export_add10(100)); return 0; } static void test_export_B_exit(void) { /* 退出內核模塊 */ printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "EXIT test_export_B!\n"); printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "\n\n\n\n\n"); } module_init(test_export_B_init); module_exit(test_export_B_exit);
test_module_B.c 的 Makefile
# must complile on customize kernel obj-m += export_B.o export_B-objs := test_export_B.o #generate the path CURRENT_PATH:=$(shell pwd) #the current kernel version number LINUX_KERNEL:=$(shell uname -r) #the absolute path LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL) #complie object all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned #clean clean: rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned
+ 測試方法
1. 將 test_export_A.c 和對應的 Makefile 拷貝到 module_A 文件夾中
2. 將 test_export_B.c 和對應的 Makefile 拷貝到 module_B 文件夾中
3. 編譯 module_A 中的 test_export_A.c
4. 將編譯 module_A 後生成的 Module.symvers 拷貝到 module_B 文件夾中
5. 編譯 module_B 中的 test_export_B.c
6. 先安裝 模塊A,再安裝模塊B
7. dmesg 查看log
8. 用 rmmod 卸載模塊B 和 模塊A (注意卸載順序,先卸載B再卸載A)
[root@vbox chap17]# ll total 8 drwxrwxr-x 2 root root 4096 Dec 7 22:14 module_A drwxrwxr-x 2 root root 4096 Dec 7 22:14 module_B [root@vbox chap17]# ll module_A total 8 -rw-r--r-- 1 root root 517 Dec 7 21:58 Makefile -rw-r--r-- 1 root root 893 Dec 7 21:58 test_export_A.c [root@vbox chap17]# ll module_B total 8 -rw-r--r-- 1 root root 532 Dec 7 21:58 Makefile -rw-r--r-- 1 root root 830 Dec 7 21:58 test_export_B.c [root@vbox chap17]# cd module_A/ [root@vbox module_A]# ll total 8 -rw-r--r-- 1 root root 517 Dec 7 21:58 Makefile -rw-r--r-- 1 root root 893 Dec 7 21:58 test_export_A.c [root@vbox module_A]# make make -C /usr/src/kernels/2.6.32-279.el6.x86_64 M=/root/chap17/module_A modules make[1]: Entering directory `/usr/src/kernels/2.6.32-279.el6.x86_64' CC [M] /root/chap17/module_A/test_export_A.o LD [M] /root/chap17/module_A/export_A.o Building modules, stage 2. MODPOST 1 modules CC /root/chap17/module_A/export_A.mod.o LD [M] /root/chap17/module_A/export_A.ko.unsigned NO SIGN [M] /root/chap17/module_A/export_A.ko make[1]: Leaving directory `/usr/src/kernels/2.6.32-279.el6.x86_64' rm -rf modules.order .*.cmd *.o *.mod.c .tmp_versions *.unsigned [root@vbox module_A]# ll total 120 -rw-r--r-- 1 root root 110452 Dec 7 22:31 export_A.ko -rw-r--r-- 1 root root 517 Dec 7 21:58 Makefile -rw-r--r-- 1 root root 69 Dec 7 22:31 Module.symvers -rw-r--r-- 1 root root 893 Dec 7 21:58 test_export_A.c [root@vbox module_A]# cd ../module_B [root@vbox module_B]# ll total 8 -rw-r--r-- 1 root root 532 Dec 7 21:58 Makefile -rw-r--r-- 1 root root 830 Dec 7 21:58 test_export_B.c [root@vbox module_B]# cp ../module_A/Module.symvers . [root@vbox module_B]# ll total 12 -rw-r--r-- 1 root root 532 Dec 7 21:58 Makefile -rw-r--r-- 1 root root 69 Dec 7 22:32 Module.symvers -rw-r--r-- 1 root root 830 Dec 7 21:58 test_export_B.c [root@vbox module_B]# make make -C /usr/src/kernels/2.6.32-279.el6.x86_64 M=/root/chap17/module_B modules make[1]: Entering directory `/usr/src/kernels/2.6.32-279.el6.x86_64' CC [M] /root/chap17/module_B/test_export_B.o LD [M] /root/chap17/module_B/export_B.o Building modules, stage 2. MODPOST 1 modules CC /root/chap17/module_B/export_B.mod.o LD [M] /root/chap17/module_B/export_B.ko.unsigned NO SIGN [M] /root/chap17/module_B/export_B.ko make[1]: Leaving directory `/usr/src/kernels/2.6.32-279.el6.x86_64' rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned [root@vbox module_B]# ll total 116 -rw-r--r-- 1 root root 108596 Dec 7 22:32 export_B.ko -rw-r--r-- 1 root root 532 Dec 7 21:58 Makefile -rw-r--r-- 1 root root 830 Dec 7 21:58 test_export_B.c [root@vbox module_B]# insmod ../module_A/export_A.ko [root@vbox module_B]# insmod export_B.ko [root@vbox module_B]# dmesg | tail -18 ************************* ENTRY test_export_A! ************************* ************************* ENTRY test_export_B! ************************* param from other module is : 100 result from test_export_A: 110 [root@vbox module_B]# rmmod export_B [root@vbox module_B]# rmmod export_A
注:
1. 必須把編譯模塊A後生成的 Module.symvers 拷貝到module_B 中再編譯模塊B,否在模塊B找不到模塊A導出的函數
2. 先安裝模塊A,再安裝模塊B。
3. 先卸載模塊B,再卸載模塊A。
4. 安裝卸載若是不按照上面的順序,會有錯誤提示,你們能夠試試看。
5. 我實驗的系統是 CentOS6.3 x86_64
2.6內核中增長了一個引人注目的新特性--統一設備模型(device model)。
統一設備模型的最初動機是爲了實現智能的電源管理,linux 內核爲了實現智能電源管理,須要創建表示系統中全部設備拓撲關係的樹結構,
這樣在關閉電源時,能夠從樹的節點開始關閉。
實現了統一設備模型以後,還給內核帶來了以下的好處:
1. 代碼重複最小化(統一處理的東西多了)
2. 能夠列舉系統中全部設備,觀察它們的狀態,並查看它們鏈接的總線
3. 能夠將系統中的所有設備以樹的形式完整,有效的展現出來--包括全部總線和內部鏈接
4. 能夠將設備和其對應的驅動聯繫起來,反之亦然
5. 能夠將設備按照類型加以歸類,無需理解物理設備的拓撲結構
6. 能夠沿設備樹的葉子向其根的反向依次遍歷,以保證能以正確的順序關閉設備電源
統一設備模型的核心部分就是 kobject,經過下面對kobject結構體的介紹,能夠大體瞭解它是如何使得各個物理設備可以以樹結構的形式組織起來的。
3.1.1. kobject
kobject的定義在 <linux/kobject.h> 中
struct kobject { const char *name; /* kobject 名稱 */ struct list_head entry; /* kobject 鏈表 */ struct kobject *parent; /* kobject 的父對象,說明kobject是有層次結構的 */ struct kset *kset; /* kobject 的集合,接下來有詳細介紹 */ struct kobj_type *ktype; /* kobject 的類型,接下來有詳細介紹 */ struct sysfs_dirent *sd; /* 在sysfs中,這個結構體表示kobject的一個inode結構體,sysfs以後也會介紹 */ struct kref kref; /* 提供 kobject 的引用計數 */ /* 一些標誌位 */ 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; unsigned int uevent_suppress:1; };
kobject 自己不表明什麼實際的內容,通常都是嵌在其餘數據結構中來發揮做用。(感受有點像內核數據結構鏈表的節點)
好比 <linux/cdev.h> 中的 struct cdev (表示字符設備的struct)
struct cdev { struct kobject kobj; /* 嵌在 cdev 中的kobject */ struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; };
cdev中嵌入了kobject以後,就能夠經過 cdev->kboj.parent 創建cdev之間的層次關係,經過 cdev->kobj.entry 獲取關聯的全部cdev設備等。
總之,嵌入了kobject以後,cdev設備之間就有了樹結構關係,cdev設備和其餘設備之間也有可層次關係。
3.1.2. ktype
ktype是爲了描述一族的kobject所具備的廣泛屬性,也就是將這一族的kobject的屬性統必定義一下,避免每一個kobject分別定義。
(感受有點像面嚮對象語言中的抽象類或者接口)
ktype的定義很簡單,參見<linux/kobject.h>
struct kobj_type { void (*release)(struct kobject *kobj); /* kobject的引用計數降到0時觸發的析構函數,負責釋放和清理內存的工做 */ struct sysfs_ops *sysfs_ops; /* sysfs操做相關的函數 */ struct attribute **default_attrs; /* kobject 相關的默認屬性 */ };
3.1.3. kset
kset是kobject對象的集合體,能夠全部相關的kobject置於一個kset之中,好比全部「塊設備」能夠放在一個表示塊設備的kset中。
kset的定義也不復雜,參見 <linux/kobject.h>
struct kset { struct list_head list; /* 表示kset中全部kobject的鏈表 */ spinlock_t list_lock; /* 用於保護 list 的自旋鎖*/ struct kobject kobj; /* kset中嵌入的一個kobject,使得kset也能夠表現的像同樣kobject同樣*/ struct kset_uevent_ops *uevent_ops; /* 處理kset中kobject的熱插拔事件 提供了與用戶空間熱插拔進行通訊的機制 */ };
3.1.4. kobject,ktype和kset之間的關係
這3個概念中,kobject是最基本的。kset和ktype是爲了將kobject進行分類,以便將共通的處理集中處理,從而減小代碼量,也增長維護性。
這裏kset和ktype都是爲了將kobject進行分類,爲何會有2中分類呢?
從整個內核的代碼來看,其實kset的數量是多於ktype的數量的,同一種ktype的kobject能夠位於不一樣的kset中。
作個不是很恰當的比喻,若是把kobject比做一我的的話,kset至關於一個一個國家,ktype則至關於人種(好比黃種人,白種人等等)。
人種的類型只有少數幾個,可是國家確有不少,人種的目的是描述一羣人的共通屬性,而國家的目地則是爲了管理一羣人。
一樣,ktype側重於描述,kset側重於管理。
3.1.5. kref
kref記錄kobject被引用的次數,當引用計數降到0的時候,則執行release函數釋放相關資源。
kref的定義參見:<linux/kref.h>
struct kref { atomic_t refcount; /* 只有一個表示引用計數的屬性,atomic_t 類型表示對它的訪問是原子操做 */ }; void kref_set(struct kref *kref, int num); /* 設置引用計數的值 */ void kref_init(struct kref *kref); /* 初始化引用計數 */ void kref_get(struct kref *kref); /* 增長引用計數 +1 */ int kref_put(struct kref *kref, void (*release) (struct kref *kref)); /* 減小引用計數 -1 當減小到0時,釋放相應資源 */
上面這些函數的具體實現能夠參考內核代碼 lib/kref.c
kobject的相關都在 <linux/kobject.h> 中定義了,主要由如下一些:
extern void kobject_init(struct kobject *kobj, struct kobj_type *ktype); /* 初始化一個kobject,設置它是哪一種ktype */ extern int __must_check kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...); /* 設置此kobject的parent,將此kobject加入到現有對象層次結構中 */ extern int __must_check kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...); /* 初始化kobject,完成kobject_add 函數的功能*/ extern void kobject_del(struct kobject *kobj); /* 將此kobject從現有對象層次結構中取消 */ extern struct kobject * __must_check kobject_create(void); /* 建立一個kobject,比kobject_init更經常使用 */ extern struct kobject * __must_check kobject_create_and_add(const char *name, struct kobject *parent); /* 建立一個kobject,並將其加入到現有對象層次結構中 */ extern int __must_check kobject_rename(struct kobject *, const char *new_name); /* 改變kobject的名稱 */ extern int __must_check kobject_move(struct kobject *, struct kobject *); /* 給kobject設置新的parent */ extern struct kobject *kobject_get(struct kobject *kobj); /* 增長kobject的引用計數 +1 */ extern void kobject_put(struct kobject *kobj); /* 減小kobject的引用計數 -1 */ extern char *kobject_get_path(struct kobject *kobj, gfp_t flag); /* 生成並返回與給定的一個kobj和kset相關聯的路徑 */
上面這些函數的具體實現能夠參考內核代碼 lib/kobject.c
sysfs是一個處於內存中的虛擬文件系統,它提供了kobject對象層次結構的視圖。
能夠用下面這個命令來查看 /sys 的結構
tree /sys # 顯示全部目錄和文件 或者 tree -L 1 /sys # 只顯示一層目錄
既然sysfs是kobject的視圖,那麼內核中確定提供了在sysfs中操做kobject的API。
kobject結構體中與sysfs關聯的字段就是 「struct sysfs_dirent *sd; 」這是一個目錄項結構,它表示kobject在sysfs中的位置。
關於目錄項,能夠參考:《Linux內核設計與實現》讀書筆記(十三)- 虛擬文件系統
4.1.1. sysfs中添加和刪除kobject很是簡單,就是上面介紹的 kobject操做中提到的
extern int __must_check kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...); /* 設置此kobject的parent,將此kobject加入到現有對象層次結構中 */ extern int __must_check kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...); /* 初始化kobject,完成kobject_add 函數的功能*/ extern void kobject_del(struct kobject *kobj); /* 將此kobject從現有對象層次結構中取消 */ ... ...等等
添加了kobject以後,只會增長文件夾,不會增長文件。
由於kobject在sysfs中就是映射成一個文件夾。
添加刪除kobject的示例代碼以下:
/****************************************************************************** * @file : test_kobject.c * @author : wangyubin * @date : Tue Dec 24 09:49:53 2013 * * @brief : 測試 kobject的建立和刪除 * history : init ******************************************************************************/ #include<linux/init.h> #include<linux/module.h> #include<linux/kernel.h> #include<linux/kobject.h> MODULE_LICENSE("Dual BSD/GPL"); struct kobject* kobj = NULL; static int test_kobject_init(void) { /* 初始化kobject,並加入到sysfs中 */ kobj = kobject_create_and_add("test_kobject", NULL); /* 進入內核模塊 */ printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "test_kobject is inited!\n"); printk(KERN_ALERT "*************************\n"); return 0; } static void test_kobject_exit(void) { /* 若是 kobj 不爲空,則將其從sysfs中刪除 */ if (kobj != NULL) kobject_del(kobj); /* 退出內核模塊 */ printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "test_kobject is exited!\n"); printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "\n\n\n\n\n"); } module_init(test_kobject_init); module_exit(test_kobject_exit);
對應的Makefile
# must complile on customize kernel obj-m += mykobject.o mykobject-objs := test_kobject.o #generate the path CURRENT_PATH:=$(shell pwd) #the current kernel version number LINUX_KERNEL:=$(shell uname -r) #the absolute path LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL) #complie object all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned #clean clean: rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned
測試方法:(我使用的測試系統是:Centos6.5 x86)
[root@localhost test_kobject]# ll <-- 開始時只有2個文件,一個測試代碼,一個Makefile total 8 -rw-r--r-- 1 root root 533 Dec 24 09:44 Makefile -rw-r--r-- 1 root root 908 Dec 24 09:44 test_kobject.c [root@localhost test_kobject]# make <-- 編譯用於測試的內核模塊 make -C /usr/src/kernels/2.6.32-431.el6.i686 M=/home/wyb/chap17/test_kobject modules make[1]: Entering directory `/usr/src/kernels/2.6.32-431.el6.i686' CC [M] /home/wyb/chap17/test_kobject/test_kobject.o LD [M] /home/wyb/chap17/test_kobject/mykobject.o Building modules, stage 2. MODPOST 1 modules CC /home/wyb/chap17/test_kobject/mykobject.mod.o LD [M] /home/wyb/chap17/test_kobject/mykobject.ko.unsigned NO SIGN [M] /home/wyb/chap17/test_kobject/mykobject.ko make[1]: Leaving directory `/usr/src/kernels/2.6.32-431.el6.i686' rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned [root@localhost test_kobject]# ll <-- 編譯後多出來的一個內核模塊 ***.ko total 100 -rw-r--r-- 1 root root 533 Dec 24 09:44 Makefile -rw-r--r-- 1 root root 91902 Dec 24 09:54 mykobject.ko -rw-r--r-- 1 root root 908 Dec 24 09:44 test_kobject.c [root@localhost test_kobject]# ll /sys/ <-- 安裝內核模塊 mykobject.ko 以前的 sysfs結構 total 0 drwxr-xr-x 2 root root 0 Dec 24 09:28 block drwxr-xr-x 17 root root 0 Dec 24 09:28 bus drwxr-xr-x 40 root root 0 Dec 24 09:28 class drwxr-xr-x 4 root root 0 Dec 24 09:28 dev drwxr-xr-x 12 root root 0 Dec 24 09:28 devices drwxr-xr-x 4 root root 0 Dec 24 09:28 firmware drwxr-xr-x 3 root root 0 Dec 24 09:28 fs drwxr-xr-x 2 root root 0 Dec 24 09:44 hypervisor drwxr-xr-x 5 root root 0 Dec 24 09:28 kernel drwxr-xr-x 84 root root 0 Dec 24 09:46 module drwxr-xr-x 2 root root 0 Dec 24 09:44 power [root@localhost test_kobject]# insmod mykobject.ko <-- 安裝內核模塊 [root@localhost test_kobject]# ll /sys/ <-- 安裝後,sysfs中多了一個文件夾 test_kobject total 0 drwxr-xr-x 2 root root 0 Dec 24 09:28 block drwxr-xr-x 17 root root 0 Dec 24 09:28 bus drwxr-xr-x 40 root root 0 Dec 24 09:28 class drwxr-xr-x 4 root root 0 Dec 24 09:28 dev drwxr-xr-x 12 root root 0 Dec 24 09:28 devices drwxr-xr-x 4 root root 0 Dec 24 09:28 firmware drwxr-xr-x 3 root root 0 Dec 24 09:28 fs drwxr-xr-x 2 root root 0 Dec 24 09:44 hypervisor drwxr-xr-x 5 root root 0 Dec 24 09:28 kernel drwxr-xr-x 85 root root 0 Dec 24 09:54 module drwxr-xr-x 2 root root 0 Dec 24 09:44 power drwxr-xr-x 2 root root 0 Dec 24 09:55 test_kobject [root@localhost test_kobject]# ll /sys/test_kobject/ <-- 追加kobject只能增長文件夾,文件夾中是沒有文件的 total 0 [root@localhost test_kobject]# rmmod mykobject.ko <-- 卸載內核模塊 [root@localhost test_kobject]# ll /sys/ <-- 卸載後,sysfs 中的文件夾 test_kobject 也消失了 total 0 drwxr-xr-x 2 root root 0 Dec 24 09:28 block drwxr-xr-x 17 root root 0 Dec 24 09:28 bus drwxr-xr-x 40 root root 0 Dec 24 09:28 class drwxr-xr-x 4 root root 0 Dec 24 09:28 dev drwxr-xr-x 12 root root 0 Dec 24 09:28 devices drwxr-xr-x 4 root root 0 Dec 24 09:28 firmware drwxr-xr-x 3 root root 0 Dec 24 09:28 fs drwxr-xr-x 2 root root 0 Dec 24 09:44 hypervisor drwxr-xr-x 5 root root 0 Dec 24 09:28 kernel drwxr-xr-x 84 root root 0 Dec 24 09:55 module drwxr-xr-x 2 root root 0 Dec 24 09:44 power
4.1.2. sysfs中添加文件
kobject是映射成sysfs中的目錄,那sysfs中的文件是什麼呢?
其實sysfs中的文件就是kobject的屬性,屬性的來源有2個:
+ 默認屬性 :: kobject所關聯的ktype中的 default_attrs 字段
默認屬性 default_attrs 的類型是結構體 struct attribute, 定義在 <linux/sysfs.h>
struct attribute { const char *name; /* sysfs文件樹中的文件名 */ struct module *owner; /* x86體系結構中已經再也不繼續使用了,可能在其餘體系結構中還會使用 */ mode_t mode; /* sysfs中該文件的權限 */ };
ktype中的 default_attrs 字段(即默認屬性)描述了sysfs中的文件,還有一個字段 sysfs_ops 則描述瞭如何使用默認屬性。
struct sysfs_ops 的定義也在 <linux/sysfs.h>
struct sysfs_ops { /* 在讀sysfs文件時該方法被調用 */ ssize_t (*show)(struct kobject *kobj, struct attribute *attr,char *buffer); /* 在寫sysfs文件時該方法被調用 */ ssize_t (*store)(struct kobject *kobj,struct attribute *attr,const char *buffer, size_t size); };
show 方法在讀取sysfs中文件時調用,它會拷貝attr提供的屬性到buffer指定的緩衝區
store 方法在寫sysfs中文件時調用,它會從buffer中讀取size字節的數據到attr提供的屬性中
增長默認屬性的示例代碼
/****************************************************************************** * @file : test_kobject_default_attr.c * @author : wangyubin * @date : Tue Dec 24 10:28:09 2013 * * @brief : 測試 kobject 的默認屬性的建立和刪除 * history : init ******************************************************************************/ #include<linux/init.h> #include<linux/module.h> #include<linux/kernel.h> #include<linux/kobject.h> #include<linux/sysfs.h> MODULE_LICENSE("Dual BSD/GPL"); static void myobj_release(struct kobject*); static ssize_t my_show(struct kobject *, struct attribute *, char *); static ssize_t my_store(struct kobject *, struct attribute *, const char *, size_t); /* 自定義的結構體,2個屬性,而且嵌入了kobject */ struct my_kobj { int ival; char* cname; struct kobject kobj; }; static struct my_kobj *myobj = NULL; /* my_kobj 的屬性 ival 所對應的sysfs中的文件,文件名 val */ static struct attribute val_attr = { .name = "val", .owner = NULL, .mode = 0666, }; /* my_kobj 的屬性 cname 所對應的sysfs中的文件,文件名 name */ static struct attribute name_attr = { .name = "name", .owner = NULL, .mode = 0666, }; static int test_kobject_default_attr_init(void) { struct attribute *myattrs[] = {NULL, NULL, NULL}; struct sysfs_ops *myops = NULL; struct kobj_type *mytype = NULL; /* 初始化 myobj */ myobj = kmalloc(sizeof(struct my_kobj), GFP_KERNEL); if (myobj == NULL) return -ENOMEM; /* 配置文件 val 的默認值 */ myobj->ival = 100; myobj->cname = "test"; /* 初始化 ktype */ mytype = kmalloc(sizeof(struct kobj_type), GFP_KERNEL); if (mytype == NULL) return -ENOMEM; /* 增長2個默認屬性文件 */ myattrs[0] = &val_attr; myattrs[1] = &name_attr; /* 初始化ktype的默認屬性和析構函數 */ mytype->release = myobj_release; mytype->default_attrs = myattrs; /* 初始化ktype中的 sysfs */ myops = kmalloc(sizeof(struct sysfs_ops), GFP_KERNEL); if (myops == NULL) return -ENOMEM; myops->show = my_show; myops->store = my_store; mytype->sysfs_ops = myops; /* 初始化kobject,並加入到sysfs中 */ memset(&myobj->kobj, 0, sizeof(struct kobject)); /* 這一步很是重要,沒有這一步init kobject會失敗 */ if (kobject_init_and_add(&myobj->kobj, mytype, NULL, "test_kobj_default_attr")) kobject_put(&myobj->kobj); printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "test_kobject_default_attr is inited!\n"); printk(KERN_ALERT "*************************\n"); return 0; } static void test_kobject_default_attr_exit(void) { kobject_del(&myobj->kobj); kfree(myobj); /* 退出內核模塊 */ printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "test_kobject_default_attr is exited!\n"); printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "\n\n\n\n\n"); } static void myobj_release(struct kobject *kobj) { printk(KERN_ALERT, "release kobject"); kobject_del(kobj); } /* 讀取屬性文件 val 或者name時會執行此函數 */ static ssize_t my_show(struct kobject *kboj, struct attribute *attr, char *buf) { printk(KERN_ALERT "SHOW -- attr-name: [%s]\n", attr->name); if (strcmp(attr->name, "val") == 0) return sprintf(buf, "%d\n", myobj->ival); else return sprintf(buf, "%s\n", myobj->cname); } /* 寫入屬性文件 val 或者name時會執行此函數 */ static ssize_t my_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t len) { printk(KERN_ALERT "STORE -- attr-name: [%s]\n", attr->name); if (strcmp(attr->name, "val") == 0) sscanf(buf, "%d\n", &myobj->ival); else sscanf(buf, "%s\n", myobj->cname); return len; } module_init(test_kobject_default_attr_init); module_exit(test_kobject_default_attr_exit);
對應的Makefile以下:
# must complile on customize kernel obj-m += mykobject_with_default_attr.o mykobject_with_default_attr-objs := test_kobject_default_attr.o #generate the path CURRENT_PATH:=$(shell pwd) #the current kernel version number LINUX_KERNEL:=$(shell uname -r) #the absolute path LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL) #complie object all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned #clean clean: rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned
測試方法:(我使用的測試系統是:Centos6.5 x86)
############################ 編譯 ######################################################## [root@localhost test_kobject_defalt_attr]# ll total 8 -rw-r--r-- 1 root root 582 Dec 24 15:02 Makefile -rw-r--r-- 1 root root 4032 Dec 24 16:58 test_kobject_default_attr.c [root@localhost test_kobject_defalt_attr]# make make -C /usr/src/kernels/2.6.32-431.el6.i686 M=/home/wyb/chap17/test_kobject_defalt_attr modules make[1]: Entering directory `/usr/src/kernels/2.6.32-431.el6.i686' CC [M] /home/wyb/chap17/test_kobject_defalt_attr/test_kobject_default_attr.o /home/wyb/chap17/test_kobject_defalt_attr/test_kobject_default_attr.c: In function ‘myobj_release’: /home/wyb/chap17/test_kobject_defalt_attr/test_kobject_default_attr.c:109: warning: too many arguments for format LD [M] /home/wyb/chap17/test_kobject_defalt_attr/mykobject_with_default_attr.o Building modules, stage 2. MODPOST 1 modules CC /home/wyb/chap17/test_kobject_defalt_attr/mykobject_with_default_attr.mod.o LD [M] /home/wyb/chap17/test_kobject_defalt_attr/mykobject_with_default_attr.ko.unsigned NO SIGN [M] /home/wyb/chap17/test_kobject_defalt_attr/mykobject_with_default_attr.ko make[1]: Leaving directory `/usr/src/kernels/2.6.32-431.el6.i686' rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned [root@localhost test_kobject_defalt_attr]# ll total 104 -rw-r--r-- 1 root root 582 Dec 24 15:02 Makefile -rw-r--r-- 1 root root 96805 Dec 24 16:58 mykobject_with_default_attr.ko -rw-r--r-- 1 root root 4032 Dec 24 16:58 test_kobject_default_attr.c ############################ 安裝 ######################################################## [root@localhost test_kobject_defalt_attr]# insmod mykobject_with_default_attr.ko [root@localhost test_kobject_defalt_attr]# ll /sys/ <-- kobject對應的文件夾 total 0 drwxr-xr-x 2 root root 0 Dec 24 15:50 block drwxr-xr-x 17 root root 0 Dec 24 15:50 bus drwxr-xr-x 40 root root 0 Dec 24 15:50 class drwxr-xr-x 4 root root 0 Dec 24 15:50 dev drwxr-xr-x 12 root root 0 Dec 24 15:50 devices drwxr-xr-x 4 root root 0 Dec 24 15:50 firmware drwxr-xr-x 3 root root 0 Dec 24 15:50 fs drwxr-xr-x 2 root root 0 Dec 24 16:06 hypervisor drwxr-xr-x 5 root root 0 Dec 24 15:50 kernel drwxr-xr-x 85 root root 0 Dec 24 16:59 module drwxr-xr-x 2 root root 0 Dec 24 16:06 power drwxr-xr-x 2 root root 0 Dec 24 16:59 test_kobj_default_attr [root@localhost test_kobject_defalt_attr]# ll /sys/test_kobj_default_attr/ <-- kobject的2個屬性文件 total 0 -rw-rw-rw- 1 root root 4096 Dec 24 16:59 name -rw-rw-rw- 1 root root 4096 Dec 24 16:59 val [root@localhost test_kobject_defalt_attr]# dmesg <-- dmesg 中只有初始化的信息 ************************* test_kobject_default_attr is inited! ************************* ############################ 讀取屬性文件 ############################################### [root@localhost test_kobject_defalt_attr]# cat /sys/test_kobj_default_attr/val <-- 屬性值就是咱們在測試代碼中輸入的值 100 [root@localhost test_kobject_defalt_attr]# cat /sys/test_kobj_default_attr/name <-- 屬性值就是咱們在測試代碼中輸入的值 test [root@localhost test_kobject_defalt_attr]# dmesg <-- dmesg 中多了2條讀取屬性文件的log SHOW -- attr-name: [val] SHOW -- attr-name: [name] ############################ 寫入屬性文件 ################################################ [root@localhost test_kobject_defalt_attr]# echo "200" > /sys/test_kobj_default_attr/val <-- val文件中寫入 200 [root@localhost test_kobject_defalt_attr]# echo "abcdefg" > /sys/test_kobj_default_attr/name <-- name文件中寫入 adcdefg [root@localhost test_kobject_defalt_attr]# dmesg <-- dmesg 中又多了2條寫入屬性文件的log STORE -- attr-name: [val] STORE -- attr-name: [name] [root@localhost test_kobject_defalt_attr]# cat /sys/test_kobj_default_attr/val <-- 再次查看 val文件中的值,已變爲200 200 [root@localhost test_kobject_defalt_attr]# cat /sys/test_kobj_default_attr/name <-- 再次查看 name文件中的值,已變爲abcdefg abcdefg ############################ 卸載 ######################################################## [root@localhost test_kobject_defalt_attr]# rmmod mykobject_with_default_attr.ko
注:參考博客 Linux設備模型 (2)
+ 新屬性 :: kobject 本身定義的屬性
通常來講,使用默認屬性就足夠了。由於具備共同ktype的kobject在本質上區別都不大,好比都是塊設備的kobject,
它們使用默認的屬性集合不但可讓事情簡單,有助於代碼合併,還可使相似的對象在sysfs中的外觀一致。
在一些特殊的狀況下,kobject可能會須要本身特有的屬性。內核也充分考慮到了這些狀況,提供了建立或者刪除新屬性的方法。
在sysfs中文件的操做方法參見: fs/sysfs/file.c
/* 文件的相關操做很是多,這裏只列出建立文件和刪除文件的方法 */ /** * 給 kobj 增長一個新的屬性 attr * kobj 對應sysfs中的一個文件夾, attr 對應sysfs中的一個文件 */ int sysfs_create_file(struct kobject * kobj, const struct attribute * attr) { BUG_ON(!kobj || !kobj->sd || !attr); return sysfs_add_file(kobj->sd, attr, SYSFS_KOBJ_ATTR); } /** * 給 kobj 刪除一個新的屬性 attr * kobj 對應sysfs中的一個文件夾, attr 對應sysfs中的一個文件 */ void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr) { sysfs_hash_and_remove(kobj->sd, attr->name); }
除了能夠在sysfs中增長/刪除的文件,還能夠在sysfs中增長或者刪除一個符號連接。
具體實現參見:fs/sysfs/symlink.c
/* 下面只列出了建立和刪除符號連接的方法,其餘方法請參考 symlink.c 文件 */ /** * 在kobj對應的文件夾中建立一個符號連接指向 target * 符號連接的名稱就是 name */ int sysfs_create_link(struct kobject *kobj, struct kobject *target, const char *name) { return sysfs_do_create_link(kobj, target, name, 1); } void sysfs_remove_link(struct kobject * kobj, const char * name) { struct sysfs_dirent *parent_sd = NULL; if (!kobj) parent_sd = &sysfs_root; else parent_sd = kobj->sd; sysfs_hash_and_remove(parent_sd, name); }
增長新的屬性的示例代碼 (這裏只演示了增長文件的方法,增長符號連接的方法與之相似)
/****************************************************************************** * @file : test_kobject_new_attr.c * @author : wangyubin * @date : Tue Dec 24 17:10:31 2013 * * @brief : 測試 kobject 中增長和刪除新屬性 * history : init ******************************************************************************/ #include<linux/init.h> #include<linux/module.h> #include<linux/kernel.h> #include<linux/kobject.h> #include<linux/sysfs.h> MODULE_LICENSE("Dual BSD/GPL"); static void myobj_release(struct kobject*); static ssize_t my_show(struct kobject *, struct attribute *, char *); static ssize_t my_store(struct kobject *, struct attribute *, const char *, size_t); /* 自定義的結構體,其中嵌入了kobject,經過屬性 c_attr 來控制增長或者刪除新屬性 */ struct my_kobj { int c_attr; /* 值爲0:刪除新屬性, 值爲1:增長新屬性*/ int new_attr; struct kobject kobj; }; static struct my_kobj *myobj = NULL; /* my_kobj 的屬性 c_attr 所對應的sysfs中的文件,文件名 c_attr */ static struct attribute c_attr = { .name = "c_attr", .owner = NULL, .mode = 0666, }; /* 用於動態增長或者刪除的新屬性 */ static struct attribute new_attr = { .name = "new_attr", .owner = NULL, .mode = 0666, }; static int test_kobject_new_attr_init(void) { struct attribute *myattrs[] = {NULL, NULL}; struct sysfs_ops *myops = NULL; struct kobj_type *mytype = NULL; /* 初始化 myobj */ myobj = kmalloc(sizeof(struct my_kobj), GFP_KERNEL); if (myobj == NULL) return -ENOMEM; /* 配置文件 val 的默認值 */ myobj->c_attr = 0; /* 初始化 ktype */ mytype = kmalloc(sizeof(struct kobj_type), GFP_KERNEL); if (mytype == NULL) return -ENOMEM; /* 增長1個默認屬性文件 */ myattrs[0] = &c_attr; /* 初始化ktype的默認屬性和析構函數 */ mytype->release = myobj_release; mytype->default_attrs = myattrs; /* 初始化ktype中的 sysfs */ myops = kmalloc(sizeof(struct sysfs_ops), GFP_KERNEL); if (myops == NULL) return -ENOMEM; myops->show = my_show; myops->store = my_store; mytype->sysfs_ops = myops; /* 初始化kobject,並加入到sysfs中 */ memset(&myobj->kobj, 0, sizeof(struct kobject)); /* 這一步很是重要,沒有這一步init kobject會失敗 */ if (kobject_init_and_add(&myobj->kobj, mytype, NULL, "test_kobj_new_attr")) kobject_put(&myobj->kobj); printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "test_kobject_new_attr is inited!\n"); printk(KERN_ALERT "*************************\n"); return 0; } static void test_kobject_new_attr_exit(void) { kobject_del(&myobj->kobj); kfree(myobj); /* 退出內核模塊 */ printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "test_kobject_new_attr is exited!\n"); printk(KERN_ALERT "*************************\n"); printk(KERN_ALERT "\n\n\n\n\n"); } static void myobj_release(struct kobject *kobj) { printk(KERN_ALERT "release kobject"); kobject_del(kobj); } /* 讀取屬性文件 c_attr 或者 new_attr 時會執行此函數 */ static ssize_t my_show(struct kobject *kboj, struct attribute *attr, char *buf) { printk(KERN_ALERT "SHOW -- attr-name: [%s]\n", attr->name); if (strcmp(attr->name, "c_attr") == 0) return sprintf(buf, "%d\n", myobj->c_attr); else if (strcmp(attr->name, "new_attr") == 0) return sprintf(buf, "%d\n", myobj->new_attr); return 0; } /* 寫入屬性文件c_attr 或者 new_attr 時會執行此函數 */ static ssize_t my_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t len) { printk(KERN_ALERT "STORE -- attr-name: [%s]\n", attr->name); if (strcmp(attr->name, "c_attr") == 0) sscanf(buf, "%d\n", &myobj->c_attr); else if (strcmp(attr->name, "new_attr") == 0) sscanf(buf, "%d\n", &myobj->new_attr); if (myobj->c_attr == 1) /* 建立新的屬性文件 */ { if (sysfs_create_file(kobj, &new_attr)) return -1; else myobj->new_attr = 100; /* 新屬性文件的值默認設置爲 100 */ } if (myobj->c_attr == 0) /* 刪除新的屬性文件 */ sysfs_remove_file(kobj, &new_attr); return len; } module_init(test_kobject_new_attr_init); module_exit(test_kobject_new_attr_exit);
對應的Makefile以下:
# must complile on customize kernel obj-m += mykobject_with_new_attr.o mykobject_with_new_attr-objs := test_kobject_new_attr.o #generate the path CURRENT_PATH:=$(shell pwd) #the current kernel version number LINUX_KERNEL:=$(shell uname -r) #the absolute path LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL) #complie object all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned #clean clean: rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned
測試方法:(我使用的測試系統是:Centos6.5 x86)
根據測試代碼,增長/刪除新屬性是根據默認屬性 c_attr 的值來決定的。
c_attr 設置爲0 時,刪除新屬性 new_attr
c_attr 設置爲1 時,新增新屬性 new_attr
############################ 編譯,安裝,卸載同測試默認屬性時同樣 ####################### ... 省略 ... ############################ 動態增長新屬性文件 ######################################### [root@localhost test_kobject_new_attr]# ll /sys/test_kobj_new_attr/ <-- 默認沒有新屬性 new_attr total 0 -rw-rw-rw- 1 root root 4096 Dec 24 18:47 c_attr [root@localhost test_kobject_new_attr]# cat /sys/test_kobj_new_attr/c_attr <-- c_attr 的值爲0 0 [root@localhost test_kobject_new_attr]# echo "1" > /sys/test_kobj_new_attr/c_attr <-- c_attr 的值設爲1 [root@localhost test_kobject_new_attr]# ll /sys/test_kobj_new_attr/ <-- 增長了新屬性 new_attr total 0 -rw-rw-rw- 1 root root 4096 Dec 24 19:02 c_attr -rw-rw-rw- 1 root root 4096 Dec 24 19:02 new_attr ############################ 動態刪除屬性文件 ########################################### [root@localhost test_kobject_new_attr]# echo "0" > /sys/test_kobj_new_attr/c_attr <-- c_attr 的值爲0 [root@localhost test_kobject_new_attr]# ll /sys/test_kobj_new_attr/ <-- 刪除了新屬性 new_attr total 0 -rw-rw-rw- 1 root root 4096 Dec 24 19:03 c_attr
4.1.3. sysfs相關約定
爲了保持sysfs的乾淨和直觀,在內核開發中涉及到sysfs相關內容時,須要注意如下幾點:
+ sysfs屬性保證每一個文件只導出一個值,該值爲文本形式而且能夠映射爲簡單的C類型
+ sysfs中要以一個清晰的層次組織數據
+ sysfs提供內核到用戶空間的服務
內核事件層也是利用kobject和sysfs來實現的,用戶空間經過監控sysfs中kobject的屬性的變化來異步的捕獲內核中kobject發出的信號。
用戶空間能夠經過一種netlink的機制來獲取內核事件。
內核空間向用戶空間發送信號使用 kobject_uevent() 函數,具體參見: <linux/kobject.h>
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
下面用個小例子演示一些內核事件的實現原理:
4.2.1. 內核模塊安裝或者刪除時,會發送 KOBJ_ADD 或者 KOBJ_REMOVE 的消息
內核模塊的代碼就用上面最簡單的那個例子 test_kobject.c 的代碼便可
4.2.2. 用戶態程序: 經過 netlink機制來接收 kobject 的事件通知
/****************************************************************************** * @file : test_netlink_client.c * @author : wangyubin * @date : Tue Dec 24 19:48:54 2013 * * @brief : 經過 netlink機制接收kobject發出的信號 * history : init ******************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <asm/types.h> #include <sys/socket.h> #include <linux/netlink.h> void MonitorNetlinkUevent() { int sockfd; struct sockaddr_nl sa; int len; char buf[4096]; struct iovec iov; struct msghdr msg; int i; memset(&sa,0,sizeof(sa)); sa.nl_family = AF_NETLINK; sa.nl_groups = NETLINK_KOBJECT_UEVENT; sa.nl_pid = 0;//getpid(); both is ok memset(&msg,0,sizeof(msg)); iov.iov_base = (void *)buf; iov.iov_len = sizeof(buf); msg.msg_name = (void *)&sa; msg.msg_namelen = sizeof(sa); msg.msg_iov = &iov; msg.msg_iovlen = 1; sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT); if(sockfd == -1) printf("socket creating failed:%s\n",strerror(errno)); if(bind(sockfd,(struct sockaddr *)&sa,sizeof(sa)) == -1) printf("bind error:%s\n", strerror(errno)); while(1) { memset(buf, 0, sizeof(buf)); len=recvmsg(sockfd, &msg, 0); if(len < 0){} //printf("receive error\n"); else if(len < 32||len > sizeof(buf)) printf("invalid message"); for(i=0; i<len; i++) if(*(buf+i) == '\0') buf[i] = '\n'; printf("received %d bytes\n%s\n", len, buf); } } int main(int argc, char *argv[]) { MonitorNetlinkUevent(); return 0; }
注:代碼是拷貝的 《Linux設備節點建立》內核kobject上報uevent過濾規則 中的用戶態程序部分
4.2.3. 測試方法:(我使用的測試系統是:Centos6.5 x86)
############################ 編譯並啓動用戶態程序 (窗口1)################################### [root@localhost test_kobject_event]# ll total 4 -rw-r--r-- 1 root root 1846 Dec 24 20:36 test_netlink_client.c [root@localhost test_kobject_event]# gcc -o test_netlink_client test_netlink_client.c <-- 編譯用戶態程序 [root@localhost test_kobject_event]# ./test_netlink_client <-- 啓動後等待內核kobject的事件到來 ############################ 安裝內核模塊並查看用戶態程序輸出 (窗口2)####################### [root@localhost test_kobject]# insmod mykobject.ko <-- 在窗口2中安裝內核模塊,窗口1中會接收到 KOBJ_ADD 信號 ############################ 卸載內核模塊並查看用戶態程序輸出 (窗口2)####################### [root@localhost test_kobject]# rmmod mykobject.ko <-- 在窗口2中安裝內核模塊,窗口1中會接收到 KOBJ_REMOVE 信號
kobject加sysfs給內核帶來的好處不是三言兩語可以說得清楚的,上面的例子也只是經過簡單的使用來直觀的感覺一下kobject,例子自己沒有什麼實際意義。
最後一個例子中使用了 netlink機制,我在以前的項目中使用過netlink來監控系統進程的I/O信息,對netlink有一些初步的瞭解。
可是例子中只是用來獲取一下 kobject的事件而已,這裏主要爲了說明kobject,netlink的相關內容之後有機會再補充。