内核链表 list.h

记录一下整理的关于内核链表相关函数内容

一、双指针双向循环链表(list)

1. 基本原理

struct list_head
{
    struct list_head* pPre;
    struct list_head* pNext;
};

typedef struct _STU
{
    char cSex;
    char szAddress[128];
    int iNumber;
    int iFlag;
    struct list_head mylist;
}STU;

typedef struct _TEACH
{
    char cSex;
    int iNumber;
    int iFlag;
    struct list_head mylist;
}TEACH;

如程序所示,我们有结构体STU和结构体TEACH,相较于一般链表的结点类型和大小是固定的情况,内核链表的结点可以是不同类型,使用的时候可以根据 iFlag 来判断结点类型,遍历的时候可以通过计算偏移量来得到结点的首地址。为了方便演示,之后的程序说明全是用同一类型结构体STU。

2. 初始化及链表判断

1 对于链表头的初始化

/*
用于第一个结点的初始化,使两个字段都指向自己
相当于
name.pPre = &name;
name.pNext = &name;
*/
#define LIST_HEAD_INIT(name)    {&(name), &(name)}

/*
创建一个struct list_head 结构体 并初始化
name: 需要定义的链表头的名字
*/
#define LIST_HEAD(name)       \
        struct list_head name = LIST_HEAD_INIT(name)

2 对于结点的初始化

// 初始化结点,使字段都指向自己
#define INIT_LIST_HEAD(ptr) \
do                          \
{                           \
    ptr->pPre = ptr;        \
    ptr->pNext = ptr;       \
} while (0);

3 判断链表是否为空

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

/*
基本的list_empty()仅以头指针的pNext是否指向自己来判断链表是否为空,Linux链表另行提供了一个list_empty_careful()宏,
它同时判断头指针的pNext和pPre,仅当两者都指向自己时才返回真。
这主要是为了应付另一个cpu正在处理同一个链表而造成pNext、pPre不一致的情况。
但代码注释也承认,这一安全保障能力有限:除非其他cpu的链表操作只有list_del_init(),否则仍然不能保证安全,也就是说,还是需要加锁保护。
*/
static inline int list_empty_careful(const struct list_head *head)   
{
        struct list_head *next = head->pNext;
        return (next == head) && (next == head->pPre);
}

3. 添加结点

1 基本实现函数

// 在ppre和pnext中插入pnew
static inline void _list_add(struct list_head *pnew, struct list_head *ppre, struct list_head *pnext)
{
    pnew->pPre = ppre;
    pnew->pNext = pnext;
    ppre->pNext = pnew;
    pnew->pPre = pnew;
}

2 头插法

/*
pentry:需要被插入的结点
phead:链表头结点
使用_list_add实现
*/
static inline void list_add(struct list_head *pentry, struct list_head *phead)
{
    _list_add(pentry, phead, phead->pNext);
}

3 尾插法

/*
pentry:需要被插入的结点
phead:链表头结点
使用_list_add实现
*/
static inline void list_add_tail(struct list_head *pentry, struct list_head *phead)
{
    _list_add(pentry, phead->pPre, phead);
}

4. 移除结点

1 基本实现函数

static inline void __list_del(struct list_head * ppre, struct list_head * pnext)     
{
       pnext->pPre = ppre;
       ppre->pNext = pnext;
}

2 移除结点

#define LIST_POISON1  0
#define LIST_POISON2  0

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

// 移除结点并重新初始化
static inline void list_del_init(struct list_head *entry)    
{
        __list_del(entry->pPre, entry->pNext);
        INIT_LIST_HEAD(entry);            
}

3 移除结点并插入链表头部

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

4 移除结点并插入链表尾部

static inline void list_move_tail(struct list_head *list, struct list_head *head)
{
        __list_del(list->pPre, list->pNext);
        list_add_tail(list, head);
}

5. 链表合并

// 合并链表, 将链表list与head合并
static inline void __list_splice(struct list_head *list, struct list_head *head)
{
        struct list_head *first = list->pNext;
        struct list_head *last = list->pPre;
        struct list_head *at = head->pNext;
 
        first->pPre = head;
        head->pNext = first;
 
        last->pNext = at;
        at->pPre = last;
}

// list不为空的情况下,调用__list_splice()实现list与head的合并
static inline void list_splice(struct list_head *list, struct list_head *head)
{
        if (!list_empty(list))
                __list_splice(list, head);
}

// 将两链表合并,并将list初始化
static inline void list_splice_init(struct list_head *list, struct list_head *head)
{
        if (!list_empty(list)) {
                __list_splice(list, head);
                INIT_LIST_HEAD(list);
        }
}

6. 遍历链表

1 顺序遍历

/*
从头结点的下一个结点开始遍历,直到再次遇到头结点结束
prefetch(),预热pos->next,加速下一次读取pos->next的速度,但是后来好像被取消了  
参见:http://blog.chinaunix.net/uid-23586403-id-5761969.html
*/
#define list_for_each(pos, head)    \
        for(pos = (head)->pNext; prefetch((pos)->pNext), pos != (head); pos = (pos)->pNext)

// 从头结点的下一个结点开始遍历,直到再次遇到头结点结束 
// 去除预热prefetch()
#define __list_for_each(pos, head)  \
        for(pos = (head)->pNext; pos != (head); pos = (pos)->pNext)

/*
list_for_each_safe() 也是链表顺序遍历,只是更加安全。即使在遍历过程中,当前节点从链表中删除,也不会影响链表的遍历
参数上需要加一个暂存的链表节点指针n。
这个宏中使用了n这个struct list_head类型的临时变量,每次迭代之后pos的下一个节点储存在临时变量n中,则在迭代中删除pos节点后,
下一次迭代会重新给pos赋值为临时变量n,然后再做迭代。这样在迭代的过程中就可以安全地删除节点pos了。
*/
#define list_for_each_safe(pos, n, head)    \
        for (pos = (head)->pNext, n = (pos)->pNext; pos != (head); pos = n, n = (pos)->pNext)  

2 逆序遍历

//list_for_each_prev与list_for_each的遍历顺序相反,从链表尾逆向遍历到链表头。  
#define list_for_each_prev(pos, head)   \
        for(pos = (head)->pPre; prefetch((pos)->pPre), pos != (head); pos = (pos)->pPre)

7. 遍历链表获取宿主地址

1 基础定义

/*
例:
struct tmp
{
    char i;
    int j;
    char k;
};

offsetof(struct tmp, k) ===> ((size_t) &((struct tmp *)0)->k)
含义
(struct tmp *)0)        创建一个struct tmp的指针变量,该变量虽然未能分配空间,但是将该变量的起始位置设为0,也就是其指向位置设为0
(struct tmp *)0)->k     获取变量的字段k,单纯的一个char型数据
&((struct tmp *)0)->k   获取字段k的地址,为char *类型,但由于整个结构体的指针的起始位置为0,所以这个char *的值,为字段k相对于整个结构体的偏移量
(size_t)                为了计算方便,将偏移量转换为无符号整型
*/
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)


/*
const typeof( ((type *)0)->member ) *__mptr = (ptr);
含义
用const typeof( ((type *)0)->member ) *类型定义一个名为__mptr的变量,并以ptr为之初始化
typeof()是C语言关键字,可以取得变量的类型,或者表达式的类型,所以这句话的意思是获得((type *)0)->member的类型,也就是member的类型,
但是这句话的意义在于 检测参数的合法性,如果开发者使用时输入的参数有问题:ptr与member类型不匹配,编译时便会有warnning

(type *)( (char *)__mptr - offsetof(type,member) )
含义:
offsetof(type,member)                   是member成员相对于结构体起始位置的偏移量
(char *)__mptr                          ptr是指向宿主结构体中list_head成员的指针,转为char *也就获取了这个指针的地址
(char *)__mptr - offsetof(type,member)  指针地址-这个指针成员相对与结构体的偏移量=结构体的起始地址
(type *)                                强转为type *,目的是设置指针步长

所以container_of()返回的是宿主结构体的起始地址
*/
#define container_of(ptr, type, member) ({                  \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})



/*
Linux链表中仅保存了数据项结构中struct list_head成员变量的地址,那么我们如何通过这个成员访问到作为它的所有者的节点数据呢?
Linux为此提供了一个list_entry(ptr,type,member)宏,
其中ptr是指向该数据中struct list_head成员的指针,也就是存储在链表中的地址值,
type是数据项的类型,
member则是数据项类型定义中struct list_head成员的变量名,
根据struct list_head型指针ptr换算成其宿主结构的起始地址,
该宿主结构是type型的,而ptr在其宿主结构中定义为member成员。
*/
#define list_entry(ptr, type, member) container_of(ptr, type, member) 

2 顺序遍历

/*
循环遍历每一个pos中的member子项
pos:宿主结构体指针
head:链表头结点指针
member:宿主结构体中struct list_head的字段名
之所以从(head)->pNext开始 是因为(head)->pNext是第一个有寄主的结点(顺序插入的话)
*/
#define list_for_each_entry(pos, head, member)                                  \
        for (pos = list_entry((head)->pNext, typeof(*pos), member);             \
             prefetch(pos->member.pNext), &pos->member != (head);               \
             pos = list_entry(pos->member.pNext, typeof(*pos), member))


// 它们要求调用者另外提供一个与pos同类型的指针n,在for循环中暂存pos下一个节点的地址,避免因pos节点被释放而造成的断链
#define list_for_each_entry_safe(pos, n, head, member)                          \
        for (pos = list_entry((head)->pNext, typeof(*pos), member),             \
                n = list_entry((pos)->member.pNext, typeof(*pos), member);      \
             &(pos)->member != (head);                                          \
             pos = n, n = list_entry((n)->member.pNext, typeof(*n), member))

3 逆序遍历

/*
逆序遍历每一个pos中的member子项
之所以从(head)->pNext开始 是因为(head)->pNext是第一个有寄主的结点(逆序插入的话)
*/
#define list_for_each_entry_reverse(pos, head, member)                          \
        for (pos = list_entry((head)->pPre, typeof(*pos), member);              \
             prefetch(pos->member.pPre), &pos->member != (head);                \
             pos = list_entry(pos->member.pPre, typeof(*pos), member))

4 中间遍历

/*
如果遍历不是从链表头开始,而是从已知的某个pos结点开始,则可以使用list_for_each_entry_continue(pos,head,member)。
但为了确保pos的初始值有效,Linux专门提供了一个list_prepare_entry(pos,head,member)宏,如果pos有值,则其不变;如果没有,
则从链表头强制扩展一个虚pos指针。将它的返回值作为list_for_each_entry_continue()的pos参数,就可以满足这一要求。
*/
#define list_prepare_entry(pos, head, member) \
        ((pos) ? : list_entry(head, typeof(*pos), member))

// 如果遍历不是从链表头开始,而是从已知的某个节点pos开始,则可以使用list_for_each_entry_continue(pos,head,member)
#define list_for_each_entry_continue(pos, head, member)                         \
        for (pos = list_entry(pos->member.pNext, typeof(*pos), member);         \
             prefetch(pos->member.pNext), &pos->member != (head);               \
             pos = list_entry(pos->member.pNext, typeof(*pos), member))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值