Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1371842
  • 博文数量: 244
  • 博客积分: 10311
  • 博客等级: 上将
  • 技术积分: 3341
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-14 21:50
文章分类

全部博文(244)

文章存档

2013年(6)

2012年(5)

2011年(16)

2010年(11)

2009年(172)

2008年(34)

分类: LINUX

2009-03-15 10:31:43

学习内核模块编程是一件很有意义的事——要想开发Linux驱动程序, 必须深入理解内核模块编程: 驱动程序就是以模块的形式加载到内核中的嘛! 本文介绍了内核模块编程的"hello, world!". 解释了一些概念, 提出了一些值得注意的问题. 由于初涉模块编程, 难免有所纰漏, 欢迎朋友们指正!  (daily.zp@gmail.com)

前期准备

前提: 这里假设当前运行的内核是你自行编译的. 这里只讨论将内核加载到当前正在运行的内核中的情况. 一般而言, 除非你在配置内核时打开了"CONFIG_MODVERSIONS"选项, 否则你针对某个内核编译的模块是无法加载到当前运行的不同版本内核中去的.
      
模块可以加载到当前运行的内核中, 也可以加载到另一个未运行的内核. 这里暂时只考虑将模块加载到启动的内核中.

学习模块编程, 先要重新编译内核, 为什么要编译内核的? 原因有二:

1, 我们使用的Linux发行版中的内核针对kernel.org的官方内核添加了许多补丁, 提供的内核头文件并不完整, 内核API也可能被修改了, 要学习模块编程, 最好使用官方内核编译.

2, 发行版的内核中, 一般默认的CONFIG_MODVERSIONS被设置为y.  这样你在加载模块时会由于版本问题失败, 所以应该不设置CONFIG_MODVERSIONS.

要想进行内核变成, 配置内核时要激活某些选项.
Loadable module support: Module versioning support许选, loadable module support,  module unloading要选上.
Kernel hacking: "Use 4Kb for kernel stacks instead of 8Kb"不选, 其他都可选上. (若无特殊需要, "Debug slab memory allocations"可不选, 它会影响运行速度)
      
关于编译内核, 参考我blog里另外一篇文章:编译Linux2,6 内核总结

通过 $ uname -r 查看你当前运行的内核版本.


模块编程的"hello, world!"

看看这个简单的模块程序:

/*  
 *  hello-1.c - The simplest kernel module.
 */
#include     /* Needed by all modules */
#include     /* Needed for KERN_INFO */

int init_module(void)
{
    printk(KERN_INFO "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_INFO "Goodbye world 1.\n");
}

这里不讨论语法, 函数调用问题. 可参考文末列出的参考资料.

针对它编写这样一个makefile:

obj-m += hello-1.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

执行make之后, 在hello-1.c和Makefile所在目录会多出这样几个文件:
hello-1.ko  hello-1.mod.c  hello-1.mod.o  hello-1.o

其中的hello-1.ko就是内核模块了! 内核模块扩展名为 ".ko", 不同于一般的.o 目标文件。.ko中包含了额外的".modinfo"段用来记录有关模块的信息。
       
运行 $ modinfo hello-1.ko, 可显示下列信息:

filename:       hello-1.ko
vermagic:       2.6.15.6zp0509 PENTIUMM REGPARM gcc-4.0
depends:

现在已经编译好了一个内核模块,通过这个命令将它加载到内核中去:
# insmode ./hello-1.ko  (注意要root权限)

: 我靠! 什么反应都没有?
: 运行 $ tail /var/log/messages 看! 最后一句:
May 10 21:46:41 localhost kernel: [17182219.832000] Hello world 1.

运行 # rmmod hello-1 来卸载这个模块,  可以看到/var/log/messages后面多了这样一句:
May 10 21:49:11 localhost kernel: [17182370.224000] Goodbye world 1.


关于输出信息

: 为什么在X终端中看不到结果呢?
: 这里有两方面的原因:

(1) 要理解printk()的优先级标志.
看到printk(KERN_INFO "Hello world 1.\n");语句了没? 它可和我们常用的printf()不一样!
       
使用printk()是内核程序区别于其它C程序的一个显著特征——内核并不提供printf()函数。printk()将格式化字符窜拷贝到内核的log buffer, log buffer又由syslog程序读取。
   
看看这个例子: printk("Hello world! A string: %s and an integer: %d\n", a_string, an_integer); 它是不是和我们用的printf()差不多? 但printk()有显著区别于printf()的特征. 其中之一就是你能对它设定优先级标志(priority flag). syslogd(8)以这个优先级标志来判断将内核信息显示在何处: 是记录在/var/log/messages中, 还是显示到console? 比如:printk(KERN_ERR "this is an error!\n");

好了, 了解了printk()函数, 这下知道为什么看不到显示信息了么?——printk只是内核的一种log机制, 并非用于向用户显示信息 (当然, 显示信息也是它的功能之一).

关于优先级
在内核源码数的/include/linux/kernel.h中,有下列的内容:

#define    KERN_EMERG     "<0>"    /* system is unusable                         */
#define    KERN_ALERT      "<1>"    /* action must be taken immediately  */
#define    KERN_CRIT         "<2>"    /* critical conditions                           */
#define    KERN_ERR          "<3>"    /* error conditions                              */
#define    KERN_WARNING "<4>"    /* warning conditions                         */
#define    KERN_NOTICE     "<5>"    /* normal but significant condition    */
#define    KERN_INFO         "<6>"    /* informational                                  */
#define    KERN_DEBUG      "<7>"    /* debug-level messages                     */

以刚才的printk(KERN_ERR "this is an error!\n");为例,它等效于 printk(<3> "this is an error!\n");但最好不要使用幻数!

: 如果不给printk()传递priority flag, 又会是怎样?
: 如果不设定,则默认为DEFAULT_MESSAGE_LOGLEVEL, 它目前等同于KERN_WARNING. 但它的值也许会变, 所以在调用printk()时, 应该指定优先级标志!
如果优先级小于console_loglevel, 消息就会打印到当前的终端上. 若syslog和klogd同时在运行,消息也会被写入/var/log/messages中!
       
你可以根据适当的需要指定优先级以满足需要.

关于printk的更多信息,参考LKD 2rd第18章

(2) 内核信息是显示到文本模式的控制台的
: 如果设定了比较高的优先级(数值低),还是在屏幕上看不到输出消息,这是为何?
: 推荐使用console, (Alt + Ctrl + F1 ~ F6),而非图形界面中的terminal. 如果使用Xterm终端, 消息只是被记录到/var/log/messages中. 屏幕上看不到输出!

下面试验一下, 把刚才的hello-1.c 的printk()语句改为下列内容:

printk(KERN_DEBUG "Hello world, priority = 7\n");
printk(KERN_INFO "Hello world, priority = 6\n");
printk("Hello world, priority = DEFAULT_MESSAGE_LOGLEVEL\n");
printk(KERN_NOTICE "Hello world, priority = 5\n");
printk(KERN_WARNING "Hello world, priority = 4\n");
printk(KERN_ERR "Hello world, priority = 3\n");
printk(KERN_CRIT "Hello world, priority = 2\n");
printk(KERN_ALERT "Hello world, priority = 1\n");
printk(KERN_EMERG "Hello world, priority = 0\n");

经过试验,发现0~3这4个优先级能在console中显示, 也就是说, console_loglevel为4. KERN_DEBUG无论是在/var/log/messages中还是在console上都没有显示. 6~4在/var/log/messages中有记录, 但3~0没有.

文本模式下,分辨率太低,按如下方法调整,很简单:
编辑 /boot/grub/menu.lst
修改内核启动选项
kernel  /boot/vmlinuz-2.6.12-10  root=/dev/hda3 ro quiet splash vga=0x???
???参考:
800 * 600 :  314(16位色)  315(24位色)
1024*768:  317(16位色)  318(24位色)
本本上用 vga=0x318, 台式机上用vga=0x315
CRT显示器比较恶心,如果用1024*768分辨率,从gnome切换到文本模式画面发生明显的偏移


参考资料

(1) LDP上的module-programming-howto,有分别针对2.4和2.6内核的版本
2.6版本打包下载(包括示例代码):


(2) 关于makefile: 参考linux/Documentation/kbuild/makefiles.txt及与它相关的文档. 内核模块编译的Makefile一定要理解!

(3)
阅读(959) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

o_Chapman2009-04-06 21:26:47

讲得很详细!学习了