记录一下整理的关于内核链表相关函数内容
一、双指针双向循环链表(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))