一、加载卸载内核模块
1.什么是:Linux?
Linux,全称GNU/Linux,是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX的多用户、多任务、支持多线程和多CPU的操作系统。
2.实验要求
创建内核模块并将其加载到 Linux 内核,完成该项目 Linux 操作系统。要求使用终端应用程序来编译程序,并且还要在命令行中输入命令来管理内核模块。
3.需要设备
ubuntu 20
内核文件simple.c 和Makefile
4.Makefile
Linux内核中Makefile的作用是根据配置的情况,构造出需要编译的源文件列表,然后分别编译,并把目标代码链接到一起,最终形成 linux 内核二进制文件
5.基本命令
which gcc 查看是否安装gcc编译器
mkdir src: 创建一个src目录
cd src 进入src目录
6.加载内核模块步骤
(1)准备工作
将simple.c和Makefile文件复制进虚拟机文件内并打开终端。
查看是否有gcc和make:
ls 查看目录:
报错:还未解压,是一个.zip文件
解压文件
make 编译内核模块:
simple.ko表示已编译的内核模块,接下来将此模块插入 Linux 内核,
输入密码后界面没显示?输入不了密码?这是因为系统的保护,直接输密码后按回车就行。
“Invalid module format" 格式错误?
原因:驱动文件编译的内核版本与当前运行系统的内核版本不一致导致。
-
通过modinfo 查看simple.ko的内核版本vermagic
-
通过uname -r查看系统的内核版本
是差不多的版本啊,百度发现: 在系统的/lib/modules下面有系统自带的内核module,也会有用户自己安装的内核; 我本系统ubuntu内核是3.11.0-15-generic, 安装的是: 3.2.62; 在编译hello.c时,建议使用ubuntu系统自带的内核来编译; 使用下面的命令: make -C /lib/modules/3.11.0-15-generic/build M=pwd
modules 就能顺利编译通过; 后续使用insmod hello.ko时就不会因为版本的问题报错。
dmesg查看内核日志缓冲区信息,看到了loading Module就加载成功了!
7.卸载内核:
删除内核模块需要调用 rmmod 命令(请注意,.ko 后缀是不必要的);
二、内核数据结构
1.要求:
修改内核模块,以便使用内核的链表数据结构。
在模块入口点中,创建一个链表以包含五个 struct birthday 元素。遍历该链表并将其内 容输出到内核日志缓冲区。调用 dmesg 命令以确保在模块加载时,该列表构建正确。
在模块退出点,从链表中删除元素,然后将空闲内存返回到内核。另外,调用 dmesg 命 令以检查在模块卸载时,该列表已被删除
2.编写代码: vim(vi improved),是Linux系统中提供的编辑器,它是vi的增强版本,与vi向上兼容。通常,在LInux中用到的vi实际上是vim,即使输入命令时输入的是vi,但是使用的仍然是vim。
终端输入vim进入vim
vim: 两种模式:命令模式,编辑模式
需要输入 vim simple.c 进入simple文件,进入后看到simple.c的内容代码。现在需要输入 i 进入输入模式。
将下列代码打入simple文件,完成后 按Esc键进入命令模式,输入 “:wq"回车,就会保存并退出vim。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/types.h>//----->list-head
struct birthday{
int day;
int month;
int year;
struct list_head list;
};
static LIST_HEAD(birthday_list);
/* This function is called when the module is loaded. */
int simple_init(void)
{
struct birthday *person1,*person2,*person3,*person4,*person5;
struct birthday *ptr;
person1=kmalloc(sizeof(*person1),GFP_KERNEL);
person1->day=2;
person1->month=8;
person1->year=2003;
INIT_LIST_HEAD(&person1->list);
list_add_tail(&person1->list,&birthday_list);
person2=kmalloc(sizeof(*person2),GFP_KERNEL);
person2->day=3;
person2->month=9;
person2->year=2004;
INIT_LIST_HEAD(&person2->list);
list_add_tail(&person2->list,&birthday_list);
person3=kmalloc(sizeof(*person3),GFP_KERNEL);
person3->day=4;
person3->month=10;
person3->year=2005;
INIT_LIST_HEAD(&person3->list);
list_add_tail(&person3->list,&birthday_list);
person4=kmalloc(sizeof(*person4),GFP_KERNEL);
person4->day=5;
person4->month=11;
person4->year=2006;
INIT_LIST_HEAD(&person4->list);
list_add_tail(&person4->list,&birthday_list);
person5=kmalloc(sizeof(*person5),GFP_KERNEL);
person5->day=6;
person5->month=12;
person5->year=2007;
INIT_LIST_HEAD(&person5->list);
list_add_tail(&person5->list,&birthday_list);
printk(KERN_INFO "Loading Module\n");
list_for_each_entry(ptr,&birthday_list,list){
printk(KERN_INFO "YEAR:%d, Month: %d, Day: %d\n",ptr->year,ptr->month,ptr->day);
}
return 0;//模块入口点函数必须返回一个整数值,0 表示成功,其他值表示失败。
}
/* This function is called when the module is removed. */
void simple_exit(void) {
struct birthday *ptr,*next;
list_for_each_entry_safe(ptr,next,&birthday_list,list){
printk(KERN_INFO"Removing Year: %d, Month: %d, Day: %d\n",ptr->year,ptr->month,ptr->day);
list_del(&ptr->list);
kfree(ptr);
}
printk(KERN_INFO "Removing Module\n");
}
/* Macros for registering module entry and exit points. */
module_init( simple_init );
module_exit( simple_exit );
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Simple Module");
MODULE_AUTHOR("SGG");
代码解释: printk(): 是等价于 printf() 的内核函数,但是它的输出被发送到内核日志缓冲区,其内容可以由 dmesg 命令读取。printf ()和 printk()之间的一个区别是 printk()允许指定一个优先级标志,其具体值在 include 文件中给出。在本例中,优先级是 KERN_INFO,表示这是一个信息性消息。
注意成员 struct list_head list。结构 list_head 的定义位于头文件<中。意思是将所形成的链表嵌入节点。结构 list_head 非常简单,它仅拥有两个成员,next 和 prev, 它们指向列表中的下一个和前一个节点。通过在结构内的嵌入链表成员,linux 可以使用一 组宏函数来管理这个数据结构。
头文件:
这些是Linux内核开发中常用的头文件。它们包含了许多Linux内核中的数据结构、宏定义和函数原型等,可以在Linux内核开发中使用。
<linux/init.h>:包含了Linux内核初始化和清理相关的宏定义和函数原型。
<linux/module.h>:包含了Linux内核模块相关的宏定义和函数原型。
<linux/kernel.h>:包含了Linux内核中常用的宏定义和函数原型,如打印调试信息的printk函数等。 <linux/list.h>:包含了Linux内核中双向链表的数据结构和相关函数。
<linux/slab.h>:包含了Linux内核中的内存分配和释放相关的函数原型,如kmalloc和kfree等。 <linux/types.h>:包含了Linux内核中的基本数据类型的定义,如u8、u16、u32等。 这些头文件都是Linux内核开发中必不可少的,通过它们,可以访问到Linux内核中的许多功能和数据结构。
3.加载卸载内核
打开vim:终端输入vim——进入输入模式:i——退出编辑模式 esc——保存输入:w仅仅保存。 输入:q退出。输入:wq保存并且退出。 :wq 文件名.格式 (退出并且保存)注意要打冒号!
再次编译:
查看日志缓冲区信息:
卸载内核模块