内核模块
1. 实验目的
模块是 Linux 系统的一种特有机制,可用以动态扩展操作系统内核功能。编写实现某些 特定功能的模块,将其作为内核的一部分在管态下运行。本实验通过内核模块编程在/porc
文件系统中实现系统时钟的读操作接口。
2. 实验内容
设计并构建一个在/proc
文件系统中的内核模块clock
,支持read()
操作,read()
返回值为
一个字符串,其中包块一个空各分开的两个子串,为别代表xtime.tv_sec
和xtime.tv_usec
。
3. 实验原理
Linux 模块是一些可以作为独立程序来编译的函数和数据类型的集合。在装载这些模块 式,将它的代码链接到内核中。Linux 模块可以在内核启动时装载,也可以在内核运行的过 程中装载。如果在模块装载之前就调用了动态模块的一个函数,那么这次调用将会失败。如 果这个模块已被加载,那么内核就可以使用系统调用,并将其传递到模块中的相应函数。
4. 实验步骤
编写内核模块
文件中主要包含四个函数:
clock_proc_show()
clock_proc_open()
init_clock()
cleanup_clock()
其中init_clock()
,cleanup_clock()
负责将模块从系统中加载或卸载,以及增加或删除模块在/proc
中的入口。clock_proc_open()
负责打开/proc/clock
文件,并通过clock_proc_show()
对文件进行读写操作。
在linux 3.0以上版本的内核中,实验参考书中使用的create_proc_read_entry()函数已经被标记为过时的函数,而应使用proc_create()来创建/proc文件系统模块。
Makefile
文件
在2.6版本以后的Linux内核中,考虑到内核源代码的依赖关系的复杂程度,Linux使用了新的KMAKE方式来编译内核模块。
obj-m += clock.o # 添加目标文件
KERNEL_DIR := /lib/modules/`uname -r`/build # 设置内核文件位置
MODULEDIR := $(shell pwd) # 设置生成的模块文件位置
modules:
make -C $(KERNEL_DIR) M=$(MODULEDIR) modules # 使用KMAKE来生成模块
内核模块源代码clock.c
#define _GNU_SOURCE
#define MODULE_NAME "clock"
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
int clock_proc_show(struct seq_file * m, void * v) {
struct timeval xtime;
do_gettimeofday(&xtime); // 获取系统时间
// 将系统时间写入proc文件中
seq_printf(m, "%lu, %lu\n", xtime.tv_sec, xtime.tv_usec);
printk("clock: read_func()\n");
return 0;
}
int clock_proc_open(struct inode * inode, struct file * file) {
// 使用single_open操作来打开顺序文件
return single_open(file, clock_proc_show, NULL);
}
struct proc_dir_entry * proc_my_clock;
// 文件操作结构体
const struct file_operations clock_proc_fops = {
.owner = THIS_MODULE,
.open = clock_proc_open, // 使用clock_proc_open()方法来打开文件
.read = seq_read, // 使用seq_read()方法以顺序文件方式读取文件
.llseek = seq_lseek,
.release = single_release,
};
int __init init_clock(void)
{
printk("clock: init_module()\n");
// 创建一个proc模块
proc_my_clock=proc_create("clock", 0, NULL, &clock_proc_fops);
printk(KERN_INFO"%s %s has initialized.\n",
MODULE_NAME, "1.0");
return 0;
}
void __exit cleanup_clock(void)
{
printk("clock: cleanup_module()\n");
remove_proc_entry("clock", NULL);
printk(KERN_INFO"%s %s has removed.\n",
MODULE_NAME, "1.0");
}
// 设置初始化模块方法为init_clock()
module_init(init_clock);
// 设置卸载模块方法为cleanup_clock()
module_exit(cleanup_clock);
编译代码
$ make
加载内核模块
在命令行下执行以下代码来加载内核模块
$ sudo insmod clock.ko
测试
gettime.c
#include <stdio.h>
#include <sys/time.h>
#include <fcntl.h>
int main() {
struct timeval getSystemTime;
char procClockTime[256];
int infile, len;
gettimeofday(&getSystemTime, NULL);
infile = open("/proc/clock", O_RDONLY);
len = read(infile, procClockTime, 256);
close(infile);
procClockTime[len] = '\0';
printf("SystemTime is %lu %lu\nProcClockTime is %s\n",
getSystemTime.tv_sec,
getSystemTime.tv_usec,
procClockTime);
sleep(1);
return 0;
}
卸载内核模块
在命令行下执行以下代码来卸载内核模块
$ sudo insmod clock.o
5. 实验结果及分析
编译内核模块
加载内核模块
通过dmesg
命令查看模块加载情况
可以看到模块输出的提示信息,说明模块已经被加载。
编译测试程序gettime.c
执行测试程序gettime.c
可以看到测试程序正确获取到了内核模块提供的时间信息。
卸载内核模块
通过dmesg
命令查看模块卸载情况
可以看到模块输出的提示信息,read_func()
表示该模块被调用过一次,之后的提示信息表明模块已经被成功卸载。