Linux内核模块大扩展

一、一个最简单的Linux内核模块的插入和删除

(一)内核模块的插入

执行指令vi test_hello.c创建test_hello.c文件,代码如下:

#include <linux/init.h>		//init.h包含了宏__init和__exit
#include <linux/module.h>	//module.h头文件包含了对模块的版本控制;
#include <linux/kernel.h>	//kernel.h包含了常用的内核函数;

//模块许可声明
MODULE_LICENSE("Dual BSD/GPL");
//模块加载函数
static int  hello_init(void)
{
//KERN_ALERT为消息打印级别,内核中定义:#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */
	printk(KERN_ALERT "hello,I am here\n");		
	return 0;
}

//模块卸载函数
static void hello_exit(void)
{
	printk(KERN_ALERT "goodbye,kernel\n");
}

//模块注册
module_init(hello_init);	//指明入口点
module_exit(hello_exit);	//指明出口点

//可选
MODULE_AUTHOR("edsionte Wu");
MODULE_DESCRIPTION("This is a simple example!\n");
MODULE_ALIAS("A simplest example");

执行指令vi Makefile创建Makefile文件,代码如下:

#obj-m表示把文件test_hello.o作为"模块"进行编译,不会编译到内核,但是会生成一个独立的 "test_hello.ko" 文件
obj-m += test_hello.o

CURRENT_PATH:=$(shell pwd)	#模块所在的当前所在路径
LINUX_KERNEL:=$(shell uname -r)	#linux内核代码的当前版本
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)	#linux内核的当前版本源码路径

all:
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules	#编译模块
#				内核的路径		  当前目录编译完放哪   表明编译的是内核模块

clean:
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean	#清理模块

执行指令make进行编译,编译后,使用ls命令查看当前目录,其中test_hello.ko就是我们生成的模块

然后依次执行如下指令:

sudo insmod test_hello.ko //加载内核模块

dmesg //查看内核打印信息

指令lsmod是显示所有内核模块的信息,这里我们加上参数查看刚插入的内核模块test_hello,执行指令lsmod |grep ‘test_hello’

(二)内核模块的删除

执行指令sudo rmmod test_hello进行模块的删除,经lsmod |grep ‘test_hello’指令查找也确实没有了这个模块

执行指令make clean清除上次的make命令所产生的object文件(后缀为“.o”的文件)及可执行文件,执行ls可以看到只剩下了test_hello.c和Makefile两个文件

二、内核模块中,采用内核中的求最大数和最小数的代码求最大数和最小数,并输出。

(一)求最大值

max.c代码:

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>

/**
* 模块的初始化函数,模块的入口函数,加载模块,需超级用户权限
*/
static int __init lk_maxnum(void)
{
        int x = 1, y = 2;
        printk("max=%d\n", max(x++, y++));
        printk("x = %d, y = %d\n", x, y);
        return 0;
}

/**
* 出口函数,卸载模块,需超级用户权限
*/
static void __exit lk_exit(void)
{
        printk("The maxnum moudle has exited!\n");
}

module_init(lk_maxnum); //内核入口点,调用初始化函数,包含在module.h中
module_exit(lk_exit); //出口点
MODULE_LICENSE("GPL"); //许可证

Makefile文件除了第一行.o文件名不同外其他的都是相同的,下面不再做说明

运行结果:

在这里插入图片描述

(二)求最小值

min.c代码:

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>

/**
* 模块的初始化函数,模块的入口函数,加载模块,需超级用户权限
*/
static int __init lk_minnum(void)
{
        int x = 1, y = 2;
        printk("min=%d\n", min(x++, y++));
        printk("x = %d, y = %d\n", x, y);
        return 0;
}

/**
* 出口函数,卸载模块,需超级用户权限
*/
static void __exit lk_exit(void)
{
        printk("The minnum moudle has exited!\n");
}

module_init(lk_minnum); //内核入口点,调用初始化函数,包含在module.h中
module_exit(lk_exit); //出口点
MODULE_LICENSE("GPL"); //许可证

运行结果:

在这里插入图片描述

三、内核模块中,利用内核提供的API,在双链表中插入100个节点,并删除其中的10个节点,把未删除的节点输出。

do_list.c

#include <linux/kernel.h> 
#include <linux/module.h> 
#include <linux/slab.h> 
#include <linux/list.h>
MODULE_LICENSE("GPL");

#define N 100		//链表节点数

struct numlist {
int num;			//数据
struct list_head list;	//指向双链表前后节点的指针 
};

struct numlist numhead;		//头节点

static int __init doublelist_init(void) 
{	
	//初始化头节点 
	struct numlist *listnode;	//每次申请链表节点时所用的指针 
	struct list_head *pos; 
	struct numlist *p; 
	int i;
    
    printk("doublelist is starting...\n"); 
    INIT_LIST_HEAD(&numhead.list);
    
    //建立 100 个节点,依次加入到链表当中 
    for (i = 0; i < N; i++) { 
    	listnode = (struct numlist *)kmalloc(sizeof(struct numlist), GFP_KERNEL); 
    	listnode->num = i+1;
        list_add_tail(&listnode->list, &numhead.list);
        printk("Node %d has added to the doublelist...\n", i+1); 
	}
     
    //依次删除其中的10个节点
	struct list_head *n; 
    i = 1; 
    list_for_each_safe(pos, n, &numhead.list) { 	//为了安全删除节点而进行的遍历 
    	if(i<=10){
    		list_del(pos);								//从双链表中删除当前节点 
    		p= list_entry(pos, struct numlist, list);	//得到当前数据节点的首地址,即指针 
    		kfree(p);									//释放该数据节点所占空间 
    		printk("Node %d has removed from the doublelist...\n", i++);
    	}
    	if(i==11) {break;}
	}
     
     
	//遍历链表,把未删除的节点输出。
	i = 1; 
	list_for_each(pos, &numhead.list){
    	p = list_entry(pos, struct numlist, list); 
    	printk("Node %d's data:%d\n", i, p->num);
        i++; 
   	}
   	
    return 0;
}

static void __exit doublelist_exit(void)
{
	printk("doublelist is exiting !\n");
}

module_init(doublelist_init); 
module_exit(doublelist_exit);

涉及相关源代码:

在这里插入图片描述

在这里插入图片描述

WRITE_ONCE(list->next, list)的作用就是安全地将list->next指向list!

GFP_KERNEL是linux内存分配器的标志,标识着内存分配器将要采取的行为。

运行结果:

在这里插入图片描述

……

在这里插入图片描述

……

在这里插入图片描述

……

在这里插入图片描述

……

在这里插入图片描述

四、内核模块传递参数,参考《Linux内核实验手册》

知识点补充:

module_param(name, type, perm)

功能:内核模块传参

参数:

@name 变量名/传参名

@type 参数的数据类型,short ,ushort(无符号短整型),int ,uint ,charp(字符指针)

@perm 权限,一般情况下,我们不需要在模块执行以后,进行参数传递,所以perm权限一般设置成0

对求最大值代码进行下修改,代码如下:

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>

/**
* 模块的初始化函数,模块的入口函数,加载模块,需超级用户权限
*/
int x=1,y=2;
module_param(x,int,0);
module_param(y,int,0);
static int __init lk_maxnum(void)
{
        printk("max=%d\n", max(x++, y++));
        printk("x = %d, y = %d\n", x, y);
        return 0;
}

/**
* 出口函数,卸载模块,需超级用户权限
*/
static void __exit lk_exit(void)
{
        printk("The maxnum moudle has exited!\n");
}

MODULE_LICENSE("GPL"); //许可证
module_init(lk_maxnum); //内核入口点,调用初始化函数,包含在module.h中
module_exit(lk_exit); //出口点

依次执行如下指令:

make

//加载的时候,如果传递参数,则变量值就是传递过来的值,否则就是默认的初始化值

sudo insmod max.ko x=3 y=4

dmesg

运行结果:

在这里插入图片描述

五、Makefile文件中,至少有2个以上的文件

这里修改一下求最小值的代码 ,把min.c文件写成两个文件main.c和minnum.c

main.c代码如下:

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>

extern int minnum(int x,int y);		//extern表明变量或者函数是定义在其他其他文件中的
/**
* 模块的初始化函数,模块的入口函数,加载模块,需超级用户权限
*/
static int __init lk_minnum(void)
{
        int x = 1, y = 2;
		printk("x = %d, y = %d\n", x, y);
        printk("minnum=%d\n", minnum(x, y));
        return 0;
}

/**
* 出口函数,卸载模块,需超级用户权限
*/
static void __exit lk_exit(void)
{
        printk("The minnum moudle has exited!\n");
}

module_init(lk_minnum); //内核入口点,调用初始化函数,包含在module.h中
module_exit(lk_exit); //出口点
MODULE_LICENSE("GPL"); //许可证

minnum.c代码如下:

int minnum(int x,int y)
{
	if(x>y) return y;
	return x;
}

Makefile代码如下:

#产生目标文件
obj-m:=min.o
min-objs :=main.o minnum.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

然后依次执行如下指令:

make

sudo insmod min.ko

dmesg

运行结果:

在这里插入图片描述

六、内核模块中采用红黑树算法,并输出树中的每个节点(参考内核代码)

​ 红黑树是一颗二叉搜索树,它在每个节点上增加了一个存储位表示节点的颜色,可以是RED或BLACK。通过对任何一条从根到叶子的简单路径上各个节点的颜色进行约束,红黑树确保没有路径会比其他路径长出2倍,因而是近乎平衡的

红黑树性质:

  1. 每个节点或是红色,或是黑色
  2. 根节点是黑色
  3. 每个叶子节点是黑色的(叶子是空节点)
  4. 如果一个节点是红色的,则它的两个子节点都是黑色的
  5. 对每个节点,从该结点到其所有后代结点的简单路径上,均包含相同的黑色结点

Linux内核红黑树的算法都定义在/include/linux/rbtree.h和/lib/rbtree.c两个文件中。

红黑树节点定义:

struct rb_node {
	unsigned long  __rb_parent_color;
	struct rb_node *rb_right;
	struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));

这里的巧妙之处是使用成员rb_parent_color同时存储两种数据,一是其双亲结点的地址,另一是此结点的着色。attribute((aligned(sizeof(long))))属性保证了红黑树中的每个结点的首地址都是32位对齐的(在32位机上),也就是说每个结点首地址的bit[1]和bit[0]都是0,因此就可以使用bit[0]来存储结点的颜色属性而不干扰到其双亲结点首地址的存储。

指向红黑树根结点的指针:

struct rb_root
{
	struct rb_node *rb_node;
};

初始化新结点:

static inline void rb_link_node(struct rb_node * node, struct rb_node * parent,
				struct rb_node ** rb_link)
{
	node->rb_parent_color = (unsigned long )parent;   //设置其双亲结点的首地址(根结点的双亲结点为NULL),且颜色属性设为黑色
	node->rb_left = node->rb_right = NULL;   //初始化新结点的左右子树
 
	*rb_link = node;  //指向新结点
}

插入函数:

extern void rb_insert_color(struct rb_node *, struct rb_root *);

删除函数:

extern void rb_erase(struct rb_node *, struct rb_root *);

遍历:rb_first和rb_next函数可组成中序遍历,即以升序遍历红黑树中的所有结点。

struct rb_node *rb_first(const struct rb_root *root)
{
	······
}

struct rb_node *rb_next(const struct rb_node *node)
{
	······
}

rb_entry()函数:通过结构体成员的指针来返回结构体的指针。

#define	rb_entry(ptr, type, member) container_of(ptr, type, member)

rbtree.c代码如下:

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include <linux/rbtree.h>
#include <linux/slab.h>
MODULE_LICENSE("GPL");

struct mytype {
    struct rb_node mynode;
    int num;
};

static int __init rbtree_init(void)
{
	struct rb_root root = RB_ROOT;
	struct mytype *newnode;
	struct rb_node *pos;
	int i;
	
	//插入10个节点
	for(i=0;i<10;i++)
	{
		newnode=(struct mytype *)kmalloc(sizeof(struct mytype), GFP_KERNEL);
		newnode->num=i+1;
		rb_insert_color(&newnode->mynode,&root);
		printk("Node %d has added to the rb_tree...\n", i+1);
	}
	
	//遍历红黑树
	for (pos = rb_first(&root); pos; pos = rb_next(pos))
		printk("Node's data:%d\n", rb_entry(pos, struct mytype, mynode)->num);
	
	return 0;
	
}

static void __exit rbtree_exit(void)
{
	printk("rbtree is exiting !\n");
}

module_init(rbtree_init); 
module_exit(rbtree_exit);

运行结果:

在这里插入图片描述

运行指令期间未见报错,但仅实现了插入10个节点,未能遍历红黑树,对此表示不解。

经学习张瑞婷的作业,发现在插入节点处代码编写的有问题,经修改成功运行:

rbtree.c代码如下:

#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include <linux/rbtree.h>
#include <linux/slab.h>

struct mytype {
	struct rb_node node;
	int key;
};

static void print_rbtree(struct rb_root *tree)
{
    struct rb_node *tmp;
    
    for(tmp = rb_first(tree); tmp; tmp = rb_next(tmp))
    	printk("%d ", rb_entry(tmp, struct mytype, node)->key);
}
static int my_insert(struct rb_root *root, struct mytype *data)
{
	struct rb_node **new = &(root->rb_node), *parent = NULL;
	while(*new) {
		struct mytype *this = container_of(*new, struct mytype,node);
		int result = data->key-this->key;

		parent = *new;
		if(result < 0) {
			new = &((*new)->rb_left);
		} else if(result > 0) {
			new = &((*new)->rb_right);
		} else {
			return 0;
		}
	}

	rb_link_node(&data->node, parent, new);
	rb_insert_color(&data->node, root);

	return 1;
}

static int __init lkm_init(void)
{
	struct rb_root mytree = RB_ROOT;
	struct mytype *tmp;

	int i;
	printk("order of data inputing:\n");
	for(i=1;i<=9;i++)
	{
		tmp=kmalloc(sizeof(struct mytype),GFP_KERNEL);
		if(i%2==1)
			tmp->key=12+i;
		else
			tmp->key=12-i;
		my_insert(&mytree,tmp);
		printk("%d ",tmp->key);
	}
	printk("\nred-balck tree is:\n");
	print_rbtree(&mytree);
	printk("\n");

	return 0;
}

static  void __exit lkm_exit(void)
{
	printk("Goodbye\n");
}

module_init(lkm_init);
module_exit(lkm_exit);

MODULE_LICENSE("GPL");

运行结果:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_58538265/article/details/133920336