如何实现一个简单的含有输入参数的Hello内核模块,并在其中实现单链表

Linux内核模块编程

这篇文章的目的是使个人:

1.熟悉操作系统内核编程开发环境

2.初步掌握Linux环境下内核模块编写、编译、调试及运行的基本流程

3.熟悉内核编程的基本方法和步骤

编程环境

VMware Workstation Pro + Ubuntu 16.04 LTS

VMware+Ubuntu 16.04 LTS虚拟机安装教程参考:

2021安装Vmware和Ubuntu教程(特详细) - 知乎 (zhihu.com)

其中Ubuntu 16.04 LTS镜像下载参考:

(39条消息) ubuntu 16.04 镜像下载(国内开源镜像站)_ubtun16.04_dongjuexk的博客-CSDN博客

什么是内核模块?

内核模块的全称是动态可加载内核模块(Loadable Kernel Modul,KLM),可以动态载入内核,让它成为内核代码的一部分。一个模块一般由一组函数和数据结构组成。

模块要求

1.Ubuntu 16.04环境下实现含输入参数的Hello内核模块,可以按照输入的打印行数及姓名输出相应的信息。

2.在模块中实现单链表,完成insert、delete、lookup、print操作

如何编写带有输入参数的最简单的Hello World模块

打开终端,创建项目文件夹Linux-expr,命令如下:

1
2
3
mkdir Linux-expr 

cd Linux-expr

创建hello.c文件,文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<linux/init.h>

#include<linux/module.h> //每个模块都要包括的头文件

#include<linux/kernel.h> //使用到了printk()函数进行输出

#include<linux/moduleparam.h>

MODULE_LICENSE("GPL"); //没有指定license会出现error

static char*whom = "world";

static int num = 1; //定义静态变量使变量只被当前文件访问

//传递命令参数 S_IRUGO 指明参数可以被所有人读取

module_param(whom,charp,S_IRUGO);

module_param(num,int,S_IRUGO);

//作为驱动模块的加载函数,将相应的驱动模块hello_init加载到Linux内核中,相当于普通文件的c函数

static int hello_init(void)

{

int i;
for (i=0;i<num;i++){
//只能使用内核里定义好的C函数,printk会根据日志级别将指定信息输出到控制台或日志文件中,KERN_ALERT会输出到控制台
printk(KERN_ALERT "hello,%s\n",whom);
}
return 0;

}
//将已存在的驱动模块hello_exit从内核中卸载
static void hello_exit(void)
{
printk(KERN_ALERT "goodbye,kernel/n");
}

//加载or卸载模块

module_init(hello_init);

module_exit(hello_exit);

// 可选的一些属性

MODULE_AUTHOR("Maplemeo-silence");

MODULE_DESCRIPTION("This is a simple example!/n");

MODULE_VERSION("v1.0");

MODULE_ALIAS("A simplest example");

创建makefile文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
obj-m:=hello.o


CURRENT_PATH :=$(shell pwd)
VERSION_NUM :=$(shell uname -r)
LINUX_PATH :=/usr/src/linux-headers-$(VERSION_NUM)

all :
make -C $(LINUX_PATH) M=$(CURRENT_PATH) modules
clean :
make -C $(LINUX_PATH) M=$(CURRENT_PATH) clean

通过make命令,编译文件:

1
make

可以看到目录下出现以下文件,其中hello.ko文件为我们所需要的内核模块文件:

image-20230510201153770

使用insmod 命令加载模块(使用sudo权限)

1
sudo insmod hello.ko

使用lsmod查看模块是否加载进去,可以看到hello已经可以看到

1
sudo lsmod
image-20230510201738501

使用demesg命令查看输出,可以看到有hello,world的输出

1
sudo demesg
image-20230510201956647

最后使用rmmod 命令卸载模块

1
sudo rmmod hello

如何编在模块中实现单链表

内核模块中实现单链表和在普通c程序中相差不多,值得注意的是,内存分配还是malloc被kmalloc所替换

listnode结构体如下所示,就是最简单的单链表结构:

1
2
3
4
struct listnode{  
int val_; //data
struct listnode* next_;
};

hello.c文件改写为如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/moduleparam.h>
#include <linux/slab.h>


MODULE_LICENSE("GPL");

static char*whom = "world";
static int num = 1;

struct listnode{
int val_;
struct listnode* next_;
};

//插入操作
int insert(int val,struct listnode* head){
if(head == NULL){
printk(KERN_ALERT "the head is null, can not insert\n");
return -1;
}else{
struct listnode* curr;
curr = head;
int num;
num = 0;
while(curr->next_!=NULL){
curr = curr->next_;
num++;
}
struct listnode *new_node;
new_node=kmalloc(sizeof(*new_node),GFP_KERNEL);
new_node->val_ = val;
new_node->next_ = NULL;
curr->next_ = new_node;
printk(KERN_ALERT "insert success!!! the new %d val = %d\n",num,new_node->val_);
return 1;
}
}

//打印操作
void print(struct listnode* head){
if(head == NULL){
printk(KERN_ALERT "the head is null, can not printf\n");
return;
}
int num;
num = 0;
struct listnode* curr;
curr = head;
printk(KERN_ALERT "---------List Print--------\n");
while(curr->next_!=NULL){
curr = curr->next_;
printk(KERN_ALERT "the %d val = %d\n",num,curr->val_);
num++;
}
printk(KERN_ALERT "-------List Print end--------\n");

return;
}

//删除操作
void delete(int val,struct listnode* head){
if(head == NULL){
printk(KERN_ALERT "the head is null, can not delete\n");
return;
}
struct listnode* curr;
curr = head;
int num;
num = 0;
while(curr->next_ != NULL){
struct listnode* prev;
prev = curr;
curr = curr->next_;
if(curr->val_ == val){
prev->next_ = curr->next_;
kfree(curr); //使用kfree,而不是free
printk(KERN_ALERT "delete success!!! the delete %d val = %d\n",num,val);
return 1;
}
num++;
}

printk(KERN_ALERT "delete fail!!! can not find val = %d\n",val);
return -1;
}

//查找操作
void lookup(int val,struct listnode* head){
if(head == NULL){
printk(KERN_ALERT "the head is null, can not delete\n");
return;
}
struct listnode* curr;
curr = head;
int num;
num = 0;
while(curr->next_ != NULL){
curr = curr->next_;
if(curr->val_ == val){
printk(KERN_ALERT "lookup success!!! the lookup %d val = %d\n",num,curr->val_ );
return 1;
}
num++;
}

printk(KERN_ALERT "lookup fail!!! can not lookup val = %d\n",curr->val_);
return -1;
}


//传递命令参数 S_IRUGO 指明参数可以被所有人读取
module_param(whom,charp,S_IRUGO);
module_param(num,int,S_IRUGO);

//程序中必须有下列两个函数
static int hello_init(void)
{
int i;
for (i=0;i<num;i++){
//使用的是printk 不是printf(其是C库函数)
printk(KERN_ALERT "hello,%s\n",whom);
}
//创建头节点
struct listnode *head;
//使用的是kmalloc 不是malloc(其是C库函数)
head=kmalloc(sizeof(*head),GFP_KERNEL);
if(head != NULL){
head->val_ = 0;
head->next_ = NULL;
printk(KERN_ALERT "the head val: %d\n",head->val_);
}
//进行测试操作
print(head); //打印操作
insert(7,head); //插入操作
print(head);
delete(2,head); //删除操作
print(head);
insert(1,head);
insert(5,head);
print(head);
delete(1,head);
lookup(7,head); //查找操作
print(head);
return 0;
}

static void hello_exit(void)
{
printk(KERN_ALERT "goodbye,kernel/n");
}

//加载or卸载模块
module_init(hello_init);
module_exit(hello_exit);
// 可选
MODULE_AUTHOR("Maplemeo-silence");
MODULE_DESCRIPTION("This is a simple example!/n");
MODULE_VERSION("v1.0");
MODULE_ALIAS("A simplest example");

使用make、insmod、demesg、 rmmod命令查看链表是否正常使用,至此整个内核模块实现完成。

image-20230510203504418