发新话题
打印

[转载]FreeBSD 4.0 动态内核链接机制(KLD)编程指南

[转载]FreeBSD 4.0 动态内核链接机制(KLD)编程指南

文章作者:Andrew Reiter
信息来源:http://www.nsfocus.net/index.php ... do=view&mid=678

作者: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&#39;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. &#39;vn&#39;      */
   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://iittss.com/ kijs与牛人在一起不是有理由的让自己变懒,那是为了让视野更开阔

TOP

发新话题