实验要求
设计、实现Windows/Linux内核驱动
实验思路
先简单的写一个Hello World的内核模块代码,进行运行,而后再深入一点,尝试在内核编程中读取文件。
实验环境
使用的虚拟机:VMware16中的Ubuntu2020
使用的操作系统:Linux操作系统
使用的语言:C语言[gcc (Ubuntu 11.1.0-1uUbuntu1-20.04) 11.1.0]
使用的内核版本:Linux ubuntu2020 5.15.0-69-generic
内核编程的基本结构
一个Linux内核模块主要由如下几个部分组成:
(1)模块加载函数(一般需要)
当通过insmod或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。
(2)模块卸载函数(一般需要)
当通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块卸载函数相反的功能。
(3)模块许可证声明(必须)
许可证(LICENSE)声明描述内核模块的许可权限,如果不声明LICENSE,模块被加载时,将收到内核被污染 (kernel tainted)的警告。在Linux 2.6内核中,可接受的LICENSE包括“GPL”、“GPL v2”、“GPL and additional rights”、“Dual BSD/GPL”、“Dual MPL/GPL”和“Proprietary”。大多数情况下,内核模块应遵循GPL兼容许可权。Linux 2.6内核模块最常见的是以MODULE_LICENSE( “Dual BSD/GPL” )语句声明模块采用BSD/GPL双LICENSE。
(4)模块参数(可选)
模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量。
(5) 模块导出符号(可选)
内核模块可以导出符号(symbol,对应于函数或变量),这样其它模块可以使用本模块中的变量或函数。
(6)模块作者等信息声明(可选)
用于申明模块作者的相关信息,一般用于备注作者姓名、邮箱等。
实验过程中遇到的问题
这次实验遇到的问题主要就是内核编程中读取文件不能和平常在用户态读取文件的方式一样。在用户态,读写文件可以通过read和write这两个系统调用来完成(C库函数实际上是对系统调用的封装)。 但是,在内核态没有这样的系统调用。
在内核空间中,使用的读取文件的函数为:filp_open()、filp_close()、vfs_read()、vfs_write()(或kernel_read()和kernel_write())。因此要用这些函数进行文件的读取。
测试结果
首先运行HelloWorld模块的代码
使用make命令编译文件
如上图,可知成功编译并生成了内核模块HelloWorld.ko
下图是执行make命令后,文件夹中的文件
下面加载模块并查看输出(内核模块的打印函数是printk,且printk函数是输出到日志(控制台)的,需要进入管理员权限进行查看)
加载HelloWorld.ko模块并使用lsmod命令查看已加载的模块:
由上图可以看到,已经成功加载了模块HelloWorld,大小是16384。
进入管理员权限查看控制台信息(使用sudo su 命令进入管理员权限,dmesg命令查看控制台消息)
接下来运行第二版的代码(读取文件)
编写完代码后,使用make命令进行编译,然后加载模块并查看模块是否加载
可以看到,成功进行了编译并加载了模块(因为模块名称没有改,所以还是HelloWorld)
进入管理员权限查看控制条信息:
如图所示,成功读取文件,图中的“This is test message!”就是从文件中读取出来的
读取的文件的文件名是“test.txt”
如下图,可以看到文件夹中已经出现了test.txt文件,因为是在内核空间中创建的文件,所以上了锁,需要管理员权限才能进行修改。
打开test.txt文件查看其中的内容:
可以看到,文件test.txt确实写入了“This is test message!”,并且和读取的信息一样。
说明成功程序成功在内核空间中进行文件的写入和读取。
源代码
本次实验共有两个版本的代码,第一个版本的代码是简单的输出HelloWorld,第二版本的代码是在内核空间中进行文件读取。此外,一个Makefile工程文件。
第一版的代码:(输出HelloWorld)
#include <linux/init.h>
#include <linux/module.h>
static int hello_init(void)//加载函数
{
printk(KERN_INFO " Hello World enter\n");
return 0;
}
static void hello_exit(void)//卸载函数
{
printk(KERN_INFO " Hello World exit\n ");
}
module_init(hello_init);//声明模块的加载函数
module_exit(hello_exit);//声明模块的卸载函数
MODULE_LICENSE("Dual BSD/GPL");//模块许可证声明
MODULE_AUTHOR("XIE YUCHEN");//声明作者
MODULE_DESCRIPTION("A simple Hello World Module");//声明模块的描述
MODULE_ALIAS("a simplest module");//声明模块的别名
第二版的代码:(读取文件)
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/uaccess.h>
#define MY_FILE "test.txt"
char buf[256];
loff_t pos = 0;
struct file* filep = NULL;
static int hello_init(void)//加载函数
{
printk(KERN_INFO " Hello World enter\n");
printk("Hello, I'm the module that intends to write messages to file.\n");
if (filep == NULL) {//如果当前文件指针为空,那么进行文件读取,如果文件不存在,会进行创建
filep = filp_open(MY_FILE, O_RDWR | O_APPEND | O_CREAT, 0777);
}
if (IS_ERR(filep)) {//判断文件是否读取成功
printk("Open file %s error\n", MY_FILE);
return -1;
}
sprintf(buf, "%s\n", "This is test message!");//构造输入文件的字符串
memset(buf, 0, sizeof(buf));
kernel_write(filep, buf, strlen(buf), &pos);//将字符串写入文件中
memset(buf, 0, sizeof(buf));
kernel_read(filep, buf, sizeof(buf), &pos);//从文件中读取字符串,后面进行输出
printk("Read buf -> %s\n", buf);
return 0;
}
static void hello_exit(void)//卸载函数
{
printk(KERN_INFO " Hello World exit\n ");
}
module_init(hello_init);//声明模块的加载函数
module_exit(hello_exit);//声明模块的卸载函数
MODULE_LICENSE("Dual BSD/GPL");//模块许可证声明
MODULE_AUTHOR("XIE YUCHEN");//声明作者
MODULE_DESCRIPTION("A simple Hello World Module");//声明模块的描述
MODULE_ALIAS("a simplest module");//声明模块的别名
Makefile工程文件中的代码:
# Makefile 4.0
obj-m := HelloWorld.o
CURRENT_PATH := $(shell pwd)
LINUX_KERNEL := $(shell uname -r)
LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean