Linux 内核/模块
内核模块是可以在需要时加载到内核映像中的代码,而无需用户重新构建内核或重启计算机。模块化设计确保您不必创建包含所有硬件和情况所需代码的单片内核。
常见的内核模块是设备驱动程序,它们直接访问计算机和外设硬件。
从内核 3.0 开始,内核模块的扩展名为 .ko。
要确定已加载的模块,请运行 lsmod 命令,该命令读取文件 /proc/modules。以下是 lsmod 的示例输出
Module Size Used by ctr 13049 1 ccm 17773 1 snd_hda_codec_hdmi 46368 1 snd_hda_codec_cirrus 18855 1 btusb 32412 0 hid_generic 12548 0 arc4 12608 2 b43 387371 0 mac80211 630669 1 b43 hid_apple 13386 0 bcm5974 17589 0 joydev 17381 0 cfg80211 484040 2 b43,mac80211 ssb 62379 1 b43 hid_holtek_mouse 12625 0
请注意,lsmod 打印的输出通常很长,并且包含列出的多个内核模块。出于显而易见的原因,此示例已缩短。
为了将模块添加到内核,您可以使用 modprobe 命令。内核模块守护进程 kmod 通常执行 modprobe 以加载模块。Modprobe 传递一个字符串参数,该参数可以是实际的模块名称或模块的别名。Modprode 搜索 /proc/modprobe.conf 以将模块别名与实际模块关联。为了确定模块依赖关系,modprobe 搜索 /lib/module/version/modules.dep 并确定在加载请求的模块之前是否必须加载其他模块。modules.dep 文件使用 depmod -a 命令创建,该命令确定特定模块是否调用其他模块定义的函数或变量(符号)。
如果有依赖项,insmod 会在加载请求的模块之前加载它们。虽然 insmod 需要有关请求模块的路径和文件名的详细说明,但 modprobe 不需要。以下是如何使用 "spidev.ko" 的示例
sudo modprobe spidev
Modprobe、insmod 和 depmod 在 module-init-tools 或 kmod 包中找到。
已加载的模块列在 /proc/modules 中。使用 modinfo modulename(针对文件 modulename.ko)来确定模块信息。以下是一个示例
modinfo spidev
请注意,您不能使用 X(例如 xterm),因为内核只会将目录打印到控制台或日志文件。
最简单的内核模块
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
printk(KERN_ALERT "Hello, world\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);
内核模块必须始终包含这两个函数:init_module 和 cleanup_module。Insmod 在将模块加载到内核时调用 init_module,而 rmsmod 在删除模块时调用 cleanup_module。printk() 是内核使用的日志宏,并分配了 <1> 的优先级。内核优先级共有八个,在 kernel.h 中定义。每个内核优先级都有其特定的预期含义和相关定义
43 #define KERN_EMERG "<0>" /* system is unusable */
44 #define KERN_ALERT "<1>" /* action must be taken immediately */
45 #define KERN_CRIT "<2>" /* critical conditions */
46 #define KERN_ERR "<3>" /* error conditions */
47 #define KERN_WARNING "<4>" /* warning conditions */
48 #define KERN_NOTICE "<5>" /* normal but significant condition */
49 #define KERN_INFO "<6>" /* informational */
50 #define KERN_DEBUG "<7>" /* debug-level messages */
在使用 printk() 时,如果 syslogd 和 klogd 正在运行,printk() 的输出将追加到 /var/log/messages。如果以低优先级调用 printk(),它只会追加到 /var/log/messages。为了确保它打印到控制台,请使用 KERN_ALERT 优先级。
内核模块的编译方式与用户空间代码不同。以下是一个示例 makefile
obj-m += hello.o all: modules .DEFAULT: $(MAKE) -C $(KDIR) M=$$PWD $@
执行 make 命令来编译你的简单模块。
使用 module_init() 和 module_exit() 宏,您可以更改 init_module() 和 cleanup_module(void) 的名称。
__init 宏在内置驱动程序(例如)加载后释放模块中初始化函数的内存。但是,这在使用 modprobe 时不会发生。
__exit 宏可以防止在初始化期间加载 cleanup_module,因为它在内核使用期间不需要在内核中。这些宏可以在 linux/init.h 中找到,对管理内存非常有用。__initdata 与 __init 相似,但用于变量。
MODULE_LICENSE() 宏允许您确定代码的许可证,并为您的模块定义它。
MODULE_PARM() 允许您在 insmod 初始化模块时传递命令行参数。
为了跨多个文件扩展模块,必须更改 make 文件。
obj-m += hello.o obj-m += startstop.o startstop-objs := start.o stop.o default: modules .DEFAULT: $(MAKE) -C $(KDIR) M=$$PWD $@
% make make[1]: Entering directory `/usr/src/linux-2.6.10' CC [M] /home/ldd3/src/misc-modules/hello.o Building modules, stage 2. MODPOST CC /home/ldd3/src/misc-modules/hello.mod.o LD [M] /home/ldd3/src/misc-modules/hello.ko make[1]: Leaving directory `/usr/src/linux-2.6.10' % su root# insmod ./hello.ko Hello, world root# rmmod hello Goodbye cruel world root#
可用于模块的符号可以在 /proc/kallsyms 中找到。
可以编写模块来替换内核的系统调用。
要查看用户空间进程执行了哪些系统调用,请使用 **strace** 执行该进程。
CPU 有不同的模式,80386 有四个。有关环的描述,请参阅维基百科环条。
参考资料