Linked list, red-black tree and KFIFO in Linux kernel

  Linked list, red-black tree and KFIFO are widely used in the Linux kernel code.

1. Linked list

  The linux kernel code makes extensive use of the linked list data structure. The linked list is a data structure that solves the defect that the array cannot be dynamically expanded. The elements contained in the linked list can be dynamically created and inserted and deleted. Each element of the linked list is stored discretely, so it does not need to occupy continuous memory. A linked list usually consists of several nodes, and each node has the same structure, consisting of two parts, the effective data area and the pointer area. The valid data area is used to store valid data information, and the pointer area is used to point to the predecessor or successor node of the linked list. Therefore, a linked list is a storage structure that uses pointers to connect nodes in series.
  l The use of linked lists in the Linux kernel is ubiquitous, and it can be said to be the foundation of the foundation. The struct list_head structure variable is embedded in many data structures, which can add the structure to a doubly linked list. The interface for the initialization, addition, and deletion of the linked list is in nclude\linux\list.h. The files, kobjects, devices, drivers, etc. in the Kernel are all connected by the linked list.

1.1. Definition of linked list structure

  l The definition of the linked list structure is as follows, defined in include\linux\types.h

struct list_head {
    
    
	struct list_head *next, *prev;
};

  l Its members are two pointers to list_head, next points to the next linked list node, and prev points to the previous linked list node. It doesn't make much sense to use the linked list alone, and it is generally embedded in the "host structure". The code logic does not pay attention to the list itself, but uses the list to connect the "host structure" in series. The path of the linked list API in the source code is: include\linux\list.h

1.2. Initialization

  In the file include\linux\list.h

#define LIST_HEAD_INIT(name) {
      
       &(name), &(name) }

#define LIST_HEAD(name) \
	struct list_head name = LIST_HEAD_INIT(name)

static inline void INIT_LIST_HEAD(struct list_head *list)
{
    
    
	list->next = list;
	list->prev = list;
}

  l Initialize both the next and prev pointers and point to itself, thus initializing an empty linked list with the leading node.

1.3. Add and delete nodes

  l The operation of inserting a new node is very simple, that is, disconnect the original linked list from the insertion point, and then connect the new node

/*
 * Insert a new entry between two known consecutive entries.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
#ifndef CONFIG_DEBUG_LIST
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;
}
#else
extern void __list_add(struct list_head *new,
			      struct list_head *prev,
			      struct list_head *next);
#endif

/**
 * list_add - add a new entry
 * @new: new entry to be added
 * @head: list head to add it after
 *
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 */
static inline void list_add(struct list_head *new, struct list_head *head)
{
    
    
	__list_add(new, head, head->next);
}
/**
 * list_add_tail - add a new entry
 * @new: new entry to be added
 * @head: list head to add it before
 *
 * Insert a new entry before the specified head.
 * This is useful for implementing queues.
 */
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
    
    
	__list_add(new, head->prev, head);
}

  l Before inserting: head <–> next
  l After inserting: head <–> new <–> next
  l The same is true for deleting nodes, which is also achieved by re-changing the pointer content of the front and back nodes:

static inline void __list_del_entry(struct list_head *entry)
{
    
    
	__list_del(entry->prev, entry->next);
}

static inline void list_del(struct list_head *entry)
{
    
    
	__list_del(entry->prev, entry->next);
	entry->next = LIST_POISON1;
	entry->prev = LIST_POISON2;
}

  l The front and rear pointers of the deleted node are all pointed to a special address, which is used to identify that the linked list pointer cannot be used:

/*
 * These are non-NULL pointers that will result in page faults
 * under normal circumstances, used to verify that nobody uses
 * non-initialized list entries.
 */
#define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)
#define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA)

  l It can be seen from the code of adding and deleting nodes that there is no lock in the list. If there is a thread safety problem, the caller needs to lock it by himself.

1.4. Traversing the linked list

/**
 * list_for_each	-	iterate over a list
 * @pos:	the &struct list_head to use as a loop cursor.
 * @head:	the head for your list.
 */
#define list_for_each(pos, head) \
	for (pos = (head)->next; pos != (head); pos = pos->next)

/**
 * list_for_each_prev	-	iterate over a list backwards
 * @pos:	the &struct list_head to use as a loop cursor.
 * @head:	the head for your list.
 */
#define list_for_each_prev(pos, head) \
	for (pos = (head)->prev; pos != (head); pos = pos->prev)

/**
 * list_for_each_safe - iterate over a list safe against removal of list entry
 * @pos:	the &struct list_head to use as a loop cursor.
 * @n:		another &struct list_head to use as temporary storage
 * @head:	the head for your list.
 */
#define list_for_each_safe(pos, n, head) \
	for (pos = (head)->next, n = pos->next; pos != (head); \
		pos = n, n = pos->next)

/**
 * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
 * @pos:	the &struct list_head to use as a loop cursor.
 * @n:		another &struct list_head to use as temporary storage
 * @head:	the head for your list.
 */
#define list_for_each_prev_safe(pos, n, head) \
	for (pos = (head)->prev, n = pos->prev; \
	     pos != (head); \
	     pos = n, n = pos->prev)

/**
 * list_for_each_entry	-	iterate over list of given type
 * @pos:	the type * to use as a loop cursor.
 * @head:	the head for your list.
 * @member:	the name of the list_head within the struct.
 */
#define list_for_each_entry(pos, head, member)				\
	for (pos = list_first_entry(head, typeof(*pos), member);	\
	     &pos->member != (head);					\
	     pos = list_next_entry(pos, member))

  The llist_for_each function traverses the linked list in order from front to back, and continuously points to the next element of the element until the pointer of the element and the address of the head pointer of the linked list are the same, indicating that the traversal of the linked list is completed.
  The llist_for_each_prev function traverses forward from the tail element of the linked list.
  The llist_for_each_safe function introduces a pointer n to store the address of the next element of pos. The introduction of pointer n can facilitate the deletion of the element pointed to by pos when traversing the linked list without affecting the traversal. list_for_each can't do that.
  The difference between the llist_for_each_prev_safe function and the list_for_each_safe function is to traverse from the back to the front.
  The llist_for_each_entry function is a combination of list_for_each and list_entry. It has three parameters: pos, head, and member. pos is an intermediate variable that points to the currently accessed linked list element, head refers to the head of the linked list, and member refers to the name of the linked list member variable in the structure pointed to by pos. ,

1.5. Find the elements of the linked list

/**
 * list_entry - get the struct for this entry
 * @ptr:	the &struct list_head pointer.
 * @type:	the type of the struct this is embedded in.
 * @member:	the name of the list_head within the struct.
 */
#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)

  The list_entry macro has three parameters ptr, type, member. ptr refers to the address of the struct list_head variable member in the data structure, type refers to the type of the data structure, and member refers to the variable name of the struct list_head in the data structure. The result of the list_entry macro is the variable address of the type data structure pointed to by ptr.

1.6. Example of use

#include <linux/kernel.h>  
#include <linux/module.h>  
#include <linux/init.h>  
#include <linux/slab.h>  
#include <linux/list.h>    
//实际数据结构
struct student  
{
    
      
   char name[100];  
   int num;  
   struct list_head list;  
};  
//链表的头结点, (无数据)  
struct list_head student_list;  
static int mylist_init(void)  
{
    
      
        int i = 0;  
        printk( "###############################\n");      
        INIT_LIST_HEAD(&student_list);              //链表头结点student_list前驱和后继都指向自身  
        struct student *pstudent;
        pstudent= kmalloc(sizeof(struct student)*5,GFP_KERNEL);  //申请了5个结点的内存,内存地址是pstudent  ,而且这5个内存块是连续的。
        memset(pstudent,0,sizeof(struct student)*5);
        printk(KERN_INFO "************list_add_tail************\n");
        //1. 向链表添加结点
        for(i=0;i<5;i++)  
        {
    
      
                sprintf(pstudent[i].name,"Student%d",i+1);  
                pstudent[i].num= i+1;  
                list_add_tail(&(pstudent[i].list), &student_list);      //尾插法, 添加到链表的末尾
                //list_add_tail(&(pstudent[i].list), &student_list);    //头插法
                printk("<0>---student%d name: %s\n",pstudent[i].num,pstudent[i].name);
        }
        printk(KERN_INFO "************list_for_each************\n");
        //2. 从头依次遍历节点  方法一
        struct student *tmp_student;
        struct list_head *pos;
        list_for_each(pos,&student_list)  
        {
    
      
                //pos 是 每个节点中list 成员的地址
                tmp_student= list_entry(pos,struct student,list);  //根据pos获取每个节点的地址并赋值给tmp_student
                printk("<1>---student%d name: %s\n",tmp_student->num,tmp_student->name);  
        }

        printk(KERN_INFO "************list_for_each_entry************\n");
        //3. 从头依次遍历节点   方法二:  方法一的简化版
        struct student *tmp_student2;
        list_for_each_entry(tmp_student2,&student_list, list)  
        {
    
      
                printk("<2>---student%d name: %s\n",tmp_student2->num,tmp_student2->name);  
        }
                for(i=0;i<5;i++)  
                {
    
      
                list_del(&(pstudent[i].list));      
                }  
                
                kfree(pstudent);  
                printk( "###############################\n");
        return 0;  
}   
static void mylist_exit(void)  
{
    
            
}    
module_init(mylist_init);  
module_exit(mylist_exit);
MODULE_LICENSE("GPL");  

  Test the code in the ubuntu virtual machine, the makefile is as follows:

KERNELDIR :=/usr/src/linux-headers-5.4.0-149-generic
CURRENT_PATH := $(shell pwd)
#禁用签名
CONFIG_MODULE_SIG=n

obj-m := list.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

  The running result is shown in the figure below:
insert image description here

Two, red-black tree

  Red Black Tree (Red Black Tree) is widely used in kernel memory management and process scheduling to organize sorted elements into trees. Red-black trees are widely used in various fields of computer science, and they provide a good balance between speed and implementation complexity.
  A red-black tree is a binary tree with the following characteristics.
  Each node is either red or black.
  Each leaf node is black.
  If the node is both red, then both child nodes are black.
  On a simple path from an internal node to a leaf node, the number of black nodes is the same for all leaf nodes.
  One advantage of red-black trees is that all significant operations (such as insert, delete, search) can be done in O(log n) time, where n is the number of elements in the tree. Here is just an example of using a red-black tree in the kernel. This example can be found in the documentation/Rbtree.txt file of the kernel code.

#include <linux/init.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/rbtree.h>



  struct mytype {
    
     
     struct rb_node node;
     int key; 
};

/*红黑树根节点*/
 struct rb_root mytree = RB_ROOT;
/*根据key来查找节点*/
struct mytype *my_search(struct rb_root *root, int new)
  {
    
    
     struct rb_node *node = root->rb_node;

     while (node) {
    
    
          struct mytype *data = container_of(node, struct mytype, node);

          if (data->key > new)
               node = node->rb_left;
          else if (data->key < new)
               node = node->rb_right;
          else
               return data;
     }
     return NULL;
  }

/*插入一个元素到红黑树中*/
  int my_insert(struct rb_root *root, struct mytype *data)
  {
    
    
     struct rb_node **new = &(root->rb_node), *parent=NULL;

     /* 寻找可以添加新节点的地方 */
     while (*new) {
    
    
          struct mytype *this = container_of(*new, struct mytype, node);

          parent = *new;
          if (this->key > data->key)
               new = &((*new)->rb_left);
          else if (this->key < data->key) {
    
    
               new = &((*new)->rb_right);
          } else
               return -1;
     }

     /* 添加一个新节点 */
     rb_link_node(&data->node, parent, new);
     rb_insert_color(&data->node, root);

     return 0;
  }

static int __init my_init(void)
{
    
    
     int i;
     struct mytype *data;
     struct rb_node *node;

     /*插入元素*/
     for (i =0; i < 20; i+=2) {
    
    
          data = kmalloc(sizeof(struct mytype), GFP_KERNEL);
          data->key = i;
          my_insert(&mytree, data);
     }

     /*遍历红黑树,打印所有节点的key值*/
      for (node = rb_first(&mytree); node; node = rb_next(node)) 
          printk("key=%d\n", rb_entry(node, struct mytype, node)->key);

     return 0;
}

static void __exit my_exit(void)
{
    
    
     struct mytype *data;
     struct rb_node *node;
     for (node = rb_first(&mytree); node; node = rb_next(node)) {
    
    
          data = rb_entry(node, struct mytype, node);
          if (data) {
    
    
                rb_erase(&data->node, &mytree);
                kfree(data);
          }
     }
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");

  mytree is the root node of the red-black tree, my_insert() implements inserting an element into the red-black tree, and my_search() finds the node according to the key. The kernel uses red-black trees extensively, such as the management of the virtual address space VMA.
  Regarding the red-black tree, you can refer to:
  How to use the red-black tree in the Linux kernelAnalysis of the
  insertion principle of the red-black tree node in the
  Linux kernelAnalysis of the deletion principle of the red-black tree node in the Linux kernel

3. Lock-free ring buffer kfifo

3.1 Introduction to kfifo

3.1.1. Function of kfifo

  The producer and consumer model is one of the most common in computer programming. The producer produces data, and the consumer consumes data, such as a network device, the hardware device receives network packets, and then the application program reads the network packets. A ring buffer is a classic algorithm for implementing the producer and consumer model. A ring buffer usually has a read pointer and a write pointer. The read pointer points to the readable data in the ring buffer, and the write pointer points to the writable data in the ring buffer. The buffer data is read and written by moving the read pointer and write pointer. FIFOs are primarily used to buffer communications with mismatched speeds.
  In the Linux kernel, KFIFO is implemented using a lock-free ring buffer. The full name of FIFO is "First In First Out", which is a first-in-first-out data structure. It is implemented by a ring buffer method and provides an unbounded byte stream service. The advantage of using a ring buffer is that when a data element is consumed, the remaining data elements do not need to move their storage locations, thereby reducing duplication and improving efficiency.

3.1.2, kfifo principle

  kfifo is the implementation of the queue function of the linux kernel. In the kernel, it's called a lock-free ring queue. The so-called lock-free means that when there is only one producer and only one consumer, no lock is required to operate fifo. This is because when kfifo is dequeued and enqueued, it will not change to the same variable.
  kfifo uses two variables in and out as the index of entering and exiting the queue respectively:

  When entering n data into the queue, the in variable will be +n.
  When k data is out of the queue, the out variable will be +k.
  out is not allowed to be greater than in (when out is equal to in, the fifo is empty)
   in is not allowed to be larger than out and exceed the fifo space
   if in and out are larger than the fifo space, these two indexes will always be added forward, and it is not easy to look back, saving a few instruction cycles for the operation of entering and exiting the queue.
   Where does the data entering and exiting the queue start to be stored/read? We would immediately think that it is enough to use "%" for in/out to take the remainder of the fifo size, right? No, how can kernel developers easily adopt this resource-consuming operation of taking the remainder? The kfifo method is to combine in/out with fifo->mask. This mask is equal to the space size of fifo minus one (it requires that the space of fifo must be a power of 2). This "and" operation is much faster than the remainder operation.
   As a result, kfifo implements a "lock-free" "ring" queue.
   After understanding the above principles, we can realize that this lock-free is only for "single producer-single consumer". In the case of "multiple producers", it is necessary to lock the enqueue operation; similarly, in the case of "multiple consumers", it is necessary to lock the dequeue operation.

3.2. Implementation of kfifo in linux

  The source code of kfifo is in the include/linux/kfifo.h file of the linux kernel

3.2.1. Structure

  The structure of kfifo is as follows:

struct __kfifo {
    
    
	unsigned int	in;//入队
	unsigned int	out;//出队
	unsigned int	mask;//大小掩码
	unsigned int	esize;大小
	void		*data;队列缓存指针
};

3.2.2. Initialization and memory application

  Before using KFIFO, it needs to be initialized. There are two ways of static initialization and dynamic initialization.
  1), dynamic application
  is dynamically initialized as kfifo_alloc in the file include/linux/kfifo.h:

/**
 * kfifo_alloc - dynamically allocates a new fifo buffer
 * @fifo: pointer to the fifo
 * @size: the number of elements in the fifo, this must be a power of 2
 * @gfp_mask: get_free_pages mask, passed to kmalloc()
 *
 * This macro dynamically allocates a new fifo buffer.
 *
 * The numer of elements will be rounded-up to a power of 2.
 * The fifo will be release with kfifo_free().
 * Return 0 if no error, otherwise an error code.
 */
#define kfifo_alloc(fifo, size, gfp_mask) \
__kfifo_int_must_check_helper( \
({
      
       \
	typeof((fifo) + 1) __tmp = (fifo); \
	struct __kfifo *__kfifo = &__tmp->kfifo; \
	__is_kfifo_ptr(__tmp) ? \
	__kfifo_alloc(__kfifo, size, sizeof(*__tmp->type), gfp_mask) : \
	-EINVAL; \
}) \
)

  This function creates and allocates a KFIFO ring buffer of size size. The first parameter fifo is the struct kfifo data structure pointing to the ring buffer; the second parameter size is the number of specified buffer elements; the third parameter gfp_mask indicates the allocation mask used to allocate KFIFO elements.
  The __kfifo_alloc function is in the \lib\kfifo.c file:

int __kfifo_alloc(struct __kfifo *fifo, unsigned int size,
		size_t esize, gfp_t gfp_mask)
{
    
    
	/*
	 * round down to the next power of 2, since our 'let the indices
	 * wrap' technique works only in this case.
	 */
	size = roundup_pow_of_two(size);

	fifo->in = 0;
	fifo->out = 0;
	fifo->esize = esize;

	if (size < 2) {
    
    
		fifo->data = NULL;
		fifo->mask = 0;
		return -EINVAL;
	}

	fifo->data = kmalloc(size * esize, gfp_mask);

	if (!fifo->data) {
    
    
		fifo->mask = 0;
		return -ENOMEM;
	}
	fifo->mask = size - 1;

	return 0;
}

  Through the code, you can see the memory space that kfifo finally applies for, which is the power of 2 of the space requested by the caller. For example, if you want to apply for 7 bytes, you end up applying for 8 bytes; if you want to apply for 9 bytes, you end up applying for 16 bytes. In this way, the in/out index can be "ANDed" with the mask size mask, and the queue loop can be realized (avoiding the remainder calculation).
  If you don't understand this rule, you may step on the pit. For example, a program wants to apply for 100 bytes (use the dynamic method of kfifo_alloc or the element size is 1), but the actual application is 128 bytes without knowing it. Assume that this program is 10 bytes each time it enters and exits the queue. When the fifo is full, the last 10 bytes that enter the queue actually only save 8 bytes. After that, it will still be dequeued by 10 bytes each time. , it will always misplace 2 bytes.
2) Static application
  Static allocation can use the following macros.

#define DEFINE_KFIFO(fifo, type, size)
#define INIT_KFIFO(fifo)

  Defined in include/linux/kfifo.h file

/**
 * DEFINE_KFIFO - macro to define and initialize a fifo
 * @fifo: name of the declared fifo datatype
 * @type: type of the fifo elements
 * @size: the number of elements in the fifo, this must be a power of 2
 *
 * Note: the macro can be used for global and local fifo data type variables.
 */
#define DEFINE_KFIFO(fifo, type, size) \
	DECLARE_KFIFO(fifo, type, size) = \
	(typeof(fifo)) {
      
       \
		{
      
       \
			{
      
       \
			.in	= 0, \
			.out	= 0, \
			.mask	= __is_kfifo_ptr(&(fifo)) ? \
				  0 : \
				  ARRAY_SIZE((fifo).buf) - 1, \
			.esize	= sizeof(*(fifo).buf), \
			.data	= __is_kfifo_ptr(&(fifo)) ? \
				NULL : \
				(fifo).buf, \
			} \
		} \
	}
/**
 * INIT_KFIFO - Initialize a fifo declared by DECLARE_KFIFO
 * @fifo: name of the declared fifo datatype
 */
#define INIT_KFIFO(fifo) \
(void)({
      
       \
	typeof(&(fifo)) __tmp = &(fifo); \
	struct __kfifo *__kfifo = &__tmp->kfifo; \
	__kfifo->in = 0; \
	__kfifo->out = 0; \
	__kfifo->mask = __is_kfifo_ptr(__tmp) ? 0 : ARRAY_SIZE(__tmp->buf) - 1;\
	__kfifo->esize = sizeof(*__tmp->buf); \
	__kfifo->data = __is_kfifo_ptr(__tmp) ?  NULL : __tmp->buf; \
})

3.2.3, enqueue operation

  Writing data into the KFIFO ring buffer can use the kfifo_in() function interface.

int kfifo_in(fifo, buf, n)

  This function copies the n data pointed by the buf pointer to the KFIFO ring buffer. The first parameter fifo refers to the KFIFO ring buffer; the second parameter buf points to the buffer of the data to be copied; the third data is the number of data elements to be copied.
  The kfifo_in() function is defined in the include/linux/kfifo.h file

/**
 * kfifo_in - put data into the fifo
 * @fifo: address of the fifo to be used
 * @buf: the data to be added
 * @n: number of elements to be added
 *
 * This macro copies the given buffer into the fifo and returns the
 * number of copied elements.
 *
 * Note that with only one concurrent reader and one concurrent
 * writer, you don't need extra locking to use these macro.
 */
#define	kfifo_in(fifo, buf, n) \
({
      
       \
	typeof((fifo) + 1) __tmp = (fifo); \
	typeof(__tmp->ptr_const) __buf = (buf); \
	unsigned long __n = (n); \
	const size_t __recsize = sizeof(*__tmp->rectype); \
	struct __kfifo *__kfifo = &__tmp->kfifo; \
	(__recsize) ?\
	__kfifo_in_r(__kfifo, __buf, __n, __recsize) : \
	__kfifo_in(__kfifo, __buf, __n); \
})

  The __kfifo_in() function is defined in the \lib\kfifo.c file

unsigned int __kfifo_in(struct __kfifo *fifo,
		const void *buf, unsigned int len)
{
    
    
	unsigned int l;

	l = kfifo_unused(fifo);
	if (len > l)
		len = l;

	kfifo_copy_in(fifo, buf, len, fifo->in);
	fifo->in += len;
	return len;
}

  There are 3 steps to enqueue:
  query the remaining space (confirm the maximum length that can be enqueued),
  copy data into the memory,
  in index, update
  the used space is in-out, and the total space is mask+1:

3.2.4, dequeue operation

  To list or extract data from the KFIFO ring buffer, you can use the kfifo_out() function interface.

#define    kfifo_out(fifo, buf, n)

  This function copies n data elements from the ring buffer pointed to by fifo to the buffer pointed to by buf. If the data elements of the KFIFO ring buffer are less than n, the copied data elements are less than n.
  The kfifo_out() function is defined in the include/linux/kfifo.h file


/**
 * kfifo_out - get data from the fifo
 * @fifo: address of the fifo to be used
 * @buf: pointer to the storage buffer
 * @n: max. number of elements to get
 *
 * This macro get some data from the fifo and return the numbers of elements
 * copied.
 *
 * Note that with only one concurrent reader and one concurrent
 * writer, you don't need extra locking to use these macro.
 */
#define	kfifo_out(fifo, buf, n) \
__kfifo_uint_must_check_helper( \
({
      
       \
	typeof((fifo) + 1) __tmp = (fifo); \
	typeof(__tmp->ptr) __buf = (buf); \
	unsigned long __n = (n); \
	const size_t __recsize = sizeof(*__tmp->rectype); \
	struct __kfifo *__kfifo = &__tmp->kfifo; \
	(__recsize) ?\
	__kfifo_out_r(__kfifo, __buf, __n, __recsize) : \
	__kfifo_out(__kfifo, __buf, __n); \
}) \
)

  The dequeue operation is similar to the enqueue, where the __kfifo_out() function is defined in the \lib\kfifo.c file

3.3 Example of using kfifo

#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/mutex.h>
#include <linux/kfifo.h>
struct kfifo Kfifo_Test;
//定义fifo最大保存的元素个数
#define DM_FIFO_ELEMENT_MAX     1024
static int mykfifo_init(void)
{
    
    
	char buf[100];
    int i = 0;
    int ret = 0;
    printk(KERN_INFO "###############################\n");
    //申请fifo内存空间,一般在模块初始化时调用
    printk(KERN_INFO "************kfifo_alloc************\n");
    ret = kfifo_alloc(&Kfifo_Test, DM_FIFO_ELEMENT_MAX, GFP_KERNEL);
    if (ret) 
    {
    
    
        printk(KERN_ERR "kfifo_alloc fail ret=%d\n", ret);
        return 1;
    }
    printk(KERN_INFO "************kfifo_in************\n");
	for ( i = 0; i < 10; i++) 
    {
    
    
        memset(buf, 0x00, 100);
		memset(buf, 'a' + i, i + 1);
		kfifo_in(&Kfifo_Test, buf, i + 1);
        printk(KERN_ERR "kfifo_in:%s\n", buf);
	}
    printk(KERN_INFO "************kfifo_out************\n");
	while (!kfifo_is_empty(&Kfifo_Test)) 
    {
    
    
        memset(buf, 0x00, 100);
		ret = kfifo_out(&Kfifo_Test, buf, sizeof(buf));
		printk(KERN_INFO "kfifo_out: %s\n",buf);
	}
    printk(KERN_INFO "************kfifo_free************\n");
    //释放内存空间,一般在模块退出时调用
    kfifo_free(&Kfifo_Test);
    printk(KERN_INFO "###############################\n");
return 0;
}
static void mykfifo_exit(void)  
{
    
            
}   
module_init(mykfifo_init);  
module_exit(mykfifo_exit);
MODULE_LICENSE("GPL");  

  Test the code in the ubuntu virtual machine, the makefile is as follows:

KERNELDIR :=/usr/src/linux-headers-5.4.0-149-generic
CURRENT_PATH := $(shell pwd)
#禁用签名
CONFIG_MODULE_SIG=n

obj-m := kfifo.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

  The running result is shown in the figure below:insert image description here

Guess you like

Origin blog.csdn.net/xxxx123041/article/details/131735814