编写调用hlist的内核模块具体实现及分析
题目内容
分析include/linux/list.h中哈希表的实现,给出分析报告,并编写内核模块,调用其中的函数和宏,实现哈希表的建立和查找
关于头文件中函数、宏、结构的分析
默认已经理解用 数组链表实现哈希表的原理
- struct hlist_head,strutc hlist_node
- head相当于数组,head结构里有一个hlist_node类型的first指针,是链表的头结点
- node结构里有hlist_node类型的两个指针,其实这个链表是一个双向链表,不用理解怎么实现,有函数可以直接调用
- kmalloc,kfree
- 内核中申请内存和释放内存模块程序
- INIT_HLIST_HEAD
- 一个参数,hlist_head类型
- 初始化头结点
- INIT_HLIST_NODE
- 一个参数,hlist_node类型
- 初始化普通结点
- hlist_add_head
- 两个参数,hlist_node类型,hlist_head类型
- 头插法,将赋好值的node类型结点接到相应head类型中的头结点的后面
- 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
- 就按链表删除操作需要两个指针来理解
- hlist_entry
- 三个参数ptr,type,member
- 指针ptr指向结构体type中的成员member,所以ptr和member的类型是一样的
- 返回type类型指针
- hlist_del
- 一个参数,hlist_node类型
- 选中要删除的结点即可
代码流程
- 因为node结点需要存key,而node结构只有两个指针,所以另外定义一个结构hlist_data,变量有node和key
- head是数组的指针,在初始化中按大小申请内存
- 接着为准备好的关键字初始化结点并插入数组对应的位置
- 循环打印所有结点的key和地址
- 按给出关键字查找,用哈希函数得出key相应的散列地址,根据散列地址就能找到数组中对应的头结点,再遍历链表进行查找
- 退出就是删除结点释放内存
代码
#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);
结果
总结
- 第一次进行内核模块的编写,有很多不足的地方
- 强制转换有warning,但是不知道怎么去解决
- 不要全局声明变量,会有不可重入问题,虽然我执行时没有出现过
- kfree时注意释放方法是否正确,因为在内核操作,操作错误会死机
- 正确的应该是用户程序调用正在运行的内核模块,不是直接在代码中确定关键字数组、查找关键字之类的