linux内核之hlist哈希链表的应用---C语言代码实现(内核态)

来源:互联网 发布:日本人 二战 知乎 编辑:程序博客网 时间:2024/05/24 02:05

        本来很久前就应该写这篇博文的,但因为工作原因拖到现在。本文是继linux内核之链表操作解析 之后对linux内核中链表和哈希链表的操作宏以及操作函数的一个应用。这是工作中的一段代码(是linux内核网络代码),稍微修改了下。在文章也会穿插些内核编程需要注意的事项

        第一步:是定义数据类型,主要是哈希链表,因为用的是拉链法(也叫数组链表)处理碰撞,所以也可以当作是链表的一个应用。对哈希链表来说除了定义数据项节点外还要定义个哈希表。对数据项解释下,hNode是链表连接的,key是真正的数据。key是个比较复杂的结构体,姑且认为他里面有个整型成员,作为关键字,其他成员不管。注意点:数据项节点中的成员尽量都不要用指针(尤其时hNode成员一定不能用指针),因为:第一、在用container_of()宏获取到数据项节点指针时,数据项节点成员若是指针那获取将会失败,具体的可以查看linux内核之container_of应用。第二、如果定义了指针,当内存分配和销毁时,都要额外的对这个指针操作,多了些麻烦。

        

        第二步:定义好了数据,开始的时候当然是初始化了。

        

        上面是对哈希表的初始化:内存申请之类的。第401行定义了个全局变量,实际是不应该这么做的,因为这是内核环境下,有中断发生,关系到可重入问题。具体的可以看下什么是可重入。这个代码是截取的一段,然后稍作修改的,主要是为了说明怎么使用内核定义好的一些操作宏,所以其他问题就不作过多的考虑。注意点:初始化程序一般都是在驱动加载时,放在模块初始化中执行的,并且只执行一次。还有个就是书写规范,上面用的是linux内核中的规范,可以看下第419行的for循环。最后一点就是函数的参数问题,在内核编程中如果函数的参数是空的,那么不允许空着(用户空间允许就这样空着),应该填void,如果不填会报警告。

        第三步:查找一个节点。

        

        我想这个要多解释下了,首先是第435行代码开始到445行,其实就是查找数据key是属于哈希表中的哪个head下所连接的链表数据项。因为所有的链表都是通过结构体key中的某个成员来哈希得到的。可以不深究,因为没给出key的结构体。第445行,是真正的查找哪个head下的链表,用的取模法。具体的可以查看hlist哈希链表介绍。内核中不能用整除和取余,所以一般通过第445行那种方法来求的,但LEN(模长)一定要是2的n次幂,才有效。然后是对head连接的链表进行遍历,其中用到内核定义好的操作宏:hlist_for_each_entry()来操作(因为公司重写过这个操作宏,所以里面传3个参数就可以)。注意点:除了第445行那种方法在内核中用来求模的方法外,还有点就是所有的变量尽量都定义到函数开始位置,虽然这不是必须的。但这样定义有利于后期维护,如果当一个函数很长时,要查找某个变量是否定义过,只要在函数开始地方查找就可以,不用整个函数一行一行的去查找。

        第四步:增加一个节点

       

        

        这是个增加节点函数,这个比较容易没什么需要注意的。

        第五步:删除任意一个节点

        

        这个函数是删除节点函数,首先把该节点从链表中移除,用内核定义好的宏:hlist_del_init(),然后直接销毁掉这个节点的空间就可以了。

        第六步:删除所有节点

        

        这个函数是删除所有节点的函数,也是比较容易的函数。关键点是在第525行代码,首先是对哈希表的遍历,然后是对每个head连接的链表进行遍历。链表遍历用了内核定义好的操作宏:hlist_for_each_entry_safe(),(重写过这个宏,所以里面只要穿3个参数即可)。注意点:这个函数是释放所有空间的函数,所以该函数应该在驱动模块卸载时调用,应该由扫尾函数来调用。还有个就是内核函数中不存在返回bool类型的,可以用返回整型来代替。

        以上就是对内核定义的一些操作哈希链表的宏和函数的一个简单的应用,代码比较简单,只是为了说明怎么使用这些宏。还有个习惯就是如果内核中有定义好的宏或者函数尽量用它定义好的,不要自己重新再去定义。因为第一省时间,第二有个统一的接口,便于移植和理解。

0 0