[ 永远的UNIX::UNIX技术资料的宝库 ]

首页 > 编程技术 > 其它 > 正文
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论坛

相关文章

===更多相关===
 

★  樊强制作 欢迎分享  ★