郑州市城市发展规划:为FreeBSD编写内核模块

来源:百度文库 编辑:九乡新闻网 时间:2024/04/28 06:48:06
FreeBSD 7.0已经释出。如果你是个真正的黑客的话,投入并学习它的最好途径便是编写一个入门级的内核模块了。本文中我将实现一个非常基本的模块,当它被装载时将打印出一条信息,并在被卸载的时候打印出另外的一条信息。我还将概述使用标准工具手工编译我们的模块以及重新构建现有的FreeBSD内核的过程。我们开始吧!

入门

本文需要具备一些必要条件。我假定认为你具备一定的C编程知识,尽管没有太复杂的内容。如果我提及一个指针或者一个结构,我希望你不需要太多的解释就能理解那些概念。我还希望你熟悉类Unix操作系统并了解基本的Shell相关用法。

首先要做的事情是确信你工作将会用到的开发环境已经包括了你要用的所有的东西并正确地被配置好了。我将会认为你已经安装好并正在运行着FreeBSD。如果你没有并想要一些指导的话,你可以阅读我的这篇文章:Secure emails servers with FreeBSD。我会以不同方式做一些事情;所以,我会建议你把它当作安装的资料,这对这两篇文章都是一样的。

你还将需要确信你已经安装了sudo实用程序并已经为你主要使用的用户帐户做好了相关配置。kldload和kldunload实用程序将需要用它来进行以“root”运行的操作。Sudo的FreeBSD port在/usr/ports/security/sudo下。
  1. cd /usr/ports/security/sudo
  2. make install && make clean
复制代码现在su到root用户并运行visudo程序,visudo依据环境变量EDITOR来决定使用哪个编辑器。如果你不喜欢默认的,如果你使用的是默认的CSH,使用setenv EDITOR vim来重写该变量,或者如果使用bash的话使用exportEDITOR=vim。然后简单地重新运行visudo即可。
  1. su
  2. visudo
复制代码visudo会对文件语法以及确信没有两个人在同时编辑sudoers文件做基本完整的检查。

# 请留意这一行,复制这行并把“root”用户名改成你主要使用的用户名。
  1. root    ALL=(ALL) SETENV: ALL
  2. yourabi ALL=(ALL) SETENV: ALL
复制代码你的第一个内核

如我最近的一篇文章Review of FreeBSD 7.0中所提到的那样,ULE调度器给FreeBSD带来了新级别的性能以及多处理器的伸缩性。尽管其已经被认为稳定并已经是最佳时期,但在7.1发行版之前它默认是不被启用的。对你的安装水平的最好的实测将是编译一个启用ULE调度器的定制内核。

如果你正使用基于x86架构的机器,内核位于/usr/src/sys/i386目录下。对于amd64机器而言,只需要把i386换成amd64,也就是/usr/src/sys/amd64。内核配置文件位于一个叫做conf的目录。进入该目录并通过拷贝“generic”默认配置文件为一个我们命名的名字来创建你的定制内核配置文件。
  1. cd /usr/src/sys/amd64/conf
  2. cp GENERIC CUSTOM
复制代码现在该启用ULE调度器了。对于旧版的调度器而言,新的ULE调度器带来了增强的性能以及可伸缩性,并为构建安装一个定制内核提供了一个不错的示范。

用你选择的文本编辑器打开“CUSTOM”文件然后找到启用旧版44BSD调度器的那行并将其替换成ULE。在现有的内核配置文件中它应该在第30行左右(在我写这篇文章的时候)。
  1. options     SCHED_4BSD  # 4BSD scheduler
  2. # 改为 #
  3. options     SCHED_ULE   # ULE scheduler
复制代码现在构建你的新内核并重新启动使其生效。FreeBSD有个确切简单的使用标准的“make”程序来构建系统的方法。只不过是更改你的目录到源代码树并使用“buildkernel”和“installkernel”的目标来调用make。如果你不加任何参数地调用它们,GENERIC(默认的)内核将被构建并安装。由于你是要安装定制的内核的,需要将目标内核名称传递给KERNCONF标识。在这个案例里,它的名字将会是你刚才从通用内核拷贝成的:CUSTOM。
  1. cd /usr/src
  2. make buildkernel KERNCONF="CUSTOM"
  3. make installkernel KERNCONF="CUSTOM"
  4. reboot
复制代码祝贺你!当系统启动起来后,它将运行启用了ULE调度器的新的定制内核。你已经被证实能够编译并安装一个内核了,所以现在是时候开始你的下一个任务了:编写一个简单的内核模块。

注意:如果你是在VMWare中运行的FreeBSD,这里有个非常重要的性能设置来使你的系统适合本文。内核计时器的频率需要从“1000”降低到“100”次每秒。用你喜爱的编辑器编辑/boot/loader.conf文件并添加如下内容:
  1. echo kern.hz=100 >> /boot/loader.conf
复制代码内核 Hello World

你可能已经注意到了,FreeBSD在构建以及安装内核(还有操作系统的其余任务)中有效地使用了make程序。你可能还不知道吧,但你将不会惊讶了,FreeBSD开发者还开发了一些makefile来简化了困难的内核模块开发。

对makefile以及make程序更深入地研究都超出了本文的范围。然而,直接相关的两点是bsd.kmod.mk这个makefile以及它能够与其他的makefile两者相互包括在内。

bsd.kmod.mk这个makefile位于/usr/src/share/mk/bsd.kmod.mk,它使所有的痛苦远离正确地构建并连接内核模块。如你即将看到的,你只不过需要设置两个变量:
  • 通过“KMOD”变量设置内核模块名;
  • 通过直观的“SRCS”变量设置配置好的源文件;

然后,你所必须做的所有事情就是使用include 包含进bsd.kmod.mk文件来构建该模块。这个简明的设置使得你只需要用以下骨架makefile并简单地通过“make”程序调用即可着手构建你的内核模块。

我们入门的内核模块的Makefile类似于这样的内容:
  1. # Note: It is important to make sure you include the makefile after declaring the KMOD and SRCS variables.

  2. # Declare Name of kernel module
  3. KMOD    =  hello_fsm

  4. # Enumerate Source files for kernel module
  5. SRCS    =  hello_fsm.c

  6. # Include kernel module makefile
  7. .include
复制代码在你的主目录下新建一个叫做kernel的目录。拷贝并粘贴上面的文本内容到一个叫做Makefile的文件中去。这将是你之后的工作基地。

创建一个模块

现在你有个关于构建环境的提示,是时候来看看一个FreeBSD内核模块背后的实际代码以及在运行中的内核插入和移除一个模块的机制了。

内核模块允许动态功能性添加到一个运行中的内核中。当一个内核模块被插入,“load”事件被触发。当一个内核模块被移除时,“unload”事件被触发。内核模块负责实现一个处理相关情况的时间处理器。

运行中的内核将传入/usr/include/sys/module.h()头文件中所定义的符号常量框架中的事件。MOD_LOAD以及MOD_UNLOAD这两个主要的事件是你需要关心的。

运行中的内核如何象调用传递参数一样来获知某个函数调用并传递一个事件类型呢?模块负责使用DECLARE_MODULE宏来将逆向调用配置好。

DECLARE_MODULE宏在头文件的117行中定义。其使用了下面的四个参数:
  •       name. 定义名字
  •       data. 指定moduledata_t结构的名字,在我的实现过程中我已经将其命名为hello_conf。moduledata_t类型被定义在的第55行。稍后我将简要说明。
  •       sub. 设定子系统接口,它定义了模块的类型。
  •       order. 在被定义的子系统中定义模块初始化顺序。

这个moduledata结构包括了定义为一个char变量的名字和一般情况下在中第50行所定义modeventhand_t结构的事件处理器。最后,对额外的数据而言,moduledata结构拥有一个空的指针,你将不可以使用它。

也许你看着这篇上下文不包括任何代码的概述脑子都快要炸了,别怕。那都是在你开始编写内核模块前所需要知道的一个综述,所以会那样,“好朋友们,再接再厉,向缺口冲去吧!”在你开始之前,确信你在与之前创建好的Makefile文件相同的内核目录,启动你所选择的文本编辑器并打开一个叫做hello_fsm.c的文件。

首先将所使用的数据类型所需求的头文件包括进去。你已经看到了以及其他的include语句都是所支持的头文件。
  1. #include
  2. #include
  3. #include
  4. #include
复制代码下面,你将要实现event_handler函数。这是内核将通过事件参数调用MOD_LOAD或者MOD_UNLOAD所使用的函数。如果一切都运行的正常,正常完成后将返回一个为0的值。然而,你应当处理一些可能性会出错的东西以及如果事件参数不是MOD_LOAD或MOD_UNLOAD,你将要设定e这个错误跟踪变量为EOPNOTSUPP。
  1. /* The function called at load/unload. */
  2. static int event_handler(struct module *module, int event, void *arg) {
  3.         int e = 0; /* Error, 0 for normal return status */
  4.         switch (event) {
  5.         case MOD_LOAD:
  6.                 uprintf("Hello Free Software Magazine Readers! \n");
  7.                 break;
  8.         case MOD_UNLOAD:
  9.                 uprintf("Bye Bye FSM reader, be sure to check http://freesoftwaremagazine.com !\n");
  10.                 break;
  11.         default:
  12.                 e = EOPNOTSUPP; /* Error, Operation Not Supported */
  13.                 break;
  14.         }

  15.         return(e);
  16. }
复制代码下面,你将会把第二个参数定义为DECLARE_MODULE宏,它是moduledata_t类型。这是你设置模块名,以及指明event_handler在从内核中装载和卸载的时候所调用的地方。
  1. /* The second argument of DECLARE_MODULE. */
  2. static moduledata_t hello_conf = {
  3.     "hello_fsm",    /* module name */
  4.      event_handler,  /* event handler */
  5.      NULL            /* extra data */
  6. };
复制代码最后,你将会使用模块名以及hello_conf结构来调用我们谈论最多的DECLARE_MODULE。

DECLARE_MODULE(hello_fsm, hello_conf, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);

其他所需要做的也就是构建模块了。反复查看你正和模块的makefile处于同一目录并简单地执行:
  1. make
复制代码装载和卸载模块

要装载该模块,你有两个选择:使用kldload实用程序或者是通过makefile装载make目标。所以不管你选择哪个都必须通过使用“sudo”程序来获取装载和卸载内核模块所需要的root权限。
  1. sudo kldload ./hello_fsm.ko
  2. # 或者 #
  3. sudo make load
复制代码你应该在你的控制台看到了“Hello Free Software Magazine Readers!”这条信息了。要查看所有已装载的模块,请使用不加任何参数的kldstat。Kldstat不需要root权限以及你能够验证该模块已经确实被装载了。

kldstat
Id Refs Address    Size     Name
1    8 0xc0400000 926ed4   kernel
2    1 0xc0d27000 6a1c4    acpi.ko
3    1 0xc317e000 22000    linux.ko
4    1 0xc4146000 2000     hello_fsm.ko


想要卸载该模块的话,使用kldunload或者是makefile中的卸载目标。你应该会看到MOD_UNLOAD事件时的所打印出的信息,也就是“Bye Bye FSM reader, be sure to check http://freesoftwaremagazine.com !”

sudo kldunload hello_fsm
  或者
sudo make unload


结语

现在你有了一个基本的,骨架的内核模块。它在被装载是打印一条信息以及在被从内核中卸载时打印出另外一条单独的信息。本文涵盖了构建、插入和移除模块的技巧。你知道了最基本的知识积累来对付更加高级的项目:我想建议你看一下写字符设备的作家的文章,因为它恐怕是最简单的设备驱动了吧。

希望这带给了你如同这曾带给过我的无穷乐趣!