By沈东良(良少)http://blog.csdn.net/shendl
Linux内核的实现,大量使用了数据结构,包括了数组、链表和散列表。其中用的最多的是双向循环链表。Linux内核使用的是自己定义的链表和散列表,简单而高效,使用方法也非常的别具一格。
研究Linux内核的链表和散列表对于看懂Linux内核源代码有重要的意义。本文基于kernel2.6.39版本进行分析。
Linux的链表和散列表定义在include/linux/types.h文件中
structlist_head { 223 struct list_head *next, *prev; 224}; 225
226structhlist_head { 227 struct hlist_node *first; 228}; 229
230structhlist_node { 231 struct hlist_node *next, **pprev; 232}; 233
list_head就是使用最为广泛的双向循环链表。这个数据结构可以说是LinuxKernel的基石,大量内核源代码使用了这个数据结构。
hlist_head和hlist_node常常用于散列表中。
Linux的链表和散列表的操作函数的定义在include/linux/list.h文件中
初始化双向循环链表,只有一个元素的双向循环链表,next和prev指向自身。
staticinline voidINIT_LIST_HEAD(structlist_head *list)
25{
26list->next = list;
27list->prev = list;
28}
29
初始化散列表的链表。
空的散列表链表的first==NULL。每一个散列表链表的元素初始化时next和pprev指针都是NULL,而不是指向自身。
我们可以看到,散列表链表hlist_node虽然和双向循环链表list_head一样,都有两个指针,但有本质的区别。
散列表链表hlist_node不是循环链表。它有头和尾,是单向的链表。
散列表链表hlist_node之所以有两个指针,是为了提高插入和删除链表的效率。hlist_node的插入,只需要一个相邻的hlist_head或者hlist_node节点即可。它的删除,只需要它本身即可定位其相邻的前后两个元素。
570
571#defineHLIST_HEAD_INIT {
.first =NULL }
572#defineHLIST_HEAD(name)
struct hlist_headname =
{ .first =NULL }
573#defineINIT_HLIST_HEAD(ptr)
((ptr)->first = NULL)
574staticinline voidINIT_HLIST_NODE(structhlist_node *h)
575{
576h->next = NULL;
577h->pprev = NULL;
578}
579
脱离链表的元素的状态
staticinline void__list_add(structlist_head *new,
38 struct list_head *prev,
39 struct list_head *next)
40{
41next->prev = new;
42new->next = next;
43new->prev = prev;
44prev->next = new;
45}
46
/*
80*
Delete a list entry by making the prev/next entries
81*
point to each other.
82*
83*
This is only for internal list manipulation where we know
84*
the prev/next entries already!
85*/
86staticinline void__list_del(structlist_head *prev,
structlist_head *next)
87{
88next->prev = prev;
89prev->next = next;
90}
91
92/**
93*
list_del - deletes entry from list.
94*
@entry: the element to delete from the list.
95*
Note: list_empty() on entry does not return true after this, the entry is
96*
in an undefined state.
97*/
98#ifndefCONFIG_DEBUG_LIST
99staticinline void__list_del_entry(structlist_head *entry)
100{
101__list_del(entry->prev,entry->next);
102}
103
104staticinline voidlist_del(structlist_head *entry)
105{
106__list_del(entry->prev,entry->next);
107entry->next = LIST_POISON1;
108entry->prev = LIST_POISON2;
109}
110#else
散列表链表的脱离链表代码:
90staticinline void__hlist_del(structhlist_node *n)
591{
592 struct hlist_node *next = n->next;
593 struct hlist_node **pprev = n->pprev;
594 *pprev =next;
595 if
(next)
596next->pprev = pprev;
597}
598
599staticinline voidhlist_del(structhlist_node *n)
600{
601__hlist_del(n);
602n->next = LIST_POISON1;
603n->pprev = LIST_POISON2;
604}
605
看看LIST_POISON1和LIST_POISON2是何方神圣。
16
17/*
18*
These are non-NULL pointers that will result in page faults
19*
under normal circumstances, used to verify that nobody uses
20*
non-initialized list entries.
21*/
22#defineLIST_POISON1 ((void
*) 0x00100100 + POISON_POINTER_DELTA)
23#defineLIST_POISON2 ((void
*) 0x00200200 + POISON_POINTER_DELTA)
24
表示链表元素是未初始化的,既不在链表中,也没有经过初始化,不应该使用。
遍历Linuxkernel的链表时删除元素的方法
在list_head双向循环链表的迭代中删除元素的方法,见我之前写的《遍历Linuxkernel的链表时删除元素的方法》一文,地址:http://blog.csdn.net/shendl/article/details/6397898
散列表链表也有同样的问题:
665
pos是当前元素
666#definehlist_for_each(pos,head)
\
667 for
(pos = (head)->first;pos &&
({prefetch(pos->next);
1; }); \
668pos =pos->next)
669多了一个n元素,保存当前元素的下一个元素,这样就可以避免删除当前元素后,无法继续迭代的问题了。
670#definehlist_for_each_safe(pos,n,head)
\
671 for
(pos = (head)->first;pos &&
({n =pos->next;
1; }); \
672pos =n)
673
链表元素的使用方式和container_of宏
LinuxKernel的链表使用方式和其他一般的链表的使用方式大相径庭!
一般的链表,总是有一个链表结构体,它的内部有指针指向实际的对象。一般是这样子的:
struct node{
struct node * next;
struct node * prev;
void * data;
};
然后有一些操作函数,负责使用这样的链表元素实现链表。这应该算是非常标准的链表形式了。大量已有的各类语言的链表都是类似这样实现的。
再回头看看LinuxKernel的链表定义:
structlist_head {
223 struct list_head *next,
*prev;
224};
225
226structhlist_head {
227 struct hlist_node *first;
228};
229
230structhlist_node {
231 struct hlist_node *next,
**pprev;
232};
咦!实际的数据元素呢?这种链表有什么用?一堆指针链接在一起,却没有数据,有个毛用!
:-)确实挺奇怪的,放眼全球各类语言,也没有哪一个数据结构库的链表是这样子的!
我们看看kernel是怎样使用这些链表的:
structinode {
736/*
RCU path lookup touches following: */
737umode_ti_mode;
738uid_ti_uid;
739gid_ti_gid;
740 const
struct inode_operations *i_op;
741 struct super_block *i_sb;
742
743spinlock_ti_lock;/*
i_blocks, i_bytes, maybe i_size */
744 unsigned
int i_flags;
745 struct mutexi_mutex;
746
747 unsigned
long i_state;
748 unsigned
long dirtied_when;/*
jiffies of first dirtying */
749
750structhlist_nodei_hash;
751 struct list_headi_wb_list;/*
backing dev IO list */
752 struct list_headi_lru;/*
inode LRU list */
753 struct list_headi_sb_list;
754 union
{
755 struct list_headi_dentry;
756 struct rcu_headi_rcu;
757 };
758 unsigned
long i_ino;
759atomic_ti_count;
760 unsigned
int i_nlink;
761
…...
这些链表的实例成了数据结构的属性!
是的,kernel的链表的使用方式和一般的链表是相反的。不是数据在链表内,而是链表在数据内!
inode的i_hash散列表链表,把inode实例链接到一个全局的inode散列表中,fs/inode.c文件定义了一些操作散列表的方法:
/**
432*
__insert_inode_hash - hash an inode
433*
@inode: unhashed inode
434*
@hashval: unsigned long value used to locate this object in the
435*
inode_hashtable.
436*
437*
Add an inode to the inode hash for this superblock.
438*/
439void__insert_inode_hash(structinode *inode,
unsigned longhashval)
440{
441 struct hlist_head *b = inode_hashtable +hash(inode->i_sb,hashval);
442
443spin_lock(&inode_hash_lock);
444spin_lock(&inode->i_lock);
445hlist_add_head(&inode->i_hash,b);
446spin_unlock(&inode->i_lock);
447spin_unlock(&inode_hash_lock);
448}
449EXPORT_SYMBOL(__insert_inode_hash);
450
451/**
452*
remove_inode_hash - remove an inode from the hash
453*
@inode: inode to unhash
454*
455*
Remove an inode from the superblock.
456*/
457voidremove_inode_hash(structinode *inode)
458{
459spin_lock(&inode_hash_lock);
460spin_lock(&inode->i_lock);
461hlist_del_init(&inode->i_hash);
462spin_unlock(&inode->i_lock);
463spin_unlock(&inode_hash_lock);
464}
465EXPORT_SYMBOL(remove_inode_hash);
466
我们看到,这些函数调用了我们熟悉的hlist_node的操作函数,对inode的hlist_node类型属性进行了处理。
这样就把具有相同hash值的inode的i_hash链接到了一个链表上。
回到我们的老问题,我们怎么样才能从链表元素找到包括该链表元素的真实对象呢?毕竟真实对象才是我们的链表真正需要链接的东西。
再看一个函数:
1082/*
1083*
search the inode cache for a matching inode number.
1084*
If we find one, then the inode number we are trying to
1085*
allocate is not unique and so we should not use it.
1086*
1087*
Returns 1 if the inode number is unique, 0 if it is not.
1088*/
1089static
int test_inode_iunique(structsuper_block *sb,
unsigned longino)
1090{
1091 struct hlist_head *b = inode_hashtable +hash(sb,ino);
1092 struct hlist_node *node;
1093 struct inode *inode;
1094
1095spin_lock(&inode_hash_lock);
1096hlist_for_each_entry(inode,node,b,i_hash)
{
1097 if
(inode->i_ino == ino &&inode->i_sb == sb)
{
1098spin_unlock(&inode_hash_lock);
1099 return
0;
1100 }
1101 }
1102spin_unlock(&inode_hash_lock);
1103
1104 return
1;
1105}
1106
hlist_for_each_entry是怎么把inode找到的?
/**
*hlist_for_each_entry - iterate over list of given type
*@tpos: thetype * to use as a loop cursor. 类型的指针类型的循环变量,也就是真正的值。
*@pos: the&structhlist_node to use as a loop cursor.循环链表的变量
*@head: the head for your list.表头
*@member: the name of the hlist_node within thestruct.类型内的成员
*/
#definehlist_for_each_entry(tpos, pos, head, member) \
for(pos = (head)->first; \
pos && ({ prefetch(pos->next); 1;}) && \
({tpos = hlist_entry(pos,typeof(*tpos),member); 1;}); \
pos = pos->next)
上面的typeof(*tpos),typeof是GCC的扩展,现在好像是C99的特性,用来获得对象的类型。
这里,使用*得到指针指向的对象,然后取得类型,就得到了对象的真正类型!
传递的是:包含list_head/hlist_node的结构体类型和对应的属性名i_hash,这些信息有什么用?
344/**
345*
list_entry - get the struct for this entry
346*
@ptr: the &struct list_head pointer.
347*
@type: the type of the struct this is embedded in.
348*
@member: the name of the list_struct within the struct.
349*/
350#definelist_entry(ptr,type,member)
\
351container_of(ptr,type,member)
352
664#definehlist_entry(ptr,type,member)container_of(ptr,type,member)
谜底在就在container_of宏:
606/**
607*
container_of - cast a member of a structure out to the containing structure
608*
@ptr: the pointer to the member.
609*
@type: the type of the container struct this is embedded in.
610*
@member: the name of the member within the struct.
611*
612*/
613#definecontainer_of(ptr,type,member)
({ \
614 const typeof(
((type *)0)->member )
*__mptr = (ptr);
\
615 (type *)(
(char *)__mptr - offsetof(type,member)
);})
616
这里使用了C语言隐秘的技巧:
606 const typeof( ((type *)0)->member ) *__mptr = (ptr);
(type*)0把地址0强制类型转换为包含hlist_node链表元素的结构体的指针类型。
我们知道地址0是不允许使用的,会造成崩溃。但是这里并没有给地址0开头的数据赋值,所以没有关系。
然后求其->member成员的类型,得到了一个hlist_node*类型的临时变量,给它赋值,就是该链表元素的地址。
15#defineoffsetof(TYPE,MEMBER)
((size_t) &((TYPE *)0)->MEMBER)
16#endif
17
这里,故伎重演,利用地址0进行强制类型转换,转换成结构体类型的指针,然后求i_hash成员的地址。成员地址-0就得到了该成员相对于结构体实例的距离。
实际的链表元素的地址-链表元素在结构体内的相对偏移,就得到了该结构体实例的地址。然后(type*)强制类型转换。
606 tpos= hlist_entry(pos,typeof(*tpos),member); 1;});
这个赋值语句,被宏替换,变成:
606 tpos=container_of(pos,typeof(*tpos),member); 1;});
又被宏替换,成了:
606 tpos=({ consttypeof( ((typeof(*tpos)*)0)->member ) *__mptr = (pos);(typeof(*tpos) *)( (char *)__mptr -offsetof(typeof(*tpos),member) );})
这就是最后得到的C语句。 这里左式是待赋值的变量,右式是一个语句块! 怎么可以呢? 确实可以! 这里()把语句块包围起来,使整个语句块能够参与表达式。右式的返回值是语句块内最后一个表达式的值。 至此,我们能够从结构体内的链表元素属性,反向得到结构体实例!Linux Kernel双向循环链表和散列表链表的谜底揭开了!
LinuxKernel如何实现散列表
前文中,我们说了,hlist_head和hlist_node这两个数据结构是用来实现散列表的。我把它们称为散列表链表,因为它们实现的是散列表的一部分:链表。
散列表,逻辑上是<hash,value>这样一个表格形式的数据对的集合。
让我们先考虑一个散列表的伪实现:
如,考虑一个散列表查询函数的原型示例:void* lookup(void * obj,int hash);
这个函数首先通过hash在散列表中搜索,然后会得到[0,1...n]个value。
因为hash可能会出现冲突,多个不同对象的hash值相同。
然后可以根据查询对象的其他特征进行匹配,找出真正需要的对象。如obj的name,uuid,或者就是obj的地址。
如果找到,返回hashtable内的对象的指针。如果找不到返回NULL。
由此,我们想到如何构造一个散列表:
1,一个数组,元素是hlist_head对象。hlist_head指向一个散列表链表。
2,hash就是数组的索引。
hash值相同的对象,存入相同的hlist_head链表。
LinuxKernel的散列表就是这样实现的。
实例Inode-cache
LinuxKernel文件系统中,inode存放在一个全局的散列表Inode-cache中,代码在fs/inode.c中:
static structhlist_head *inode_hashtable__read_mostly;
1628inode_hashtable =
1629alloc_large_system_hash("Inode-cache",
1630 sizeof(struct hlist_head),
1631ihash_entries,
1632 14,
1633HASH_EARLY,
1634 &i_hash_shift,
1635 &i_hash_mask,
1636 0);
1637
它使用内核启动时保留的内存分配了一个数据。
/*
*search theinode cache for a matchinginode number.
*If we find one, then theinode number we are trying to
*allocate is not unique and so we should not use it.
*如果是唯一的,返回1。也就是说inode_hash表里没有数据。
否则不是唯一的,返回0。也就是说表里有这个inode。
*Returns 1 if theinode number is unique, 0 if it is not.
*/
staticinttest_inode_iunique(structsuper_block *sb, unsignedlongino)
{
根据sb的地址+inode的唯一号码(在一个超级块内是唯一的)得到散列值。
structhlist_head *b = inode_hashtable + hash(sb, ino);
structhlist_node *node;
structinode *inode;
锁住整个散列表
spin_lock(&inode_hash_lock);
hlist_for_each_entry(inode,node, b, i_hash) {
如果i_sb超级块的地址相同,并且inode的i_ino序号也相同,那么解锁整个表,返回0。
if(inode->i_ino== ino && inode->i_sb== sb) {
spin_unlock(&inode_hash_lock);
return0;
}
}
spin_unlock(&inode_hash_lock);
否则返回1。
return1;
}
上例中,如果hash相同,那么就根据inode所属的i_sb超级块对象的地址是否相同,以及inode的i_ino是否相同决定散列表中的inode对象是否就是我们正在寻找的对象。
inode可以通过i_sb和i_ino在逻辑上唯一定位。
实例 Dentrycache
LinuxKernel的dentry也保存在一个散列表Dentrycache中,代码在fs/dcache.c:
102static
struct hlist_bl_head *dentry_hashtable__read_mostly;
103
3003dentry_hashtable =
3004alloc_large_system_hash("Dentry
cache",
3005 sizeof(struct hlist_bl_head),
3006dhash_entries,
3007 13,
3008HASH_EARLY,
3009 &d_hash_shift,
3010 &d_hash_mask,
3011 0);
3012·
3013 for
(loop = 0;loop <
(1 <<d_hash_shift);loop++)
3014INIT_HLIST_BL_HEAD(dentry_hashtable + loop);
3015
实现方法都一样!
PS: Blog格式有点难看,上传了PDF版本, 想要的朋友请移步: http://d.download.csdn.net/down/3441424/shendl