复习数据结构的时候,顺便找来经典的Linux内核链表来强化下。
由于Linux内核链表没有采用经典的“指针+数据域”的结构进行组织,而是由链表只负责管理前驱指针和后继指针,而由存储实际数据的数据结构来负责数据的组织。所以要采用这种链表,数据结构中必须有list_head这个数据域。
这个数据结构的特点是,把类型信息与链表结构独立开来。不像C++是采用模板,那么需要对template< class T >中的每种T都重载某些函数和操作符,这个链表是把这些类型信息都独立到使用该链表的数据中去,链表只是作为一个载体。
访问通过这些list_head连接起来的链表时,是巧妙地利用了内存对struct分配的空间关系。因为一个struct的内容是在内存中连续分配的,所以可以用指针直接移动一定的字节来移动到数据的场所。
我自己写了一个DATA的数据结构。在这个结构中struct list_head list是处于内存的低地址空间,int val是出于相对的高地址空间。而且sizeof(struct list_head) = 8就是两个指针的大小,所以可以把上个链表节点指向当前节点的链表指针加8就可以移动到val的地址。
比如
需要注意的是,temp的赋值过程。因为dat2.list.next的类型是——struct list_head*,大小为8个字节,所以后面sizeof(struct list_head)= 8,那么实际上移动了8×8=64个字节,而不是预料中的8个字节。因为指针的加减也要依据数据类型。所以要达到加8个字节的效果,必须先把指针转换为int指针。
最后应该成功输出1。
这是我对linux内核链表的最初的一点体会,还很不成熟,以后慢慢研究。
P.S. 在这个过程中,顺便学习了gdb的调试的一些简单操作,以前是只会break, list, print, run,今天继续学习info, backtrace
(gdb) info f
Stack level 0, frame at 0x22ff30:
eip = 0x4013e9 in main (list.c:21); saved eip 0x4010db
source language c.
Arglist at 0x22ff28, args:
Locals at 0x22ff28, Previous frame's sp is 0x22ff30
Saved registers:
ebp at 0x22ff28, eip at 0x22ff2c
(gdb) p &dat1.list
$1 = (struct list_head *) 0x22ff10
(gdb) p &dat1.val
$2 = (int *) 0x22ff18
(gdb) p dat2.list.next
$3 = (struct list_head *) 0x22ff10
(gdb) p sizeof(list_head)
No symbol "list_head" in current context.
(gdb) p sizeof(struct list_head)
$4 = 8
(gdb) p *(int*)(dat2.list.next + 8)
$5 = 1991710238
(gdb) p (int*)(dat2.list.next + 8)
$6 = (int *) 0x22ff50