Linux内核链表的实现与使用

Linux内核中的双向链表定义了如下简单结构:

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

这个不含任何数据项的结构,注定了它的通用性和未来使用的灵活性,例如前面的例子就可以按如下方式定义:
struct my_list{ 
void *mydata; 
struct list_head list;
};   

在此,进一步说明几点:
1)list字段,隐藏了链表的指针特性,但正是它,把我们要链接的数据组织成了链表。
2)struct list_head可以位于结构的任何位置
3)可以给struct list_head起任何名字。
4)在一个结构中可以有多个list

向链表中添加,删除节点等很简单。但问题是,怎么取到链表结果中的数据呢?我们有的只是list_head的指针,而不是my_list的指针。在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) \
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

下面是一个使用这种链表以及读取节点上数据的例子:

#include <stdio.h>
#include <stdlib.h>

#define list_entry(ptr, type, member) \
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

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

struct my_data {
	int * int_data;
	struct list_head link;
};

int main() {
	struct list_head head;
	struct my_data* data = malloc(sizeof(struct my_data));
	data->int_data = malloc(4);
	*(data->int_data) = 5000;
	data->link.pre = &head;
	(&head)->next = &(data->link);

	printf("%d\n", *(data->int_data));

	int* value = list_entry((&head)->next, struct my_data, link)->int_data;
	printf("%d\n", *value);

	return 0;
}

输出为:

5000
5000

list_entry(pos, mylist, list)宏,就可以根据pos的值,获取mylist的地址,也就是指向mylist的指针,这样,我们就可以存取mylist->mydata字段了。 可为什么能够达到这样的效果?

list_entry(pos, mylist, list) 展开以后为:

((struct my_list *)((char *)(pos) – (unsigned long)(&((struct my_list *)0)->list)))

这看起来会使大多数人眩晕,但仔细分析一下,实际很简单。
((size_t) &(type *)0)->member)把0地址转化为type结构的指针,然后获取该结构中member成员的指针,并将其强制转换为size_t类型。于是,由于结构从0地址开始定义,因此,这样求出member的成员地址,实际上就是它在结构中的偏移量。

写一段程序来验证这个宏:

#include <stdio.h>
#include <stdlib.h>

struct foobar{
	unsigned int foo;
	char bar;
	char boo;
};

int main(int argc, char** argv){

	struct foobar tmp;

	printf("address of &tmp is= %p\n\n", &tmp);
	printf("address of tmp->foo= %p \t offset of tmp->foo= %lu\n", &tmp.foo, (unsigned long) &((struct foobar *)0)->foo);
	printf("address of tmp->bar= %p \t offset of tmp->bar= %lu\n", &tmp.bar, (unsigned long) &((struct foobar *)0)->bar);
	printf("address of tmp->boo= %p \t offset of tmp->boo= %lu\n\n", &tmp.boo, (unsigned long) &((struct foobar *)0)->boo);

	printf("computed address of &tmp using:\n");
	printf("\taddress and offset of tmp->foo= %p\n",
	(struct foobar *) (((char *) &tmp.foo) - ((unsigned long) &((struct foobar *)0)->foo)));
	printf("\taddress and offset of tmp->bar= %p\n",
	(struct foobar *) (((char *) &tmp.bar) - ((unsigned long) &((struct foobar *)0)->bar)));
	printf("\taddress and offset of tmp->boo= %p\n",
	(struct foobar *) (((char *) &tmp.boo) - ((unsigned long) &((struct foobar *)0)->boo)));

	return 0;
}

输出为:

address of &tmp is= 0xbfffed00

address of tmp->foo= 0xbfffed00 offset of tmp->foo= 0
address of tmp->bar= 0xbfffed04 offset of tmp->bar= 4
address of tmp->boo= 0xbfffed05 offset of tmp->boo= 5

computed address of &tmp using:
address and offset of tmp->foo= 0xbfffed00
address and offset of tmp->bar= 0xbfffed00
address and offset of tmp->boo= 0xbfffed00


你可能感兴趣的:(Linux内核链表的实现与使用)