在上一篇日志《增加Linux系统调用——通过重新编译内核》中已经介绍了如何通过编译Linux内核的方式来增加系统调用。这里将继续介绍如何通过增加内核模块的方式增加Linux系统调用。
●添加系统调用的入口参数
#define __NR_zzr_callmod 336
●在linux-2.6.30.6/arch/x86/kernel/syscall_table_32.S 中添加:.long sys_zzr_callmod /* 336 */
.long sys_zzr_callmod /* 336 */
●添加自定义系统响应函数
修改linux-2.6.30.6/kernel/sys.c文件,在文件末尾添加自定义的系统响应函数。函数实现如下:
/* The function point that is used to call system module Added by ZZR */ int (*zzr_function)(int *, int, int, char) = NULL; //函数指针 EXPORT_SYMBOL_GPL(zzr_function); asmlinkage int sys_zzr_callmod(int *result, int first, int second, char op)//模块调用函数 { if(zzr_function){ return zzr_function(result,first,second,op); } }
在sys_zzr_callmod之前定义与要调用的外部模块中函数同类型的函数指针zzr_function,并且把zzr_function通过EXPORT_SYMBOL_GPL放到内核全局符号表中
注:EXPORT_SYMBOL(符号名);EXPORT_SYMBOL_GPL(符号名); //只有包含GPL许可权的模块才可以访问该符号名
●编写内核模块代码
新建文件夹放置内核模块代码和Makefile文件。
内核模块代码testmodule.c:
#include <linux/kernel.h> #include <linux/module.h> MODULE_LICENSE("GPL"); extern int (*zzr_function)(int *, int, int, char); //函数指针 int my_function_module(int *result, int first, int second, char op){ printk("In the kernel module\n"); switch(op){ case '+': *result = first + second; break; case '-': *result = first - second; break; case '*': *result = first * second; break; case '/': if(second == 0){ printk("divisor can't be 0.\n"); return -1; } *result = first / second; break; default: printk("operator is illegal.\n"); return -1; } return 0; } static int my_init(void) { zzr_function = my_function_module; printk("ZZR_module_init success!\n"); return 0; } static int my_exit(void) { printk("ZZR_module_exit success!\n"); return 0; } module_init(my_init); module_exit(my_exit);Makefie文件:
ifneq ($(KERNELRELEASE),) obj-m := testmodule.o else KERNEL_VER := $(shell uname -r) KERNEL_DIR := /lib/modules/$(KERNEL_VER)/build CUR_PATH := $(shell pwd) default: $(MAKE) -C $(KERNEL_DIR) M=$(CUR_PATH) modules modules_install: $(MAKE) -C $(KERNEL_DIR) M=$(CUR_PATH) modules_install clean: $(MAKE) -C $(KERNEL_DIR) M=$(CUR_PATH) clean endif注意testmodule.o文件的名字必须与内核模块代码的文件名相同。
●编译内核模块
$ make
$ insmod testmodule.ko插入内核模块
$ lsmod 查看目前已经加载的内核模块
●编写测试代码
#include <stdio.h> #include <sys/syscall.h> #include <unistd.h> #include <time.h> #define __NR_zzr_calculator 335 #define __NR_zzr_callmod 336 int main(){ clock_t start,finish; double duration1,duration2; start = clock(); int result; syscall(__NR_zzr_callmod,&result,3,4,'+'); printf("+:\t%d\n",result); syscall(__NR_zzr_callmod,&result,3,4,'-'); printf("-:\t%d\n",result); syscall(__NR_zzr_callmod,&result,3,4,'*'); printf("*:\t%d\n",result); syscall(__NR_zzr_callmod,&result,3,4,'/'); printf("/:\t%d\n",result); finish = clock(); duration1 = (double)(finish-start)/CLOCKS_PER_SEC; printf("Kernel:\t%f\n",duration1); return 0; }程序运行结果与《增加Linux系统调用——通过重新编译内核》完全相同,实验成功。
这里要注意的是,事实上,虽然可以动态地修改内核模块代码,但是最开始的时候还是要通过编译内核来增加函数指针,通过这个函数指针来调用内核模块。也就是说,这个函数指针的形式事实上已经固定了,这也就限制了内核模块函数的形式。这种动态加载并不是完全的自由。