1. 实验目的

模块是 Linux 系统的一种特有机制,可用以动态扩展操作系统内核功能。编写实现某些 特定功能的模块,将其作为内核的一部分在管态下运行。本实验通过内核模块编程在/porc文件系统中实现系统时钟的读操作接口。

2. 实验内容

设计并构建一个在/proc文件系统中的内核模块clock,支持read()操作,read()返回值为 一个字符串,其中包块一个空各分开的两个子串,为别代表xtime.tv_secxtime.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()表示该模块被调用过一次,之后的提示信息表明模块已经被成功卸载。