Erweiterung des Linux-Kernelmoduls

1. Einfügen und Löschen des einfachsten Linux-Kernelmoduls

(1) Einfügen des Kernelmoduls

Führen Sie den Befehl vi test_hello.c aus, um die Datei test_hello.c zu erstellen. Der Code lautet wie folgt:

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

Führen Sie den Befehl vi Makefile aus, um eine Makefile-Datei zu erstellen. Der Code lautet wie folgt:

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

Führen Sie zum Kompilieren den Befehl make aus. Verwenden Sie nach der Kompilierung den Befehl ls, um das aktuelle Verzeichnis anzuzeigen, in dem test_hello.ko das von uns generierte Modul ist.

Führen Sie dann nacheinander die folgenden Anweisungen aus:

sudo insmod test_hello.ko //Kernelmodul laden

dmesg //Kernel-Druckinformationen anzeigen

Der Befehl lsmod zeigt Informationen zu allen Kernelmodulen an. Hier fügen wir Parameter hinzu, um das neu eingefügte Kernelmodul test_hello anzuzeigen, und führen den Befehl lsmod |grep 'test_hello' aus.

(2) Löschen von Kernelmodulen

Führen Sie den Befehl sudo rmmod test_hello aus, um das Modul zu löschen. Nach der Suche mit dem Befehl lsmod | grep 'test_hello' fehlt das Modul tatsächlich.

Führen Sie den Befehl make clean aus, um die Objektdateien (Dateien mit dem Suffix „.o“) und ausführbaren Dateien zu löschen, die durch den letzten make-Befehl generiert wurden. Führen Sie ls aus und Sie können sehen, dass nur noch zwei Dateien, test_hello.c und Makefile, übrig sind.

2. Im Kernelmodul wird der Code zum Ermitteln der maximalen und minimalen Zahlen im Kernel verwendet, um die maximalen und minimalen Zahlen zu ermitteln und auszugeben.

(1) Finden Sie den Maximalwert

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

Die Makefile-Dateien sind alle gleich, mit Ausnahme des Dateinamens in der ersten Zeile.o, der im Folgenden nicht erläutert wird.

Operationsergebnis:

Fügen Sie hier eine Bildbeschreibung ein

(2) Finden Sie den Mindestwert

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

Operationsergebnis:

Fügen Sie hier eine Bildbeschreibung ein

3. Verwenden Sie im Kernelmodul die vom Kernel bereitgestellte API, um 100 Knoten in die doppelt verknüpfte Liste einzufügen, 10 davon zu löschen und die nicht gelöschten Knoten auszugeben.

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

Einschließlich relevanter Quellcode:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Die Funktion von WRITE_ONCE(list->next, list) besteht darin, list->next sicher auf list zu verweisen!

GFP_KERNEL ist das Flag des Linux-Speicherzuweisers, das das Verhalten des Speicherzuweisers angibt.

Operationsergebnis:

Fügen Sie hier eine Bildbeschreibung ein

……

Fügen Sie hier eine Bildbeschreibung ein

……

Fügen Sie hier eine Bildbeschreibung ein

……

Fügen Sie hier eine Bildbeschreibung ein

……

Fügen Sie hier eine Bildbeschreibung ein

4. Informationen zum Übergeben von Parametern an das Kernelmodul finden Sie im „Linux Kernel Experiment Manual“.

Zusätzliche Wissenspunkte:

module_param(name, type, perm)

Funktion: Übergabe von Kernel-Modulparametern

Parameter:

@name Variablenname/Parametername

@type-Parameterdatentyp, short, ushort (vorzeichenlose kurze Ganzzahl), int, uint, charp (Zeichenzeiger)

@perm-Berechtigungen. Im Allgemeinen müssen wir nach der Ausführung des Moduls keine Parameter übergeben, daher werden die Perm-Berechtigungen im Allgemeinen auf 0 gesetzt.

Ändern Sie den Code zum Ermitteln des Maximalwerts wie folgt:

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

Führen Sie die folgenden Anweisungen nacheinander aus:

machen

// Wenn beim Laden ein Parameter übergeben wird, ist der Variablenwert der übergebene Wert, andernfalls ist es der Standardinitialisierungswert

sudo insmod max.ko x=3 y=4

dmesg

Operationsergebnis:

Fügen Sie hier eine Bildbeschreibung ein

5. Es müssen mindestens 2 Dateien im Makefile vorhanden sein.

Ändern Sie hier den Code zum Ermitteln des Mindestwerts und schreiben Sie die Datei min.c in zwei Dateien, main.c und minnum.c.

Der main.c-Code lautet wie folgt:

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

Der minnum.c-Code lautet wie folgt:

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

Der Makefile-Code lautet wie folgt:

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

Führen Sie dann nacheinander die folgenden Anweisungen aus:

machen

sudo insmod min.ko

dmesg

Operationsergebnis:

Fügen Sie hier eine Bildbeschreibung ein

6. Das Kernelmodul verwendet den Rot-Schwarz-Baum-Algorithmus und gibt jeden Knoten im Baum aus (siehe Kernel-Code).

​ Der Rot-Schwarz-Baum ist ein binärer Suchbaum. Er fügt jedem Knoten ein Speicherbit hinzu, um die Farbe des Knotens darzustellen, die ROT oder SCHWARZ sein kann. Durch die Einschränkung der Farbe jedes Knotens auf jedem einfachen Pfad von der Wurzel zum Blatt stellt der Rot-Schwarz-Baum sicher, dass kein Pfad doppelt so lang ist wie jeder andere Pfad und daher nahezu ausgeglichen ist .

Eigenschaften rotschwarzer Bäume:

  1. Jeder Knoten ist entweder rot oder schwarz
  2. Der Wurzelknoten ist schwarz
  3. Jeder Blattknoten ist schwarz (Blätter sind leere Knoten)
  4. Wenn ein Knoten rot ist, sind beide untergeordneten Knoten schwarz
  5. Für jeden Knoten enthält der einfache Pfad von diesem Knoten zu allen seinen Nachkommenknoten denselben schwarzen Knoten.

Die Rot-Schwarz-Baum-Algorithmen des Linux-Kernels sind in zwei Dateien definiert: /include/linux/rbtree.h und /lib/rbtree.c.

Definition des rot-schwarzen Baumknotens:

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

Das Schlaue hier ist, das Mitglied rb_parent_color zu verwenden, um zwei Arten von Daten gleichzeitig zu speichern, eine ist die Adresse seines übergeordneten Knotens und die andere ist die Farbe dieses Knotens. Das Attribut attribute((aligned(sizeof(long)))) stellt sicher, dass die erste Adresse jedes Knotens im rot-schwarzen Baum 32-Bit-ausgerichtet ist (auf einem 32-Bit-Computer), also die erste Adresse Sowohl Bit[1] als auch Bit[0] jedes Knotens sind 0, sodass Bit[0] zum Speichern des Farbattributs des Knotens verwendet werden kann, ohne die Speicherung der ersten Adresse seines übergeordneten Knotens zu stören.

Zeiger auf den Wurzelknoten des rot-schwarzen Baums:

struct rb_root
{
	struct rb_node *rb_node;
};

Neue Knoten initialisieren:

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

Funktion einfügen:

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

Löschfunktion:

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

Durchquerung: Die Funktionen rb_first und rb_next können eine Durchquerung in der richtigen Reihenfolge bilden, dh alle Knoten im rot-schwarzen Baum in aufsteigender Reihenfolge durchqueren.

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

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

Funktion rb_entry(): Gibt den Zeiger der Struktur über den Zeiger des Strukturmitglieds zurück.

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

Der rbtree.c-Code lautet wie folgt:

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

Operationsergebnis:

Fügen Sie hier eine Bildbeschreibung ein

Während der Ausführung des Befehls wurde kein Fehler gemeldet, aber es wurden nur 10 Knoten eingefügt und der rot-schwarze Baum konnte nicht durchlaufen werden. Das hat mich verwirrt.

Nachdem ich die Hausaufgaben von Zhang Ruiting studiert hatte, stellte ich fest, dass beim Schreiben des Codes am Einfügungsknoten ein Problem aufgetreten war. Nach der Änderung wurde es erfolgreich ausgeführt:

Der rbtree.c-Code lautet wie folgt:

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

Operationsergebnis:

Fügen Sie hier eine Bildbeschreibung ein

Supongo que te gusta

Origin blog.csdn.net/qq_58538265/article/details/133920336
Recomendado
Clasificación