当前位置: 首页 > news >正文

Linux内核中的container_of宏详解 - 指南

Linux内核中的container_of宏详解 - 指南

作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生,研究方向无线联邦学习
擅长领域:驱动开发,嵌入式软件开发,BSP开发
❄️作者主页:一个平凡而乐于分享的小比特的个人主页
✨收录专栏:Linux,本专栏目的在于,记录学习Linux操作系统的总结
欢迎大家点赞 收藏 ⭐ 加关注哦!

在这里插入图片描述

Linux内核中的container_of宏详解

1. 什么是container_of宏?

1.1 基本概念

container_of是Linux内核中一个非常巧妙且常用的宏,它的作用是通过结构体成员的地址反向推导出包含该成员的结构体的地址。

1.2 类比理解

想象一下这样的场景:

2. 为什么需要container_of

2.1 问题背景

在Linux内核编程中,经常使用链表来管理各种数据结构。一个典型的设计模式是:

// 示例:内核链表节点结构
struct list_head {
struct list_head *next, *prev;
};
// 业务数据结构
struct my_struct {
int data;
struct list_head list;  // 嵌入的链表节点
};

问题:当我们遍历链表时,只能拿到list_head的指针,如何获取包含它的my_struct结构体呢?

2.2 对比表格:传统方法 vs container_of

特性传统方法(直接访问)container_of方法
数据组织结构体包含指针指向数据数据包含链表节点
内存效率需要额外指针,效率低内存紧凑,效率高
代码复杂度简单直观需要宏包装,但复用性高
典型场景普通应用编程内核、嵌入式等系统编程
可维护性修改时需要同步多处结构体修改影响小

3. container_of的原型与实现

3.1 宏定义

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

或者更简洁的版本:

#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))

3.2 参数解释

参数说明表格:
┌─────────┬─────────────────────────────────────────┐
│ 参数    │ 说明                                    │
├─────────┼─────────────────────────────────────────┤
│ ptr     │ 结构体成员的指针                        │
│ type    │ 包含该成员的结构体类型                  │
│ member  │ 成员在结构体中的名称                    │
└─────────┴─────────────────────────────────────────┘

4. 实现机制逐步解析

4.1 核心原理图示

内存布局示意图:
┌─────────────────────────────────────────────┐
│            struct container                 │
│  ┌──────────────────────────────────────┐  │
│  │ 成员1                                │  │
│  ├──────────────────────────────────────┤  │
│  │ 成员2                                │  │
│  ├──────────────────────────────────────┤  │
│  │ ...                                  │  │
│  ├──────────────────────────────────────┤  │
│  │ member (我们已知它的地址: ptr)       │←─┘ 已知点
│  │                                      │
│  ├──────────────────────────────────────┤
│  │ 其他成员...                          │
│  └──────────────────────────────────────┘
└─────────────────────────────────────────────┘
↑
container_of计算出的结构体起始地址

4.2 关键步骤拆解

步骤1:计算成员偏移量(offsetof)
// offsetof宏计算成员在结构体中的偏移
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)

工作原理

  1. (TYPE *)0:将0地址强制转换为TYPE类型指针
  2. &((TYPE *)0)->MEMBER:获取成员在"假想"结构体中的地址
  3. 因为结构体起始地址为0,成员的地址就是它的偏移量

示例计算

struct example {
int a;      // 偏移量:0
char b;     // 偏移量:4(假设int为4字节)
double c;   // 偏移量:8(考虑内存对齐)
};
// offsetof(struct example, c) = 8
步骤2:反向计算结构体地址
计算公式:
结构体地址 = 成员地址 - 成员偏移量↓
container = ptr - offsetof(type, member)

4.3 完整计算示例

// 实际数据结构
struct person {
int age;
char name[20];
struct list_head node;  // 链表节点
};
// 假设我们知道node的地址
struct list_head *node_ptr = &some_person.node;
// 使用container_of
struct person *person_ptr = container_of(node_ptr, struct person, node);
// 展开计算过程:
// 1. offsetof(struct person, node) = 
//    (age:4字节 + name:20字节) = 24字节(假设对齐)
// 2. 结构体地址 = node_ptr - 24

5. 图解计算过程

内存地址可视化:
地址       内容                        说明
0x1000     [age]                      person结构体开始
0x1004     [name[0..19]]              name字段
0x1018     [node.next]                ← node成员开始
0x1020     [node.prev]                我们已知这里的地址:ptr=0x1018
计算过程:
ptr = 0x1018 (node的地址)
offset = 0x18 (24字节,node在person中的偏移)
person地址 = 0x1018 - 0x18 = 0x1000 ✓

6. 实际应用示例

6.1 链表遍历示例

#include <stdio.h>#include <stddef.h>// 简化版offsetof和container_of#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)#define container_of(ptr, type, member) \((type *)((char *)(ptr) - offsetof(type, member)))// 链表节点struct list_head {struct list_head *next;};// 业务数据struct task {int pid;int priority;struct list_head list;  // 嵌入的链表节点};int main() {// 创建一些任务struct task task1 = {.pid = 100, .priority = 1};struct task task2 = {.pid = 101, .priority = 2};// 链接它们(简化版)task1.list.next = &task2.list;// 遍历链表:从list_head获取完整的task结构struct list_head *current = &task1.list;while (current != NULL) {// 关键步骤:通过list_head找到包含它的taskstruct task *task_ptr = container_of(current, struct task, list);printf("Found task: pid=%d, priority=%d\n",task_ptr->pid, task_ptr->priority);current = current->next;}return 0;}

6.2 内核中的实际使用

// Linux内核中的真实示例(简化)
struct inode {
// ... 各种字段 ...
struct hlist_node i_hash;  // 哈希链表节点
};
// 在哈希表中查找inode
struct hlist_node *node = ...;  // 从哈希表获取的节点
// 通过节点找到inode
struct inode *inode = container_of(node, struct inode, i_hash);

7. 技术细节与注意事项

7.1 类型安全版本

// 内核中的实际实现包含类型检查
#define container_of(ptr, type, member) ({              \
void *__mptr = (void *)(ptr);                       \
BUILD_BUG_ON(!__same_type(*(ptr), ((type *)0)->member) && \
!__same_type(*(ptr), void));           \
((type *)(__mptr - offsetof(type, member))); })

7.2 内存对齐考虑

内存对齐示例:
struct aligned_example {char a;      // 地址:0// 填充3字节(假设int需要4字节对齐)int b;       // 地址:4short c;     // 地址:8
};
offsetof(struct aligned_example, c) = 8
而不是 1 + 4 = 5

7.3 优势总结

优势说明
类型安全编译时检查类型匹配
高效编译时计算偏移,运行时只有减法操作
通用适用于任何结构体和成员类型
内存友好不需要为链表单独分配节点内存

8. 常见问题解答

Q1: 为什么用char*指针做减法?

A: char*指针的加减法以字节为单位,而其他类型指针的加减法以类型大小为步长。

Q2: 这个宏安全吗?

A: 如果正确使用是安全的,但需要确保:

  1. ptr确实指向type结构体中的member
  2. member确实是type的成员

Q3: 在用户空间可以使用吗?

A: 可以,但需要自己实现或使用<stddef.h>中的offsetof

9. 扩展应用

9.1 嵌套结构体

struct inner {
int data;
};
struct outer {
char tag;
struct inner embed;
float value;
};
struct inner *inner_ptr = &some_outer.embed;
struct outer *outer_ptr = container_of(inner_ptr, struct outer, embed);

9.2 多个链表节点

struct complex_struct {
int id;
struct list_head hash_node;   // 用于哈希表
struct list_head lru_node;    // 用于LRU缓存
struct list_head free_node;   // 用于空闲列表
};
// 同一个结构体可以同时存在于多个链表中

总结

container_of宏是Linux内核中嵌入式数据结构模式的核心工具,它体现了C语言指针运算的强大能力。通过巧妙的偏移量计算,实现了从部分到整体的逆向查找,是内核高效链表实现的基础。

核心要点记忆

  1. 已知:成员地址 + 成员名 + 结构体类型
  2. 计算:偏移量 = offsetof(type, member)
  3. 结果:结构体地址 = 成员地址 - 偏移量
  4. 关键:所有计算都在编译时或简单的运行时完成,效率极高

这种设计模式不仅在Linux内核中广泛应用,在许多嵌入式系统、高性能服务器和底层库中都有重要应用。

http://www.jsqmd.com/news/389561/

相关文章:

  • OFA模型在Typora中的图像描述插件开发
  • 影子学习(Shadow Learning)
  • StructBERT模型安全防护:对抗样本检测与防御
  • Svelte深度解析
  • LeetCode 378 有序矩阵中第 K 小的元素 - 指南
  • 2026年口碑好的H型钢管/贵州H型钢管热门厂家推荐汇总 - 行业平台推荐
  • 手把手教你用REX-UniNLU做社交媒体情感监测
  • 2026年知名的上海低碳矿山/智慧矿山实力推荐榜厂家 - 行业平台推荐
  • OFA-large模型使用教程:Pillow+requests图片加载与英文文本预处理要点
  • 基于EmbeddingGemma-300m的语义搜索系统开发实战
  • Janus-Pro-7B论文精读:解读统一多模态架构设计思想
  • 人工智能应用- 推荐算法:01. 什么是推荐算法
  • 实测才敢推 10个降AIGC软件测评:MBA降AI率必备工具推荐
  • 人工智能应用- 推荐算法:02.推荐算法的基本思想
  • translategemma-27b-it图文教程:Ollama安装与多语言翻译实战
  • 这次终于选对!10个AI论文平台测评:研究生毕业论文与科研写作必备工具推荐
  • ERNIE-4.5-0.3B-PT持续学习方案:灾难性遗忘应对策略
  • 2026必备!10个AI论文网站深度测评,自考毕业论文写作与格式规范全攻略
  • 2026年老工厂车间升级改造浙江标准化工厂布局/标准化工厂布局用户认可推荐企业 - 行业平台推荐
  • 互联网大厂Java面试实录:智慧城市场景下的核心技术与AI应用
  • 2026年比较好的洗衣机柜一体盆/异形洗衣机柜定制源头直供参考哪家便宜 - 行业平台推荐
  • 2026年口碑好的西安一体盆洗衣柜/整体阳台洗衣柜销售厂家推荐哪家好(真实参考) - 行业平台推荐
  • 2026年口碑好的防晒洗衣柜/西安洗衣柜畅销厂家采购指南如何选 - 行业平台推荐
  • 真的太省时间!继续教育专属的一键生成工具 —— 千笔写作工具
  • 2026年口碑好的石英石台面橱柜/厨房橱柜定做生产商实力参考哪家质量好(更新) - 行业平台推荐
  • DeepSeek写论文AI率99%怎么急救?3步降到安全线(实测有图)
  • 别再瞎找了!8个降AI率软件降AIGC网站:继续教育必备测评与推荐
  • 基于SpringBoot+协同过滤推荐算法+智能AI推荐的影院票务管理平台开题报告
  • 2026年评价高的双联齿轮滚齿机/行星齿轮滚齿机哪家强生产厂家实力参考 - 行业平台推荐
  • 写作小白救星!千笔AI,深得人心的降AIGC工具