Linux内核第八记

来源:互联网 发布:中国余数定理 算法讲解 编辑:程序博客网 时间:2024/05/12 05:16

在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 00000001addr of s : bfe5d424addr of s.a : bfe5d424addr of s.b : bfe5d428addr of s.c : bfe5d43caddr of ptr : bfe5d444offset of list: 00000020addr 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;

原创粉丝点击