Linux内核的整体架构本就非常庞大,其包含的组件也非常多。而我们怎样把需要的部分都包含在内核中呢?
一种方法是把所有需要的功能都编译到Linux内核中。这会导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核。
有没有另一种机制可使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码被动态地加载到内核中呢?
Linux提供了这样的机制,这种机制被称为模块(Module)。模块具有这样的特点。
·模块本身不被编译入内核映像,从而控制了内核的大小。
·模块一旦被加载,它就和内核中的其他部分完全一样。
为了使读者初步建立对模块的感性认识,我们先来看一个最简单的内核模块“Hello World”,如代码清单4.1所示。
代码清单4.1 一个最简单的Linux内核模块
1 /* 2 * a simple kernel module: hello 3 * 4 * Copyright (C) 2014 Barry Song (baohua@kernel.org) 5 * 6 * Licensed under GPLv2 or later. 7 */ 8 9 #include <linux/init.h> 10 #include <linux/module.h> 11 12 static int __init hello_init(void) 13 { 14 printk(KERN_INFO "Hello World enter\n"); 15 return 0; 16 } 17 module_init(hello_init); 18 19 static void __exit hello_exit(void) 20 { 21 printk(KERN_INFO "Hello World exit\n "); 22 } 23 module_exit(hello_exit); 24 25 MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); 26 MODULE_LICENSE("GPL v2"); 27 MODULE_DESCRIPTION("A simple Hello World Module"); 28 MODULE_ALIAS("a simplest module");
这个最简单的内核模块只包含内核模块加载函数、卸载函数和对GPL v2许可权限的声明以及一些描述信息,位于本书配套Ubuntu的/home/baohua/develop/training/kernel/drivers/hello目录。编译它会产生hello.ko目标文件,通过“insmod./hello.ko”命令可以加载它,通过“rmmod hello”命令可以卸载它,加载时输出“Hello World enter”,卸载时输出“Hello World exit”。
内核模块中用于输出的函数是内核空间的printk()而不是用户空间的printf(),printk()的用法和printf()基本相似,但前者可定义输出级别。printk()可作为一种最基本的内核调试手段,在Linux驱动的调试章节中将会详细讲解。
在Linux中,使用lsmod命令可以获得系统中已加载的所有模块以及模块间的依赖关系,例如:
Module Size Used by hello 9 472 0 nls_iso8859_1 12 032 1 nls_cp437 13 696 1 vfat 18 816 1 fat 57 376 1 vfat ...
lsmod命令实际上是读取并分析“/proc/modules”文件,与上述lsmod命令结果对应的“/proc/modules”文件如下:
$ cat /proc/modules hello 12393 0 - Live 0xe67a2000 (OF) nls_utf8 12493 1 - Live 0xe678e000 isofs 39596 1 - Live 0xe677f000 vboxsf 42561 2 - Live 0xe6767000 (OF) …
内核中已加载模块的信息也存在于/sys/module目录下,加载hello.ko后,内核中将包含/sys/module/hello目录,该目录下又有一个refcnt文件和一个sections目录,在/sys/module/hello目录下运行“tree–a”可得到如下目录树:
root@barry-VirtualBox:/sys/module/hello# tree -a . ├── coresize ├── holders ├── initsize ├── initstate ├── notes │ └── .note.gnu.build-id ├── refcnt ├── sections │ ├── .exit.text │ ├── .gnu.linkonce.this_module │ ├── .init.text │ ├── .note.gnu.build-id │ ├── .rodata.str1.1 │ ├── .strtab │ └── .symtab ├── srcversion ├── taint └── uevent 3 directories, 15 f iles
modprobe命令比insmod命令要强大,它在加载某模块时,会同时加载该模块所依赖的其他模块。使用modprobe命令加载的模块若以“modprobe-r filename”的方式卸载,将同时卸载其依赖的模块。模块之间的依赖关系存放在根文件系统的/lib/modules/<kernel-version>/modules.dep文件中,实际上是在整体编译内核的时候由depmod工具生成的,它的格式非常简单:
kernel/lib/cpu-notifier-error-inject.ko: kernel/lib/notifier-error-inject.ko kernel/lib/pm-notifier-error-inject.ko: kernel/lib/notifier-error-inject.ko kernel/lib/lru_cache.ko: kernel/lib/cordic.ko: kernel/lib/rbtree_test.ko: kernel/lib/interval_tree_test.ko: updates/dkms/vboxvideo.ko: kernel/drivers/gpu/drm/drm.ko
使用modinfo<模块名>命令可以获得模块的信息,包括模块作者、模块的说明、模块所支持的参数以及vermagic:
# modinfo hello.ko filename: /home/baohua/develop/training/kernel/drivers/hello/hello.ko alias: a simplest module description: A simple Hello World Module license: GPL v2 author: Barry Song <21cnbao@gmail.com> depends: vermagic: 4.0.0-rc1 SMP mod_unload 686