Linux 内核中双向链表及list.h 文件分析

在Linux 内核中双向链表有大量的应用。弄清内核中双向链表的使用及常用的操作函数对学习内核至关重要。完全可以对文件稍作修改后,用到用户空间编程中。

list主要定义在内核源码部分的list.h文件中。/usr/src/kernels/$(uname -r) /include/linux/list.h

本文基于内核:2.6.35.14-106.fc14.i686


1、Linux内核实现了一个双向链表的抽象定义:

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

这个链表不含任何数据域,可以嵌入任何结构中,从而形成链表。例如:

struct numlist{
    int num;
    struct list_head list;   
};

注意:一个结构结构中完全可以有多个list域,形成多个相关链表。


2、链表头的初始化

在list.h 对于链表头初始化定了2个宏和一个函数

#define LIST_HEAD_INIT(name) { &(name), &(name) }    /* 仅初始化*/

#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;
}
其实我们看到,头节点初始化的过程就是将next、prev指向自己,形成了一个空链表。


3、在链表中插入一个节点

static inline void list_add(struct list_head *new, struct list_head *head)       /*头插*/

static inline void list_add_tail(struct list_head *new, struct list_head *head)  /*尾插*/

定义了一个内部函数,供list_add()与list_add_tail()调用,这与面向对象中的封装类似,而且简化了外部调用时的参数。

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


4、在链表中删除一个节点

static inline void list_del(struct list_head *entry)(常用)

static inline void list_del(struct list_head *entry)
{
        __list_del(entry->prev, entry->next);   /*也定义了一个内部函数__list_del()*/
        entry->next = LIST_POISON1;
        entry->prev = LIST_POISON2;
}

注意:此处删除并没有将节点 entry 删除,释放。只是将指针next、prev指针指向了固定位置。因为list_head 域只是整个struct的一小部分,所以只是单纯的从链表中摘除,并未对整个数据节点产生影响。


另一个删除节点函数:

static inline void list_del_init(struct list_head *entry)
{
        __list_del(entry->prev, entry->next);
        INIT_LIST_HEAD(entry);
}
该函数删除节点后,并初始化该节点作为链表头。


5、遍历链表

list.h中定义了一个宏list_for_each,来实现链表遍历。

#define list_for_each(pos, head) \
        for (pos = (head)->next; prefetch(pos->next), pos != (head); \
                pos = pos->next)
注意:其中pos  是list_head * 类型,是指向当前节点(list_head部分)的指针,head是链表头的指针。

但是,对于试图删除链表节点时,需要进行安全遍历链表,需要使用另一个宏:list_for_each_safe

/**
 * list_for_each_safe - iterate over a list safe against removal of list entry
 * @pos:        the &struct list_head to use as a loop cursor.
 * @n:          another &struct list_head to use as temporary storage
 * @head:       the head for your list.
 */
#define list_for_each_safe(pos, n, head) \
        for (pos = (head)->next, n = pos->next; pos != (head); \
                pos = n, n = pos->next)
可以以上两个宏的区别是,安全遍历链表宏用一个指针n将将要删除的节点保存下来了。

如果试图用list_for_each来遍历删除,那么当删除pos时,pos->next就丢失了,链就中断了。


6、从指向节点链表域(list_head ),获取整个数据节点,对整个数据节点其他部分进行操作

以上我们都是对一个数据节点的list_head (链表域)部分进行操作。但是获取链表域不是我们本身的目的,我们获取链表域只是为了定位数据节点。那么我们如何通过某个节点的链表域得到该节点的整个起始位置呢?


我们需要由pos位置,找到“节点位置”。

list.h 定义了宏list_entry

/**
 * list_entry - get the struct for this entry
 * @ptr:        the &struct list_head pointer.
 * @type:       the type of the struct this is embedded in.
 * @member:     the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member)  container_of(ptr, type, member)

/*在kernel.h 中定义了container_of*/		
#define container_of(ptr, type, member) ({			\
        const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
        (type *)( (char *)__mptr - offsetof(type,member) );})
注意:type 是数据节点结构体类型。member 是 list _head 域的名字。

分析:container_of :其中offsetof(type,member) 是member在type中的偏移量。((type *)0)->member ) *__mptr = (ptr); __mprt 存放的是ptr的绝对地址。相减就获取了type类型的起始地址。

这样就获取了type 数据结构体的位置,就可以对数据部分进行操作了。


7、链表中节点替换

static inline void list_replace(struct list_head *old,struct list_head *new)    //new 替换old
static inline void list_replace_init(struct list_head *old,truct list_head *new)  //new替换old,并且对old初始化

/**
 * list_replace - replace old entry by new one
 * @old : the element to be replaced
 * @new : the new element to insert
 *
 * If @old was empty, it will be overwritten.
 */
static inline void list_replace(struct list_head *old,struct list_head *new)
{
        new->next = old->next;
        new->next->prev = new;
        new->prev = old->prev;
        new->prev->next = new;
}

static inline void list_replace_init(struct list_head *old,struct list_head *new)
{
        list_replace(old, new);
        INIT_LIST_HEAD(old);
}
注意:在list_repalce 中虽然new节点成功替换了链表中的old 。但是old节点自身pre、next指针任然指向链表中,这或许是一个不安全的因素。如果old是个NULL那么显然会出现问题了。


在来看另外两个相关的函数:删除一个节点,在头尾插入新的节点

static inline void list_move(struct list_head *list, struct list_head *head)      //将“节点lis”t从链表中删除,然后在链表头部插入“节点head”
static inline void list_move_tail(struct list_head *list,struct list_head *head)    //将“节点lis”t从链表中删除,然后在链表尾部插入“节点head”

/**
 * list_move - delete from one list and add as another's head
 * @list: the entry to move
 * @head: the head that will precede our entry
 */
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_tail - delete from one list and add as another's tail
 * @list: the entry to move
 * @head: the head that will follow our entry
 */
static inline void list_move_tail(struct list_head *list,
                                  struct list_head *head)
{
        __list_del(list->prev, list->next);
        list_add_tail(list, head);
}



8、判断一个链表是否为空

static inline int list_empty(const struct list_head *head)  /*为空返回1*/

static inline int list_empty(const struct list_head *head)
{
        return head->next == head;
}
来看另外一个判空函数:

static inline int list_empty_careful(const struct list_head *head)  /*稍稍安全一点,但是局限性*/

/**
 * list_empty_careful - tests whether a list is empty and not being modified
 * @head: the list to test
 *
 * Description:
 * tests whether a list is empty _and_ checks that no other CPU might be
 * in the process of modifying either member (next or prev)
 *
 * NOTE: using list_empty_careful() without synchronization
 * can only be safe if the only activity that can happen
 * to the list entry is list_del_init(). Eg. it cannot be used
 * if another CPU could re-list_add() it.
 */
static inline int list_empty_careful(const struct list_head *head)
{
        struct list_head *next = head->next;
        return (next == head) && (next == head->prev);
}
我们看到这个判空的方法是:检查自己的下一个是否指向自己,并且自己的下一个节点的prev也指向自己。

从注释了解到:该函数主要是为了防止判空的同时另一个cpu正在修改链表,使判空不准确。但是注释也说明,仅当另一个CPU做list_del_init操作时是安全的。其实还是需要加锁。


9、判断节点是否为链表的最后一个节点。

static inline int list_is_last(const struct list_head *list, const struct list_head *head)   /*是最后一个返回1*/

/**
 * list_is_last - tests whether @list is the last entry in list @head
 * @list: the entry to test
 * @head: the head of the list
 */
static inline int list_is_last(const struct list_head *list,
                                const struct list_head *head)
{
        return list->next == head;
}



10、测试链表是否只有一个节点(除头节点外)

static inline int list_is_singular(const struct list_head *head)  /*判断除头结点外,是否只剩一个节点*/

/**
 * list_is_singular - tests whether a list has just one entry.
 * @head: the list to test.
 */
static inline int list_is_singular(const struct list_head *head)
{
        return !list_empty(head) && (head->next == head->prev);
}

11、链表的左旋转

static inline void list_rotate_left(struct list_head *head)  /*将head从头节点旋转到尾节点*/

/**
 * list_rotate_left - rotate the list to the left
 * @head: the head of the list
 */
static inline void list_rotate_left(struct list_head *head)
{
        struct list_head *first;

        if (!list_empty(head)) {
                first = head->next;
                list_move_tail(first, head);
        }
}
看到类似于将一个从左->右的链表,将头节点右半部分“甩”到头节点的左边。这时头结点已然成为了最后一个节点。




后续继续分析

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值