Linux内核中的一个list实现的理解

一 C中宏的应用

如果我们写个排序方法, 用c语言实现的话,由于没有通用的类型,也没有c++的模板,没有java的泛化,这样不同的数据类型,我们就要实现多个排序方法,比如实现个int的排序方法,实现double类型的排序方法,但是这样重复的代码很多,其实可以用宏来实现。

同样有个需求要我们写个list,如果里面保存的数据类型不同,就要实现多个,这种实现方法会有太多代码重复,用宏实现是个不错的思路,用宏来实现相关函数还可以提升性能, 当然宏也有明显的缺点,比如不利于调试,写不好也容易在展开的时候出错。

二 内核中的一个list的实现

这个list 可以看作是双向链表,具体的实现如下:

#ifndef B4EDBD16_2948_4595_9135_6BBBAF9B6341
#define B4EDBD16_2948_4595_9135_6BBBAF9B6341
/*
 * Tail queue definitions.
 */
#define _TAILQ_HEAD(name, type, qual)                   \
struct name {                               \
    qual type *tqh_first;       /* first element */     \
    qual type *qual *tqh_last;  /* addr of last next element */ \
}
#define TAILQ_HEAD(name, type)  _TAILQ_HEAD(name, struct type,)

#define TAILQ_HEAD_INITIALIZER(head)                    \
    { NULL, &(head).tqh_first }

#define _TAILQ_ENTRY(type, qual)                    \
struct {                                \
    qual type *tqe_next;        /* next element */      \
    qual type *qual *tqe_prev;  /* address of previous next element */\
}
#define TAILQ_ENTRY(type)   _TAILQ_ENTRY(struct type,)
#define TAILQ_END(head)         NULL
#define TAILQ_FOREACH_SAFE(var, head, field, tvar)                  \
    for((var) = TAILQ_FIRST(head), \
        (tvar) = TAILQ_FIRST(head) ? TAILQ_NEXT(TAILQ_FIRST(head), field): NULL ; \
        (var) != TAILQ_END(head);                   \
        (var = tvar), (tvar) = var ? TAILQ_NEXT(var, field): NULL)
/*
 * Tail queue functions.
 */
#define TAILQ_INIT(head) do {                       \
    (head)->tqh_first = NULL;                   \
    (head)->tqh_last = &(head)->tqh_first;              \
} while (/*CONSTCOND*/0)

#define TAILQ_INSERT_HEAD(head, elm, field) do {            \
    if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)    \
        (head)->tqh_first->field.tqe_prev =         \
            &(elm)->field.tqe_next;             \
    else                                \
        (head)->tqh_last = &(elm)->field.tqe_next;      \
    (head)->tqh_first = (elm);                  \
    (elm)->field.tqe_prev = &(head)->tqh_first;         \
} while (/*CONSTCOND*/0)

#define TAILQ_INSERT_TAIL(head, elm, field) do {            \
    (elm)->field.tqe_next = NULL;                   \
    (elm)->field.tqe_prev = (head)->tqh_last;           \
    *(head)->tqh_last = (elm);                  \
    (head)->tqh_last = &(elm)->field.tqe_next;          \
} while (/*CONSTCOND*/0)

#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do {      \
    if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
        (elm)->field.tqe_next->field.tqe_prev =         \
            &(elm)->field.tqe_next;             \
    else                                \
        (head)->tqh_last = &(elm)->field.tqe_next;      \
    (listelm)->field.tqe_next = (elm);              \
    (elm)->field.tqe_prev = &(listelm)->field.tqe_next;     \
} while (/*CONSTCOND*/0)

#define TAILQ_INSERT_BEFORE(listelm, elm, field) do {           \
    (elm)->field.tqe_prev = (listelm)->field.tqe_prev;      \
    (elm)->field.tqe_next = (listelm);              \
    *(listelm)->field.tqe_prev = (elm);             \
    (listelm)->field.tqe_prev = &(elm)->field.tqe_next;     \
} while (/*CONSTCOND*/0)

#define TAILQ_REMOVE(head, elm, field) do {             \
    if (((elm)->field.tqe_next) != NULL)                \
        (elm)->field.tqe_next->field.tqe_prev =         \
            (elm)->field.tqe_prev;              \
    else                                \
        (head)->tqh_last = (elm)->field.tqe_prev;       \
    *(elm)->field.tqe_prev = (elm)->field.tqe_next;         \
} while (/*CONSTCOND*/0)
#define TAILQ_REPLACE(head, elm, elm2, field) do {          \
    if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL)   \
        (elm2)->field.tqe_next->field.tqe_prev =        \
            &(elm2)->field.tqe_next;                \
    else                                \
        (head)->tqh_last = &(elm2)->field.tqe_next;     \
    (elm2)->field.tqe_prev = (elm)->field.tqe_prev;         \
    *(elm2)->field.tqe_prev = (elm2);               \
    _Q_INVALIDATE((elm)->field.tqe_prev);               \
    _Q_INVALIDATE((elm)->field.tqe_next);               \
} while (0)
#define TAILQ_FOREACH(var, head, field)                 \
    for ((var) = ((head)->tqh_first);               \
        (var);                          \
        (var) = ((var)->field.tqe_next))

#define TAILQ_FOREACH_REVERSE(var, head, headname, field)       \
    for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last));    \
        (var);                          \
        (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last)))

#define TAILQ_CONCAT(head1, head2, field) do {              \
    if (!TAILQ_EMPTY(head2)) {                  \
        *(head1)->tqh_last = (head2)->tqh_first;        \
        (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
        (head1)->tqh_last = (head2)->tqh_last;          \
        TAILQ_INIT((head2));                    \
    }                               \
} while (/*CONSTCOND*/0)

/*
 * Tail queue access methods.
 */
#define TAILQ_EMPTY(head)       ((head)->tqh_first == NULL)
#define TAILQ_FIRST(head)       ((head)->tqh_first)
#define TAILQ_NEXT(elm, field)      ((elm)->field.tqe_next)

#define TAILQ_LAST(head, headname) \
    (*(((struct headname *)((head)->tqh_last))->tqh_last))
#define TAILQ_PREV(elm, headname, field) \
    (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#endif /* B4EDBD16_2948_4595_9135_6BBBAF9B6341 */

利用这个宏实现的小例子:

typedef struct _stu {
 char name[54];
 int age;
        // 在链表成员中的使用
 TAILQ_ENTRY(_stu) next;
} stu;

typedef struct _sch {
        // 链表
 TAILQ_HEAD(_stu_head, _stu) stu_list;
} sch;


int main(int argc, char **argv)
{
 sch *psch = (sch *)calloc(1, sizeof(sch));
         // 链表初始化
 TAILQ_INIT(& (psch->stu_list));

 stu s1 = {.name = "s1", .age = 1};
 stu s2 = {.name = "s2", .age = 2};
 stu s3 = {.name = "s3", .age = 3};
 stu s4 = {.name = "s4", .age = 4};
 stu s5 = {.name = "s5", .age = 5};
 stu s6 = {.name = "s6", .age = 6};

         // 在list前面插入
 TAILQ_INSERT_HEAD(&(psch->stu_list), &s1, next);
 TAILQ_INSERT_HEAD(&(psch->stu_list), &s2, next);

        // 在list的队尾插入
 TAILQ_INSERT_TAIL(&(psch->stu_list), &s3, next);
 TAILQ_INSERT_TAIL(&(psch->stu_list), &s5, next);

        // 在特定元素前面插入
 TAILQ_INSERT_BEFORE(&s5, &s4, next);
 TAILQ_INSERT_TAIL(&(psch->stu_list), &s6, next);
  printf("queue_head:%p, first:%p, last:%p\n", (void*)&(psch->stu_list), (void*)(psch->stu_list).tqh_first, (void*)(psch->stu_list).tqh_last);

 stu *p = NULL;
         // 正向遍历
 TAILQ_FOREACH(p, &(psch->stu_list), next) {
    printf("item:%p, next:%p, &next:%p, prev:%p, *prev:%p\n", p,p->next.tqe_next, &p->next.tqe_next,p->next.tqe_prev,& p->next.tqe_prev);
  printf("name:%s\t age:%d\n", p->name, p->age);
 }
 printf("---------------------------------\n");

       // 反向遍历
 TAILQ_FOREACH_REVERSE(p, &(psch->stu_list), _stu_head, next) {
  printf("name:%s\t age:%d\n", p->name, p->age);
 } 
        free(psch);
 return 0;
}

三 示意图说明

3.1 获取链表的最后一个元素

上述链表的示意图如下:f858d1607a8734b495834fabe8b0160b.png

注意这个链表和我们一般自己在初学c语言的链表不一样,这个里面的tqe_prev指向的不是前一个元素,而是指向前一个元素的tqe_next, 注意这个tqe_next是在stu里面的next的成员里面的。

这个宏有个比较难以理解的地方,就是得到最后一个元素和得到前一个元素:

#define TAILQ_LAST(head, headname) \
    (*(((struct headname *)((head)->tqh_last))->tqh_last))
#define TAILQ_PREV(elm, headname, field) \
    (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))

我们拿一个来解释TAILQ_LAST,如下图展示: 假如有如下的list:e8d0056196b709fb2ae0bb55e83eb562.png获取最后一个元素的示意图如下:0e18772023272d1a8f92190100aa5ec1.png

TAILQ_LAST分为两步:

1. 获取tqh_last 指针,即指到最后一个元素的tqe_next;
2. 通过最后一个元素的tqe_next 获取到指向最后一个元素即绿色的stu的地址。

第一步很容易理解,通过(head)->tqh_last 获取到最后一个元素的tqe_next. 第二步很难理解,因为它把这个地址强制转成队列,成员怎么转成队列那,那是因为两个都是指针,指针的大小都是一样的,而且都是两个指针变量,所以可以互转,转了之后可以看到如上图的对应关系,tqe_next 变成了队列的tqh_first 了、同样tqe_prev也就变成了tqh_last,而tqh_last 即tqe_prev指向黄色元素的tqe_next了,然后通过* 取值即变成了绿色的stu的地址,即最后一个元素的地址了。

3.2 插入元素

#define TAILQ_INSERT_TAIL(head, elm, field) do {            \
    (elm)->field.tqe_next = NULL;                   \
    (elm)->field.tqe_prev = (head)->tqh_last;           \
    *(head)->tqh_last = (elm);                  \
    (head)->tqh_last = &(elm)->field.tqe_next;          \
} while (/*CONSTCOND*/0)

再画个插入的示意图:1f9c12d29b1628bf6ec23ad9a4ba538a.png

插入一个元素:1c65cda56e6844697ef7ae5efeb06703.png线条上的数字即插入的顺序。

再次插入一个元素:fb4c4742ce146aabff499d38ec14f49a.png

最终形成的链表:672d1c1fb776800827eed67a2312f5a6.png

3.3 宏调试相关

宏在调试之前,我们可以先通过gcc命令进行展开,可以获取到展开宏的源码。

gcc -E -P main.c > main.expand.c

在gdb调试的时候,可以通过:

gdb) macro expand 宏

gcc编译的时候、添加-g3 便于调试宏

gcc -g3

四 参考

https://www.361shipin.com/blog/1534986025145729024
https://www.getdami.com/questions/wei-dui-lie-dai-ma-zhong-de-tailq_lastzen-yao-li-jie-89811/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值