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 倍の長さのパスがないことを保証し、したがってほぼバランスが保たれます。
赤黒木の性質:
- 各ノードは赤または黒のいずれかです
- ルートノードは黒です
- 各葉ノードは黒です (葉は空のノードです)
- ノードが赤の場合、その子ノードは両方とも黒になります
- 各ノードについて、そのノードからすべての子孫ノードへの単純なパスには、同じ黒いノードが含まれます。
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");
操作結果: