免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1248 | 回复: 0
打印 上一主题 下一主题

FreeBSD4.0 动态内核链接机制(KLD)编程指南 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2006-07-24 15:32 |只看该作者 |倒序浏览

FreeBSD4.0 动态内核链接机制(KLD)编程指南
本文出自: http://www.asfocus.com (2001-07-04 09:04:00)

作者:Andrew Reiter   
整理:小四   
出处: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   
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 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/  
系统自带的其他字符型设备驱动程序例子  



本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/6180/showart_144996.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP