当第一个穴居人程序员在第一个穴居计算机的墙壁上凿刻第一个程序时,那是一个用羚羊图片绘制字符串“Hello, world”的程序。罗马编程教科书以“Salut, Mundi”程序开头。我不知道那些打破这一传统的人会怎么样,但我认为最好不要去了解。我们将从一系列 hello world 程序开始,这些程序演示了编写内核模块基础知识的不同方面。
这是最简单的模块。先不要编译它;我们将在下一节介绍模块编译。
例 2-1. hello-1.c
/* hello-1.c - The simplest kernel module. * * Copyright (C) 2001 by Peter Jay Salzman * * 08/02/2006 - Updated by Rodrigo Rubira Branco <rodrigo@kernelhacking.com> */ /* Kernel Programming */ #define MODULE #define LINUX #define __KERNEL__ #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"); } MODULE_LICENSE("GPL"); |
内核模块必须至少有两个函数:一个“启动”(初始化)函数,称为init_module()它在模块被 insmoded 到内核时调用,以及一个“结束”(清理)函数,称为cleanup_module()它在被 rmmoded 之前调用。实际上,从内核 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 节中了解它。
尽管您可能认为,printk()并非旨在向用户传达信息,即使我们在 hello-1 中正是为此目的使用了它!它恰好是内核的日志记录机制,用于记录信息或发出警告。因此,每个printk()语句都带有一个优先级,这就是您看到的<1>和KERN_ALERT。共有 8 个优先级,内核为它们提供了宏,因此您不必使用神秘的数字,您可以在linux/kernel.h中查看它们(及其含义)。如果您没有指定优先级,则将使用默认优先级DEFAULT_MESSAGE_LOGLEVEL。
花时间阅读优先级宏。头文件还描述了每个优先级的含义。在实践中,不要使用数字,例如<4>。始终使用宏,例如KERN_WARNING.
如果优先级低于int console_loglevel,消息将打印在您当前的终端上。如果 syslogd 和 klogd 都在运行,那么消息也将附加到/var/log/messages,无论它是否打印到控制台。我们使用高优先级,例如KERN_ALERT,以确保printk()消息打印到您的控制台,而不仅仅是记录到您的日志文件中。当您编写真正的模块时,您会希望使用对当前情况有意义的优先级。