Linux 模块编写实践
在Linux虚拟机上编写一个简单的模块并加载到内存中。之所以不用双系统是怕bug直接让系统崩溃
一.准备工作
首先采用如下语句查看内核版本:
我的得到:
之后要建立目录树,其语句为:
1
| sudo apt-get install linux-headers-<Linux内核版本号>
|
二.编写Makefile文件
Makefile文件可以让我们用简单的命令将.c文件编译成可被加载的.ko文件
我的Makefile文件的写法是:
1 2 3 4 5 6 7
| KERNELDIR ?=/lib/modules/$(shell uname -r)/build PWD :=$(shell pwd) obj-m :=<文件名称>.o all: make -C $(KERNELDIR) M=$(PWD) modules clean: rm -rf *.o *~core .depend .*.cmd *.ko *.mod.c .tmp_versions *.mod *.order *.symvers
|
要编译其他的文件,只需要修改其中的文件名称即可
我们用:
命令来编译,用:
命令来清除中间产生的中间文件。
三.编写内核模块
创建xxx.c文件,比如我们这里以seconds.c为例。该模块可以在读取的时候告诉你从他被加载已经过去了多久。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/proc_fs.h> #include <asm/uaccess.h> #include <linux/jiffies.h>
#define PROC_FILE_NAME "seconds" #define BUFFER_SIZE 128 typedef unsigned long volatile ulv; ulv begintime; ssize_t proc_read(struct file *file ,char __user *user_buf,size_t count,loff_t *pos) { int rv = 0; char buffer[BUFFER_SIZE]; static int completed=0; if(completed) { completed=0; return 0; } completed = 1;
ulv curtime = (jiffies-begintime)/HZ; rv = sprintf(buffer,"the time passed is:%lu",curtime); copy_to_user(user_buf,buffer,rv); return rv; }; static const struct proc_ops proc_ops ={ .proc_read = proc_read, }; static int __init proc_init(void) { proc_create(PROC_FILE_NAME,0x0666,NULL,&proc_ops); begintime = jiffies; return 0; } static void __exit proc_exit(void) { remove_proc_entry(PROC_FILE_NAME,NULL); } module_init(proc_init); module_exit(proc_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Otsutsuki_Orance");
|
对其做如下解读:
1.框架部分:
声明模块在加载时执行的函数,函数体为:
1
| static int __init proc_init(void){}
|
声明模块在退出内核时执行的函数,函数体为:
1
| static void __exit proc_exit(void)
|
当然还有一些后摇:
1 2 3 4
| module_init(proc_init); module_exit(proc_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Otsutsuki_Orance");
|
2.使用/proc文件系统
如果是正常的模块,写到这也就差不多了,我们只需要在函数中加入如下语句就可以在缓冲区查看输出了:
1
| printk(KERN_INFO"XXXX");
|
用这个看:
但是这里我们用了/proc文件系统,他是内存中的伪文件系统(in-memory pseudo-file system)。该目录下保存的不是真正的文件和目录,而是一些“运行时”信息,如系统内存、磁盘io、设备挂载信息和硬件配置信息等。proc目录是一个控制中心,用户可以通过更改其中某些文件来改变内核的运行状态。
那我们来控制一下
首先它的逻辑是:
1
| proc_create(PROC_FILE_NAME,0x0666,NULL,&proc_ops);
|
来创建一个/proc入口,其中PROC_FILE_NAME是可以在/proc/PROC_FILE_NAME找到的模块名,0x0666为权限,NULL为父目录名,设成NULL的话就是/proc,
然后来谈proc_ops:
首先他是proc_ops类型(这里千万不要信恐龙书上的file_operation)
其次它里面可以声明:
一个函数,我们执行cat /proc/模块名时要干的事情,即怎么读;
1
| .proc_write = proc_write
|
我们用echo /proc/模块名 “内容”时要往内核内存中写的东西。
赋值右面的函数是要我们自己实现的,详情看代码。
三.导入模块
用以下语句来加载模块:
如图,在执行cat /proc/seconds后会告诉你时间
另外,在/sys/module/文件夹下也可以找到我们的模块:
因此我们也可以用
来读模块。
最后,用以下语句来卸载模块:
1
| sudo rmmod <模块名>(没有.ko!!!)
|