Before I started writing the Linux kernel two-way circular linked list, I had been wondering if I would like to use a long article to describe the Linux kernel two-way circular linked list? After careful consideration, I refused to describe the Linux kernel bidirectional circular linked list in boring words, because for programming languages, I believe that most readers should not like to face boring texts, prefer to see code, and that is what readers want to achieve after reading the text, so I decided to use the code with appropriate text descriptions to explain it here, which makes it impossible for me to explain it in one article, so I will write two articles to explain this knowledge point. It is hoped that readers will continue to read and learn how to write two-way circular linked lists in their applications without having to write the troublesome operation functions themselves, make full use of the operation functions provided in the 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
After understanding the implementation of the two-way circular linked list in the Linux kernel, we have to wonder that its implementation is so clever. In order for readers to smoothly complete this Linux kernel two-way circular linked list tour with me, before that, I wrote an article titled the byte alignment of the little secrets in C language, you can go back and check the byte alignment of the little secrets in C language and continue reading the full text.
Because we have a large amount of data structures in the Linux kernel, we need to use a two-way circular linked list. If we use the traditional two-way circular linked list implementation method, we have to maintain their respective linked lists for these data structures and design operation functions such as insert, search, and delete for each linked list. This is because the next and Prev pointers used to maintain the linked list in the conventional linked list point to the corresponding type of objects. Therefore, the linked list operation function of a data structure cannot be used to operate the linked list of other data structures. To solve this problem, a type-independent bidirectional circular linked list implementation method is adopted in the Linux kernel, its implementation eliminates the need to design operation functions such as insert, search, and delete for each linked list. The implementation method is to extract the pointers Prev and next in the struct from the specific data structure to form a general two-way circular linked list data structure list_head. To construct a specific linked list of a type of objects, you only need to define a member of The list_head type in its struct, and connect these objects through the list_head type member defined in this definition, form the required bidirectional cyclic linked list, and then operate it through the universal linked list function. Obviously, we only need to write a universal linked list function to construct and operate the linked list of different objects, instead of writing a special function for each created bidirectional cyclic linked list, which greatly achieves code reuse.
Next we will start our Linux kernel two-way circular linked list tour. You can download a list of two-way circular linked lists of Linux kernel from the Internet. the header file of H is worth noting that there are some differences in the header files that may be downloaded due to different kernel versions, but this does not affect our explanation of it. Readers can read the complete text first and then start it later. We use the list. h header file to implement our two-way circular linked list. For ease of explanation, we will explain the code in the list. h header file in sequence.
Add: (Note: if the reader does not understand the following code, he can continue to read it without affecting the next study. The following sections will also explain it, this part of the code is added after I finish writing the full text, because at first I used # define list_entry (PTR, type, member) (type *) (char *) (PTR)-(unsigned long) (& (type *) 0)-> member) instead of # define list_entry (PTR, type, member) container_of (PTR, type, member ))
#define container_of(ptr, type, member) ( { \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) ); } )
Use typeof (type *) 0)-> member) to obtain the type of the Member member. Assign the pointer PTR pointing to the member to _ mptr, __mptr pointer type is the type of member data member. Use (char *) _ mptr to forcibly convert _ mptr to a char pointer, And then subtract offsetof (type, member) to obtain the pointer of the host struct. If you are not familiar with offsetof (type, member), refer to my previous article titled byte alignment of small secrets in C language.
First, let's look at the implementation of the list_head structure.
struct list_head { struct list_head *next, *prev;};
In the Linux kernel bidirectional cyclic linked list, we use the list_head Type above to define a variable and embed it as a member into the host structure. What is a host struct? It is the structure of the two-way circular linked list we have created. The linked list structure can be placed anywhere in the host structure, or any name can be used for the linked list structure, in this way, we can use the members in list_head and corresponding processing functions to traverse the linked list. If we want to get a pointer to the host structure, we can use list_entry to calculate it, don't worry about what the list_entry is. We will explain it below and continue to look at it.
After the list_head is defined in the host struct, initialization of the defined header node is required. The initialization can be implemented in either of the following ways.
#define LIST_HEAD_INIT(name) { &(name), &(name) }#define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name)#define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \} while (0)
The code above shows that after we define a header node using list_head in the code, we need to initialize the defined header node. We can use the init_list_head (PTR) macro to initialize it, alternatively, you can use the list_head (name) macro to complete the definition and initialization without having to define it yourself. After the initialization of the header node is complete, the next step is to add the node.
static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next){next->prev = new;new->next = next;new->prev = prev;prev->next = new;}
_ List_add () is used to insert a node into two non-empty nodes. It is worth noting that new, Prev, And next cannot both be null values. Of course, Prev can be equal to next, indicating that a new node is inserted into the linked list containing only the first node. Now that we know the implementation of the _ list_add () function, let's take a look at the implementation of list_add () and list_add_tail.
static inline void list_add(struct list_head *new, struct list_head *head){__list_add(new, head, head->next);}static inline void list_add_tail(struct list_head *new, struct list_head *head){__list_add(new, head->prev, head);}
After reading the above implementation methods, we know that they are implemented by calling the underlying _ list_add. Let's see how to add nodes by passing different parameters in the _ list_add () function. The double dashes before the _ list_add () function usually indicate that this is an underlying function for other modules to call.
The first parameter passed by list_add () is implemented by inserting the node pointed to by new between the nodes pointed to by the head and head-> next pointers. That is, insert a new point to the end of the head pointer. Head is not necessarily a header node. If our linked list contains only one header node, the above _ list_add (new, head, head-> next) will still be true.
The second list_add_tail () function is to insert the node pointed by new before the node pointed by the node pointer head. If the head points to a head node, it is equivalent to adding a new node to the end node. In this function, it is worth noting that head-> Prev cannot be empty. If head is a header node, head-> Prev must point to a value, which generally points to the end node, create a circular linked list.
Speaking of this, some readers may have been eager to look at the code, so we can use list. h In the Linux kernel to write our code at the application layer.
#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);}free(pstu);return 0;}
The running result is:
root@ubuntu:/home/paixu/dlist_node# ./astudent num: 5student name: Stu5student num: 4student name: Stu4student num: 3student name: Stu3student num: 2student name: Stu2student num: 1student name: Stu1
Look at the code above. What are the basic work we have done?
1. A host struct Stu is defined, and a list variable of the struct list_head type is defined in the host struct;
2. Define a header node and initialize it;
3. Apply for memory space for a defined host struct variable;
4. initialize and add the requested host struct variable to the linked list with stu_list as the header node.
It is worth noting that the list_for_each () and list_entry () are described in the following sections, here, the reader only needs to know that the two of them are combined to print each data in the stu of the host structure. The use of sprintf () is not explained here. It is very simple. I believe that readers can guess its functions. If you have any questions or questions about the text description at the beginning, you should understand the code implementation. The use of list_add_tail () is similar to that of list_add, you can modify the code. If you do not understand list_add () at the beginning, you can refer to the running result and the text description above for your current understanding of list_add.
Let's look down.
static inline void __list_del(struct list_head * prev, struct list_head * next){next->prev = prev;prev->next = next;}
Between the nodes pointed by the prev and next pointers, the two refer to each other. In fact, Prev is the first node of the node to be deleted, and next is the next node of the node to be deleted.
static inline void list_del(struct list_head *entry){__list_del(entry->prev, entry->next);entry->next = LIST_POISON1;entry->prev = LIST_POISON2;}
Delete the node indicated by the entry, and block the node pointer field pointed to by the entry. It is worth noting that list_poison1 and list_poison2. Their Macros in list. h are defined as follows:
# Define list_poison1 (void *) 0x00100100)
# Define list_poison2 (void *) 0x00200200)
For the descriptions of list_poison1 and list_poison2, the Linux kernel has the following sentence: these are non-null pointers that will result in page faults under normal circumstances, used to verify that nobody uses non-initialized list entries. That is to say, they are not null pointers, but access to such pointers will normally cause errors. In fact, according to our general idea, the entry-> next and entry-> Prev values are null, so that they cannot be accessed through this node. However, a special method is used here. Note: In the Linux environment, the preceding macro values do not need to be modified and will not cause errors. However, errors will occur in the VC environment. The two values are not allowed, so we need to change them to null.
static inline void list_del_init(struct list_head *entry){ __list_del(entry->prev, entry->next); INIT_LIST_HEAD(entry);}
The functions of the above functions are to delete the node pointed to by the entry, and call list_init_head () to create a new empty double loop linked list as the linked list header.
# 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_del (& (pstu [3]. list); printf ("use list_del () to delete pstu [3] \ 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);} list_del_init (& (pstu [2]. list); printf ("use list_del_init () to delete pstu [2] \ 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 use list_del () to delete pstu [3] student num: 5 Student name: stu5student num: 3 student name: stu3student num: 2 Student name: stu2student num: 1 student Name: stu1 use list_del_init () to delete pstu [2] student num: 5 Student name: stu5student num: 2 Student name: stu2student num: 1 student name: stu1
After reading the code, it is much easier to understand the previous explanation. Readers can analyze the code based on the corresponding text description to deepen their impressions. Next, let's take a look at the last two functions of this blog.
static inline void list_move(struct list_head *list, struct list_head *head){ __list_del(list->prev, list->next); list_add(list, head);}static inline void list_move_tail(struct list_head *list, struct list_head *head){ __list_del(list->prev, list->next); list_add_tail(list, head);}
Let's take a look at the above two functions list_move () and list_move_tail (). The first list_move () function is to move the list between the nodes pointed by the head and head-> next pointers. The function of the second list_move_tail () function is to move the list to the node pointed to by the head and head-> Prev pointers. If the reader thinks this is not very specific, 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_move (& (pstu [3]. list), & stu_list); printf ("Move pstu [3] to the node between the head and head-> next pointer \ 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);} list_move_tail (& (pstu [2]. list), & stu_list); printf ("Move pstu [2] to the node pointing to head and head-> Prev \ 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 moves pstu [3] to the node pointing to head and head> next. Student num: 4 Student name: stu4student num: 5 Student name: stu5student num: 3 student name: stu3student num: 2 Student name: stu2student num: 1 student Name: stu1 moves pstu [2] to the node pointed to by the head and head-> Prev pointers. Student num: 4 Student name: stu4student num: 5 Student name: stu5student num: 2 Student name: stu2student num: 1 student name: stu1student num: 3 student name: stu3
Note this before, so that some readers think that the results are incorrect. The subscript in pstu [] starts from 0, so pstu [3] corresponds to stu4.
Here, we will continue with the rest in the next article "linked list of small secrets of C language (4. 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.