编写调用hlist的内核模块具体实现及分析

题目内容

分析include/linux/list.h中哈希表的实现,给出分析报告,并编写内核模块,调用其中的函数和宏,实现哈希表的建立和查找

关于头文件中函数、宏、结构的分析

默认已经理解用 数组链表实现哈希表的原理

  1. struct hlist_head,strutc hlist_node
    • head相当于数组,head结构里有一个hlist_node类型的first指针,是链表的头结点
    • node结构里有hlist_node类型的两个指针,其实这个链表是一个双向链表,不用理解怎么实现,有函数可以直接调用
  2. kmalloc,kfree
    • 内核中申请内存和释放内存模块程序
  3. INIT_HLIST_HEAD
    • 一个参数,hlist_head类型
    • 初始化头结点
  4. INIT_HLIST_NODE
    • 一个参数,hlist_node类型
    • 初始化普通结点
  5. hlist_add_head
    • 两个参数,hlist_node类型,hlist_head类型
    • 头插法,将赋好值的node类型结点接到相应head类型中的头结点的后面
  6. hlist_for_each,hlist_for_each_safe
    • 都是循环单链表
    • 循环中要进行删除操作使用safe
    • hlist_for_each,两个参数,hlist_node类型的pos,hlist_head类型的head
      - pos用来暂存遍历中的结点
      - head是你要进入数组中哪个头结点
    • hlist_for_each_safe,三个参数,hlist_node类型的pos,hlist_node类型的n,hlist_head类型的headhead,两个同上
      - 多了一个参数n,因为要进行删除操作,不能影响pos
      - 就按链表删除操作需要两个指针来理解
  7. hlist_entry
    • 三个参数ptr,type,member
    • 指针ptr指向结构体type中的成员member,所以ptr和member的类型是一样的
    • 返回type类型指针
  8. hlist_del
    • 一个参数,hlist_node类型
    • 选中要删除的结点即可

代码流程

  1. 因为node结点需要存key,而node结构只有两个指针,所以另外定义一个结构hlist_data,变量有node和key
  2. head是数组的指针,在初始化中按大小申请内存
  3. 接着为准备好的关键字初始化结点并插入数组对应的位置
  4. 循环打印所有结点的key和地址
  5. 按给出关键字查找,用哈希函数得出key相应的散列地址,根据散列地址就能找到数组中对应的头结点,再遍历链表进行查找
  6. 退出就是删除结点释放内存

代码

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

int init(void);//初始化
int hashFunc(int);//哈希函数
void addAllNodes(int[]);//加入所有关键字结点
void showAllNodes(void);//打印所有关键字结点
int search(int);//根据关键字查询结点地址

//哈希表长度及哈希的余数
#define N 8
//关键字数组长度
#define LENGTH 20
//结点结构体
struct hlist_data{
	int key;
	struct hlist_node hNode;
};
struct hlist_head *head;

//初始化,申请内存
int init(void){
	int index;
	
	head=(struct hlist_head*)kmalloc(sizeof(struct hlist_head)*N,GFP_KERNEL);
	if(!head) return 0;
	printk("\n开始初始化哈希表!\n");
	for(index=0;index<N;index++){
		INIT_HLIST_HEAD(&head[index]);
	}
	return 1;
}

//最简单的哈希函数----求余
int hashFunc(int key){
	return key%N;
}

//插入所有关键字结点
void addAllNodes(int array[]){
	int index;
	int i;
	int hashAddr;//地址;
	struct hlist_data *hd;
	
	for(index=0,i=1;index<LENGTH;index++) {
		//为关键字结点申请内存
		hd=(struct hlist_data*)kmalloc(sizeof(struct hlist_data),GFP_KERNEL);
		INIT_HLIST_NODE(&(hd->hNode));
		hd->key=array[index];
		printk("添加结点%d-----%d!\n",i++,array[index]);
		
		//关键字根据哈希函数得到散列地址
		hashAddr=hashFunc(array[index]);
		hlist_add_head(&(hd->hNode),&(head[hashAddr]));//头插法
	}
}

//遍历打印所有结点
void showAllNodes(void){
	int index;
	struct hlist_data *hd;
	struct hlist_node *pos;
	
	printk("\n遍历打印!\n");
	//循环数组
	for(index=0;index<N;index++){
		printk("%d\n",index);
		//循环单链表
		hlist_for_each(pos,&(head[index])){
			//找出pos指向的链表结点的首地址
			hd=hlist_entry(pos,struct hlist_data,hNode);
			printk("%d(%d)->",hd->key,(int)pos);
		}
		printk("NULL");
		printk("\n");
	}
}

//根据关键字查找结点地址
int search(int searchKey){
	int hashAddr;
	struct hlist_data *hd;
	struct hlist_node *pos;
	
	printk("finding %d!\n",searchKey);
	
	//求出地址
	hashAddr=hashFunc(searchKey);
	printk("根据哈希函数求得地址=%d!\n",hashAddr);
	
	hlist_for_each(pos,&(head[hashAddr])){
		hd=hlist_entry(pos,struct hlist_data,hNode);
		if(hd->key==searchKey) return (int)pos;
	}
	return -1;
}

static int __init hlist_init(void){
	int keys[LENGTH]={1,2,3,4,5,6,7,16,18,20,31,44,47,51,55,60,66,69,72,73};//一组关键字
	int addr;//查找时要使用
	int searchKey;
	
	//初始化
	if(!init()) return -1;
	
	//按照简单的求余作为哈希函数插入结点
	addAllNodes(keys);
	
	//打印所有结点
	showAllNodes();
	
	//给出关键字
	searchKey=19;
	addr=search(searchKey);
	if(addr==-1) printk("Search failed\n\n");
	else printk("找到key为%d的结点地址是%d\n\n",searchKey,addr);
	
	return 0;
}

static void __exit hlist_exit(void){
	int index;
	int i;
	struct hlist_data *hd;
	struct hlist_node *pos,*n;
	for(index=0,i=1;index<N;index++){
		//这里的遍历需要用safe
		hlist_for_each_safe(pos,n,&(head[index])){
			hd=hlist_entry(pos,struct hlist_data,hNode);
			printk("删除结点%d-----%d,地址是%d\n", i++,hd->key,(int)pos);
			hlist_del(pos);
			kfree(hd);
		}
	}
	kfree(head);
	
	printk("删除完成!\n");
}

module_init(hlist_init);
module_exit(hlist_exit);

结果

总结

  1. 第一次进行内核模块的编写,有很多不足的地方
  2. 强制转换有warning,但是不知道怎么去解决
  3. 不要全局声明变量,会有不可重入问题,虽然我执行时没有出现过
  4. kfree时注意释放方法是否正确,因为在内核操作,操作错误会死机
  5. 正确的应该是用户程序调用正在运行的内核模块,不是直接在代码中确定关键字数组、查找关键字之类的
posted @ 2020-10-06 14:03  肥斯大只仔  阅读(466)  评论(0编辑  收藏  举报