Linux內核設計與實現 學習筆記(7)設備與模塊

主要內容:

  • 設備類型
  • 內核模塊
  • 內核對象
  • sysfs
  • 總結

 

1. 設備類型

linux中主要由3種類型的設備,分別是:

設備類型

代表設備

特點

訪問方式

塊設備 硬盤,光盤 隨機訪問設備中的內容 一般都是把設備掛載爲文件系統後再訪問
字符設備 鍵盤,打印機 只能順序訪問(一個一個字符或者一個一個字節) 一般不掛載,直接和設備交互
網絡設備 網卡 打破了Unix "所有東西都是文件" 的設計原則 通過套接字API來訪問

 

除了以上3種典型的設備之外,其實Linux中還有一些其他的設備類型,其中見的較多的應該算是"僞設備"。

所謂"僞設備",其實就是一些虛擬的設備,僅提供訪問內核功能而已,沒有物理設備與之關聯。

典型的"僞設備"就是 /dev/random(內核隨機數發生器), /dev/null(空設備), /dev/zero(零設備), /dev/full(滿設備)

 

2. 內核模塊

Linux內核是模塊化組成的,內核中的模塊可以按需加載,從而保證內核啓動時不用加載所有的模塊,即減少了內核的大小,也提高了效率。

通過編寫內核模塊來給內核增加功能或者接口是個很好的方式(既不用重新編譯內核,也方便調試和刪除)。

 

2.1 內核模塊示例

內核模塊可以帶參數也可以不帶參數,不帶參數的內核模塊比較簡單。

我之前的幾篇隨筆中用於測試的例子都是用不帶參數的內核模塊來實驗的。

 

2.1.1. 無參數的內核模塊

參考:

Linux內核設計與實現 讀書筆記(6)內核數據結構

Linux內核設計與實現 讀書筆記(8)中斷處理下半部

Linux內核設計與實現 學習筆記(1)定時器和時間管理

 

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 內核模塊的位置

2.2.1.  內核代碼外

上面的例子,以及之前博客中內核模塊的例子都是把模塊代碼放在內核之外來運行的。

 

2.2.2. 內核代碼中

內核模塊的代碼也可以直接放到內核代碼樹中。

如果你開發了一種驅動,並且希望被加入到內核中,那麼,可以在編寫驅動的時候就將完成此驅動功能的內核模塊加到內核代碼樹中 driver 的相應位置。

將內核模塊加入內核代碼樹中之後,不需要另外寫 Makefile,修改內核代碼樹中的已有的 Makefile 就行。

比如,寫了一個某種字符設備相關的驅動,可以把它加到內核代碼的 /drivers/char 下,

同時修改 /drivers/char下的Makefie,仿照裏面已有的內容,增加新驅動的編譯相關內容即可。

 

之後,在編譯內核的時候會將新的驅動以內核模塊的方式編譯出來。

 

2.3 內核模塊相關操作

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

 

 

3. 內核對象

2.6內核中增加了一個引人注目的新特性--統一設備模型(device model)。

統一設備模型的最初動機是爲了實現智能的電源管理,linux 內核爲了實現智能電源管理,需要建立表示系統中所有設備拓撲關係的樹結構,

這樣在關閉電源時,可以從樹的節點開始關閉。

 

實現了統一設備模型之後,還給內核帶來了如下的好處:

1. 代碼重複最小化(統一處理的東西多了)

2. 可以列舉系統中所有設備,觀察它們的狀態,並查看它們連接的總線

3. 可以將系統中的全部設備以樹的形式完整,有效的展示出來--包括所有總線和內部連接

4. 可以將設備和其對應的驅動聯繫起來,反之亦然

5. 可以將設備按照類型加以歸類,無需理解物理設備的拓撲結構

6. 可以沿設備樹的葉子向其根的反向依次遍歷,以保證能以正確的順序關閉設備電源

 

3.1 kobject 簡介

統一設備模型的核心部分就是 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的熱插拔事件 提供了與用戶空間熱插拔進行通信的機制 */
};

 

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

 

3.2 kobject 操作

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

 

4. sysfs

sysfs是一個處於內存中的虛擬文件系統,它提供了kobject對象層次結構的視圖。

可以用下面這個命令來查看 /sys 的結構

tree /sys       # 顯示所有目錄和文件
或者
tree -L 1 /sys  # 只顯示一層目錄

 

4.1 sysfs中的kobject

既然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提供內核到用戶空間的服務

 

4.2 基於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 信號
複製代碼

 

5. 總結

kobject加sysfs給內核帶來的好處不是三言兩語能夠說得清楚的,上面的例子也只是通過簡單的使用來直觀的感受一下kobject,例子本身沒有什麼實際意義。

最後一個例子中使用了 netlink機制,我在之前的項目中使用過netlink來監控系統進程的I/O信息,對netlink有一些初步的瞭解。

但是例子中只是用來獲取一下 kobject的事件而已,這裏主要爲了說明kobject,netlink的相關內容以後有機會再補充。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章