Linux kernel module expansion

1. Insertion and deletion of the simplest Linux kernel module

(1) Insertion of kernel module

Execute the command vi test_hello.c to create the test_hello.c file. The code is as follows:

#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");

Execute the command vi Makefile to create a Makefile file. The code is as follows:

#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	#清理模块

Execute the command make to compile. After compilation, use the ls command to view the current directory, where test_hello.ko is the module we generated.

Then execute the following instructions in sequence:

sudo insmod test_hello.ko //Load kernel module

dmesg //View kernel printing information

The command lsmod displays information about all kernel modules. Here we add parameters to view the newly inserted kernel module test_hello, and execute the command lsmod |grep 'test_hello'

(2) Deletion of kernel modules

Execute the command sudo rmmod test_hello to delete the module. After searching with the lsmod | grep 'test_hello' command, the module is indeed missing.

Execute the command make clean to clear the object files (files with the suffix ".o") and executable files generated by the last make command. Execute ls and you can see that only two files, test_hello.c and Makefile, are left.

2. In the kernel module, the code for finding the maximum and minimum numbers in the kernel is used to find the maximum and minimum numbers and output them.

(1) Find the maximum value

max.c code:

#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"); //许可证

The Makefile files are all the same except for the first line.o file name, which will not be explained below.

operation result:

Insert image description here

(2) Find the minimum value

min.c code:

#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"); //许可证

operation result:

Insert image description here

3. In the kernel module, use the API provided by the kernel to insert 100 nodes into the double-linked list, delete 10 of them, and output the undeleted nodes.

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);

Involving relevant source code:

Insert image description here

Insert image description here

The function of WRITE_ONCE(list->next, list) is to safely point list->next to list!

GFP_KERNEL is the flag of the Linux memory allocator, which identifies the behavior that the memory allocator will take.

operation result:

Insert image description here

……

Insert image description here

……

Insert image description here

……

Insert image description here

……

Insert image description here

4. To pass parameters to the kernel module, refer to the "Linux Kernel Experiment Manual"

Additional knowledge points:

module_param(name, type, perm)

Function: Kernel module parameter passing

parameter:

@name variable name/parameter name

@type parameter data type, short, ushort (unsigned short integer), int, uint, charp (character pointer)

@perm permissions. Generally, we do not need to pass parameters after the module is executed, so the perm permissions are generally set to 0.

Modify the code for finding the maximum value as follows:

#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); //出口点

Execute the following instructions in sequence:

make

//When loading, if a parameter is passed, the variable value is the passed value, otherwise it is the default initialization value

sudo insmod max.ko x=3 y=4

dmesg

operation result:

Insert image description here

5. There must be at least 2 files in the Makefile.

Here, modify the code for finding the minimum value and write the min.c file into two files, main.c and minnum.c.

The main.c code is as follows:

#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"); //许可证

The minnum.c code is as follows:

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

The Makefile code is as follows:

#产生目标文件
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

Then execute the following instructions in sequence:

make

sudo insmod min.ko

dmesg

operation result:

Insert image description here

6. The kernel module uses the red-black tree algorithm and outputs each node in the tree (refer to the kernel code)

​ The red-black tree is a binary search tree. It adds a storage bit to each node to represent the color of the node, which can be RED or BLACK. By constraining the color of each node on any simple path from root to leaf, the red-black tree ensures that no path is twice as long as any other path, and is therefore nearly balanced .

Properties of red-black trees:

  1. Each node is either red or black
  2. The root node is black
  3. Each leaf node is black (leaves are empty nodes)
  4. If a node is red, both of its child nodes are black
  5. For each node, the simple path from that node to all its descendant nodes contains the same black node.

The Linux kernel red-black tree algorithms are defined in two files: /include/linux/rbtree.h and /lib/rbtree.c.

Red-black tree node definition:

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

The clever thing here is to use the member rb_parent_color to store two kinds of data at the same time, one is the address of its parent node, and the other is the coloring of this node. The attribute((aligned(sizeof(long)))) attribute ensures that the first address of each node in the red-black tree is 32-bit aligned (on a 32-bit machine), that is to say, the first address of each node Both bit[1] and bit[0] are 0, so bit[0] can be used to store the color attribute of the node without disturbing the storage of the first address of its parent node.

Pointer to the root node of the red-black tree:

struct rb_root
{
	struct rb_node *rb_node;
};

Initialize new nodes:

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;  //指向新结点
}

Insert function:

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

Delete function:

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

Traversal: The rb_first and rb_next functions can form an in-order traversal, that is, traverse all nodes in the red-black tree in ascending order.

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

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

rb_entry() function: returns the pointer of the structure through the pointer of the structure member.

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

The rbtree.c code is as follows:

#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);

operation result:

Insert image description here

No error was reported during the execution of the command, but only 10 nodes were inserted and the red-black tree could not be traversed. I was puzzled by this.

After studying Zhang Ruiting's homework, I found that there was a problem with the code writing at the insertion node. After modification, it ran successfully:

The rbtree.c code is as follows:

#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");

operation result:

Insert image description here

Guess you like

Origin blog.csdn.net/qq_58538265/article/details/133920336
Recommended