[ 永遠的UNIX::UNIX技術資料的寶庫 ]   GB | BIG5

首頁 > 編程技術 > 其它 > 正文
FreeBSD4.0 動態內核鏈接機制(KLD)編程指南
本文出自: http://www.asfocus.com (2001-07-04 09:04:00)

作者:Andrew Reiter < mailto: arr@watson.org > 
整理:小四 < mailto: scz@nsfocus.com > 
出處:http://www.watson.org/~arr/ 
主頁:http://www.nsfocus.com 
日期:2000-10-26 

目錄: 

★ 簡介 
★ 所有KLD的共性 
★ KLD系統調用實現框架 
★ KLD字符型設備驅動程序實現框架 
★ 參考資料 

★ 簡介 

本文的目的在介紹FreeBSD操作系統下基礎的KLD開發設計技術。 

FreeBSD 3.1下提供過可加載內核模塊技術(LKM),FreeBSD 4.0下提供的動態內 
核鏈接機制(KLD),可以簡單地理解成LKM的升級。採用KLD,可以增加系統調用、調 
試設備驅動程序、提供訪問內核數據空間的方便接口。下面我們對比一下LKM和KLD: 

-------------------------------------------------------------------------- 
1. LKM採用用戶態的鏈接器重定位二進制數據再壓入內核空間。 

KLD機制由內核親自進行重定位操作 

2. LKM採用特殊的數據結構,LKM Driver了解這種數據結構,並通過它與內核交互, 
比如VFS LKM採用一個結構,該結構裡包含指向VFS TABLES的指針。 

LKM的目的單純明確,很難將LKM代碼移植成真正的內核代碼。 

KLD採用常規代碼,一個KLD文件可以不包含任何模塊,也可以包含多個模塊。每 
個模塊均包含自初始化代碼,並完成自注冊。 

KLD的代碼和內核代碼保持一致。很容易從內核中提取部分代碼移植成KLD代碼。 

3. 現在KLD的依賴關系和版本信息從內核裡剝離出來,完全位模塊層。 
-------------------------------------------------------------------------- 

這份指南直奔兩個KLD開發者感興趣的主題,希望你具有基本的FreeBSD內核知識 
以及K & R C編程技能。必須提醒的是,例子代碼在FreeBSD 4.0下調試通過。下面我 
們將要介紹的主題有三: 

-------------------------------------------------------------------------- 
1. 所有KLD的共性 
2. KLD系統調用實現框架 
3. KLD字符型設備驅動程序實現框架 
-------------------------------------------------------------------------- 

本文的目的是幫助那些正在學習KLD編程的朋友快速掌握KLD編程接口,進入更高 
層次。 

★ 所有KLD的共性 

所有的KLD代碼都有一個主入口函數和一個宏,並且簡單地採用Makefile文件編譯。 

-------------------------------------------------------------------------- 
1. 主入口函數,或者說加載/卸載句柄 
2. DECLARE_MODULE()宏 
3. 利用Makefile文件進行編譯 
-------------------------------------------------------------------------- 

下面是一個典型的主入口函數: 

-------------------------------------------------------------------------- 
static int helloworld_load ( module_t mod, int what, void * arg ) 

int err = 0; 

switch ( what ) 

case MOD_LOAD: 
/* 
* uprintf() 是內核空間函數,類似printf()。當在內核空間使用 
* printf()時,輸出內容需要用dmesg查看。uprintf()將直接輸出到 
* 當前正在使用的tty上 
*/ 
printf( "MOD_LOAD: dmesg -c test\n" ); 
uprintf( "System call loaded at slot: %d\n", syscall_num ); 
break; 
case MOD_UNLOAD: 
printf( "MOD_UNLOAD: dmesg -c test\n" ); 
uprintf( "System call unloaded from slot: %d\n", syscall_num ); 
break; 
case MOD_SHUTDOWN: 
uprintf( "System shutdown\n" ); 
break; 
default: 
err = EINVAL; 
break; 
} /* end of switch */ 
return( err ); 
} /* end of helloworld_load */ 
-------------------------------------------------------------------------- 

該函數類似Linux下的init_module和cleanup_module,注意無論加載/卸載KLD,都要 
經過該函數。函數名字自己定義,將來作為函數指針傳遞給DECLARE_MODULE()宏。當 
使用kldload/kldunload加載/卸載KLD的時候,helloworld_load()被調用。 

在/usr/include/sys/module.h裡定義了一個函數指針類型: 

typedef int ( * modeventhand_t ) ( module_t mod, int /*modeventtype_t*/ what, void * arg ); 

helloworld_load()正是modeventhand_t型常量,從名字看,模塊--事件--句柄,有 
意思。 

typedef struct module * module_t; 

module_t mod是指向module結構的指針。module結構按照鏈表方式組織,可以從結構 
中獲取指向其它module結構的指針。結構成員還包含諸如KLD ID號之類的有用信息。 

int what實際是枚舉類型變量,modeventtype_t( enum modeventtype ),目前只有 
三個有效值: 

MOD_LOAD 執行kldload時被調用 
MOD_UNLOAD 執行kldunload時被調用 
MOD_SHUTDOWN shutdown時被調用 

DECLARE_MODULE()對KLD很重要,然而通常所見並不是DECLARE_MODULE(),有兩個 
宏封裝了它,使得編程更加方便。/usr/include/sys/module.h裡定義了 
DECLARE_MODULE 宏: 

-------------------------------------------------------------------------- 
#define DECLARE_MODULE(name, data, sub, order) \ 
SYSINIT(name##module, sub, order, module_register_init, &data) \ 
struct __hack 
-------------------------------------------------------------------------- 

下面我們來看看各個參數的意義: 

name 模塊名,注意這個不是KLD名,KLD名就是將來Makefile編譯產生的靜態文件名 
模塊名將在SYSINIT調用中被使用。下面這個例子清楚表明了KLD名和模塊名的 
區別。 

[root@ /usr/home/scz/src]> kldstat -v -i 4 
Id Refs Address Size Name 
4 1 0xc0ae2000 2000 flkm_2 <-- 這是KLD名 
Contains modules: 
Id Name 
84 donothing <-- 這是模塊名 
85 helloworld <-- 這也是模塊名 
[root@ /usr/home/scz/src]> 

data 指向 struct moduledata 的指針。/usr/include/sys/module.h裡定義了該結 
構: 

-------------------------------------------------------------------------- 
/* 
* Struct for registering modules statically via SYSINIT. 
*/ 
typedef struct moduledata 

char *name; /* module name */ 
modeventhand_t evhand; /* event handler */ 
void *priv; /* extra data */ 
} moduledata_t; 
-------------------------------------------------------------------------- 

name 模塊名 
evhand 對應上面介紹過的helloworld_load() 

sub 該參數的有效取值參看/usr/include/sys/kernel.h文件裡定義的 
enum sysinit_sub_id {} 枚舉列表。我們將要介紹的兩種類型的KLD固定採用 
SI_SUB_DRIVERS 

order 該參數的有效取值參看/usr/include/sys/kernel.h文件裡定義的 
enum sysinit_elem_order {} 枚舉列表。我們將要介紹的兩種類型的KLD固定採用 
SI_ORDER_MIDDLE 

一般並不直接使用DECLARE_MODULE()宏,常見的是SYSCALL_MODULE和DEV_MODULE,它 
們分別對DECLARE_MODULE進行了封裝,這種封裝便開發KLD代碼,也便理解KLD代 
碼。 

/usr/include/sys/sysent.h裡定義了 SYSCALL_MODULE 宏 

-------------------------------------------------------------------------- 
#define SYSCALL_MODULE(name, offset, new_sysent, evh, arg) \ 
static struct syscall_module_data name##_syscall_mod = { \ 
evh, arg, offset, new_sysent \ 
}; \ 

static moduledata_t name##_mod = { \ 
#name, \ 
syscall_module_handler, \ 
&name##_syscall_mod \ 
}; \ 
DECLARE_MODULE(name, name##_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE) 
-------------------------------------------------------------------------- 

name 模塊名 

offset 對應系統調用號。通常利用KLD機制增加系統調用的時候,並沒有保留系統調 
用號供它使用。正確的做法是指定NO_SYSCALL,此時系統將動態選取一個可 
用系統調用號對應我們增加的系統調用 

new_sysent 
指向struct sysent結構的指針,每個系統調用都對應一個這樣的結構,結構 
裡定義了形參個數和系統調用實現體指針。 

evh 對應上面介紹過的helloworld_load() 

arg 用struct syscall_module_data結構,通常該參數設置成NULL 

/usr/include/sys/conf.h裡定義了 DECLARE_MODULE 宏 

-------------------------------------------------------------------------- 
#define DEV_MODULE(name, evh, arg) \ 
static moduledata_t name##_mod = { \ 
#name, \ 
evh, \ 
arg \ 
}; \ 
DECLARE_MODULE(name, name##_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE) 
-------------------------------------------------------------------------- 

name 模塊名 

evh 類似上面介紹過的helloworld_load() 

arg 用struct module_data結構,通常該值設置成NULL 

無論開發什樣的KLD,至少有一個加載/卸載句柄(主入口函數),至少有一個上 
面介紹的宏。在這份編程指南裡不討論更復雜的情形, 
http://thc.pimmel.com/files/thc/bsdkern.html討論了更多的復雜的編程技巧,如 
果你對KLD編程想進一步的話,請參看上述鏈接。 

我們不必擔心Makefile的復雜性,/usr/share/mk目錄下提供了許多普適性很強 
的預設置的Makefile,可以簡單採用.include <...>命令引用它們。此次感興趣的是 
/usr/share/mk/bsd.kmod.mk文件,建議你先閱讀一下該文件。可能需要的設置是 

-------------------------------------------------------------------------- 
SRCS = flkm.c 
KMOD = flkm 
KO = ${KMOD}.ko 

.include  
-------------------------------------------------------------------------- 

SRCS 源文件名 
KMOD KLD名,注意不是模塊名 

★ KLD系統調用實現框架 

下面是一個非常簡單的例子,演示如何利用動態內核鏈接機制增加系統調用。除 
了必須有一個加載/卸載句柄和一個DECLARE_MODULE宏(或者針對它的封裝),還有四 
點需要注意: 

-------------------------------------------------------------------------- 
1. 如果增加的系統調用需要形參,必須採用自定義結構組織這些形參 
2. 系統調用實現體必須是static int型的函數 
3. 根據系統調用具體實現組織struct sysent結構 
4. 設置offset變量為NO_SYSCALL 
-------------------------------------------------------------------------- 

所有的系統調用,在內核裡的函數實現體只有兩個形參: 

-------------------------------------------------------------------------- 
1. struct proc * 
2. void * 
-------------------------------------------------------------------------- 

來自用戶空間的形參需要定義到一個自定義結構中,比如: 

-------------------------------------------------------------------------- 
/* 
* 來自用戶空間的syscall()將把函數形參組織到這個結構裡,如果對應系統調用並 
* 不需要形參,則無須定義這樣一個結構,該結構完全為了傳遞形參 
*/ 

struct helloworld_args 

char * str; 
int val; 
}; 
-------------------------------------------------------------------------- 

一般libc會將用戶空間的形參組織到類似這樣的結構中。而我們通過KLD增加的系統 
調用沒有經過libc的封裝處理,所以只能使用syscall(2)直接調用這個新增加的系統 
調用,面會有例子演示。 

下面是一個系統調用內核函數實現體: 

-------------------------------------------------------------------------- 
/* 這是我們將要增加的系統調用 */ 
static int helloworld ( struct proc * p, struct helloworld_args * arg ) 

int err = 0; /* Generic return(err) */ 
int size = 0; 
char kernel_str[ 1024 + 1 ]; /* Holds kernel land copy of arg->str */ 

/* 
* _IMPORTANT_: 

* When one has a contiguous set of data and wish to copy this from 
* user land to kernel land (or vice versa) the copy(9) functions 
* are recommended for doing this. 
*/ 

/* 
* 不知道這裡是否和Linux一樣,可以直接訪問用戶空間?看面代碼意思是 
* 可以的,只不過不建議直接訪問用戶空間而已 

* 剛才自己增加了一點代碼驗証這個問題,答案是肯定的 
* 參看flkm_call.c的演示代碼 

* 注意拷貝方向,源/目的與常見函數不一樣 
*/ 
err = copyinstr( arg->str, &kernel_str, 1024, &size ); 
if ( err == EFAULT ) 

return( err ); 

uprintf( "hello world\n" ); 
uprintf( "The user string passed was: %s\n", arg->str ); 
uprintf( "The value passed was: %d\n", arg->val ); 
uprintf( "The kernel string passed was: %s\n", kernel_str ); 
return( 0 ); 
} /* end of helloworld */ 
-------------------------------------------------------------------------- 

該系統調用取出來自用戶空間的形參,一個字符串和一個整型變量,並在當前使用的 
tty(發生該系統調用時進程所使用的終端)上顯示它們。 

接下來需要根據系統調用具體實現組織一個struct sysent結構,該結構在 
/usr/include/sys/sysent.h文件裡定義: 

-------------------------------------------------------------------------- 
struct sysent /* system call table */ 

int sy_narg; /* number of arguments */ 
sy_call_t * sy_call; /* implementing function */ 
}; 
-------------------------------------------------------------------------- 

每個系統調用對應有一個struct sysent結構,sy_narg定義來自用戶空間的形參個數, 
顯然只有函數指針對C調用風格是不夠的,想想*printf()這種可變參數的函數。 
sy_call對應系統調用內核函數實現體。/usr/include/sys/sysent.h文件裡定義了: 

typedef int sy_call_t __P( ( struct proc *, void * ) ); 

下面是該結構的例子: 

-------------------------------------------------------------------------- 
/* 
* on FreeBSD every system call is described by a sysent structure, which 
* holds the corresponding system call function (here helloworld) and the 
* appropriate count of arguments (here 2) 
*/ 

static struct sysent helloworld_sysent = 

2, /* sy_narg */ 
helloworld /* sy_call */ 
}; 
-------------------------------------------------------------------------- 

現在,如果你還記得前面提到過的,最應該提供一個offset參數到 
SYSCALL_MODULE宏。這個參數對應系統調用號,作為通過KLD動態增加的新系統調用, 
應該設置該值成NO_SYSCALL,意味著由系統找出下一個可用系統調用號,當然你可以 
明確指定一個系統調用號,不推薦這樣做。可以直接傳遞NO_SYSCALL給宏,然而最好 
給一個靜態整型變量賦值NO_SYSCALL,傳遞一個指針給宏,KLD加載成功系統會將 
最終選取的系統調用號回填到這個靜態整型變量。順便提一句, 
/usr/include/sys/syscall.h裡定義了已經實現的系統調用號列表。是,我們只需 
要這樣一行代碼: 

-------------------------------------------------------------------------- 
/* 
* every system call has a certain number (called slot or syscall_num on BSD). 
* This number represents the index in the global sysent list holding every 
* syscall. BSD is able to search a free slot for a syscall (by setting it 
* to NO_SYSCALL) which is used here. 
*/ 

static int syscall_num = NO_SYSCALL; 
-------------------------------------------------------------------------- 

NO_SYSCALL在/usr/include/sys/sysent.h裡定義,值為-1。 

我們已經介紹完通過KLD動態增加一個系統調用的必須操作,剩下的就是編寫加 
載/卸載句柄,並調用SYSCALL_MODULE()宏: 

-------------------------------------------------------------------------- 
/* 
* 該函數類似Linux下的init_module和cleanup_module 
* 函數名字自己定義,將來作為函數指針傳遞給SYSCALL_MODULE()宏 
*/ 
static int helloworld_load ( module_t mod, int what, void * arg ) 

int err = 0; 

switch ( what ) 

case MOD_LOAD: 
/* 
* uprintf() 是內核空間函數,類似printf()。當在內核空間使用 
* printf()時,輸出內容需要用dmesg查看。uprintf()將直接輸出到 
* 當前正在使用的tty上 
*/ 
printf( "MOD_LOAD: dmesg -c test\n" ); 
uprintf( "System call loaded at slot: %d\n", syscall_num ); 
break; 
case MOD_UNLOAD: 
printf( "MOD_UNLOAD: dmesg -c test\n" ); 
uprintf( "System call unloaded from slot: %d\n", syscall_num ); 
break; 
case MOD_SHUTDOWN: 
uprintf( "System shutdown\n" ); 
break; 
default: 
err = EINVAL; 
break; 
} /* end of switch */ 
return( err ); 
} /* end of helloworld_load */ 

SYSCALL_MODULE( helloworld, &syscall_num, &helloworld_sysent, helloworld_load, NULL ); 
-------------------------------------------------------------------------- 

Makefile文件很簡單,如下: 

-------------------------------------------------------------------------- 
SRCS = flkm.c 
KMOD = flkm 
KO = ${KMOD}.ko 

.include  
-------------------------------------------------------------------------- 

make -f flkm.mk產生flkm文件,可以用file flkm查看文件類型。以root身份執行 
kldload -v ./flkm加載該KLD文件。 

下面是從用戶空間通過syscall(2)調用helloworld系統調用的例子: 

-------------------------------------------------------------------------- 
#include  
#include  
#include  
#include  

int main ( int argc, char * argv[] ) 

int syscall_num; 
struct module_stat stat; 
char hello[] = "I'll be back."; 

stat.version = sizeof( stat ); 
/* 
modstat will retrieve the module_stat structure for our module named 
helloworld (see the SYSCALL_MODULE macro which sets the name to syscall) 
*/ 
modstat( modfind( "helloworld" ), &stat ); 
/* extract the slot (syscall) number */ 
syscall_num = stat.data.intval; 
/* 必須在調用前加載內核模塊,否則core dump,程序沒有做邊界檢查 */ 
return( syscall( syscall_num, hello, 1977 ) ); 
} /* end of main */ 
-------------------------------------------------------------------------- 

★ KLD字符型設備驅動程序實現框架 

絕大多數Unix系統支持字符型設備驅動程序,它們通常不對應真實物理設備,僅 
僅提供一種對偽設備的讀/寫/IO控制接口。類似前面介紹增加系統調用,下面將逐步 
介紹如何編寫KLD模式的字符設備驅動程序,幸運的是,你會發現創建一個非常有用 
的字符型設備驅動程序並不困難。 

下面4點對所有字符型設備驅動程序實現都是必要的: 

-------------------------------------------------------------------------- 
1. 定義一個struct cdevsw結構 
2. 設備回調函數 
3. 加載/卸載句柄 
4. DEV_MODULE()宏 
-------------------------------------------------------------------------- 

/usr/include/sys/conf.h裡定義了 struct cdevsw 結構 

-------------------------------------------------------------------------- 
/* 
* Character device switch table 
*/ 
struct cdevsw 

d_open_t *d_open; /* Func. pointer to dev open function */ 
d_close_t *d_close; /* Func. pointer to dev close function */ 
d_read_t *d_read; /* Func. pointer to dev read function */ 
d_write_t *d_write; /* Func. pointer to dev write function */ 
d_ioctl_t *d_ioctl; /* Func. pointer to dev ioctl function */ 
d_poll_t *d_poll; /* Func. pointer to dev poll function */ 
d_mmap_t *d_mmap; /* Func. pointer to dev mmap function */ 
d_strategy_t *d_strategy; /* Func. pointer to dev strategy func. */ 
const char *d_name; /* base device name, e.g. 'vn' */ 
int d_maj; /* Device major value */ 
d_dump_t *d_dump; /* Func. pointer to dev dump function */ 
d_psize_t *d_psize; /* Func. pointer to dev psize function */ 
u_int d_flags; /* D_TAPE, D_DISK, D_TTY, D_MEM */ 
int d_bmaj; /* Block Device major value (used by D_DISK) */ 
}; 
-------------------------------------------------------------------------- 

顯然該結構類似Linux下的struct file_operations結構,定義了設備相關的回調函 
數。並不需要提供所有的回調函數,如果你想提供一個只寫設備,不但/dev/目錄下 
的設備文件權限設置成只寫,更重要的是struct cdevsw結構中d_read成員賦值 
noread。為了簡化討論,在我們的例子中,只提供了d_open、d_close、d_read和 
d_write四個回調函數,我們的struct cdevsw結構如下: 

-------------------------------------------------------------------------- 
static struct cdevsw chardev_cdevsw = 

chardev_open, 
chardev_close, 
chardev_read, 
chardev_write, 
noioctl, 
nopoll, 
nommap, 
nostrategy, 
"chardev", /* 這裡和/dev/下的名字不必一致 */ 
38, /* /usr/src/sys/conf/majors 主設備號是重要標識 */ 
nodump, 
nopsize, 
D_TTY, /* D_TAPE, D_DISK, D_TTY, D_MEM */ 
-1 /* Block Device major value (used by D_DISK) */ 
}; 
-------------------------------------------------------------------------- 

/usr/share/examples/kld/cdev/目錄下提供了其他一些字符型設備驅動程序例子。 
注意我們的例子採用38作為主設備號,/usr/src/sys/conf/majors文件裡對此定義如 
下: 

38 lkm ssigned to Loadable Kernel Modules 

假設將來來自應用層的調用步驟如下: 

open(2) -> write(2) -> read(2) -> close(2) 

首先打開/dev/目錄下的設備文件,然寫一個字符串到該設備,攜入的字符串被保 
存在驅動程序靜態緩沖區中,稍應用程序會讀取這個字符串,最關閉前面所打開 
的設備文件。 

-------------------------------------------------------------------------- 

/******************************************************************* 
* * 
* Function Prototype * 
* * 
*******************************************************************/ 

static int chardev_close ( dev_t dev, int cflag, int devtype, struct proc * p ); 
static int chardev_open ( dev_t dev, int oflags, int devtype, struct proc * p ); 
static int chardev_read ( dev_t dev, struct uio * uio, int ioflag ); 
static int chardev_write ( dev_t dev, struct uio * uio, int ioflag ); 

/******************************************************************* 
* * 
* Static Global Var * 
* * 
*******************************************************************/ 

/* 
* Used as the variable that is the reference to our device 
* in devfs... we must keep this variable sane until we 
* call kldunload. 
*/ 
static dev_t chardev; 
static char chardev_buf[ 512 + 1 ]; /* 設備驅動程序維護的內部緩沖區 */ 
static int chardev_buflen; 

/*----------------------------------------------------------------------*/ 

/* 
* Simply "closes" our device that was opened with chardev_open. 
*/ 
static int chardev_close ( dev_t dev, int cflag, int devtype, struct proc * p ) 

memset( chardev_buf, 0, 513 ); 
chardev_buflen = 0; 
uprintf( "Closing device \"chardev\"\n" ); 
return( 0 ); 
} /* end of chardev_close */ 


/* 
* This open function soley checks for open(2) flags. We are only 
* allowing for the flags to be O_RDWR for the purpose of showing 
* how one could only allow a read-only device, for example. 
*/ 
static int chardev_open ( dev_t dev, int oflags, int devtype, struct proc * p ) 

memset( chardev_buf, 0, 513 ); 
chardev_buflen = 0; 
uprintf( "Opened device \"chardev\" successfully\n" ); 
return( 0 ); 
} /* end of chardev_open */ 

/* 
* The read function just takes the buf that was saved 
* via chardev_write() and returns it to userland for 
* accessing. 
*/ 
static int chardev_read ( dev_t dev, struct uio * uio, int ioflag ) 

int err = 0; 

if ( chardev_buflen <= 0 ) 

err = -1; 

else 

/* 對象是以NULL結尾的串,長度包括結尾的NULL */ 
/* copy buf to userland */ 
err = copystr( chardev_buf, uio->uio_iov->iov_base, 513, &chardev_buflen ); 

return( err ); 
} /* end of chardev_read */ 

/* 
* chardev_write takes in a character string and saves it 
* to buf for later accessing. 
*/ 
static int chardev_write ( dev_t dev, struct uio * uio, int ioflag ) 

int err = 0; 

/* 對象是以NULL結尾的串,長度包括結尾的NULL */ 
err = copyinstr( uio->uio_iov->iov_base, chardev_buf, 513, &chardev_buflen ); 
if ( err != 0 ) 

uprintf( "Write to \"chardev\" failed\n" ); 

return( err ); 
} /* end of chardev_write */ 
-------------------------------------------------------------------------- 

現在你該相信我了吧,實現一個簡單的字符型設備驅動程序相當容易。通過這種 
技術向內核空間傳遞數據,對比sysctl能夠實現的功能。man 3 sysctl, 
man 8 sysctl看看。 

下面是這個字符型設備驅動程序的加載/卸載句柄。對設備驅動程序,在 
MOD_LOAD流程那裡,必須調用make_dev()向設備文件系統(devfs)中注冊我們的設備。 
devfs是設備文件系統,提供訪問FreeBSD內核中設備名字空間的能力。在 
MOD_UNLOAD流程那裡,必須調用destroy_dev(),形參來自make_dev()的返回值 
(dev_t型)。 

-------------------------------------------------------------------------- 
/* 
* 該函數類似Linux下的init_module和cleanup_module 
* 函數名字自己定義,將來作為函數指針傳遞給DEV_MODULE()宏 
*/ 
static int chardev_load ( module_t mod, int what, void * arg ) 

int err = 0; 

switch ( what ) 

case MOD_LOAD: 
chardev = make_dev( &chardev_cdevsw, 
0, 
UID_ROOT, 
GID_WHEEL, 
0600, 
"chardev" ); 
uprintf( "chardev loaded\n" ); 
break; 
case MOD_UNLOAD: 
destroy_dev( chardev ); 
uprintf( "chardev unloaded\n" ); 
break; 
case MOD_SHUTDOWN: 
uprintf( "System shutdown\n" ); 
break; 
default: 
err = EINVAL; 
break; 
} /* end of switch */ 
return( err ); 
} /* end of chardev_load */ 

DEV_MODULE( chardev, chardev_load, NULL ); 
-------------------------------------------------------------------------- 

無論什類型的KLD,必須有一個*_MODULE宏,至少指明本模塊加載/卸載句柄以及何 
種類型。如上最一行代碼所示。 

至此一個非常簡單的字符型設備驅動程序框架完成了。編寫類似前面的Makefile, 
編譯產生KLD靜態文件,並在/dev/目錄下創建設備文件: 

[root@ /usr/home/scz/src]> mknod /dev/chardev c 38 0 
[root@ /usr/home/scz/src]> ls -l /dev/chardev 
crw-r--r-- 1 root wheel 38, 0 Oct 28 04:56 /dev/chardev 
[root@ /usr/home/scz/src]> 

這個KLD被加載,open()、close()、read()和write()系統調用都可以用 
/dev/chardev設備文件。記得在KLD被卸載出內核前調用close()關閉該設備,否則, 
嘿嘿,你死定了。 

正如簡介裡所言,本文講述的是KLD編寫基礎知識,相當簡短。再深入的技巧請 
翻閱THC的技術資料。 

★ 參考資料 

1) man 4 kld 
關KLD的man手冊 

2) http://thc.pimmel.com/files/thc/bsdkern.html 
THC編寫的利用LKM/KLD攻擊FreeBSD的經典文獻 

3) /usr/share/mk/* 
缺省Makefile 

4) http://subterrain.net/~awr/KLD-Tutorial/code/kld-examples.tar.gz 
文中所附例子代碼 

5) /usr/share/examples/kld/cdev/ 
系統自帶的其他字符型設備驅動程序例子 
(http://www.fanqiang.com)
    進入【UNIX論壇

相關文章

======
 

★  樊強制作 歡迎分享  ★