Hello, World (part 1): 最简单的内核模块

当第一个洞穴程序员在第一台洞穴计算机的墙上上凿写第一个程序时,这是一个在羚羊皮上输出`Hello, world'的字符串。罗马的编程书籍上是以`Salut, Mundi'这样的程序开始的。 我不明白人们为什么要破坏这个传统,但我认为还是不明白为好。我们将从编写一系列的`Hello, world'模块开始,一步步展示编写内核模块的基础的方方面面。

这可能是一个最简单的模块了。先别急着编译它。我们将在下章模块编译的章节介绍相关内容。

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(),当内核模块被insmod加载进入内核时被执行。还有一个“结束” (干一些收尾清理的工作)的函数被称为cleanup_module(),当内核模块被rmmod卸载时被执行。实际上,从内核版本2.3.13开始这种情况有些改变。 你可以为你的开始和结束函数起任意的名字。 你将在以后学习如何实现这一点。2.3小节。实际上,这个新方法时推荐的实现方法。但是,许多人仍然使用init_module()cleanup_module() 作为他们的开始和结束函数。

一般,init_module() 要么向内核注册它可以处理的事物,要么用自己的代码 替代某个内核函数(代码通常这样做然后再去调用原先的函数代码)。函数 cleanup_module() 应该撤消任何 init_module() 做的事,从而内核模块可以被安全的卸载。

最后,任一个内核模块需要包含 linux/module.h文件。 我们仅仅需要包含linux/kernel.h 当需要使用 printk() 记录级别的宏扩展时KERN_ALERT,相关内容将在2.1.1小节中学习。

2.1.1. 介绍printk()

不管你可能怎么想,printk() 并不是设计用来同用户交互的,虽然我们在hello-1就是出于这样的目的使用它!它实际上是为内核提供日志功能,记录内核信息或用来给出警告。因此,每个printk()声明都会带一个优先级,就像你看到的<1>KERN_ALERT 那样。内核总共定义了八个优先级的宏, 所以你不必使用晦涩的数字代码,并且你可以从文件 linux/kernel.h查看这些宏和它们的意义。如果你不指明优先级,默认的优先级 DEFAULT_MESSAGE_LOGLEVEL 将被采用。

阅读一下这些优先级的宏。头文件同时也描述了每个优先级的意义。在实际中,使用宏而不要使用数字,就像 <4>。总是使用宏,就像 KERN_WARNING

当优先级低于“int console_loglevel”时,信息将直接打印在你的终端上。如果同时 syslogdklogd 都在运行,信息也同时添加在文件 /var/log/messages,而不管是否显示在控制台上与否。我们使用像 KERN_ALERT这样的高优先级,来确保 printk() 将信息输出到控制台而不是只是添加到日志文件中。 当你编写真正的实用的模块时,你应该针对可能遇到的情况使用合适的优先级。