理解內(nèi)核模塊原理及正確編寫源代碼
原理:內(nèi)核模塊可以作為獨立程序來編譯的函數(shù)和數(shù)據(jù)類型的集合。之所以提供模塊機制势木,是因為Linux本身是一個單內(nèi)核。單內(nèi)核由于所有內(nèi)容都集成在一起稠腊,效率很高篓跛,但可擴展性和可維護性相對較差,模塊機制可以彌補這一缺陷瀑凝。
Linux模塊可以通過靜態(tài)或動態(tài)的方法加載到內(nèi)核空間序芦,靜態(tài)加載是指在內(nèi)核啟動過程中加載;動態(tài)加載是指在內(nèi)核運行的過程中隨時加載粤咪。
一個模塊被加載到內(nèi)核中時谚中,就成為內(nèi)核代碼的一部分。模塊加載入系統(tǒng)時,系統(tǒng)修改內(nèi)核中的符號表宪塔,將新加載的模塊提供的資源和符號添加到內(nèi)核符號表中磁奖,以便模塊間通信比搭。
編寫模塊代碼
- 模塊構(gòu)造函數(shù):
- 執(zhí)行insmod或modprobe指令加載內(nèi)核模塊時會調(diào)用的初始化函數(shù)南誊。函數(shù)原型必須是module_init(),內(nèi)是函數(shù)指針戚长。
- 模塊析構(gòu)函數(shù)(釋放模塊對象所占用的內(nèi)存空間前所必須執(zhí)行的一個函數(shù)):
- 執(zhí)行rmmod指令卸載模塊時調(diào)用的函數(shù)。函數(shù)原型是module_exit();
- 模塊模塊許可聲明:
- 函數(shù)原型是MODULE_LICENSE()怠苔,告訴內(nèi)核程序使用的許可證柑司,不然在加載時它會提示該模塊污染內(nèi)核蟆湖。一般會寫GPL隅津。
向內(nèi)核輸出簡單信息的代碼:
test.c
#include<linux/init.h>
#include<linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static char *name="tomorrow";
static void __init name_init(void)
{
printk("Hello World~\n");
printk("Hello %s\n",name);
}
static void __exit name_exit(void)
{
printk(KERN_INFO"Name module exit\n");
}
module_init(name_init);
module_exit(name_exit);
module_param(name,charp,S_IRUGO);
Makefile文件
obj-m:=test.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-4.4.0-21-generic
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
make運行成功后會在原目錄下生成以下文件:
將模塊加載進內(nèi)核:
sudo insmod test.ko
查看模塊向內(nèi)核輸入的信息:
dmesg
卸載模塊:
sudo rmmod test
實現(xiàn)輸出當前進程信息的功能
module2.c
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/sched.h>
static struct task_struct *pcurrent;
int print_current_task_info(void);
static int __init print_init(void)
{
printk(KERN_INFO "print current task info\n");
print_current_task_info();
return 0;
}
static void __exit print_exit(void)
{
printk(KERN_INFO "Finished\n");
}
int print_current_task_info(void)
{
pcurrent=get_current();
printk(KERN_INFO "Task state: %ld\n",current->state);
printk(KERN_INFO "pid: %d\n",current->pid);
printk(KERN_INFO "tgid: %d\n",current->tgid);
printk(KERN_INFO "prio: %d\n",current->prio);
return 0;
}
module_init(print_init);
module_exit(print_exit);
Makefile
obj-m:=module2.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-4.4.0-21-generic
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
運行后在內(nèi)核中將輸出以下信息:
卸載后效果如下:
module3.c
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/sched.h>
static struct task_struct *pcurrent;
static int __init print_init(void)
{
printk(KERN_INFO "print current task info\n");
printk("pid\ttgid\tprio\tstate\n");
for_each_process(pcurrent){
printk("%d\t",pcurrent->pid);
printk("%d\t",pcurrent->tgid);
printk("%d\t",pcurrent->prio);
printk("%ld\n",pcurrent->state);
}
return 0;
}
static void __exit print_exit(void)
{
printk(KERN_INFO "Finished\n");
}
module_init(print_init);
module_exit(print_exit);
Makefile
obj-m:=module3.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-4.4.0-21-generic
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
加載后效果如下:
卸載后效果如下:
修改系統(tǒng)調(diào)用的模塊
在/usr/include/i386-linux-gnu/asm/unistd_32.h文件中查看系統(tǒng)調(diào)用序號
發(fā)現(xiàn)222號和223號系統(tǒng)調(diào)用是空的谓苟,因此選取223作為新的系統(tǒng)調(diào)用號。
在/boot/System.map-3.16.0-30-generic查看系統(tǒng)調(diào)用表的內(nèi)存地址:
為0xc1697140
編寫模塊文件:
module5.c
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/unistd.h>
#include <linux/sched.h>
MODULE_LICENSE("Dual BSD/GPL");
#define SYS_CALL_TABLE_ADDRESS 0xc1697140 //sys_call_table對應的地址
#define NUM 223 //系統(tǒng)調(diào)用號為223
int orig_cr0; //用來存儲cr0寄存器原來的值
unsigned long *sys_call_table_my=0;
static int(*anything_saved)(void); //定義一個函數(shù)指針湾趾,用來保存一個系統(tǒng)調(diào)用
static int clear_cr0(void) //使cr0寄存器的第17位設(shè)置為0(內(nèi)核空間可寫)
{
unsigned int cr0=0;
unsigned int ret;
asm volatile("movl %%cr0,%%eax":"=a"(cr0));//將cr0寄存器的值移動到eax寄存器中搀缠,同時輸出到cr0變量中
ret=cr0;
cr0&=0xfffeffff;//將cr0變量值中的第17位清0,將修改后的值寫入cr0寄存器
asm volatile("movl %%eax,%%cr0"::"a"(cr0));//將cr0變量的值作為輸入,輸入到寄存器eax中歧譬,同時移動到寄存器cr0中
return ret;
}
static void setback_cr0(int val) //使cr0寄存器設(shè)置為內(nèi)核不可寫
{
asm volatile("movl %%eax,%%cr0"::"a"(val));
}
asmlinkage long sys_mycall(void) //定義自己的系統(tǒng)調(diào)用
{
printk("模塊系統(tǒng)調(diào)用-當前pid:%d瑰步,當前comm:%s\n",current->pid,current->comm);
return current->pid;
}
static int __init call_init(void)
{
sys_call_table_my=(unsigned long*)(SYS_CALL_TABLE_ADDRESS);
printk("call_init......\n");
anything_saved=(int(*)(void))(sys_call_table_my[NUM]);//保存系統(tǒng)調(diào)用表中的NUM位置上的系統(tǒng)調(diào)用
orig_cr0=clear_cr0();//使內(nèi)核地址空間可寫
sys_call_table_my[NUM]=(unsigned long) &sys_mycall;//用自己的系統(tǒng)調(diào)用替換NUM位置上的系統(tǒng)調(diào)用
setback_cr0(orig_cr0);//使內(nèi)核地址空間不可寫
return 0;
}
static void __exit call_exit(void)
{
printk("call_exit......\n");
orig_cr0=clear_cr0();
sys_call_table_my[NUM]=(unsigned long)anything_saved;//將系統(tǒng)調(diào)用恢復
setback_cr0(orig_cr0);
}
module_init(call_init);
module_exit(call_exit);
MODULE_AUTHOR("25");
MODULE_VERSION("BETA 1.0");
MODULE_DESCRIPTION("a module for replace a syscall");
Makefile
obj-m:=module5.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-3.16.0-30-generic
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
~
測試程序:
test.c
#include<stdio.h>
#include<stdlib.h>
int main()
{
unsigned long x = 0;
x = syscall(223); //測試223號系統(tǒng)調(diào)用
printf("Hello, %ld\n", x);
return 0;
}
測試結(jié)果:
將2號系統(tǒng)調(diào)用fork替換為獲取當前進程的pid:
編寫模塊文件module.c
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/unistd.h>
#include <linux/sched.h>
MODULE_LICENSE("Dual BSD/GPL");
#define SYS_CALL_TABLE_ADDRESS 0xc1697140 //sys_call_table對應的地址
#define NUM 2 //系統(tǒng)調(diào)用號為2
int orig_cr0; //用來存儲cr0寄存器原來的值
unsigned long *sys_call_table_my=0;
static int(*anything_saved)(void); //定義一個函數(shù)指針袁滥,用來保存一個系統(tǒng)調(diào)用
static int clear_cr0(void) //使cr0寄存器的第17位設(shè)置為0(內(nèi)核空間可寫)
{
unsigned int cr0=0;
unsigned int ret;
asm volatile("movl %%cr0,%%eax":"=a"(cr0));//將cr0寄存器的值移動到eax寄存器中题翻,同時輸出到cr0變量中
ret=cr0;
cr0&=0xfffeffff;//將cr0變量值中的第17位清0,將修改后的值寫入cr0寄存器
asm volatile("movl %%eax,%%cr0"::"a"(cr0));//將cr0變量的值作為輸入,輸入到寄存器eax中猾普,同時移動到寄存器cr0中
return ret;
}
static void setback_cr0(int val) //使cr0寄存器設(shè)置為內(nèi)核不可寫
{
asm volatile("movl %%eax,%%cr0"::"a"(val));
}
asmlinkage long sys_mycall(void) //定義自己的系統(tǒng)調(diào)用
{
printk("模塊系統(tǒng)調(diào)用-當前pid:%d,當前comm:%s\n",current->pid,current->comm);
return current->pid;
}
static int __init call_init(void)
{
sys_call_table_my=(unsigned long*)(SYS_CALL_TABLE_ADDRESS);
printk("call_init......\n");
anything_saved=(int(*)(void))(sys_call_table_my[NUM]);//保存系統(tǒng)調(diào)用表中的NUM位置上的系統(tǒng)調(diào)用
orig_cr0=clear_cr0();//使內(nèi)核地址空間可寫
sys_call_table_my[NUM]=(unsigned long) &sys_mycall;//用自己的系統(tǒng)調(diào)用替換NUM位置上的系統(tǒng)調(diào)用
setback_cr0(orig_cr0);//使內(nèi)核地址空間不可寫
return 0;
}
static void __exit call_exit(void)
{
printk("call_exit......\n");
orig_cr0=clear_cr0();
sys_call_table_my[NUM]=(unsigned long)anything_saved;//將系統(tǒng)調(diào)用恢復
setback_cr0(orig_cr0);
}
module_init(call_init);
module_exit(call_exit);
MODULE_AUTHOR("25");
MODULE_VERSION("BETA 1.0");
MODULE_DESCRIPTION("a module for replace a syscall");
測試程序:
test.c
#include<stdio.h>
#include<stdlib.h>
int main()
{
unsigned long x = 0;
x = syscall(2); //測試2號系統(tǒng)調(diào)用
printf("Hello, %ld\n", x);
return 0;
}
測試結(jié)果:
插入模塊前輸出子進程的pid和子進程中的返回值0,為fork調(diào)用:
插入模塊后,將2號fork進程替換為getpid赏参,輸出當前進程的pid:
卸載模塊后纫溃,將2號進程還原為fork進程: