当前位置:操作系统 > Unix/Linux >>

Linux内核模块编程指南(一)

当第一个原始的程序员在最开始的窑洞计算机之墙上凿过第一个程序时,那是一个在羚羊图案上画上“Hello, world”的程序。罗马人的编程书籍上用“Salut, Mundi”程序开始。我不知道打破这个传统的人身上发生了什么而且我想不去追究这个比较安全。

  一个内核模块必须至少有两个功能: init_module 在该模块被插入内核时被调用, cleanup_module 仅仅在它被清除前调用。 典型的, init_module 要么在内核里为什么东西登记一个指针,要么用它自己的代码代替内核的某个功能 (通常那个代码做一些事情然后调用原始的功能). cleanup_module 功能被假定撤消init_module 做的任何事情, 因此模块可以被安全地卸载。

  范例 hello.c

  /* hello.c* Copyright (C) 1998 by Ori Pomerantz** "Hello, world" - 内核模块版本.*//* 必要的头文件 *//* 内核模块标准 */#include /* 我们在做内核的工作 */#include /* 明确的,一个模块 *//* 处理 CONFIG_MODVERSIONS */#if CONFIG_MODVERSIONS==1#define MODVERSIONS#include#endif/* 初始化模块 */int init_module(){printk("Hello, world - this is the kernel speaking

  ");/* 如果我们返回一个非零值, 那就意味着* init_module 初始化失败并且内核模块* 不能加载 */return 0;}/* Cleanup - 撤消 init_module 所做的任何事情 */void cleanup_module(){printk("Short is the life of a kernel module

  ");}


  内核模块的Make文件

  一个内核模块单独是不可执行的,但目标文件在运行时被连接进内核。因此,在编译时要使用 -c 标记. 而且, 所有的内核模块必须结合特定的定义过的符号进行编译。

  __KERNEL__ -- 这个符号告诉头文件这些代码将在内核模式运行,不要当作用户进程的一部分。

  MODULE -- 这个符号告诉头文件为内核模块给出适当的定义。

  LINUX -- 从技术上说这不是必要的。然而,如果你曾经想过写一系列要在不止一个的操作系统 上编译的内核模块,那么你将为你在这所做的高兴。这将允许你条件编译平台独立性的部分。

  还有其他一些需要或不需要包括的符号,这取决于内核用什么标记编译。如果你不能确定内核是如何编译的,请查看 /usr/include/linux/config.h

  

  __SMP__ -- Symmetrical MultiProcessing(对称多处理). 如果内核被编译为支持对称多处理 (即使它仅仅运行于单CPU上),这个符号必须包含.如果你使用对称多处理,你还有很多其他的事情需要 做(参看第12章).

  CONFIG_MODVERSIONS -- 如果CONFIG_MODVERSIONS 被激活, 你需要使它在编译内核模块时已定义 并且包含/usr/include/linux/modversions.h. 这也可以比、被代码自己完成。

  范例 Makefile

  # 为基本的内核模块写的Make文件

  CC=gccMODCFLAGS := -Wall -DMODULE -D__KERNEL__ -DLINUXhello.o: hello.c /usr/include/linux/version.h$(CC) $(MODCFLAGS) -c hello.cecho insmod hello.o to turn it onecho rmmod hello to turn if offechoecho X and kernel programming do not mix.echo Do the insmod and rmmod from outside X.

  因此,现在唯一剩下的事情就是su 为root用户 (你不能作为root用户编译,对吗? 生活在边缘1.1...), 然后insmod hello 和 rmmod hello 到你的系统核心. 当你完成这个,注意 /proc/modules中的新内核模块。

  顺便说一下,之所以推荐不要在 X下做insmod是因为当内核用printk打印消息时它将消息发送到控制台。当你不使用 X, 它就发往你正在使用的虚拟终端(你用Alt-F选定的那个) 而使你可以看见。另一方面,当你使用 X, 有两种可能。要么你有一个用xterm -C打开的终端,在这种情况下输出将被发送到那儿;或者你没有打开终端,在这种情况下输出被发往被X“隐蔽”的虚拟终端7。

  如果你的内核变得不稳定,不用X得到调试信息更有可能。在X之外printk 直接从内核打印到控制台。另一方面,在X下,printk变成用户模式进程 (xterm -C). 当该进程正占用CPU,它被假设发往X服务进程,然后,当X服务进程占用 CPU, 它被假设去显示该信息 --但是一个不稳定的内核通常意味着崩溃或重新启动然而你不想延迟得到错误信息,这也许可以向你解释什么地方出错了。

  多文件内核模块

  有时候将内核模块分为几个源文件是有意义的,你需要做下面的事情:

  1. 除了一个文件外在所有的源文件中加入一行 #define __NO_VERSION__. 这是很重要的,因为module.h通常包含kernel_version的定义, 它是一个和模块一起编译的内核版本的全局变量。如果你需要version.h, 你需要自己包含它,因为module.h 在有 __NO_VERSION__的定义的情况下不自动包含它。

  

  2. 像通常那样编译所有的源文件。

  3. 将所有的目标文件合并成一个。在x86架构下使用 ld -m elf_i386 -r -o < 模块名>.o <第一个源文件名>.o <第二个源文件?gt;.o.

  这里有一个这样的内核模块的范例。

  范例 start.c

  /* start.c* Copyright (C) 1999 by Ori Pomerantz** "Hello, world" - 内核模块版本.* 这个文件只包含启动程序*//* 必要的头文件 *//* 内核模块标准头文件 */#include /* 我们正在做内核的工作 */#include /* 明确的指定是内核模块 *//* 处理 CONFIG_MODVERSIONS */#if CONFIG_MODVERSIONS==1#define MODVERSIONS#include#endif/* 初始化模块 */int init_module(){printk("Hello, world - this is the kernel speaking

  ");/* If we return a non zero value, it means that* init_module failed and the kernel module* can't be loaded */return 0;}


  范例 stop.c

  /* stop.c* Copyright (C) 1999 by Ori Pomerantz** "Hello, world" - 内核模块版本* 这个文件只包含终止程序*//* 必要的头文件 *//* 内核模块的标准头文件 */#include /* 我们正在做内核的工作 */#define __NO_VERSION__ /* 这不是内核模块文件 */#include /* 明确的指定是内核模块 */#include /* 因为有 __NO_VERSION__ 而不能被自动包含*//* 处理 CONFIG_MODVERSIONS */#if CONFIG_MODVERSIONS==1#define MODVERSIONS#include#endif/* Cleanup - 撤消init_module 所做的任何事情 */void cleanup_module(){printk("Short is the life of a kernel module

  ");}


  范例 Makefile

  # 多文件内核模块的Make文件

  CC=gccMODCFLAGS := -Wall -DMODULE -D__KERNEL__ -DLINUXhello.o: start.o stop.old -m elf_i386 -r -o hello.o start.o stop.ostart.o: start.c /usr/include/linux/version.h$(CC) $(MODCFLAGS) -c start.cstop.o: stop.c /usr/include/linux/version.h$(CC) $(MODCFLAGS) -c stop.c

  

  多内核版本源文件

  内核展现给进程的主要界面是系统调用,它通常跨版本保持相同。新的系统调用被加入,但通常老的保持和原来严格的一样。这对于向后兼容性是必要的--新的内核版本不应打破常规的进程。在大多情况下,设备文件也将保持相同。另一方面,和内核内部的接口可以并且在版本之间有改变。

  Linux内核版本分为稳定版 (n.<偶数>.m) 和开发版 (n.<奇数>.m).开发版包含所有的好的新思想,包括那些被认为是错误或需要在下一版重新实现的东西。结果,你不能期盼在那些版本中界面保持相同(这也是我为什么不在这本书中操心去支持它的原因,那需要太多的工作并且很快就过时了)。另一方面,在稳定版中我们可以期盼界面保持相同,除了错误修订版(数字m)。

  这个版本的内核模块编程指南包括对 2.0.x 和2.2.x 内核版本的支持。既然这两个版本间有差异,这就需要根据版本进行条件编译。可以使用宏 LINUX_VERSION_CODE来做这件事。在 a.b.c 版的内核中,这个宏的值是 216a+28b+c。为了 得到某个内核版本的值,我们可以使用 KERNEL_VERSION 宏. 因为在 2.0.35版中没有定义它, 我们可以在必要的时候自己定义它。
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,