优雅地使用链表

优雅地使用链表

        链表是编程中经常要用到的数据结构,结构体描述时分为数据域和指针域,本没有什么好讲。但有没有想过教科书上的这种方式有什么问题?通过这种方式定义和使用链表,对于不同的链表类型,都要定义各自的链表结构,繁琐的很。linux kernel中链表的用法才应该是教科书中出现的。
        基本思想:在Linux内核链表中,不是在链表结构中包含数据,而是在数据结构中包含链表节点。
1) 链表定义:
struct list_head {
    struct list_head *next, *prev;
};
#define LIST_HEAD_INIT(name) { &(name), &(name) }

2) 链表使用者定义:

struct user_t {
    data domain;
    struct list_head node;
};
struct list_head g_user_list = LIST_HEAD_INIT(g_user_list);
3) 通过node定位user_t:
#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 user_t* next = container_of(&(g_user_list.next->node), struct user_t, node);

这里用到了container_of,container_of又用到了offsetof。offsetof是通过将结构体起始地址强制对齐到0来计算出node和起始地址的偏移offset;而container_of在node地址基础上减去offset得到user_t结构体的地址并返回user_t。



Linux的内核链表不同与以上我们定义的链表,如果我们仔细观察,会发现在我们定义的链表结点中,包含了一个指向相同类型的结构体指针,这个指针存放得是直接前驱或直接后继结点的地址。Linux的内核链表设计得很巧妙,在Smack技术中涉及了两类链表分别是普通的链表list和受rcu锁保护的rculist
1. 首先我们来看普通的list
我们可以打开终端 ,切换到Linux源代码目录,cd /include/linux/, 这里有list.h头文件,链表的定义和操作就在这里了。我们先不去看里面的内容,我们先看看Smack中很重要的一个结构体smack_rule,它在 /smack/smack.h中定义
struct smack_rule {
      struct list_head list;
      char *smk_subject;
      char *smk_object;
      int      smk_access;
};
注意这个结构体第一个成员是 list,它是list_head结构体类型,而struct list_head { struct list_head *prev,struct list_head *next },
struct list_head list,这里的list包含了两个指向自身类型的指针变量。而如果你语言基础不错的话,应该知道结构体第一个成员变量地址就是这个结构体的地址,那于是可以将另一个结构体的list赋给这个结构体的list,这样链表就串起来了。
如下图:


这其实等价于:
struct smack_rule sr1,sr2;
sr1.list.next=&sr2.list sr2.list=&sr1.list
Linux内核链表是通过镶嵌在结点内部list_head型的变量而组成的双向循环链表
那链表的操作,如建立链表,插入结点,遍历链表呢?这些操作都被封装在list.h这个头文件里了
如初始化链表 LIST_HEAD,遍历链表 list_for_each_entry, 获取链表结点的首地址 list_entry, 向链表加入结点 list_add等
Smack代码主要就是这几个操作,而下面我们选择2个操作来分析
第一个操作LIST_HEAD,它是一个宏,#define LIST_HEAD(name) LIST_HEAD_INIT(name)
                                                            #define LIST_HEAD_INIT(name) { &(name),&(name) }
这里定义的很清楚,也就是当使用LIST_HEAD(name),实际上是构造了一个空的双向循环链表,它只有一个指向自身的头结点。
第二个操作list_for_each_entry,它的定义是:
#define list_for_each_entry(pos,head,member) 
               /*代码省略*/
这个宏用来遍历链表


使用例子,仔细琢磨吧


#include <stdio.h>

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

#define list_for_each(pos, head) \
        for (pos = (head)->next; pos != (head); pos = pos->next)

#define list_for_each_safe(pos, n, head) \
        for (pos = (head)->next, n = pos->next; pos != (head); pos = n, n = pos->next)

void INIT_LIST_HEAD(struct list_head *list);
void list_add(struct list_head *node, struct list_head *head);
void list_del(struct list_head *entry);
int list_empty(const struct list_head *head);

void INIT_LIST_HEAD(struct list_head *list) {
    list->next = list;
    list->prev = list;
}

void list_add(struct list_head *node, struct list_head *head) {
    node->next = head;
    node->prev = head->prev;
    head->prev->next = node;
    head->prev = node;
}

void list_del(struct list_head *entry) {
    entry->next->prev = entry->prev;
    entry->prev->next = entry->next;

    entry->next = 0;
    entry->prev = 0;
}

int list_empty(const struct list_head *head) {
    return head->next == head;
}

typedef struct {
    struct list_head list;
    int v;
} data;

int main() {
    struct list_head list;

    data d1, d2, d3;
    d1.v = 1;
    d2.v = 2;
    d3.v = 3;

    INIT_LIST_HEAD(&list);
    list_add((struct list_head *) &d1, &list);
    list_add((struct list_head *) &d2, &list);
    list_add((struct list_head *) &d3, &list);

    struct list_head* p = NULL;
    struct list_head* n = NULL;

    list_for_each_safe(p, n, &list) {
        printf("%d\n", ((data *)p)->v);
    }

    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值