0%

Linux Modules分析

示例

源码:

#include <linux/init.h>
#include <linux/module.h>

static int __init hello_init(void)
{
    printk("Hello, world!\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk("Goodbye, world!\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("printk");
MODULE_DESCRIPTION("\"Hello, world!\"");
MODULE_VERSION("printk");     

使用的交叉编译,Makefile:

ARCH=xxx
CROSS_COMPILE=$(ARCH)-linux-
obj-m := printk.o

KDIR := /home/workspace/kernel/2.6.27.55
PWD   := $(shell pwd)

default:
    $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KDIR) M=$(PWD) modules

clean:
    $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KDIR) M=$(PWD) clean    

obj-m := printk.o 是kbuild Makefile中的目标定义部分,同时目标定义部分也是kbuild Makefile的重要组成部分。
该例子告诉Kbuild在这目录里,有一个名为printk.o的目标文件。
将会编译成一个可加载的模块而不是直接编译进内核

makefiles.txt

根据Linux内核文档makefiles.txt的说明:

  1. obj-y Built-in object goals 对应内核配置[Y]
  2. obj-m Loadable module goals 对应内核配置[M]

obj-m由单个文件构成,其中$(CONFIG_ISDN_PPP_BSDCOMP)为配置系统配置为‘m’,示例:

obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o

obj-m由多个文件构成,其中$(CONFIG_ISDN)为配置系统配置为‘m’,示例:

obj-$(CONFIG_ISDN) += isdn.o
isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o

如果.config中存在以下配置CONFIG_SCSI=y,那么drivers/Makefile中存在以下配置obj-$(CONFIG_SCSI) += scsi/,示例:

obj-y               += base/ block/ misc/ mfd/ net/ media/
obj-$(CONFIG_NUBUS)     += nubus/
obj-$(CONFIG_ATM)       += atm/
obj-y               += macintosh/
obj-$(CONFIG_IDE)       += ide/
obj-$(CONFIG_SCSI)      += scsi/                                                                                                    
obj-$(CONFIG_ATA)       += ata/
obj-$(CONFIG_FUSION)        += message/

当将模块配置为build-in时,即表示要将这些代码的相关目录放入编译目录中!

Linux Makefile

Kernel/Documentation/kbuild/makefiles.txt 中描述Linux Makefile包含五部分:

  1. Makefile the top Makefile.
  2. .config the kernel configuration file.
  3. arch/$(ARCH)/Makefile the arch Makefile.
  4. scripts/Makefile.* common rules etc. for all kbuild Makefiles.
  5. kbuild Makefiles there are about 500 of these.

各个部分的作用:

  1. 顶层Makefile读取.config内核配置文件,顶层Makefile负责编译两个主要的镜像文件:vmlinux(驻留内核镜像)和 内核模块,
    它通过递归便利内核源码树的子目录来编译这些目标文件,访问的子目录列表取决于内核的配置。
  2. .config内核配置文件
  3. 顶层Makefile还会包含arch/$(ARCH)/Makefile,这些平台Makefile向顶层Makefile提供架构特性信息
  4. 每一个子目录下有一个kbulid Makefile会展开从上面传下来的命令,
    kbulid Makefile利用来自.config的配置信息通过kbulid构建各种不同的文件编译内置或是模块可加载的目标
  5. scripts/Makefile.* 包含了所有的定义、规则等等,他们在kbulid makefiles的基础上构建内核

Makefile.modpost

在script目录下有许多Makefile文件,由于各种情况;其中,Makefile.modpost由于module的生成。

第一步:

  1. 编译驱动的每个.o文件。
  2. 将每个.o文件链接到.o。
  3. 在$(MODVERDIR)/生成一个.mod文件,列出.ko及每个.o文件。

第二步:

  1. 找出所有在$(MODVERDIR)/的modules。
  2. 接着使用modpost
  3. 为每个module创建.mod.c
  4. 创建一个Module.symvers文件,保存了所有引出符号及其CRC校验。
  5. 编译全部 .mod.c文件。
  6. 链接所有的module成为一个文件。

第三步:替换module里一些ELF段,包括:

Version magic (see include/vermagic.h for full details)

  • Kernel release
  • SMP is CONFIG_SMP
  • PREEMPT is CONFIG_PREEMPT
  • GCC Version

Module info

  • Module version (MODULE_VERSION)
  • Module alias’es (MODULE_ALIAS)
  • Module license (MODULE_LICENSE)
  • See include/linux/module.h for more details

第四步:

Step 4 is solely used to allow module versioning in external modules,
where the CRC of each module is retrieved from the Module.symers file.
KBUILD_MODPOST_WARN can be set to avoid error out in case of undefined
symbols in the final module linking stage
KBUILD_MODPOST_NOFINAL can be set to skip the final link of modules.
This is solely usefull to speed up test compiles

编译及结果

源码结构:

.
├── hello_printk.c
└── Makefile

编译过程:

make ARCH=xxx CROSS_COMPILE=xxx-linux- -C /home/workspace/kernel/2.6.27.55 M=/data/linux_drivers/hello_printk modules
make[1]: Entering directory '/home/workspace/kernel/2.6.27.55'
CC [M]  /data/linux_drivers/hello_printk/hello_printk.o
Building modules, stage 2.
MODPOST 1 modules
CC      /data/linux_drivers/hello_printk/hello_printk.mod.o
LD [M]  /data/linux_drivers/hello_printk/hello_printk.ko
make[1]: Leaving directory '/home/workspace/kernel/2.6.27.55'

结构:

.
├── hello_printk.c
├── hello_printk.ko
├── .hello_printk.ko.cmd
├── hello_printk.mod.c
├── hello_printk.mod.o
├── .hello_printk.mod.o.cmd
├── hello_printk.o
├── .hello_printk.o.cmd
├── Makefile
├── modules.order
├── Module.symvers
└── .tmp_versions
    └── hello_printk.mod

其中生成<module>.mod.c文件, 内容如下:

#include <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>

MODULE_INFO(vermagic, VERMAGIC_STRING);

struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
    .name = KBUILD_MODNAME,
    .init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
    .exit = cleanup_module,
#endif                                                                                                                              
    .arch = MODULE_ARCH_INIT,
};



static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=";


MODULE_INFO(srcversion, "D9CAF6BFCBD9B121FB4765D");

其实就是在生成的文件里加入.gnu.linkonce.this_module这样一个段,
驱动加裁时会找到这个段并调用.init函数,卸载时调用.exit函数。

定义一个module结构类型的变量struct module __this_module
这个变量是放在ELF文件的段名为.gnu.linkonce.this_module的段中,通过readelf工具也可以看到相关的段:

$ readelf -S hello_printk.mod.o
共有 11 个节头,从偏移量 0x218 开始:

节头:
    [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
    [ 0]                   NULL            00000000 000000 000000 00      0   0  0
    [ 1] .text             PROGBITS        00000000 000034 000000 00  AX  0   0  1
    [ 2] .data             PROGBITS        00000000 000034 000000 00  WA  0   0  1
    [ 3] .bss              NOBITS          00000000 000034 000000 00  WA  0   0  1
    [ 4] .gnu.linkonce.thi PROGBITS        00000000 000034 0000f4 00  WA  0   0  4
    [ 5] .rela.gnu.linkonc RELA            00000000 000524 000018 0c      9   4  4
    [ 6] .modinfo          PROGBITS        00000000 000128 00004f 00   A  0   0  4
    [ 7] .comment          PROGBITS        00000000 000177 000043 01  MS  0   0  1
    [ 8] .shstrtab         STRTAB          00000000 0001ba 00005d 00      0   0  1
    [ 9] .symtab           SYMTAB          00000000 0003d0 0000e0 10     10  11  4
    [10] .strtab           STRTAB          00000000 0004b0 000071 00      0   0  1
Key to Flags:
    W (write), A (alloc), X (execute), M (merge), S (strings)
    I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
    O (extra OS processing required) o (OS specific), p (processor specific)

最后将hello_printk.mod.ohello_printk.o链接为ko,ko符号表如下:

nm hello_printk.ko
00000000 T cleanup_module
00000000 t hello_exit
00000000 t hello_init
00000000 T init_module
0000003c r __mod_author59
00000010 r __mod_description60
00000060 r __mod_license58
0000006c r __mod_srcversion23
00000090 r __module_depends
0000009c r __mod_vermagic5
00000000 r __mod_version61
U printk
00000000 D __this_module

module中可以使用的符号表为cleanup_moduleinit_module,其他为局部符号,对外不可见。