内核编程入门,就以最为简单的hello.c为例。 环境:Redhat 9.0,内核版本2.4.20-8。 下面总结了第一个内核程序hello.c的学习过程。 (一)第一阶段:尽量简单 /* * hello.c */ #define MODULE #i nclude <linux/module.h> int init_module(void) { printk("Hello World!/n"); return 0; } void cleanup_module(void) { printk("Goodbye!/n"); } 执行,出现错误一: [root@lqm drivers]# gcc -c hello.c [root@lqm drivers]# insmod hello.o hello.o: kernel-module version mismatch hello.o was compiled for kernel version 2.4.20 while this kernel is version 2.4.20-8. 这是因为内核源代码版本和编译器版本不一致造成的。 (1)编译器版本/usr/include/linux/version.h #define UTS_RELEASE "2.4.20" #define LINUX_VERSION_CODE 132116 #define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c)) (2)内核源代码版本/usr/src/linux-2.4.20-8/include/linux/version.h /usr/src/linux-2.4.20-8/include/linux [root@lqm linux]# cat version.h #i nclude <linux/rhconfig.h> #if defined(__module__smp) #define UTS_RELEASE "2.4.20-8smp" #elif defined(__module__BOOT) #define UTS_RELEASE "2.4.20-8BOOT" #elif defined(__module__bigmem) #define UTS_RELEASE "2.4.20-8bigmem" #else #define UTS_RELEASE "2.4.20-8" #endif #define LINUX_VERSION_CODE 132116 #define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c)) 可以采取修改编译器版本号与内核源代码版本号一致的办法来解决这个问题,即修改/usr/include/linux/version.h中 #define UTS_RELEASE "2.4.20" 为 #define UTS_RELEASE "2.4.20-8" 执行,出现错误二: [root@lqm drivers]# gcc -c hello.c [root@lqm drivers]# insmod hello.o Warning: loading hello.o will taint the kernel: no license See http://www.tux.org/lkml/#export-tainted for information about tainted modules Module hello loaded, with warnings [root@lqm drivers]# tail -n 1 /var/log/messages Jan 30 12:02:08 lqm kernel: Hello 也就是说出现了no license的警告。GNU的软件需要有GPL,所以修改源代码如下: /* * hello.c */ #define MODULE #i nclude <linux/module.h> MODULE_LICENSE("GPL"); int init_module(void) { printk("Hello World!/n"); return 0; } void cleanup_module(void) { printk("Goodbye!/n"); } 这时没有错误了。写了一个脚本,测试流程自动化: #!/bin/bash gcc -c hello.c sleep 1 insmod hello.o && echo -e "Instal module - hello.o/n" sleep 1 tail -n 1 /var/log/messages lsmod | grep "hello" && echo -e "Module hello has instaled/n" rmmod hello && echo -e "Remove module - hello/n" sleep 1 tail -n 1 /var/log/messages lsmod | grep "hello" || echo "Module hello has removed" 执行结果如下: [root@lqm hello]# ./run Instal module - hello.o Jan 30 13:31:29 lqm kernel: Hello World! hello 748 0 (unused) Module hello has instaled Remove module - hello Jan 30 13:31:30 lqm kernel: Goodbye! Module hello has removed (二)第二阶段:完善,深入一点 /* * hello.c */ #ifndef __KERNEL__ #define __KERNEL__ #endif #ifndef MODULE #define MODULE #endif #i nclude <linux/kernel.h> /*printk*/ #i nclude <linux/module.h> MODULE_LICENSE("GPL"); static int init_module(void) { printk("Hello, world!/n"); return 0; } static void cleanup_module(void) { printk("Goodbye!/n"); } 写Makefile文件如下: # Kernel Programming # Shandong University, Linqingmin # The path of kernel source code INCLUDEDIR = /usr/src/linux-2.4.20-8/include/ # Compiler CC = gcc # Options CFLAGS = -D__KERNEL__ -DMODULE -O -Wall -I$(INCLUDEDIR) # Target OBJS = hello.o all: $(OBJS) $(OBJS): hello.c /usr/include/linux/version.h $(CC) $(CFLAGS) -c $< install: insmod $(OBJS) uninstall: rmmod hello .PHONY: clean clean: rm -f *.o 写Makefile时应该注意,不要用空格来代替<TAB>。否则会出现错误:missing separator. Stop. 修改执行脚本run: #!/bin/bash # The first step make && make install && echo -e "Instal module - hello.o/n" sleep 1 tail -n 1 /var/log/messages lsmod | grep "hello" && echo -e "Module hello has instaled/n" # The second step make uninstall && echo -e "Remove module - hello/n" sleep 1 tail -n 1 /var/log/messages lsmod | grep "hello" || echo "Module hello has removed" # The last step make clean 执行结果如下: [root@lqm hello]# ./run gcc -D__KERNEL__ -DMODULE -O -Wall -I/usr/src/linux-2.4.20-8/include/ -c hello.c hello.c:18: warning: `init_module' defined but not used hello.c:25: warning: `cleanup_module' defined but not used insmod hello.o Instal module - hello.o Jan 31 13:40:23 lqm kernel: Hello, hello 728 0 (unused) Module hello has instaled rmmod hello Remove module - hello Jan 31 13:40:24 lqm kernel: Module hello has removed rm -f *.o
(三)第三阶段:总结 1、一个内核模块至少应该包括两个函数: (1)init_module:模块插入内核时调用 (2)cleanup_module:模块移除时调用 这个简单的程序就是只实现了这两个函数,而且只做了打印信息的工作,没有使用价值。典型情况下,init_module为内核中的某些东西注册一个句柄,相当于模块初始化的工作。cleanup_module则是撤销模块前期的处理工作,使模块得以安全卸载。 2、insmod实现动态加载模块。在当前OS上,动态加载模块以测试硬件等,避免了繁琐的工作。但是,在这种情况下,会出现版本不匹配的情况。另外,要分清楚内核源代码路径和编译器路径的不同,知道在编译时该指定那个路径。第二阶段开始出现过几个错误都是因为默认的路径是编译器路径,而不是内核源代码路径。体会内核模块化带来的好处! 3、应用Make工具来管理项目。即使小,也要训练。在2.4内核和2.6内核下,Makefile的编写会有所不同。只是语法形式的不同,先深入掌握一种,另一种注意一下应该可以避免犯错误。 继续努力! 2007/02/01补记 /* * Kernel Programming: hello.c */ /*define __KERNEL__ MODULE*/ #ifndef __KERNEL__ #define __KERNEL__ #endif #ifndef MODULE #define MODULE #endif #i nclude <linux/kernel.h> /*printk*/ #i nclude <linux/module.h> #i nclude <linux/init.h> /*module_init module_exit*/ MODULE_LICENSE("GPL"); /*declaration license*/ static int __init hello_init(void) { printk("<1>Hello, world!/n"); return 0; } static void __exit hello_exit(void) { printk("<1>Goodbye!/n"); } module_init(hello_init); module_exit(hello_exit); # Kernel Programming # Shandong University, Linqingmin # The path of kernel source code KERNELDIR = /usr/src/linux-2.4.20-8 # Compiler CC = gcc # Options CFLAGS = -D__KERNEL__ -DMODULE -I$(KERNELDIR)/include -O -Wall ifdef CONFIG_SMP CFLAGS += -D__SMP__ -DSMP endif # OBJS OBJS = hello.c # Target TARGET = hello.o $(TARGET): hello.c $(CC) $(CFLAGS) -c $< install: insmod $(TARGET) uninstall: rmmod hello .PHONY: clean clean: rm -f *.o #!/bin/bash # The first step make && make install && echo -e "Instal module - hello.o/n" sleep 1 lsmod | grep "hello" && echo -e "Module hello has instaled/n" # The second step make uninstall && echo -e "Remove module - hello/n" sleep 1 lsmod | grep "hello" || echo "Module hello has removed" # The last step make clean 执行结果: gcc -D__KERNEL__ -DMODULE -I/usr/src/linux-2.4.20-8/include -O -Wall -c hello.c insmod hello.o Hello,World! Instal module - hello.o hello 776 0 (unused) Module hello has instaled rmmod hello Goodbye! Remove module - hello Module hello has removed rm -f *.o 说明: 这个版本采取了显式的初始化和清除函数,显然无论是调试的便利还是编程风格,都比前几个版本要好。这种机制具体描述如下:如果你将模块初始化函数命名为my_init(而不是init_module),将清除函数命名为my_exit,则可以使用下面两行来进行标记(通常在源文件的末尾) module_init(my_init); module_exit(my_exit); 注意必须包含头文件<linux/init.h>。 |