跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C

深入解读 list_for_each_entry:Linux 内核链表遍历宏的实现

Linux 内核广泛使用 list_for_each_entry 宏遍历嵌入式链表。该宏基于 container_of 通过成员指针反推结构体首地址,利用 offsetof 计算偏移量,并以 for 循环依次访问每个节点。文章以 input 子系统和 RC map 为例展示用法,并深入拆解了 list_first_entry、list_next_entry、list_entry 和 container_of 的实现细节,最后指出了遍历起点和并发删除等注意事项。

指针猎手发布于 2026/6/17更新于 2026/8/2121 浏览

Linux 内核里有一类常见的操作:遍历由 list_head 串起来的结构体链表。list_for_each_entry 就是干这个事儿的。它藏在 include/linux/list.h,是一个宏,展开后是一个 for 循环。

/**
 * 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_struct 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))

参数很直观:pos 是循环游标,类型和你链表的节点类型一致;head 是链表头指针;member 是节点结构体里嵌的那个 list_head 字段的名字。用法上,只要你有一个以 LIST_HEAD(my_list) 定义的链表头,并且通过 list_add_tail 把节点加进去,就可以写:

struct my_struct {
    int data;
    struct list_head node;
};
struct my_struct *p;
list_for_each_entry(p, &my_list, node) {
    // 在这里访问 p->data
}

实际内核代码里到处是这个模式。比如 input 子系统遍历 handler 链表:

struct input_handler {
    void *private;
    ...
    const char *name;
    const struct input_device_id *id_table;
    struct list_head h_list;
    struct list_head node;
};

struct input_handle *handle;
static LIST_HEAD(input_handler_list);

// 先前用 list_add_tail(&dev->node, &input_handler_list) 加入链表
list_for_each_entry(handler, &input_handler_list, node)
    input_attach_handler(dev, handler);

或者 RC map 的注册和查找:

struct rc_map_list {
    struct list_head list;
    struct rc_map map;
};

static LIST_HEAD(rc_map_list);

static struct rc_map_list *seek_rc_map(const char *name)
{
    struct rc_map_list *map = NULL;
    spin_lock(&rc_map_lock);
    list_for_each_entry(map, &rc_map_list, list) {
        if (!strcmp(name, map->map.name)) {
            spin_unlock(&rc_map_lock);
            return map;
        }
    }
    spin_unlock(&rc_map_lock);
    return NULL;
}

int rc_map_register(struct rc_map_list *map)
{
    spin_lock(&rc_map_lock);
    list_add_tail(&map->list, &rc_map_list);
    spin_unlock(&rc_map_lock);
    return 0;
}

static struct rc_map_list encore_enltv_map = {
    .map = {
        .scan = encore_enltv,
        .size = ARRAY_SIZE(encore_enltv),
        .rc_type = RC_TYPE_UNKNOWN,
        .name = RC_MAP_ENCORE_ENLTV,
    }
};

static int __init init_rc_map_encore_enltv(void)
{
    return rc_map_register(&encore_enltv_map);
}

要想明白这个宏是怎么工作的,得看它依赖的几个助手宏:

#define list_first_entry(ptr, type, member) \
	list_entry((ptr)->next, type, member)

#define list_next_entry(pos, member) \
	list_entry((pos)->member.next, typeof(*(pos)), member)

#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)

整个链条归结到 container_of。

// ./kernel-3.18/include/linux/kernel.h
#define container_of(ptr, type, member) ({ \
	const typeof( ((type *)0)->member ) *__mptr = (ptr); \
	(type *)( (char *)__mptr - offsetof(type,member) );})

#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER)

container_of 的巧妙之处在于:它知道结构体里某个成员的地址 ptr,反推出整个结构体的起始地址。做法是先用 typeof(((type *)0)->member) 拿到成员的类型,将 ptr 赋给一个同类型的局部指针 __mptr,然后用 offsetof(type, member) 算出该成员在结构体里的偏移量,最后用 __mptr 减去这个偏移量,就得到了结构体指针。

offsetof 的实现很经典:把 0 强制类型转换为 (type*),那么成员 MEMBER 的地址就是它在结构体内的偏移。整个运算在编译时就能确定,没有运行时开销。

回到 list_for_each_entry:

  1. 初始化:pos = list_first_entry(head, typeof(*pos), member) 展开后是 container_of(head->next, typeof(*pos), member),也就是拿到链表头下一个元素的宿主结构体指针。注意,链表头本身不参与遍历,它的 next 指向第一个实际节点。
  2. 条件判断:&pos->member != (head) —— 当遍历指针的 member 地址等于链表头时,说明绕了一圈,循环结束。
  3. 步进:pos = list_next_entry(pos, member) 展开成 container_of(pos->member.next, typeof(*pos), member),从当前节点的 member.next 获取下一个节点的宿主结构体指针。

整个过程干净利落,完全基于编译时类型推导,没有对 void* 的显式转换,类型安全由编译器保证。

实际使用中有两点需要留意:第一,必须在链表头开始遍历,如果你从一个中间节点开始,条件 &pos->member != head 可能永远不成立,导致死循环。第二,list_for_each_entry 不保护遍历过程中的删除操作,如果需要安全删除,得用 list_for_each_entry_safe 这样的变体。

理解了这个宏,再看内核里的各种链表操作——设备链表、定时器链表、文件系统 inode 链表——就都一个套路了。

  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 鸿蒙 6.0 应用开发:仿微博文本折叠功能实现
  • Java 数据结构:链表原理与 LinkedList 核心应用
  • 全国计算机等级考试三级证书报考指南与科目解析
  • ComfyUI 工作流搭建指南:基于云环境的快速部署方案
  • C++ 哈希表封装:模拟实现 unordered_map 与 unordered_set
  • 海螺 AI 多模态架构解析与 Python 接入指南
  • 从人工运维到 ChatOps:大模型驱动运维自动化变革
  • GitHub 学生认证与 PyCharm Copilot 配置全流程指南
  • Visual Studio 中 GitHub Copilot 隐私设置与数据共享控制
  • Mac 下基于 Neo4j 与 py2neo 的知识图谱搭建步骤详解
  • 网络安全入门:新手如何从零开始学习漏洞挖掘
  • HarmonyOS6 RcInput 组件核心架构与类型系统设计
  • 深入理解前端防抖与节流:原理、区别与实战示例
  • Git 多人协作流程指南
  • GitHub Copilot Pro 学生免费认证与 VS Code 集成指南
  • 基于 YOLOv5 的车牌识别算法实现与训练
  • 基于单链表的队列完整实现
  • Spring Cloud Gateway 核心原理与实战指南
  • 使用 Python 构建机器学习预测模型全流程指南
  • OpenClaw v2026.3.8 全平台部署与安全配置指南

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online