Linked List of small secrets in C Language (4)

Source: Internet
Author: User
Tags prefetch
Most readers do not like the boring text descriptions when learning programming languages, including when I start learning programming myself. The enthusiasm for code is far higher than that for words, therefore, when I write something, I do not like to use boring text descriptions to explain it to readers. I prefer to use code and appropriate text descriptions to explain it, because some things may be described in boring text for half a day, it is better to present a simple code to the reader, so that the reader can understand it more thoroughly. But it doesn't mean that text descriptions are useless, and text descriptions are also important, but most readers want to directly achieve the final effect and want to skip the intermediate steps. Next we will continue with the content of the previous blog "linked list of small secrets of C language (iii)" to explain the two-way circular linked list of Linux kernel.

Note: I will list the header files I used when writing code in the article. h. upload files to my space without point downloads. If you need them, you can download them yourself. Of course, you can also download them online or from your own Linux system. : Http://download.csdn.net/user/bigloomy

static inline int list_empty(const struct list_head *head){        return head->next == head;}static inline int list_empty_careful(const struct list_head *head){        struct list_head *next = head->next;        return (next == head) && (next == head->prev);}

The list_empty () and list_empty_careful () functions are used to check whether the linked list is empty. However, the difference is that the detection method used by the first linked list is to determine whether the next node of the header node is itself. If yes, true is returned; otherwise, false is returned. The second function uses the detection method to determine whether the first and last nodes of the header are themselves. If both are satisfied, false is returned; otherwise, the return value is true. If you say more, the reader may be impatient. Let's take a look at the following code.

# Include <stdio. h> # include <stdlib. h> # include "list. H "typedef struct _ Stu {char name [20]; int num; struct list_head list;} Stu; int main () {Stu * pstu; Stu * tmp_stu; struct list_head stu_list; struct list_head * Pos; int I = 0; init_list_head (& stu_list); pstu = malloc (sizeof (Stu) * 5); for (I = 0; I <5; I ++) {sprintf (pstu [I]. name, "Stu % d", I + 1); pstu [I]. num = I + 1; list_add (& (pstu [I]. list), & stu_list);} list_for_each (Pos, & stu_list) {tmp_stu = list_entry (Pos, Stu, list); printf ("Student num: % d \ tstudent Name: % s \ n ", tmp_stu-> num, tmp_stu-> name);} If (list_empty (& stu_list) printf (" use list_empty () for detection, empty linked list \ n "); elseprintf (" use list_empty () detection, non-empty Chain List \ n "); If (list_empty_careful (& stu_list) printf (" use list_empty_careful () detection, the linked list is empty \ n "); elseprintf (" use list_empty_careful () detection, the linked list is not empty \ n "); free (pstu); Return 0 ;}

The running result is:

Root @ Ubuntu:/home/paixu/dlist_node #. /astudent num: 5 Student name: stu5student num: 4 Student name: stu4student num: 3 student name: stu3student num: 2 Student name: stu2student num: 1 student Name: stu1 uses list_empty () detection. list_empty_careful () is used to detect non-empty linked lists. The linked list is not empty.

Let's look at the code to know how to use it. Next, let's look at the synthesis of the linked list.

static inline void __list_splice(struct list_head *list,                                 struct list_head *head){        struct list_head *first = list->next;        struct list_head *last = list->prev;        struct list_head *at = head->next;        first->prev = head;        head->next = first;        last->next = at;        at->prev = last;}

In this case, I think the best way is to use the diagrams to explain the situation intuitively and easily. If you use text to describe the situation for a long time, you might as well take a look at it.

Insert a linked list to another linked list. Check whether the linked list is empty is not performed, which is guaranteed by the caller by default. Because each linked list has only one header node, it is meaningless to insert an empty linked list into another linked list. However, the inserted linked list can be empty.

static inline void list_splice(struct list_head *list, struct list_head *head){        if (!list_empty(list))                __list_splice(list, head);}

In this case, the header node pointed to by the list will be discarded. Because the two linked lists have two header nodes, we must remove one of them. As long as the list is not empty chain, there is no limit on the head, this function can merge linked lists.

static inline void list_splice_init(struct list_head *list,                                    struct list_head *head){        if (!list_empty(list)) {                __list_splice(list, head);                INIT_LIST_HEAD(list);        }}

The function above combines the valid information of a linked list to another linked list head and reinitializes the removed empty linked list header. This description may not be easy to understand. Let's look at a piece of code.

# Include <stdio. h> # include <stdlib. h> # include "list. H "typedef struct _ Stu {char name [20]; int num; struct list_head list;} Stu; int main () {Stu * pstu, * pstu2; Stu * tmp_stu; struct list_head stu_list, stu_list2; struct list_head * Pos; int I = 0; init_list_head (& stu_list); struct (& stu_list2); pstu = malloc (sizeof (Stu) * 3 ); pstu2 = malloc (sizeof (Stu) * 3); for (I = 0; I <3; I ++) {sprintf (pstu [I]. name, "Stu % d", I + 1); sprintf (pstu2 [I]. name, "Stu % d", I + 4); pstu [I]. num = I + 1; pstu2 [I]. num = I + 4; list_add (& (pstu [I]. list), & stu_list); list_add (& (pstu2 [I]. list), & stu_list2);} printf ("stu_list linked list \ n"); list_for_each (Pos, & stu_list) {tmp_stu = list_entry (Pos, Stu, list ); printf ("Student num: % d \ tstudent name: % s \ n", tmp_stu-> num, tmp_stu-> name);} printf ("stu_list2 linked list \ n "); list_for_each (Pos, & stu_list2) {tmp_stu = list_entry (Pos, Stu, list); printf ("Student num: % d \ tstudent name: % s \ n ", tmp_stu-> num, tmp_stu-> name);} printf ("stu_list linked list and stu_list2 linked list merged \ n"); list_splice (& stu_list2, & stu_list); list_for_each (Pos, & stu_list) {tmp_stu = list_entry (Pos, Stu, list); printf ("Student num: % d \ tstudent name: % s \ n", tmp_stu-> num, tmp_stu-> name);} Free (pstu); Return 0 ;}

The running result is:

Root @ Ubuntu:/home/paixu/dlist_node #. /astu_list linked list student num: 3 student name: stu3student num: 2 Student name: stu2student num: 1 student name: stu1stu_list2 linked list student num: 6 Student name: stu6student num: 5 Student name: stu5student num: 4 Student name: stu4stu_list linked list and stu_list2 linked list merged student num: 6 Student name: stu6student num: 5 Student name: stu5student num: 4 Student name: stu4student num: 3 student name: stu3student num: 2 Student name: stu2student num: 1 student name: stu1

With intuitive code and running results, it is easier to understand.

With the above operations, we haven't talked about the host structure that we finally care about. So let's take a look at how we can retrieve the host structure pointer? This is what I think is the most clever way to implement the two-way circular linked list of Linux kernel.

#define list_entry(ptr, type, member)  \    ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

Look at the code above and find a very familiar figure (unsigned long) (& (type *) 0)-> member )), I have already explained this in my previous blog "the byte alignment of the little secrets in C language". I will not explain it too much here, if you do not understand it, you can go back and read it again. Through (unsigned long) (& (type *) 0)-> member), we get the offset of the member variable member, and PTR is the pointer to member, because of the different pointer types, we need to first convert (char *) before computation. Therefore, we use PTR to subtract the offset of member to get the pointer of the host struct. This is a very clever place, which makes the two-way circular linked list of Linux kernel different from the key of the traditional linked list. The readers may feel very boring when they see it, but don't give up and insist on reading it, because although this explanation is boring, it is very useful. So stick to it!

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

Traversal is the basic operation of a double-loop linked list. The head node is the first node. During the traversal process, it starts from (head)-> next. When Pos = head, it exits, therefore, the head node is not accessed, which is related to the structure design of the linked list. Generally, the header node does not contain any other valid information, therefore, you can use the head node as a detection flag for traversing the two-way linked list. Readers may find a strange face in the list_for_each macro. We will not explain the prefetch here. Interested readers can view its implementation by themselves, its function is to prefetch the memory content, that is, the program tells the CPU which content may be used immediately. The CPU extracts the memory operand in advance and then sends it to the high-speed cache for optimization, yes, the execution speed is faster. The following _ list_for_each () macro and list_for_each_prev () Macro will not be explained here, which is similar to the list_for_each () Macro. That is, the traversal direction is changed or prefetch is not used.

Through the above explanation and the above code, I believe that readers are no longer unfamiliar with list_for_each. The macros of the preceding three records are similar.

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

The above list_for_each_safe () macro is also used for traversal. The difference is that there is an additional N address saved for the next node of the POs, which avoids the chain break caused by the release of the POs node, this also reflects safe. In other words, you can traverse the current node and delete it, And then access the next node. After the traversal, only one header node is left. Of course, this is the most typical application, that is, when we program multiple processes, multiple processes are waiting in the same waiting queue. If an event occurs, all processes are awakened, then it can be awakened and deleted from the waiting queue in sequence.

# Include <stdio. h> # include <stdlib. h> # include "list. H "typedef struct _ Stu {char name [20]; int num; struct list_head list;} Stu; int main () {Stu * pstu; Stu * tmp_stu; struct list_head stu_list; struct list_head * POs, * n; int I = 0; init_list_head (& stu_list); pstu = malloc (sizeof (Stu) * 3); for (I = 0; I <3; I ++) {sprintf (pstu [I]. name, "Stu % d", I + 1); pstu [I]. num = I + 1; list_add (& (pstu [I]. list), & stu_list);} printf ("use list_for_each_safe () to traverse and use list_del (POS) to delete the node \ n"); list_for_each_safe (Pos, N, & stu_list) {tmp_stu = list_entry (Pos, Stu, list); printf ("Student num: % d \ tstudent name: % s \ n", tmp_stu-> num, tmp_stu-> name); list_del (POS);} printf ("use list_for_each () to traverse and use list_del (POS) to delete the node \ n"); list_for_each (Pos, & stu_list) {tmp_stu = list_entry (Pos, Stu, list); printf ("Student num: % d \ tstudent name: % s \ n", tmp_stu-> num, tmp_stu-> name);} Free (pstu); Return 0 ;}

The running result is:

Root @ Ubuntu:/home/paixu/dlist_node #. /A uses list_for_each_safe () to traverse and use list_del (POS) to delete the node before student num: 3 student name: stu3student num: 2 Student name: stu2student num: 1 student Name: stu1 uses list_for_each () to traverse and use list_del (POS) to delete a node.

You can use the running results to read the text description above.

It is far from enough to provide only the list_head structure traversal. what we want to achieve is to traverse the host structure, that is, to directly obtain the host structure item of the current linked list Node during the traversal, instead of calling list_for_each () and list_entry () at the same time (). Therefore, the list_for_each_entry () macro is provided for Linux.

#define list_for_each_entry(pos, head, member)                                \        for (pos = list_entry((head)->next, typeof(*pos), member);        \             prefetch(pos->member.next), &pos->member != (head);         \             pos = list_entry(pos->member.next, typeof(*pos), member))

The first parameter is the passed traversal pointer pointing to the host data structure. The second parameter is the linked list header, which is the list_head structure. The third parameter is the member name of the list_head structure in the host structure. Sometimes, if you do not explain the code too much, you can explain it using code segments.

#include <stdio.h>#include <stdlib.h>#include "list.h"typedef struct _stu{    char name[20];    int num;    struct list_head list;}stu;int main(){stu *pstu;stu *tmp_stu;struct list_head stu_list;struct list_head *pos,*n;int i = 0;INIT_LIST_HEAD(&stu_list);pstu = malloc(sizeof(stu)*3);for(i=0;i<3;i++){        sprintf(pstu[i].name,"Stu%d",i+1);pstu[i].num = i+1;list_add( &(pstu[i].list), &stu_list);} list_for_each_entry(tmp_stu, &stu_list, list)printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);free(pstu);return 0;}

The running result is:

root@ubuntu:/home/paixu/dlist_node# ./astudent num: 3student name: Stu3student num: 2student name: Stu2student num: 1student name: Stu1

If the reader is unfamiliar with the text description at the beginning, then read it again with the code.

Next let's take a look at the last few.

#define list_for_each_entry_reverse(pos, head, member)                        \        for (pos = list_entry((head)->prev, typeof(*pos), member);        \             prefetch(pos->member.prev), &pos->member != (head);         \             pos = list_entry(pos->member.prev, typeof(*pos), member))#define list_prepare_entry(pos, head, member) \        ((pos) ? : list_entry(head, typeof(*pos), member))#define list_for_each_entry_continue(pos, head, member)                 \        for (pos = list_entry(pos->member.next, typeof(*pos), member);        \             prefetch(pos->member.next), &pos->member != (head);        \             pos = list_entry(pos->member.next, typeof(*pos), member))#define list_for_each_entry_safe(pos, n, head, member)                        \        for (pos = list_entry((head)->next, typeof(*pos), member),        \                n = list_entry(pos->member.next, typeof(*pos), member);        \             &pos->member != (head);                                         \             pos = n, n = list_entry(n->member.next, typeof(*n), member))

The above several items are similar to list_for_each_entry, but they are slightly different. list_prepare_entry () contains prefetch (), which has been explained above. If you have any questions, go back and read it, I will not explain it too much here. The main difference between list_for_each_entry_continue () and list_for_each_entry () Is that list_for_each_entry_continue () can be traversed without starting from the linked list header, instead, the traversal starts from the next node of a known POS node. In some cases, if you do not traverse from the first node, you must use list_prepare_entry () to ensure the validity of the POs initial value (). The meaning is that if the POS is not empty, the POs value is itself. If the POS is empty, a virtual POS pointer is forcibly extended from the linked list header, and the reader analyzes list_prepare_entry () the implementation. List_for_each_entry_safe () requires the caller to provide another pointer N of the same type as the POs, and store the address of the host struct of the next node of the POs in the for loop, avoid chain breaks caused by the release of POS nodes.

Now our Linux kernel's two-way circular linked list journey is over. Next we will start a new journey. Due to my limited level, improper or incorrect content in my blog is inevitable, and I hope readers will criticize and correct me. Readers are also welcome to discuss relevant content. If you are willing to share your comments, please leave your valuable comments.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.