[转载]The Linux Kernel Module Programming Guide
信息来源:[url]http://dev.csdn.net/article/73/73366.shtm[/url]The Linux Kernel Module Programming Guide简译(一)
sighofwraith 发表于 2005-6-12 17:35:19
How do modules get into the kernel?
首先,如果你想知道哪些模块已经载入,可以用一下lsmod,它会从/proc/modules中把你想看的东西读出来。
模块是怎么的载入的呢~~
当内核发现有一个功能,它需要,但是本来却没有,就会有一个叫kmod(老版本上它叫kerneld)的内核精灵程序运行modprobe去把相应的模块装进来。Modprobe需要个参数来知道到底要作虾米,这个参数可以有两种形式:
·模块名,比方softdog或者ppp之类的东东。
·一个很一般化的描述符,比方char-major-10-30。
如果可怜的modprobe只搞到了一个相貌平平,扔到街上就找不到的虾米描述符,它首先会去/etc/modules.conf找找这东西有没有马甲,比方如果找到下面这么一行:
alias char-major-10-30 softdog
它就知道了,原来char-major-10-30对应的模块应该是softdog.o。
下一步,modprobe会去/lib/modules/version/modules.dep中找找加载这个模块之前是不是还有别的什么要先装进去。比方说要装msdos.o之前就要先把fat.o装进去。(modules.dep这个文件是depmod –a建立的,提供依赖关系用)
最后,modprobe用insmod把这些需要装的modules挨着装进去。
Modprobe会告诉insmod模块们的标准目录——/lib/modules/version/(如果你怕覆盖掉原来的内容,可以在内核编译的时候在Makefile里改一下EXTRAVERSION)。Insmod是个需要制定模块所在位置的家伙,而modprobe则有默认的位置可用。所以,比方说你想装载msdos模块,你就要这么写:
insmod /lib/modules/2.5.1/kernel/fs/fat/fat.o
insmod /lib/modules/2.5.1/kernel/fs/msdos/msdos.o
或者光执行一个“modprobe –a msdos”。
Linux发行版都提供了modprobe、insmod、depmod,它们打成了一个叫modutils或者mod-utils的包。
结束这段内容之前,我们先从/etc/modules.conf弄个片断来看看:
# This file is automatically generated by update-modules
path[misc]=/lib/modules/2.4.?/local
keep
path[net]=~p/mymodules
options mydriver irq=10
alias eth0 eepro
# 开头的行是注释。
path[misc]这行告诉modprobe找misc模块的时候把默认的目录换成/lib/modules/2.4.?/local。
path[net]这行告诉modprobe找net模块的时候在~p/modules目录找,但是前面的“keep”则告诉modprobe是把这个新的目录加进原来的搜索目录,而不是向对misc那样替换掉。
alias这行是说kmod见到eth0这个描述符的时候装载eepro.o这个模块(声明马甲!)。
呵呵,现在大家知道模块是怎么加载的了。不过如果你想写一个依赖于其他的模块的模块还是有点问题的,不过这个比较“高级”的问题容后再说啦~~:)
1.2.1 Before we begin
研究代码之前,先介绍一点小tip。毕竟大家的系统多少都有点差别,而且每个人的习惯也不一样,所以即便是简简单单的一个“hello world”有时候可能也会变成麻烦。所以先看看下面这些在动手还是比较有用的。
1.2.1.1 Modversioning
除非你使能了内核中的CONFIG_MODVERSION,否则针对一个内核编译的模块是不能加载进其他版本系统的。我们现在不去深入到modules versioning。在我们加上modversion内容之前,如果你运行在一个打开了modversioning的系统上,这里的例子可能不能正常工作。不过,大部分的linux发行版都是打开了这个开关的。如果你因为版本错误不能加载模块,那你可以试试编译一个关上modversioning的。
1.2.1.2 Using x
这里强烈推荐你自己输入、编译、加载所有的例子。也强烈建议你在console下做这些事情,而不是在X下面。
在console上,模块会把log信息和警告之类的东西直接显示出来。而在X下的xterm里insmod加载一个模块,这些信息会被记录下来,但不在xterm上显示。这样显得比较麻烦一点。所以推荐使用console。
1.2.1.3 Compiling issues and kernel version
linux发行版往往发布一个使用各种不标准方法补丁过的内核给你,这可能会带来很多麻烦。
一个很常见的问题就是,一些linux发行版的内核和它给你的头文件不完全一样。所以最好自己下载内核code安装上(其实。。。这主意也很麻烦的说。。。。如果只是gcc默认的头文件版本不对,那就自己找对应版本的内核code用-I开关选正确的include来编译吧。)
The Linux Kernel Module Programming Guide简译(二)
sighofwraith 发表于 2005-6-20 17:37:34
2.1. Hello, World (part 1): The Simplest Module
当世界上第一个穴居人程序员在第一台石头计算机的墙上刻下第一个程序的时候,这个程序绘出一个字符串“Hello world”。在罗马人的编程手册里,一切从“Salut,Mundi”程序开始。我不知道如果有人不用这个开头会有什么结果,不过我也没什么理由不这么做。所以,让我们从Hello world开始了解linux内核开发。
下面是个尽可能简单的例子。先别编译,下一节我们会讲到怎么编译内核模块。
Example 2-1. hello-1.c
/* hello-1.c - The simplest kernel module.
*/
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_ALERT */
int init_module(void)
{
printk("<1>Hello world 1.\n");
// A non 0 return means init_module failed; module can't be loaded.
return 0;
}
void cleanup_module(void)
{
printk(KERN_ALERT "Goodbye world 1.\n");
}
内核模块至少要有两个函数:一个叫做init_module()的“start”初始化函数供insmod的时候调用,一个叫clean_module()的“end”清除函数供rmmod的时候调用。实际上,2.3.13之后的内核这两个函数不再必须使用这两个名字了。你给它们起什么名字都可以,2.3节我会将具体怎么做。给这两个函数自己起名字其实是个挺好的主意,不过还是有很多人使用init_module和clean_module函数。L
很经典的,init_module函数会向内核注册一些什么东西,或者用自己的代码替换内核的一些什么功能(通常它们做点什么动作之后还是会调用原来的函数)。而clean_module函数则会把init_module干的事情做个了结,以便安全的卸载模块。
最后,每个内核模块都要包含linux/module.h。仅仅为了KERL_ALERT(2.1.1节会讲到它)还需要在这个例子中包括linux/kernel.h。
2.1.1. Introducing printk()
不管你怎么认为,printk函数设计出来其实并不是为了和用户交流,虽然我们这个例子里面确实是这么用的。它实际上是内核的一种日志机制,用来记录下日志信息或者给出警告提示。所以每个printk都会有个优先级,比方你看的那个<1>或者KERN_ALERT。如果你没指定优先级的话,内核会选择默认的优先级。内核一共有8个优先级,它们都有宏定义,所以你大可不必每次都直接使用数字,去linux/kernel.h里找找就好了,默认的优先级是DEFAULT_MESSAGE_LOGLEVEL。
花点时间看看这几个优先级的宏吧,头文件里还描述了它们的意思。实际上你不要使用数字,还是用这些宏比较好。如果优先级数字比int console_loglevel变量小的话,消息就会打印到终端上。如果syslogd和klogd在运行的话,则不管是不是向控制台输出,消息都会被追加进/var/log/messages。这个例子里我们选了个高优先级KERN_ALERT以保证消息输出到console而不是写进日志文件了事,等你真的写一个内核模块,可以根据情况自己挑个合适的优先级来用。
2.2. Compiling Kernel Modules
为了确保内核模块能够正常工作,编译的时候需要使用一些特别的gcc选项。而且基于我们是编译成可执行的文件还是内核模块,还需要定义一些符号,这些符号定义用gcc的-D可以,在源代码中使用#define预处理命令也可以。下面我们来讲怎么编译内核模块。
-c: 内核模块不同于独立的可执行文件,它是个使用insmod运行时link进内核的目标文件(object file)。所以,模块应该用-c标志来编译。
-O2: 内核广泛的使用内联函数,所以编译模块也必须打开这个选项。没有这个优化现象的话,有些汇编宏调用会被编译器当成是函数调用。这会导致模块无法载入,因为insmod在内核当中找不到那些函数。
-W -Wall: 一个程序错误会导致你的整个系统down掉。所以你应该打开编译器的警告开关。其实这一点在编写任何程序的时候都应如此,而不仅仅是在模块编译上。
-isystem /lib/modules/`uname -r`/build/include: 这是为了让/usr/include/linux头文件目录失效,而使用对应内核版本的头文件。
-D__KERNEL__: 定义这个符号是为了告诉头文件,这个代码是要编译成内核模式。而不是一个用户进程。
-DMODULE: 这个符号告诉头文件使用内核模块该用的定义。
我们使用gcc的-isystem来代替-I是因为它会让gcc放弃一些-W –Wall情况下装入module.h文件带来的“unused variable”警告。通过在gcc-3.0下使用-isystem,内核头文件会强制指定它们把警告放弃掉。如果你使用的是-I(或者你用的虽然是-isystem,gcc却是3.0以下),你就会看到一大堆“unused variable”警告信息。看到的话忽略就可以了。
好了,现在让我们来看看编译hello-1.c的Makefile吧:
Example 2-2. Makefile for a basic kernel module
TARGET := hello-1
WARN := -W -Wall -Wstrict-prototypes -Wmissing-prototypes
INCLUDE := -isystem /lib/modules/`uname -r`/build/include
CFLAGS := -O2 -DMODULE -D__KERNEL__ ${WARN} ${INCLUDE}
CC := gcc-3.0
${TARGET}.o: ${TARGET}.c
.PHONY: clean
clean:
rm -rf {TARGET}.o
作为给读者的一个练习,请编译hello-1.c并用insmod ./hello-1.o将它插入到内核里(先忽略掉你看到的警告什么的,很快就会讲到)。很利落是吧,呵呵~~所有被载入的模块都在/proc/modules中列了出来。去那个文件里看看你的模块是否真的已经成为了内核的一部分。恭喜你,你也算是个内核代码的作者了J。等你这股新鲜劲头过去了,就用rmmod hello-1把这个模块移除出去。对了,你还可以去/var/log/messages文件看看你的日志信息。J
这还有个读者的练习。看见init_module的注释了么?把返回值改成非零值再编译加载一下看看会发生什么~~
2.3. Hello World (part 2)
在linux2.4上,init和cleanup函數不一定非要叫做init_module和cleanup_module了,你可以自己給它們起個名字。這是通過module_init和module_exit這兩個宏來實現的。這兩個宏是在linux/init.h中定義的。唯一的要求是,你的初始化函數和清除函數要寫在宏調用的前面。否則編譯是不會通過的J:
Example 2-3. hello-2.c
/* hello-2.c - Demonstrating the module_init() and module_exit() macros. This is the
* preferred over using init_module() and cleanup_module().
*/
#include <linux/module.h> // Needed by all modules
#include <linux/kernel.h> // Needed for KERN_ALERT
#include <linux/init.h> // Needed for the macros
static int hello_2_init(void)
{
printk(KERN_ALERT "Hello, world 2\n");
return 0;
}
static void hello_2_exit(void)
{
printk(KERN_ALERT "Goodbye, world 2\n");
}
module_init(hello_2_init);
module_exit(hello_2_exit);
現在我們有兩個自己定義的内核模塊了。這裡高級了,我們的Makefile不妨也高級點。下面是高級了一些得Makefile,它是針對代碼尺寸和穩定性優化的。(如果看不懂的話,建議先去看一下makefile的info pages或者GNU Make Manual)。
Example 2-4. Makefile for both our modules
WARN := -W -Wall -Wstrict-prototypes -Wmissing-prototypes
INCLUDE := -isystem /lib/modules/`uname -r`/build/include
CFLAGS := -O2 -DMODULE -D__KERNEL__ ${WARN} ${INCLUDE}
CC := gcc-3.0
OBJS := ${patsubst %.c, %.o, ${wildcard *.c}}
all: ${OBJS}
.PHONY: clean
clean:
rm -rf *.o
這裡給讀者一個練習,如果我們再同一個目錄下還有另外一個模塊,hello-3.c,Makefile應該怎麽修改?
2.4. Hello World (part 3): The __init and __exit Macros
這一節對2.2或者2.2以後的内核版本才有用処。注意一下init和cleanup這兩個函數定義的變化。__init宏使内建模塊中的init函數在執行完成后釋放掉,不過可裝載的模塊不受影響。如果你關心init函數什麽時候調用,這一點是很有用的。
還有個__initdata,和__init的作用基本上一樣,不過它是針對變量而不是函數的。
__exit宏會使那些内建到内核的模塊省略掉cleanup函數,不過和__init一樣,對loadable模塊沒影響。再説一遍,如果你關心cleanup運行的時機,這是重要的。Built-in的驅動不需要cleanup,反正它們也不能退出,不過loadable式的模塊顯然是需要一個的。
這些宏都定義在linux/init.h中,它們會釋放内核的内存。你啓動内核的時候會看到一些諸如Freeing unused kernel memory:236k freed,之類的信息,這多半就是它們干的。
Example 2-5. hello-3.c
/* hello-3.c - Illustrating the __init, __initdata and __exit macros.
*/
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_ALERT */
#include <linux/init.h> /* Needed for the macros */
static int hello3_data __initdata = 3;
static int __init hello_3_init(void)
{
printk(KERN_ALERT "Hello, world %d\n", hello3_data);
return 0;
}
static void __exit hello_3_exit(void)
{
printk(KERN_ALERT "Goodbye, world 3\n");
}
module_init(hello_3_init);
module_exit(hello_3_exit);
順便說一句,你會發現在2.2以後的内核驅動裏有“__initfunction”指令:
__initfunction(int init_module(void))
{
printk(KERN_ALERT "Hi there.\n");
return 0;
}
這個宏的目的和__init一樣,但是這個現在很少用了,我只是提一下而已,因爲你偶爾可能會看到它。在2.4.18裏,有38個__initfunction(),在2.4.20裏有37個。不過甭怎麽說,反正你就別在自己的代碼裏用它了
The Linux Kernel Module Programming Guide简译(二)续
sighofwraith 发表于 2005-6-24 16:48:39
2.5. Hello World (part 4): Licensing and Module Documentation
如果你是运行在2.4以后的内核上,你可能会在insmod模块的时候发现下面这样的话:
# insmod hello-3.o
Warning: loading hello-3.o will taint the kernel: no license
See [url]http://www.tux.org/lkml/#export-tainted[/url] for information about tainted modules
Hello, world 3
Module hello-3 loaded, with warnings
把代码的认证设置成GPL就可以了。这个认证机制都是在linux/module.h里面定义的。参考一下下面的代码就可以了。
Example 2-6. hello-4.c
/* hello-4.c - Demonstrates module documentation.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#define DRIVER_AUTHOR "Peiter Jay Salzman <[email]p@dirac.org[/email]>"
#define DRIVER_DESC "A sample driver"
int init_hello_3(void);
void cleanup_hello_3(void);
static int init_hello_4(void)
{
printk(KERN_ALERT "Hello, world 4\n");
return 0;
}
static void cleanup_hello_4(void)
{
printk(KERN_ALERT "Goodbye, world 4\n");
}
module_init(init_hello_4);
module_exit(cleanup_hello_4);
/* You can use strings, like this:
*/
MODULE_LICENSE("GPL"); // Get rid of taint message by declaring code as GPL.
/* Or with defines, like this:
*/
MODULE_AUTHOR(DRIVER_AUTHOR); // Who wrote this module?
MODULE_DESCRIPTION(DRIVER_DESC); // What does this module do?
/* This module uses /dev/testdevice. The MODULE_SUPPORTED_DEVICE macro might be used in
* the future to help automatic configuration of modules, but is currently unused other
* than for documentation purposes.
*/
MODULE_SUPPORTED_DEVICE("testdevice");
页:
[1]