linux-IDR 机制及相关API 介绍

1.概述

    IDR(Integer ID Allocator)是 Linux 内核提供的小整数 ID 动态管理机制,核心目标是解决 "整数 ID 与指针的双向映射" 问题。在内核开发中,大量场景需要为对象(如设备、文件、共享内存块)分配唯一整数标识(ID),并支持通过 ID 快速查找对应对象。IDR 通过基数树(Radix Tree)实现高效的 ID 分配、查找与释放,兼顾动态扩展性与查询性能。

1.1 与其他 ID 管理方案的对比

方案优势劣势适用场景
数组查找 O (1),实现简单需预先知道最大 ID 范围,浪费内存ID 范围固定且密集(如文件描述符)
哈希表平均查找 O (1)冲突处理复杂,内存开销大ID 范围大但查询频繁
链表动态扩展,内存紧凑查找 O (n),性能差ID 数量极少的场景
IDR动态扩展,查找 O (logN),内存高效实现较复杂动态 ID 分配、稀疏 ID 范围、需双向映射

IDR 的核心优势在于对稀疏 ID 范围的高效支持。例如,当需要分配 1~10000 的 ID 但实际仅使用其中 100 个时,数组会浪费 99% 的内存,而 IDR 通过基数树仅存储实际使用的 ID 节点,大幅减少内存开销。

1.2 典型应用场景

IDR 在 Linux 内核中应用广泛,典型场景包括:

  • 设备驱动:管理设备号(如块设备、字符设备的主 / 次设备号);

  • DRM 子系统:为 GPU 共享内存对象(GEM Object)分配 handle;

  • 进程管理:为线程、信号量等对象分配标识 ID;

  • 网络子系统:管理套接字、连接的唯一标识符;

  • 文件系统:为 inode、dentry 等对象分配临时 ID。

以 DRM 为例,当用户态程序通过ioctl创建 GEM 对象时,内核需分配一个 32 位 handle(ID)与对象绑定,用户态通过 handle 操作对象,内核通过 IDR 快速将 handle 映射到内核对象指针。

2. IDR 底层实现原理

2.1 基数树(Radix Tree)基础

IDR 的底层数据结构是基数树—— 一种多叉树,每个节点的子节点数量为 2^n(称为 "基数")。Linux 内核中 IDR 使用的基数树通常为64 叉树(节点子节点数为 64),通过将 ID 分解为多个 6 位的片段(64=2^6),每段作为树的一层索引。

2.1.1 32 位 ID 的层级分解

32 位 ID 被拆分为 5 个 6 位片段(6*5=30,剩余 2 位作为最高层):

  • 第 1 层(根节点):ID 的 26~31 位(6 位);

  • 第 2 层:ID 的 20~25 位(6 位);

  • 第 3 层:ID 的 14~19 位(6 位);

  • 第 4 层:ID 的 8~13 位(6 位);

  • 第 5 层:ID 的 2~7 位(6 位);

  • 第 6 层(叶子节点):ID 的 0~1 位(2 位)。

2.1.2 动态节点创建

基数树采用延迟创建策略:仅当需要存储 ID 时,才动态分配对应层级的节点。例如,分配 ID=0 时,仅创建根节点和叶子节点,中间层节点无需分配,大幅节省内存。

这种分解方式确保 32 位 ID 可被基数树完全覆盖,查找时通过逐层解析 ID 片段定位叶子节点,叶子节点中存储与 ID 关联的指针。

2.2 IDR 核心数据结构

IDR 的核心结构体定义在<linux/idr.h>中,关键成员如下:

struct idr {
    struct radix_tree_root idr_rt;  // 基数树根节点
    unsigned int idr_next;          // 下一个尝试分配的ID(优化连续分配)
    struct mutex idr_mutex;         // 保护IDR操作的互斥锁(可选)
    // 其他内部成员(如版本号、最大深度等)
};
  • idr_rt:基数树的根节点,所有 ID 映射通过该树管理;
  • idr_next:记录下一次分配 ID 时的起始点,优化连续 ID 分配效率(避免从 0 开始查找);
  • idr_mutex:用于并发控制,多线程访问时需加锁保护(可通过DEFINE_IDR_WITH_LOCK自动绑定锁)。

3. IDR 核心 API 详解

3.1 初始化与销毁

3.1.1 idr_init():手动初始化
void idr_init(struct idr *idp);
3.1.2 DEFINE_IDR():静态初始化
#define DEFINE_IDR(name) \
    struct idr name = { .idr_rt = RADIX_TREE_INIT, .idr_next = 0 }
  • 功能:静态声明并初始化 IDR 结构体,等价于idr_init()但更简洁;
  • 优势:编译期初始化,无需运行时调用函数,适合全局或静态 IDR;
3.1.3 DEFINE_IDR_WITH_LOCK():带锁的静态初始化
#define DEFINE_IDR_WITH_LOCK(name) \
    struct idr name = { .idr_rt = RADIX_TREE_INIT, \
                       .idr_next = 0, \
                       .idr_mutex = __MUTEX_INITIALIZER(name.idr_mutex) }
  • 功能:销毁 IDR,释放基数树中所有节点的内存;

  • 注意事项

    • 销毁前需确保所有 ID 已通过idr_remove()释放(否则会导致内存泄漏);

    • 销毁后 IDR 不可再使用,除非重新初始化;

3.2 ID 分配

3.2.1 基础分配:idr_alloc()
int idr_alloc(struct idr *idp, void *ptr, int start, 
                int end, gfp_t gfp_mask);
  • 参数详解
    • idp:指向已初始化的 IDR 结构体;

    • ptr:与 ID 关联的指针(如设备结构体指针);

    • start:分配的起始 ID(含),通常设为 0;

    • end:分配的结束 ID(不含),0 表示使用默认最大值(INT_MAX);

    • gfp_mask:内存分配标志(如GFP_KERNEL允许休眠,GFP_ATOMIC用于中断上下文);

  • 返回值

    • 成功:返回分配的 ID(≥start 且 < end);

    • 失败:返回负数(-ENOSPC表示 ID 范围耗尽,-ENOMEM表示内存不足)。

示例:为设备分配 ID(1~100 范围)

struct my_device dev;  // 设备结构体
int id = idr_alloc(&my_idr, &dev, 1, 100, GFP_KERNEL);
if (id < 0) {
    pr_err("ID allocation failed: %d\n", id);
    return id;
}
3.2.2 循环分配:idr_alloc_cyclic()
int idr_alloc_cyclic(struct idr *idp, void *ptr, int start, int end, gfp_t gfp_mask);
  • 功能:与idr_alloc()类似,但当 ID 范围接近上限时,从start重新开始分配(循环重用旧 ID);

  • 适用场景:ID 可能频繁创建与销毁,需要避免 ID 无限增长(如进程 ID 循环分配);

3.3 ID 查找:idr_find()

void *idr_find(const struct idr *idp, int id);
  • 功能:通过 ID 查找关联的指针;

  • 参数

    • idp:目标 IDR 结构体;

    • id:要查找的 ID(必须是通过idr_alloc()分配的合法 ID);

  • 返回值

    • 成功:返回与 ID 关联的指针(需强制类型转换);

    • 失败:返回NULL(ID 不存在或已被移除);

  • 性能:时间复杂度为 O (log₆₄N),N 为已分配的 ID 数量;

3.4 ID 释放:idr_remove()

void idr_remove(struct idr *idp, int id);
  • 功能:释放指定 ID,从基数树中删除该 ID 与指针的映射(不释放指针指向的内存);

  • 参数

    • idp:目标 IDR 结构体;

    • id:要释放的 ID(必须存在);

  • 注意事项

    • 释放后该 ID 可被重新分配;

    • 需手动释放指针指向的对象内存(如kfree(dev))。

3.5 遍历 IDR:idr_for_each()idr_for_each_entry()

3.5.1 回调遍历:idr_for_each()
int idr_for_each(const struct idr *idp,
                 int (*fn)(int id, void *p, void *data),
                 void *data);
  • 功能:遍历 IDR 中所有已分配的 ID,对每个 ID 执行回调函数fn

  • 参数

    • fn:回调函数,原型为int (*fn)(int id, void *p, void *data),返回 0 继续遍历,非 0 终止;

    • data:传递给回调函数的上下文数据;

3.5.2 条目遍历:idr_for_each_entry()
#define idr_for_each_entry(idr, entry, id) \
    for (id = 0; ((entry) = idr_get_next((idr), &(id))) != NULL; ++id)
  • 功能:通过循环遍历 IDR,直接获取每个 ID 关联的对象指针;

  • 参数

    • entry:循环中当前对象的指针(需声明为目标类型);

    • id:循环中当前的 ID 值(需声明为int);

3.5.3 范围遍历:idr_get_next()
void *idr_get_next(const struct idr *idp, int *nextid);
  • 功能:从*nextid开始查找下一个已分配的 ID,适合自定义范围遍历;

  • 示例:查找 ID≥10 的第一个设备:

int id = 10;
struct my_device *dev = idr_get_next(&my_idr, &id);
if (dev) {
    pr_info("First ID ≥10: %d\n", id);
}

4. 并发控制与最佳实践

IDR 本身不保证线程安全,多线程同时访问(如分配 / 查找 / 删除)时需通过锁保护。推荐使用以下两种方式:

4.1 显式加锁(适合复杂场景)
static DEFINE_IDR(my_idr);
static DEFINE_MUTEX(my_idr_lock);  // 独立的互斥锁

// 分配ID时加锁
mutex_lock(&my_idr_lock);
int id = idr_alloc(&my_idr, dev, 0, 0, GFP_KERNEL);
mutex_unlock(&my_idr_lock);

// 查找ID时加锁
mutex_lock(&my_idr_lock);
struct my_device *dev = idr_find(&my_idr, id);
mutex_unlock(&my_idr_lock);
4.2 内置锁(适合简单场景)

使用DEFINE_IDR_WITH_LOCK绑定内置锁,通过idr_lock()/idr_unlock()操作。

static DEFINE_IDR_WITH_LOCK(my_idr);  // 带内置锁的IDR

// 分配ID时加锁
idr_lock(&my_idr);
int id = idr_alloc(&my_idr, dev, 0, 0, GFP_KERNEL);
idr_unlock(&my_idr);

5. 完整示例:设备驱动中的 IDR 应用

以下是一个字符设备驱动中使用 IDR 管理设备 ID 的完整示例。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/idr.h>
#include <linux/mutex.h>

// 设备结构体
struct my_dev {
    int id;             // IDR分配的ID
    char name[32];      // 设备名称
    struct cdev cdev;   // 字符设备结构体
};

// IDR与锁
static DEFINE_IDR_WITH_LOCK(dev_idr);  // 带内置锁的IDR
static dev_t dev_base;                 // 设备号基址
static const int MAX_DEVICES = 10;     // 最大设备数量

// 设备操作函数(简化)
static int my_open(struct inode *inode, struct file *filp) {
    int id = iminor(inode) - 1;  // 从次设备号获取ID
    struct my_dev *dev;

    idr_lock(&dev_idr);
    dev = idr_find(&dev_idr, id);  // 通过ID查找设备
    idr_unlock(&dev_idr);

    if (!dev)
        return -ENODEV;

    filp->private_data = dev;
    return 0;
}

static const struct file_operations my_fops = {
    .owner = THIS_MODULE,
    .open = my_open,
    // 其他操作(read/write等)
};

// 初始化设备
static int create_devices(void) {
    int i, ret;
    struct my_dev *dev;

    // 申请设备号(假设主设备号动态分配)
    ret = alloc_chrdev_region(&dev_base, 1, MAX_DEVICES, "my_dev");
    if (ret < 0)
        return ret;

    // 批量创建设备并分配ID
    idr_preload(GFP_KERNEL);
    idr_lock(&dev_idr);

    for (i = 0; i < MAX_DEVICES; i++) {
        dev = kzalloc(sizeof(*dev), GFP_NOWAIT);  // 非阻塞分配(预加载已保证内存)
        if (!dev) {
            ret = -ENOMEM;
            goto err;
        }

        // 分配ID(范围1~MAX_DEVICES)
        dev->id = idr_alloc(&dev_idr, dev, 1, MAX_DEVICES + 1, GFP_NOWAIT);
        if (dev->id < 0) {
            ret = dev->id;
            kfree(dev);
            goto err;
        }

        // 初始化字符设备
        snprintf(dev->name, sizeof(dev->name), "my_dev%d", dev->id);
        cdev_init(&dev->cdev, &my_fops);
        cdev_add(&dev->cdev, MKDEV(MAJOR(dev_base), dev->id), 1);
    }

    idr_unlock(&dev_idr);
    idr_preload_end();
    return 0;

err:
    // 出错时清理已分配的设备
    idr_for_each_entry(&dev_idr, dev, i) {
        idr_remove(&dev_idr, dev->id);
        cdev_del(&dev->cdev);
        kfree(dev);
    }
    idr_unlock(&dev_idr);
    idr_preload_end();
    unregister_chrdev_region(dev_base, MAX_DEVICES);
    return ret;
}

// 模块退出时清理
static void cleanup_devices(void) {
    struct my_dev *dev;
    int id;

    idr_lock(&dev_idr);
    // 遍历所有设备并释放
    idr_for_each_entry(&dev_idr, dev, id) {
        idr_remove(&dev_idr, id);
        cdev_del(&dev->cdev);
        kfree(dev);
    }
    idr_unlock(&dev_idr);
    idr_destroy(&dev_idr);  // 销毁IDR
    unregister_chrdev_region(dev_base, MAX_DEVICES);
}

module_init(create_devices);
module_exit(cleanup_devices);
MODULE_LICENSE("GPL");

示例说明

  • DEFINE_IDR_WITH_LOCK创建带内置锁的 IDR,确保多线程安全;

  • 批量创建设备时使用idr_preload优化内存分配;

  • 通过 IDR 管理设备 ID 与struct my_dev的映射,open时通过次设备号(ID)查找设备;

  • 退出时遍历 IDR 释放所有设备,避免内存泄漏。

6. 总结

IDR 机制通过基数树实现了高效的 ID 管理,兼顾动态扩展性与查询性能,是 Linux 内核中处理整数 ID 映射的核心方案。其核心优势包括:

  • 动态分配:支持 ID 范围动态扩展,无需预先定义大小;

  • 高效查找:基数树保证 O (logN) 的查找复杂度,适合大量 ID 场景;

  • 内存高效:延迟创建节点,对稀疏 ID 范围友好。

在实际开发中,需注意并发控制(加锁)、错误处理(检查返回值)及资源清理(释放 ID 与对象),结合DEFINE_IDR_WITH_LOCK等宏可简化使用。掌握 IDR 机制对内核驱动、子系统开发至关重要,尤其在需要管理大量动态对象的场景中能显著提升代码质量与性能。

 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值