轉自:https://www.jianshu.com/p/64def4ed0849
理解內核模塊原理及正確編寫源代碼
原理:內核模塊可以作爲獨立程序來編譯的函數和數據類型的集合。之所以提供模塊機制,是因爲Linux本身是一個單內核。單內核由於所有內容都集成在一起,效率很高,但可擴展性和可維護性相對較差,模塊機制可以彌補這一缺陷。
Linux模塊可以通過靜態或動態的方法加載到內核空間,靜態加載是指在內核啓動過程中加載;動態加載是指在內核運行的過程中隨時加載。
一個模塊被加載到內核中時,就成爲內核代碼的一部分。模塊加載入系統時,系統修改內核中的符號表,將新加載的模塊提供的資源和符號添加到內核符號表中,以便模塊間通信。
編寫模塊代碼
- 模塊構造函數:
- 執行insmod或modprobe指令加載內核模塊時會調用的初始化函數。函數原型必須是module_init(),內是函數指針。
- 模塊析構函數(釋放模塊對象所佔用的內存空間前所必須執行的一個函數):
- 執行rmmod指令卸載模塊時調用的函數。函數原型是module_exit();
- 模塊模塊許可聲明:
- 函數原型是MODULE_LICENSE(),告訴內核程序使用的許可證,不然在加載時它會提示該模塊污染內核。一般會寫GPL。
向內核輸出簡單信息的代碼:
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運行成功後會在原目錄下生成以下文件:
將模塊加載進內核:
sudo insmod test.ko
查看模塊向內核輸入的信息:
dmesg
卸載模塊:
sudo rmmod test
實現輸出當前進程信息的功能
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
運行後在內核中將輸出以下信息:
卸載後效果如下:
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
加載後效果如下:
卸載後效果如下:
修改系統調用的模塊
在/usr/include/i386-linux-gnu/asm/unistd_32.h文件中查看系統調用序號
發現222號和223號系統調用是空的,因此選取223作爲新的系統調用號。
在/boot/System.map-3.16.0-30-generic查看系統調用表的內存地址:
爲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 //系統調用號爲223
int orig_cr0; //用來存儲cr0寄存器原來的值
unsigned long *sys_call_table_my=0;
static int(*anything_saved)(void); //定義一個函數指針,用來保存一個系統調用
static int clear_cr0(void) //使cr0寄存器的第17位設置爲0(內核空間可寫)
{
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寄存器設置爲內核不可寫
{
asm volatile("movl %%eax,%%cr0"::"a"(val));
}
asmlinkage long sys_mycall(void) //定義自己的系統調用
{
printk("模塊系統調用-當前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]);//保存系統調用表中的NUM位置上的系統調用
orig_cr0=clear_cr0();//使內核地址空間可寫
sys_call_table_my[NUM]=(unsigned long) &sys_mycall;//用自己的系統調用替換NUM位置上的系統調用
setback_cr0(orig_cr0);//使內核地址空間不可寫
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;//將系統調用恢復
setback_cr0(orig_cr0);
}
module_init(call_init);
module_exit(call_exit);
MODULE_AUTHOR("25");
MODULE_VERSION("BETA 1.0")