在Linux内核中使用了大量的链表结构来组织数据结构。这些链表大多数采用了[include/linux/list.h]中实现的一套精彩的链表数据结构。
预备知识:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
其中(TYPE *)0表示将常量0强制转化为TYPE*类型指针所指向的地址,&((TYPE *)0)->MEMBER表示取(TYPE*)0指针的成员MEMBER的地址,因为TYPE类型的结构的首地址是0,所以MEMBER成员的地址就是相对首地址的偏移量,这里的size_t为unsigned int类型,主要是便于在不同系统之间的移植。
所以,该宏的含义是求一个TYPE类型中MEMBER成员相对该类型结构首地址的偏移量。
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
其中ptr是指向member的指针,type是一个结构类型,member是结构中的成员,typeof的用法,typeof(int*)p <==> int* p;这里typeof( ((type *)0)->member ) *__mptr 相当于一个member类型的指针,但是这个指针被特殊处理过了,就是把type类型的首地址设置成0,const typeof( ((type *)0)->member ) *__mptr = (ptr);这里实际上就是把member指针赋值。(char *)__mptr - offsetof(type,member) ,首先,为什么要把__mptr的地址即member的地址,转换成char*型的,便于按字节计算。member的地址减去其在结构中的偏移量,就得到结构的首地址。
#include <stdio.h>
#define FIND(TYPE,MEMBER) (size_t)&(((TYPE*)0)->MEMBER)
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
struct list_head {
struct list_head *next, *prev;
};
struct student
{
int a;
char b[19];
double c;
struct list_head list;
};
int main(void)
{
struct student s= {1,{0},0.0,{NULL,NULL}};
struct list_head * ptr = &s.list;
printf("%08x\n",s);
printf("addr of s : %08x\n",&s);
printf("addr of s.a : %08x\n",&s.a);
printf("addr of s.b : %08x\n",&s.b);
printf("addr of s.c : %08x\n",&s.c);
printf("addr of ptr : %08x\n",ptr);
printf("offset of list: %08x\n",FIND(struct student,list));
struct student *stu = container_of(ptr,struct student,list);//这一句上有问题。
printf("addr of stu : %08x\n",stu);
return 0;
}
结果:
hubimaso@ubuntu:~/Linux$ ./container_of
00000001
addr of s : bfe5d424
addr of s.a : bfe5d424
addr of s.b : bfe5d428
addr of s.c : bfe5d43c
addr of ptr : bfe5d444
offset of list: 00000020
addr of stu : bfe5d424
#define LIST_POISON1 ((void *) 0x00100100)
#define LIST_POISON2 ((void *) 0x00200200)
第一句话是将0x00100100十六进制数强制转换成void*型指针。地址是0x00100100
第二句话同理。(貌似这两个地址没人用)用来给不用的指针赋值
#define INIT_LIST_HEAD(ptr) do { \
(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)
就是让ptr指向的结构回到最原始状态。如下面的第二个图。
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
跟上面的功能是一样的。初始化结构。
1.链表数据的结构的定义:
struct list_head
{
struct list_head *next,*prev;
}
list_head结构包含两个指向list_head结构的指针prev和next,由此可见,内核的链表具备双链表的功能,实际上,通常它都组织成双向循环链表。(注意这个结构体没有数据部分,也正是这样定义体现了他的独到之处)。
第一个图不多说,第二个图:linux内核链表的指针域不再是象第一个图那样,仅仅是指向下一结构体的指针(该结构体包含了数据域),而是这个linux的指针域内容是list_head类型的结构体,而该结构体分别有两个指针指向上一个list_head的结构体和下一个list_head的结构体。
为什么这样定义呢?
传统类型的指针是包括数据域的结构类型,如果该结构变化,则指针的类型也在变。如果下面的方式,这样的指针都是list_head类型的,不会随着结构体类型变化而变化。
那怎么样取到数据呢?
2.链表操作(*重点)
Linux内核中提供的链表操作主要有:
1)初始化链表头
INIT_LIST_HEAD(list_head *head)
原函数:
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
2)插入节点
list_add(struct list_head *new, struct list_head *head)
原函数:
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
next->prev = new;//head->next->prev,图中的1即第1步
new->next = next;//图中的1即第2步
new->prev = prev;//图中的1即第3步
prev->next = new;//head->next,//图中的1即第4步,包括打黑X的部分
}
(少画了一个实线指针,即head的下一个节点的prev是指向head的,head的prev指针指向尾节点。有些地方不好画出来,需要理解)
list_add_tail(struct list_head *new, struct list_head *head)
原型:
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
3)删除节点
list_del(struct list_head *entry)
原型:static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
而:
static inline void __list_del(struct list_head * prev, struct list_head * next)//这个函数很经典,看上去是将两个节点连接起来。
{
next->prev = prev;
prev->next = next;
}
4)static inline void list_del_init(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
INIT_LIST_HEAD(entry);//与上面唯一的不同就是将删除的节点,该节点初始化了。
}
5)#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
不用说了。
6)#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
遍历链表。pos是包装的结构指针,head是原始的结构指针,member原始结构的名。这里说的包装结构就相当要上述例子中的student结构体,原始结构相当于list,member相当于原始结构体的名字list。
关键是&pos->member ! = (head);这句话,当相等时说明以及遍历完了,这时的member就是尾节点,member的next就是head;