Introduction to Linux Kernel Modeles

Linux 模块编写实践

在Linux虚拟机上编写一个简单的模块并加载到内存中。之所以不用双系统是怕bug直接让系统崩溃

一.准备工作

首先采用如下语句查看内核版本:

1
uname -a

我的得到:

之后要建立目录树,其语句为:

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

要编译其他的文件,只需要修改其中的文件名称即可
我们用:

1
make

命令来编译,用:

1
make clean

命令来清除中间产生的中间文件。

三.编写内核模块

创建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");

用这个看:

1
dmesg

但是这里我们用了/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)
其次它里面可以声明:

1
.proc_read = proc_read,

一个函数,我们执行cat /proc/模块名时要干的事情,即怎么读;

1
.proc_write = proc_write

我们用echo /proc/模块名 “内容”时要往内核内存中写的东西。
赋值右面的函数是要我们自己实现的,详情看代码。

三.导入模块

用以下语句来加载模块:

1
sudo insmod <模块名>.ko

如图,在执行cat /proc/seconds后会告诉你时间

另外,在/sys/module/文件夹下也可以找到我们的模块:

因此我们也可以用

1
ls /sys/module/<模块名>

来读模块。
最后,用以下语句来卸载模块:

1
sudo rmmod <模块名>(没有.ko!!!)

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!