操作系统第一次实验
Topic: 操作系统概念验项目1:Linux内核模块
Requirements:
- 掌握Linux内核模块的加载和卸载方法
- 了解内核数据结构的概念和使用方法。
- 学习如何使用内核链接列表数据结构。
Procedure:
实验环境:
为了确保完成本次实验,首先需要准备实验环境,本实验使用Ubuntu系列的Linux虚拟机进行操作。
实验内容:
第一部分 - 创建内核模块:
-
打开终端应用程序,进入Linux虚拟机的命令行界面。
-
使用编辑器创建一个新的C源文件,命名为simple.c,并将以下代码复制到文件中:
// 包含必要的内核头文件
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
// 模块初始化函数
int simple_init(void) {
// 向内核日志输出加载模块信息
printk(KERN_INFO "Loading Module\n");
return 0;
}
// 模块清理函数
void simple_exit(void) {
// 向内核日志输出移除模块信息
printk(KERN_INFO "Removing Module\n");
}
// 定义模块的初始化和清理函数,在加载和卸载模块时会调用这些函数
module_init(simple_init);
module_exit(simple_exit);
// 模块许可证类型声明为GPL
MODULE_LICENSE("GPL");
// 模块描述信息
MODULE_DESCRIPTION("Simple Module");
// 模块作者姓名
MODULE_AUTHOR("ALI");
Makefile文件内容如下:
-
保存并退出编辑器。
-
使用命令make命令编译源代码,生成内核模块文件simple.ko。
编译完后ls命令查看生成的文件
-
使用命令lsmod查看当前加载的内核模块,确认simple模块未加载。
-
使用命令sudo insmod simple.ko将simple模块加载到内核中。
-
使用命令lsmod再次查看已加载的内核模块,确认simple模块已加载。
-
使用命令dmesg查看内核日志缓冲区中的信息,确认模块已加载。
-
使用命令sudo rmmod simple将simple模块从内核中卸载。
-
使用命令dmesg -c清空内核日志缓冲区。
第二部分:使用内核数据结构:
-
定义结构体:首先,我们需要定义一个结构体,其中包含要插入到链表中的元素。在这个实验中,我们定义了一个名为"birthday"的结构体,包含了day、month、year和list_head类型的成员变量。
-
创建链表头:使用LIST_HEAD()宏来创建一个链表头对象,作为链表的引用。
-
创建并初始化结构体实例:使用kmalloc()函数分配内核内存,并创建并初始化一个birthday结构体实例。然后,使用INIT_LIST_HEAD()宏来初始化结构体中的list成员。
-
将元素插入链表:使用list_add_tail()宏将该实例添加到链表的末尾。
-
遍历链表:使用list_for_each_entry()宏来遍历链表,并在每次迭代中对每个元素进行操作。
-
从链表中删除元素:使用list_del()宏从链表中删除元素,并使用kfree()函数释放之前使用kmalloc()分配的内存。
代码如下:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/types.h>
#include <linux/slab.h>
struct birthday {
int day;
int month;
int year;
struct list_head list; // 链表节点,在每个结构体实例中都包含一个list_head类型的成员
};
static LIST_HEAD(birthday_list); // 定义一个链表头,作为链表的入口
int simple_init(void) {
struct birthday *person; // 创建一个birthday结构体指针,用于存储要插入链表的数据
struct birthday *ptr; // 创建一个birthday结构体指针,用于遍历链表
printk(KERN_INFO "Loading Module\n");
// 创建并初始化结构体实例
person = kmalloc(sizeof(*person), GFP_KERNEL); // 分配内存空间来存储结构体实例
person->day = 11; // 设置结构体实例的成员变量值
person->month = 4;
person->year = 2001;
INIT_LIST_HEAD(&person->list); // 初始化结构体实例的list成员,将其设置为空链表
list_add_tail(&person->list, &birthday_list); // 将结构体实例添加到链表的尾部
// 遍历链表并输出到内核日志缓冲区
list_for_each_entry(ptr, &birthday_list, list) { // 遍历整个链表
printk(KERN_INFO "Birthday: %d/%d/%d\n", ptr->day, ptr->month, ptr->year); // 输出每个节点的生日信息到内核日志缓冲区
}
return 0;
}
void simple_exit(void) {
struct birthday *ptr, *next; // 创建两个birthday结构体指针,用于遍历链表和释放内存
printk(KERN_INFO "Removing Module\n");
// 从链表中删除元素并释放内存
list_for_each_entry_safe(ptr, next, &birthday_list, list) { // 安全地遍历整个链表(删除节点时也能正常进行)
list_del(&ptr->list); // 将当前节点从链表中删除
kfree(ptr); // 释放当前节点占用的内存空间
}
}
module_init(simple_init); // 模块加载时调用simple_init函数进行初始化
module_exit(simple_exit); // 模块卸载时调用simple_exit函数进行清理
MODULE_LICENSE("GPL"); // 模块的许可证
MODULE_DESCRIPTION("Simple Module"); // 模块的描述信息
MODULE_AUTHOR("ALI"); // 模块的作者信息
用vim命令上面代码写入到simple.c中,内容如下:
Makefile内容如下:
-
编译和加载模块:使用提供的Makefile编译模块,并使用insmod命令加载模块。
使用insmod加载模块
-
检查内核日志缓冲区:使用dmesg命令查看内核日志缓冲区中的输出,确保链表的构建和操作正确执行。
-
卸载模块:使用rmmod命令卸载模块。
- 检查链表是否被删除:使用dmesg命令检查内核模块卸载后是否已删除链表。
Results:
- 在第一部分的实验中,成功创建了一个简单的内核模块,并通过命令加载和卸载模块。在内核日志缓冲区中可以看到相应的加载和卸载信息。
- 在第二部分的实验中,成功创建了一个包含五个struct birthday元素的链表,并通过遍历链表将内容输出到内核日志缓冲区。在模块退出后,链表中的元素被成功删除,并将内存返回给内核。
实验总结:
- 通过本实验,我了解了操作系统的基本概念和组织结构,以及Linux操作系统的特点、用户界面和常用的shell命令。我还学会了如何创建和加载内核模块,并使用内核数据结构进行操作。
- 通过这个实验,我对操作系统的开发过程有了更深入的了解,也提高了对Linux内核模块的理解和应用能力。这对我今后的学都具有重要意义。