Linuxカーネルモジュールの拡張

1. 最も単純な Linux カーネル モジュールの挿入と削除

(1) カーネルモジュールの挿入

コマンド 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' を実行します。

(2) カーネルモジュールの削除

sudo rmmod test_hello コマンドを実行してモジュールを削除し、lsmod | grep 'test_hello' コマンドで検索すると、確かにモジュールが見つかりません。

make clean コマンドを実行して、オブジェクト ファイル (拡張子が「.o」のファイル) と、最後の make コマンドで生成された実行可能ファイルをクリアします。ls を実行すると、test_hello.c と Makefile の 2 つのファイルだけが残っていることがわかります。

2. カーネルモジュールでは、カーネル内の最大値と最小値を求めるコードを使用して、最大値と最小値を求めて出力します。

(1) 最大値を求める

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 ファイルは、最初の line.o ファイル名を除いてすべて同じです。これについては以下では説明しません。

操作結果:

ここに画像の説明を挿入します

(2) 最小値を求める

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

操作結果:

ここに画像の説明を挿入します

3. カーネル モジュールで、カーネルが提供する 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 メモリ アロケータのフラグで、メモリ アロケータがとる動作を識別します。

操作結果:

ここに画像の説明を挿入します

……

ここに画像の説明を挿入します

……

ここに画像の説明を挿入します

……

ここに画像の説明を挿入します

……

ここに画像の説明を挿入します

4. カーネルモジュールにパラメータを渡すには、「Linux カーネル実験マニュアル」を参照してください。

追加の知識ポイント:

module_param(名前、タイプ、パーマ)

機能: カーネルモジュールパラメータの受け渡し

パラメータ:

@name 変数名/パラメータ名

@type パラメータのデータ型、short、ushort (符号なし short 整数)、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); //出口点

次の命令を順番に実行します。

作る

//ロード時にパラメータが渡された場合、変数値は渡された値になり、それ以外の場合はデフォルトの初期化値になります。

sudo insmod max.ko x=3 y=4

dmesg

操作結果:

ここに画像の説明を挿入します

5. Makefile には少なくとも 2 つのファイルが必要です。

ここで、最小値を求めるコードを変更し、min.c ファイルを main.c と minnum.c の 2 つのファイルに書き込みます。

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

次に、次の命令を順番に実行します。

作る

sudo insmod min.ko

dmesg

操作結果:

ここに画像の説明を挿入します

6. カーネル モジュールは赤黒ツリー アルゴリズムを使用し、ツリー内の各ノードを出力します (カーネル コードを参照)。

赤黒ツリーは二分探索ツリーであり、ノードの色 (赤または黒) を表すストレージ ビットを各ノードに追加します。赤黒ツリーは、ルートからリーフまでの単純なパス上の各ノードの色を制限することで、他のパスの 2 倍の長さのパスがないことを保証し、したがってほぼバランスが保たれます。

赤黒木の性質:

  1. 各ノードは赤または黒のいずれかです
  2. ルートノードは黒です
  3. 各葉ノードは黒です (葉は空のノードです)
  4. ノードが赤の場合、その子ノードは両方とも黒になります
  5. 各ノードについて、そのノードからすべての子孫ノードへの単純なパスには、同じ黒いノードが含まれます。

Linux カーネルの赤黒ツリー アルゴリズムは、/include/linux/rbtree.h と /lib/rbtree.c の 2 つのファイルで定義されています。

赤黒ツリーノードの定義:

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 を使用して 2 種類のデータを同時に格納することです。1 つは親ノードのアドレスで、もう 1 つはこのノードの色です。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 個しか挿入されず、赤黒ツリーを走査することができませんでした。

Zhang Ruiting の宿題を研究した結果、挿入ノードでのコードの記述に問題があることがわかりましたが、修正後は正常に実行されました。

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