Operation of linked list in Linux system

Posted by o2cathy on Sat, 29 Jan 2022 18:16:41 +0100

Operation of Linux kernel queue

This system imitates the queue implementation in Linux. It is a two-way linked list. Personally, I think the implementation of two-way linked list in Linux is simply wonderful.

1, Implementation of conventional linked list

When learning the data structure course, they all learned the data structure of two-way linked list, which is basically the following structure:

struct student {
    struct student* next;
    int id;
    char name[30];
	struct student* pre;
};

The two-way linked list structure composed of many nodes is roughly as follows:

I specially spend a dotted border around each structure to represent the whole structure. (it does not mean that there is a gap between the structure itself and its members).

You will find that such a linked list has the following important characteristics:

  • Node pointers (pre and next) point to an entire node

However, it will be found that such a linked list has great limitations:

  • The structure and operation of different linked list nodes are similar (no addition, deletion, modification and query). Only the data in the linked list nodes are different
  • Whenever you create a different type of linked list, you have to write it again (including the operation of the linked list)

But in Linux, it's wonderful to realize a more general linked list structure.

2, Implementation of linked list in Linux

The implementation of two-way linked list in Linux is like this (I heard that I haven't seen the Linux source code).

1. Structure of linked list node

The following is the node structure of the linked list:

/*No data element is required in the node, only the pointer of the predecessor and successor nodes are required*/
struct list_elem {
   struct list_elem* prev; // Forequarters node
   struct list_elem* next; // Successor node
};

At first glance, it always feels strange. It's right: there is no data in the node.

What's the use of linked lists without data? It's no use stringing these nodes together?

2. Data node

In the conventional linked list, the data is placed in the linked list node. In Linux, put the linked list node into the data.

Go directly to the code demonstration. Here is a student node:

struct student {
	int id;
	char name[30];
	struct list_elem tag;
};

After seeing this, you should understand how data nodes form a linked list, basically as shown in the following figure:

We can see that such a linked list has the following features:

  • The linked list node (list_elem ent structure) is a member of the data node (student structure)
  • Data nodes (student structures) are strung together through their member tag
  • Each linked list node only points to the next linked list node

At this time, the problem comes. The data nodes are connected in series, but how to access them?

Generally, we will traverse the linked list, but in the end, what we need is data. Such a linked list can only access the member tag of the student node. How can we access the student node itself?

The question now is how to access the structure itself through its members?

3. Access the structure itself through its members

This shows the strength of C language. C language can directly operate memory (virtual memory, of course). Therefore, just calculate the address offset of the linked list node (struct list_element tag) in the whole node (struct student).

So how should it be calculated? It seems that a specific structure is needed to calculate the offset? Is the process of calculating the offset of different structures (data nodes) not universal?

3.1 calculate the offset of structural members

At this time, it fully reflects the power of macros in C language!

/*
	Desciprtion:
		offset Calculates the offset of a member of a structure from the starting address of the structure
	Parameters:
		struct_type: Type of structure
		member: Structure member
	Return: 
		The offset of a structure member from that structure
	usage method:
		For example, there is a struct student structure and a member is tag. Calculate the offset from tag to student: offset(struct student, tag)
*/
#define offset(struct_type, member) (int)(&((struct_type*)0)->member)

The above is the calculation offset realized by using the macro of C language. For example, here we need to calculate the offset from the tag member in the student structure to the structure itself:

int offset = offset(struct student, tag);

Notes:

  • (struct_type*)0
    • Here, the starting content of the 0 address is forcibly transformed into a struct_ The structure of type is very ingenious.
    • The data at address 0 was not a struct_type, but we need a struct_type structure, so we take the 0 address as a structure, borrow it, and then calculate the offset of the member address. The content at 0 address has not been changed.
    • So it is directly converted to struct_type structure. As for the content, we don't care. We only care about the address.
  • (struct_type*)0->member
  • Here, the characteristics of macros are cleverly used, and the places where macros are used will be replaced intact
    • For example, when calling offset(struct student, tag), it will be directly replaced with (struct student *) 0 - > tag in the macro
  • (int)(&((struct_type*)0)->member)
    • There is a big pit in this part.
    • Here, when you forcibly convert the address to an int type variable, you should pay attention to:
      • int type takes up 4 bytes (32 bits)
      • If the code is run on a 64 bit machine (I mean the code here is used as an ordinary application, not as a kernel code), the address is 64 bits
      • The strong conversion to int type will be truncated, and the wrong memory will be accessed when it is restored to the address at that time.
    • Therefore, if your program is running in a 64 bit operating system, change int to long or uint64_t
3.2 get the address of the structure itself

Calculate the address offset of the structure member, and then you can access the structure itself through the structure member.

/*
	Description: 
		Give the type and member of a structure, as well as the member address of the target structure to be calculated, and get the self address of the target structure
	Parameters:
		struct_type: Structure type, such as struct student
		struct_menber: Structure member name (here is the name of the variable)
		elem_ptr: Target structure member address
	usage method:
		For example, the address tag1 of the member tag of the structure student1 has been obtained_ PTR, then you need to calculate the address of student1 structure:
			struct student* s = elem2entry(struct student, tag, tag1_ptr);
*/
#define elem2entry(struct_type, struct_member_name, elem_ptr) \
	 (struct_type*)((int)elem_ptr - offset(struct_type, struct_member_name))

For example, you want to calculate the address tag1 of the tag member in the student1 structure_ PTR, calculate the starting address of student1 structure:

struct list_elem* tag1_ptr = .....;// Get the address of the structure member tag
struct student* target_student = elem2entry(struct student, tag, tag1_ptr);

Brief description:

  • According to the address tag1 of the passed in structure member tag_ PTR, and then subtract the member address offset in the struct student structure to calculate tag1_ The structure start address f of the structure member pointed to by PTR.
  • So according to the member's address tag1_ The value of PTR, and then subtract the offset to get the starting address of the structure.

4. Form a linked list

It solves how to take out the structure according to the structure members, so the remaining problems are very easy.

To form a linked list is to string all the linked list nodes together. Because the linked list node is universal (included in the data node), the operation of the linked list is also universal.

Here, I use a two-way linked list with head and tail nodes, that is, the structure is as follows:

It is mainly about the addition, deletion, modification and query of the linked list:

#define offset(struct_type,member) (int)(&((struct_type*)0)->member)
#define elem2entry(struct_type, struct_member_name, elem_ptr) \
	 (struct_type*)((int)elem_ptr - offset(struct_type, struct_member_name))

/**********   Define link list node member structure***********
*No data element is required in the node, only the pointer of the predecessor and successor nodes are required*/
struct list_elem {
   struct list_elem* prev; // Forequarters node
   struct list_elem* next; // Successor node
};

/* Linked list structure is used to realize queue */
struct list {
/* head It is the head of the team and is fixed. It is not the first element. The first element is head next */
   struct list_elem head;
/* tail It is the tail of the team, which is also fixed */
   struct list_elem tail;
};
/* Initialize bidirectional linked list */
void list_init (struct list* list) {
   list->head.prev = NULL;
   list->head.next = &list->tail;
   list->tail.prev = &list->head;
   list->tail.next = NULL;
}

/* Insert the linked list element elem before the element before */
void list_insert_before(struct list_elem* before, struct list_elem* elem) { 
/* Update the successor element of the before precursor element to elem to temporarily separate the before from the linked list*/ 
   before->prev->next = elem; 

/* Update elem's own precursor node to the precursor of before,
 * Update elem's own successor node to be before, so before returns to the linked list */
   elem->prev = before->prev;
   elem->next = before;

/* Update the precursor node of before to elem */
   before->prev = elem;
}

/* Add elements to the top of the list, similar to the stack push operation */
void list_push(struct list* plist, struct list_elem* elem) {
   list_insert_before(plist->head.next, elem); // Insert elem at the head of the team
}

/* Append elements to the end of the linked list queue, similar to the first in first out operation of the queue */
void list_append(struct list* plist, struct list_elem* elem) {
   list_insert_before(&plist->tail, elem);     // Insert in front of the tail of the team
}

/* Detach element pelem from the linked list */
void list_remove(struct list_elem* pelem) {
   pelem->prev->next = pelem->next;
   pelem->next->prev = pelem->prev;
}

/* Pop up and return the first element of the linked list, similar to the pop operation of the stack */
struct list_elem* list_pop(struct list* plist) {
   struct list_elem* elem = plist->head.next;
   list_remove(elem);
   return elem;
} 

/* Find element obj from linked list_ Elem, returns true on success and false on failure */
bool elem_find(struct list* plist, struct list_elem* obj_elem) {
   struct list_elem* elem = plist->head.next;
   while (elem != &plist->tail) {
      if (elem == obj_elem) {
	 return true;
      }
      elem = elem->next;
   }
   return false;
}

/* Pass each element elem and arg in the list plist to the callback function func,
 * arg Give func to judge whether elem meets the conditions
 * The function of this function is to traverse all elements in the list and judge whether there are qualified elements one by one.
 * Find the qualified element and return the element pointer; otherwise, return NULL */
struct list_elem* list_traversal(struct list* plist, function func, int arg) {
   struct list_elem* elem = plist->head.next;
/* If the queue is empty, there must be no qualified nodes, so NULL is returned directly */
   if (list_empty(plist)) { 
      return NULL;
   }

   while (elem != &plist->tail) {
      if (func(elem, arg)) {		  // When func returns true, it is considered that the element meets the conditions in the callback function and hits, so it stops traversing
	 return elem;
      }					  // If the callback function func returns true, continue to traverse
      elem = elem->next;	       
   }
   return NULL;
}

/* Return linked list length */
uint32_t list_len(struct list* plist) {
   struct list_elem* elem = plist->head.next;
   uint32_t length = 0;
   while (elem != &plist->tail) {
      length++; 
      elem = elem->next;
   }
   return length;
}

/* Judge whether the linked list is empty. If it is empty, return true; otherwise, return false */
bool list_empty(struct list* plist) {		// Judge whether the queue is empty
   return (plist->head.next == &plist->tail ? true : false);
}

Topics: C data structure linked list Operating System