内核部件之内核链表

概述:

    内核链表往往被包含在目标数据结构中,实现通过统一的链表成员可以找到目标数据结构的其他成员来操作.内核链表的存在意义主要是方便、简化软件上的组织和管理.

 

1.内核链表简介

    1-1.定义:

    struct list_head {
    struct list_head *next, *prev;
};

    注:内核链表定义很简单,里面主要包括指向list_head结构的两个指针next和prev,形成一个双链表.

 

    1-2.使用方法

     LINUX内核中,不是在链表结构中包含数据,而是在数据结构中包含链表节点.如下:

     struct net_device{

      ... ...;

      struct list_head dev_list;
      struct list_head napi_list;

     ... ...;

    };

 

    1-3.内核链表的意义

     内核链表,和常用的链表并不一样.它没有数据域,仅作为软件上方便、简化组织管理的一种手段,它的存在可以避免每个目标数据项类型定义自己链表的麻烦.

 

2.链表的的操作接口

    2-1.链表的定义

    链表不会横空出世、无中生有.至少它得先有个头.创建一个内核链表头如下:

    #define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)

 

    static inline void INIT_LIST_HEAD(struct list_head *list)
    {
       list->next = list;
       list->prev = list;
    }

    因此,宏LIST_HEAD(name)定义了一个指向自己的"互撸娃"空链表.依据此可以判断一个链表结构是否为空.

    比如我们要创建一个base_head的链表头.操作如下:

    struct list_head base_head;

    LIST_HEAD_INIT(&base_head);

 

    2-2.判断某链表是否为空

    static inline int list_empty(const struct list_head *head)
    {
         return head->next == head;
    }   

    返回真链表为空,否则链表非空.

     比如我们要判断我们自定义的base_head是否为空.操作如下:

     list_empty(&base_head);

   

    2-3.链表的插入、删除、合并

        2-3-1.链表的插入:

             2-3-1-1.插入表头:   

              static inline void list_add(struct list_head *new, struct list_head *head)
              {
                   __list_add(new, head, head->next);
 

              2-3.1.2.插入表尾

              static inline void list_add_tail(struct list_head *new, struct list_head *head)
              {
                        __list_add(new, head->prev, head);
              }

         比如有一个链表节点my_list要插入base_head的链表头.操作如下:

             list_add(&my_list,&base_head);

        2-3-2.链表的删除:

               static inline void list_del(struct list_head *entry)
               {
                         __list_del(entry->prev, entry->next);
                        entry->next = LIST_POISON1;
                        entry->prev = LIST_POISON2;
               }

               比如我们要删除链表头base_head下的my_list节点时.操作如下:

               list_del(&my_list);

 

        2-3-3. 链表的搬移

        static inline void list_move(struct list_head *list, struct list_head *head)
        {
               __list_del(list->prev, list->next);
               list_add(list, head);
        }

        由函数list_move()源码可知,把节点list从所在的链表头删除,并纳入新链表头head.例如把my_list节点从base_list链表删除并挪到新链表base_list2.

        操作如下:

        list_move(&my_list,&base_list2);

 

        2-3.4. 链表的合并

        static inline void list_splice(const struct list_head *list,
        struct list_head *head)
        {
               if (!list_empty(list))
               __list_splice(list, head, head->next);
        }

        假设有两表头list1和list2,要将两者合并.操作如下:

        list_splice(&list1,&list2);

        图示如下:

       

       将list1挂载到list2上后,原来的list1的next、prev仍然指向原来的节点.为了避免混乱.内核提供了list_splice_init()函数.如下:

       static inline void list_splice_init(struct list_head *list,
        struct list_head *head)
       {
              if (!list_empty(list)) {
              __list_splice(list, head, head->next);
              INIT_LIST_HEAD(list);
             }
        }

        与list_splice()函数相比,只多了INIT_LIST_HEAD()宏.这样做的目的是把被合乎的list的next、prev互指,避免了混乱.

 

3.遍历

      内核链表纯粹是一种软件上的组织手段,对我们对目标数据的操作没有任何意义,其意义在于通过这种组织手段去获取我们的目标数据内容.

上面已经说了,我们的目标数据往往包含了链表节点,而链表又被组织进某个表头.在实际编程操作中,我们要操作的对象是具体的数据内容而不是

链表本身.因此,这分两步走:

      一、找到我们目标数据包含的链表节点;

      二、通过这个链表节点找到我们要操作的目标数据内容.

      上述过程由下面两个函数完成:

      #define list_for_each(pos, head) \
         for (pos = (head)->next; prefetch(pos->next), pos != (head); \
               pos = pos->next)

      其中,pos为struct list_head类型,为暂存我们遍历过程中的某个链表节点.

      #define list_entry(ptr, type, member) \
        container_of(ptr, type, member)

      其中,ptr为struct list_head类型,type为我们具体的目标数据,member为type里面某一成员,member类型必须和ptr一致,这里为struct list_head.

返回值为type类型,即我们获取到了我们要操作的目标数据的内容的首地址.这样一来,我们就可以实现对目标数据内容的操作了.

     示意代码如下:

     我们的目标数据为:

     struct student
     {
         char name[100];
         int num;
        struct list_head list;
    };

    我们要访问目标数据的内容,如name成员.操作如下:

     struct list_head *pos;

     struct student *tmpstudent;

      list_for_each(pos,&student_list)
      {
          tmpstudent = list_entry(pos,struct student,list);
          printk("student%d name is:%s\n",tmpstudent->num,tmpstudent->name);
      }

list_for_each()的作用仅仅是遍历,遍历什么时候结束,这由list_entry()函数决定.因此,这两个函数总是成对出现的.

 

下面附上国嵌关于内核链表操作的一个示意代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/list.h>	/*struct head list*/
#include <linux/kernel.h>	/*KERN_EMERG,KERN_ALERT,DERN_CRIT and so on*/
#include <linux/slab.h>	/*kmalloc and so on*/

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Seven");
MODULE_DESCRIPTION("Show how to use kernel_list");


struct student
{
	char name[100];
	int num;
	struct list_head list;
};

struct list_head student_list;
struct list_head *pos;
struct student *pstudent;
struct student *tmpstudent;

static int __init module_list_init(void)
{
	int i = 0;	

	INIT_LIST_HEAD(&student_list);

	pstudent = kmalloc(sizeof(struct student)*5,GFP_KERNEL);
	memset(pstudent,0,sizeof(struct student)*5);

	for(i = 0; i < 5; ++i)
	{
		sprintf(pstudent[i].name,"student%d",i + 1);
		pstudent[i].num = i + 1;
		list_add(&(pstudent[i].list),&student_list);
	}

	list_for_each(pos,&student_list)
	{
		tmpstudent = list_entry(pos,struct student,list);
		printk("student%d name is:%s\n",tmpstudent->num,tmpstudent->name);
	}

	return 0;
}

static void __exit module_list_exit(void)
{
	int i = 0;
	
	for(i = 0; i < 5; ++i)
		list_del(&(pstudent[i].list));

	kfree(pstudent);
}

module_init(module_list_init);
module_exit(module_list_exit);      

  

     

 

     

      

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值