1. 程式人生 > >《Linux核心設計與實現》讀書筆記(十七)- 裝置與模組

《Linux核心設計與實現》讀書筆記(十七)- 裝置與模組

本章主要討論與linux的裝置驅動和裝置管理的相關的4個核心成分,裝置型別,模組,核心物件,sysfs。

主要內容:

  • 裝置型別
  • 核心模組
  • 核心物件
  • sysfs
  • 總結

1. 裝置型別

linux中主要由3種類型的裝置,分別是:

裝置型別

代表裝置

特點

訪問方式

塊裝置 硬碟,光碟 隨機訪問裝置中的內容 一般都是把裝置掛載為檔案系統後再訪問
字元裝置 鍵盤,印表機 只能順序訪問(一個一個字元或者一個一個位元組) 一般不掛載,直接和裝置互動
網路裝置 網絡卡 打破了Unix "所有東西都是檔案" 的設計原則 通過套接字API來訪問

除了以上3種典型的裝置之外,其實Linux中還有一些其他的裝置型別,其中見的較多的應該算是"偽裝置"。

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

典型的"偽裝置"就是 /dev/random(核心隨機數發生器), /dev/null(空裝置), /dev/zero(零裝置), /dev/full(滿裝置)

2. 核心模組

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

通過編寫核心模組來給核心增加功能或者介面是個很好的方式(既不用重新編譯核心,也方便除錯和刪除)。

2.1 核心模組示例

核心模組可以帶引數也可以不帶引數,不帶引數的核心模組比較簡單。

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

2.1.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)

[[email protected] chap17]# uname -r
2.6.32-279.el6.x86_64
[[email protected] 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
[[email protected] 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
[[email protected] 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 命令可以檢視對核心模組引數的註釋
[[email protected] 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 個引數都是預設的
[[email protected] chap17]# insmod paramed_km.ko
[[email protected] chap17]# rmmod paramed_km.ko
[[email protected] 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 個引數都被設定
[[email protected] chap17]# insmod paramed_km.ko stu_id=100 stu_name_out=myname arr_out=1,2,3,4,5
[[email protected] chap17]# rmmod paramed_km.ko
[[email protected] 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)

[[email protected] 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
[[email protected] 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
[[email protected] 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

[[email protected] chap17]# cd module_A/
[[email protected] 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
[[email protected] 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
[[email protected] 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

[[email protected] module_A]# cd ../module_B
[[email protected] 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
[[email protected] module_B]# cp ../module_A/Module.symvers .
[[email protected] 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
[[email protected] 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
[[email protected] 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

[[email protected] module_B]# insmod ../module_A/export_A.ko 
[[email protected] module_B]# insmod export_B.ko 
[[email protected] 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

[[email protected] module_B]# rmmod export_B
[[email protected] 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;  /*