Linux内核中存在一个非常有用的数据结构list_head,其定义如下:

struct list_head {
    struct list_head *prev;
    struct list_head *next;
};
这个结构在内核的许多地方出现,是很多数据结构的组成部分。通过这个部件,这些数据很容易组成一个双向链表。相比为每个需要构造双向链表的结构体都提供构建链表相关的数据和操作,统一的链表管理在代码维护、代码简洁性、代码的可靠性方面更具优势。现在分几点进行讨论。


1. list_head的用法

list_head作为一个组件嵌入到其他的结构体中,如下图所示(来自understand the linux kernel)


特别要注意的是prev是指向上一个结构体中list_head的位置而不是指向上一个结构体,next也一样。


2. list_entry

这个宏的功能主要在于当你知道了某个结构体中list_head的地址时求出这个结构体的地址,是不是很高级

在include/linux/list.h中定义了list_entry

 /**  * 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_struct within the struct.  
  */
 #define list_entry(ptr, type, member) \   
    container_of(ptr, type, member)

所以我们还得找到container_of的定义,它也是一个宏,定义在了

#define container_of(ptr, type, member) ({          \
   const typeof( ((type *)0)->member ) *__mptr = (ptr);    \ 
   (type *)( (char *)__mptr - offsetof(type,member) );})

在更早期的版本中的定义有点不太一样:

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

现就更早版本进行说明,ptr是结构体中的member成员的指针,type是目标结构体的类型(我们的目的就是为type类型的结构体求出地址),其实思想非常简单,就是ptr的地址减去member成员在type中的偏移量,然后就得出了type结构体的地址。


现通过一个例子让读者更容易理解

#include <stdio.h>
#include <stdlib.h>

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

struct test_list{
	int testdata;            // 用于测试的数据域
	struct list_head list;   // 将list_head类型嵌套进来
};

int main(void)
{
	struct test_list a;
	struct test_list *p;
	a.testdata = 5;    // 将数据赋值

       // 求出地址
	p = (struct test_list *)((char *)(&a.list) 
			- (unsigned long)(&(((struct test_list *)0)->list)));

      //通过输出证明正确性
	printf("testdata = %d\n", p->testdata);
	return 0;
}




Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐