首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第15期->技术专题
期刊号: 类型: 关键词:
FreeBSD 4.0 动态内核链接机制(KLD)编程指南

作者:Andrew Reiter < mailto: arr@watson.org >
整理:小四 < mailto: scz@nsfocus.com >
出处:http://www.watson.org/~arr/
主页:http://www.nsfocus.com
日期:2000-11-04

目录:

    ★ 简介
    ★ 所有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 <bsd.kmod.mk>
--------------------------------------------------------------------------

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 <bsd.kmod.mk>
--------------------------------------------------------------------------

make -f flkm.mk后产生flkm文件,可以用file flkm查看文件类型。以root身份执行
kldload -v ./flkm加载该KLD文件。

下面是从用户空间通过syscall(2)调用helloworld系统调用的例子:

--------------------------------------------------------------------------
#include <stdio.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/module.h>

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/
   系统自带的其他字符型设备驱动程序例子

<完>
版权所有,未经许可,不得转载