Understanding of a list implementation in the Linux kernel

Application of Macro in C

If we write a sorting method and implement it in c language, since there is no general type, no c++ template, and no java generalization, we need to implement multiple sorting methods for different data types, such as implementing an int The sorting method implements the sorting method of the double type, but there are a lot of repeated codes in this way, and it can actually be implemented with a macro.

There is also a need for us to write a list. If the data types stored in it are different, we need to implement multiple. This implementation method will have too much code duplication. It is a good idea to use macros to implement related functions. It can improve performance. Of course, macros also have obvious disadvantages, such as not conducive to debugging, poorly written and easy to make mistakes during expansion.

Implementation of a list in two kernels

This list can be regarded as a doubly linked list, and the specific implementation is as follows:

#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 */

A small example implemented using this macro:

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;
}

Three schematic illustrations

3.1 Get the last element of the linked list

The schematic diagram of the above linked list is as follows:f858d1607a8734b495834fabe8b0160b.png

Note that this linked list is different from the linked list we usually learn C language for beginners. The tqe_prev in this does not point to the previous element, but to the tqe_next of the previous element. Note that this tqe_next is in the next member of stu.

One of the more difficult things about this macro is to get the last element and get the previous element:

#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))

Let's take one to explain TAILQ_LAST, as shown in the following figure: If there is the following list: e8d0056196b709fb2ae0bb55e83eb562.pngthe schematic diagram of obtaining the last element is as follows:0e18772023272d1a8f92190100aa5ec1.png

TAILQ_LAST is divided into two steps:

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

The first step is easy to understand, by (head)->tqh_lastgetting the last element tqe_next. The second step is difficult to understand, because it forces this address into a queue, how to convert members into a queue, that is because both are pointers, pointers The size is the same, and both are two pointer variables, so they can be switched to each other. After the switch, you can see the corresponding relationship as shown in the above figure. tqh_last means that tqe_prev points to tqe_next of the yellow element, and then becomes the address of the green stu, which is the address of the last element, by * value.

3.2 Inserting elements

#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)

Then draw a schematic diagram of the insertion:1f9c12d29b1628bf6ec23ad9a4ba538a.png

Insert an element: 1c65cda56e6844697ef7ae5efeb06703.pngthe numbers on the lines are the order of insertion.

Insert an element again:fb4c4742ce146aabff499d38ec14f49a.png

The resulting linked list:672d1c1fb776800827eed67a2312f5a6.png

3.3 Macro debugging related

Before the macro is debugged, we can first expand it through the gcc command, and we can get the source code of the expanded macro.

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

When debugging with gdb, you can pass:

gdb) macro expand 宏

When gcc compiles, add -g3 to facilitate debugging macros

gcc -g3

Four references

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

Guess you like

Origin blog.csdn.net/mseaspring/article/details/127131021