示例
源码:
#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的说明:
- obj-y Built-in object goals 对应内核配置[Y]
- 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包含五部分:
- Makefile the top Makefile.
- .config the kernel configuration file.
- arch/$(ARCH)/Makefile the arch Makefile.
- scripts/Makefile.* common rules etc. for all kbuild Makefiles.
- kbuild Makefiles there are about 500 of these.
各个部分的作用:
- 顶层Makefile读取.config内核配置文件,顶层Makefile负责编译两个主要的镜像文件:vmlinux(驻留内核镜像)和 内核模块,
它通过递归便利内核源码树的子目录来编译这些目标文件,访问的子目录列表取决于内核的配置。 - .config内核配置文件
- 顶层Makefile还会包含arch/$(ARCH)/Makefile,这些平台Makefile向顶层Makefile提供架构特性信息
- 每一个子目录下有一个kbulid Makefile会展开从上面传下来的命令,
kbulid Makefile利用来自.config的配置信息通过kbulid构建各种不同的文件编译内置或是模块可加载的目标 - scripts/Makefile.* 包含了所有的定义、规则等等,他们在kbulid makefiles的基础上构建内核
Makefile.modpost
在script目录下有许多Makefile文件,由于各种情况;其中,Makefile.modpost由于module的生成。
第一步:
- 编译驱动的每个.o文件。
- 将每个.o文件链接到.o。
- 在$(MODVERDIR)/生成一个.mod文件,列出.ko及每个.o文件。
第二步:
- 找出所有在$(MODVERDIR)/的modules。
- 接着使用modpost
- 为每个module创建.mod.c
- 创建一个Module.symvers文件,保存了所有引出符号及其CRC校验。
- 编译全部 .mod.c文件。
- 链接所有的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.o
和hello_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_module
和init_module
,其他为局部符号,对外不可见。