首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Linux内核中container_of宏深度刨析

Linux内核中container_of宏深度刨析

作者头像
byte轻骑兵
发布2026-01-21 15:01:35
发布2026-01-21 15:01:35
850
举报

在 Linux 内核编程里,常常会把一个通用的数据结构(如链表节点)嵌入到其他自定义结构体中。当我们通过某种操作获取到这个通用数据结构成员的地址时,若想访问包含它的整个自定义结构体,就可以借助 container_of 宏来实现。它为内核开发者提供了一种灵活且高效的方式来处理复杂的数据结构嵌套问题。

一、定义与实现

在 Linux 内核源码的 include/linux/kernel.h 文件中,container_of 宏的定义如下:

代码语言:javascript
复制
#define container_of(ptr, type, member) ({                      \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})

或者在某些内核版本中,它可能被定义为:

代码语言:javascript
复制
#define container_of(ptr, type, member) ({ \
    void *__mptr = (void *)(ptr); \
    BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \
                     !__same_type(*(ptr), void), \
                     "pointer type mismatch in container_of()"); \
    ((type *)((char *)__mptr - offsetof(type, member))); \
})
  • ptr:指向结构体成员的指针。
  • type:包含该成员的结构体类型。
  • member:结构体中的成员名称。

二、container_of宏的工作原理

计算偏移量:首先,使用offsetof宏计算成员在结构体中的偏移量。offsetof宏的定义通常如下:

代码语言:javascript
复制
#define offsetof(type, member) ((size_t)&(((type *)0)->member))

这里,((type *)0)是一个指向类型type的空指针,通过访问其member成员并取地址,得到的是该成员相对于结构体起始位置的偏移量。

指针调整:然后,从成员指针ptr中减去这个偏移量,得到结构体的起始地址。由于ptr是指向成员的指针,因此需要先将其转换为char*类型(因为char的大小是1字节,方便进行字节级别的指针算术),然后再减去偏移量,最后将其转换回type*类型。

类型检查(在某些版本中):在将ptr转换为void*之后,使用BUILD_BUG_ON_MSG宏和__same_type函数进行类型检查,确保ptr确实是指向目标结构体内部成员的指针。如果类型不匹配,编译将失败并显示错误信息。

三、使用场景

3.1. 内核设备驱动中的使用

在 Linux 内核设备驱动开发中,常常会将设备相关的数据封装在一个结构体里,同时包含一个链表节点用于将该设备加入到全局设备链表中。当通过链表节点操作设备时,就需要使用 container_of 宏来获取设备结构体的指针,进而访问设备的其他信息。

代码语言:javascript
复制
#include <stdio.h>
#include <stddef.h>

// 定义链表节点结构体
struct list_head {
    struct list_head *next, *prev;
};

// 定义设备结构体
typedef struct {
    int device_id;
    char device_name[20];
    struct list_head list;
} Device;

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

// 全局设备链表头
struct list_head device_list;

// 初始化链表头
void init_device_list() {
    device_list.next = &device_list;
    device_list.prev = &device_list;
}

// 添加设备到链表
void add_device(Device *device) {
    device->list.next = device_list.next;
    device->list.prev = &device_list;
    device_list.next->prev = &device->list;
    device_list.next = &device->list;
}

// 遍历设备链表
void traverse_devices() {
    struct list_head *pos;
    for (pos = device_list.next; pos != &device_list; pos = pos->next) {
        Device *dev = container_of(pos, Device, list);
        printf("Device ID: %d, Name: %s\n", dev->device_id, dev->device_name);
    }
}

int main() {
    init_device_list();

    // 创建两个设备
    Device dev1 = {1, "Device1"};
    Device dev2 = {2, "Device2"};

    // 添加设备到链表
    add_device(&dev1);
    add_device(&dev2);

    // 遍历设备链表
    traverse_devices();

    return 0;
}

3.2. 链表操作

在Linux内核中,链表是一种常用的数据结构,用于管理动态分配的对象集合。链表节点通常嵌入在其他结构体中,这样可以在节点中存储与特定应用相关的数据。通过链表节点的地址,可以使用container_of宏来获取包含该节点的结构体的地址,从而访问和操作该结构体的其他成员。

代码语言:javascript
复制
#include <linux/list.h>
#include <linux/kernel.h>

// 定义一个包含链表节点的结构体
struct my_node {
    int data;
    struct list_head list;
};

// 初始化链表头
LIST_HEAD(my_list);

// 添加节点到链表的函数
void add_node(int value) {
    struct my_node *new_node = kmalloc(sizeof(*new_node), GFP_KERNEL);
    if (!new_node)
        return;

    new_node->data = value;
    INIT_LIST_HEAD(&new_node->list);
    list_add_tail(&new_node->list, &my_list);
}

// 遍历链表并打印节点数据的函数
void print_list() {
    struct list_head *pos;
    list_for_each(pos, &my_list) {
        // 使用container_of宏获取包含链表节点的结构体的地址
        struct my_node *node = container_of(pos, struct my_node, list);
        printk(KERN_INFO "Node data: %d\n", node->data);
    }
}

// 清理链表的函数
void cleanup_list() {
    struct list_head *pos, *n;
    list_for_each_safe(pos, n, &my_list) {
        struct my_node *node = container_of(pos, struct my_node, list);
        list_del(pos);
        kfree(node);
    }
}

// 在内核模块初始化时调用
static int __init my_module_init(void) {
    add_node(1);
    add_node(2);
    add_node(3);
    print_list();
    cleanup_list();
    return 0;
}

// 在内核模块卸载时调用
static void __exit my_module_exit(void) {
    // 清理工作已在cleanup_list中完成
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("byte轻骑兵");
MODULE_DESCRIPTION("A simple module demonstrating container_of with linked lists.");

3.3. 内核事件管理中的使用

在内核的事件管理系统中,不同类型的事件可能会有不同的数据结构。为了统一管理这些事件,可以将一个通用的事件节点嵌入到每个具体事件结构体中。当处理事件时,通过事件节点的地址使用 container_of 宏获取具体事件结构体的指针,以执行相应的处理逻辑。

代码语言:javascript
复制
#include <stdio.h>
#include <stddef.h>

// 定义事件节点结构体
struct event_node {
    struct event_node *next;
    int event_type;
};

// 定义具体事件结构体
typedef struct {
    int data;
    struct event_node node;
} CustomEvent;

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

// 全局事件链表头
struct event_node event_list;

// 初始化事件链表头
void init_event_list() {
    event_list.next = NULL;
}

// 添加事件到链表
void add_event(struct event_node *node) {
    node->next = event_list.next;
    event_list.next = node;
}

// 处理事件
void process_events() {
    struct event_node *pos;
    for (pos = event_list.next; pos != NULL; pos = pos->next) {
        if (pos->event_type == 1) {
            CustomEvent *event = container_of(pos, CustomEvent, node);
            printf("Processing custom event with data: %d\n", event->data);
        }
    }
}

int main() {
    init_event_list();

    // 创建一个自定义事件
    CustomEvent custom_event = {42};
    custom_event.node.event_type = 1;

    // 添加事件到链表
    add_event(&custom_event.node);

    // 处理事件
    process_events();

    return 0;
}

3.4. 内核内存池管理中的使用

在内核的内存池管理中,为了方便管理内存块,可以在每个内存块结构体中嵌入一个管理节点。当需要对内存块进行操作(如释放、标记使用状态等)时,通过管理节点的地址使用 container_of 宏获取内存块结构体的指针。

代码语言:javascript
复制
#include <stdio.h>
#include <stddef.h>

// 定义内存块管理节点结构体
struct mem_block_node {
    struct mem_block_node *next;
    int is_used;
};

// 定义内存块结构体
typedef struct {
    char data[100];
    struct mem_block_node node;
} MemoryBlock;

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

// 全局内存块链表头
struct mem_block_node mem_block_list;

// 初始化内存块链表头
void init_mem_block_list() {
    mem_block_list.next = NULL;
}

// 添加内存块到链表
void add_mem_block(MemoryBlock *block) {
    block->node.next = mem_block_list.next;
    mem_block_list.next = &block->node;
    block->node.is_used = 0;
}

// 释放内存块
void free_mem_block(struct mem_block_node *node) {
    MemoryBlock *block = container_of(node, MemoryBlock, node);
    block->node.is_used = 0;
    printf("Memory block freed.\n");
}

int main() {
    init_mem_block_list();

    // 创建一个内存块
    MemoryBlock mem_block;

    // 添加内存块到链表
    add_mem_block(&mem_block);

    // 模拟使用内存块
    mem_block.node.is_used = 1;

    // 释放内存块
    free_mem_block(&mem_block.node);

    return 0;
}

四、注意事项

  • 在使用container_of宏时,要确保传入的指针确实是指向目标结构体内部成员的指针,否则可能会导致未定义行为。
  • container_of宏依赖于正确的结构体布局和成员偏移量。如果结构体或成员的布局发生变化(例如通过添加或删除成员),宏的结果可能不再正确。
  • container_of宏通常在内核模块或驱动程序中使用,因为它依赖于C语言的类型和内存布局特性。

五、总结

container_of宏是 Linux 内核编程的关键工具,能依据结构体成员地址算出结构体起始地址。借助typeof获取成员类型,用offsetof计算偏移量,再通过指针运算得出结果。常用于链表、设备驱动等场景,极大提升了数据结构操作的灵活性与效率 。

六、参考文献

  • 《linux 内核宏 container_of 剖析(转载 - 值得一看)》:微信公众号文章。通过生动的例子解释了container_of宏的作用。
  • 《最详尽解释 Linux 内核源码中的 container_of 宏及其标准 C 版本实现》: CSDN 博客,作者参考了一些英文资料后详细解读了container_of宏。。
  • 《linux 内核 container_of 详解_linux 内核 of 是什么》:CSDN 博客。
  • 《【Linux 内核】container_of 宏浅析_containerof 宏的作用》: CSDN 博客,介绍了container_of宏可以根据结构体成员地址找到结构体变量地址以访问其他成员这一作用。
  • 《Linux 内核中的 container_of 函数简要介绍_linux container of》: CSDN 博客,对container_of宏的定义进行了分析,通过例子讲解了宏的使用方法。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-01-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、定义与实现
  • 二、container_of宏的工作原理
  • 三、使用场景
    • 3.1. 内核设备驱动中的使用
    • 3.2. 链表操作
    • 3.3. 内核事件管理中的使用
    • 3.4. 内核内存池管理中的使用
    • 四、注意事项
  • 五、总结
  • 六、参考文献
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档