Linux源码-通用双向链表的实现

我们之前在数据结构课程中学习的链表都是将数据放在链表节点中,然后将这些节点串成一个链表。例如:

c 复制代码
typedef struct Node {
    Node* prev;
    Node* next;
    
    T data_obj;
};

void addHead(Node* head, T data_obj);
......

这种实现方案直观容易理解,但是对于C语言这种非泛型的语言来说,需要为每个需要使用链表的数据结构都定义链表节点和一套链表操作,有没有更优雅的实现方案?

有的兄弟!Linux使用C语言开发,并且其各个模块中大量使用了链表。Linux的解决方案可以总结为一句话:"不将数据对象放在链表节点中,而是将链表节点存储在数据对象中"。

例如include/linux/mm.h中的mem_map_t数据结构:

c 复制代码
typedef struct page {
    struct list_head list;
......
    struct list_head lru;
......
} mem_map_t;

其中使用了两个链表,Linux并不是定义了两种链表节点同时管理该数据结构,而是在该数据结构中插入了两个链表节点。

关键问题:根据宿主结构体找list_head容易,但是如何根据list_head找到宿主结构体?

list_head的实现在include/linux/list.h中:

c 复制代码
struct list_head {
	struct list_head *next, *prev;
};

#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;
}

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;
}

static inline void list_add(struct list_head *new, struct list_head *head)
{
	__list_add(new, head, head->next);
}

其中链表的初始化和链表操作都很简单。重点关注如何根据链表节点指针,找到其宿主结构体的首地址,实现方法在宏函数list_entry()中:

c 复制代码
#define list_entry(ptr, type, member) \
    ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

参数解释:

  • ptrlist_head节点的地址
  • type,宿主结构体的类型,例如上例中的struct page
  • memberptr指向的list_head在宿主结构体中的字段名,例如上例中的lru

其中(&((type *)0)->member)))表示的是假设将0作为宿主结构体的起始地址,然后取其member的地址(其实就是member字段在宿主结构体中的偏移)。然后使用ptr减去该偏移,就得到了宿主结构体的首地址。

相关推荐
Android系统攻城狮1 分钟前
Linux PipeWire深度解析之pw_context_add_spa_lib调用流程与实战(二十九)
linux·运维·服务器·音频进阶·pipewire音频进阶
Dovis(誓平步青云)17 分钟前
《如何在CentOS 7中添加Plex官方软件源:解决文件磁盘难管理难题》
linux·运维·服务器·后端·生成对抗网络·centos
草莓熊Lotso28 分钟前
【Linux网络】深入理解Linux IO多路复用:select服务器完善、内核原理与poll实战
linux·运维·服务器·c语言·网络·c++
byte轻骑兵39 分钟前
BlueZ源码编译环境配置全指南:Linux桌面原生编译 + 嵌入式ARM交叉编译 + 定制裁剪与调试实战
linux·arm开发·蓝牙·bluez·电脑蓝牙
纪伊路上盛名在1 小时前
NVML ERROR_ RM has detected an NVML_RM version mismatch
linux·数据库·gpu·驱动
醉逍遥_祥1 小时前
Linux进程与NuttX任务(Linux Processes vs NuttX Tasks)
linux·单片机·嵌入式软件
XH华1 小时前
Linux系统第二章:常见的Linux指令(上)
linux·运维·服务器
FoldWinCard11 小时前
D5 Linux 网络及端口命令
linux·运维·服务器
零涂毕业设计12 小时前
毕业设计模块开发-OLED显示屏(IIC协议0.96寸)STM32 ESP32 FPGA Linux驱动免费分享
linux·stm32·单片机·嵌入式·esp32·fpga·oled
tedcloud12312 小时前
OmniRoute怎么部署?开源AI模型路由平台Linux部署教程
linux·服务器·人工智能·开源·音视频