Linux内核idr数据结构使用

Linux 内核的 idr(ID 映射)是一种用于将整数 ID 与指针(内核对象)关联的数据结构,核心功能是高效分配、查找、释放 ID,并通过 ID 快速定位对应的内核对象(如设备、会话、缓冲区等)。以下是其详细用法:

一、核心概念

idr 解决的问题:当需要用唯一整数 ID 标识动态创建的内核对象(如多个网卡实例、进程私有资源)时,idr 可自动分配不重复的 ID,并建立 ID 到对象指针的映射,方便通过 ID 快速访问对象。

二、使用步骤

1. 头文件与数据结构

需包含头文件 <linux/idr.h>,核心结构体为 struct idr

cpp 复制代码
#include <linux/idr.h>

struct idr my_idr; // 定义一个 idr 实例
2. 初始化与销毁
  • 初始化 :使用 idr_init() 初始化 idr 结构(必须在使用前调用)。
  • 销毁 :使用 idr_destroy() 释放资源(需确保所有 ID 已释放)。
cpp 复制代码
// 初始化
idr_init(&my_idr);

// 销毁(通常在模块退出时)
idr_destroy(&my_idr);
3. ID 分配(关联对象)

idr 提供多种分配方式,分配时需指定要关联的对象指针:

函数原型 功能描述
int idr_alloc(struct idr *idr, void *ptr, int start, int end, gfp_t gfp) [start, end) 范围内分配最小可用 ID,关联到 ptrstart=0 表示从 0 开始,end=0 表示无上限)。
int idr_alloc_cyclic(struct idr *idr, void *ptr, int start, int end, gfp_t gfp) 循环分配 ID(超过 end 后从 start 重新开始),适合需要循环利用 ID 的场景。
  • 参数说明
    • ptr:要关联的内核对象指针(如 struct my_device *)。
    • gfp:内存分配标志(GFP_KERNEL 允许睡眠,GFP_ATOMIC 用于中断上下文)。
    • 返回值:成功返回分配的 ID(非负整数),失败返回负数(-ENOMEM 内存不足,-ENOSPC 无可用 ID)。
4. 通过 ID 查找对象

使用 idr_find() 根据 ID 查找关联的对象指针,支持无锁并发读(依赖 RCU 机制):

cpp 复制代码
void *idr_find(const struct idr *idr, unsigned int id);
  • 返回值:成功返回关联的指针,失败返回 NULL(ID 未分配或已释放)。
5. 释放 ID 与解除关联

使用 idr_remove() 释放指定 ID,并解除与对象的关联(对象需手动释放):

cpp 复制代码
void idr_remove(struct idr *idr, unsigned int id);
6. 迭代所有 ID 与对象

使用 idr_for_each() 遍历所有已分配的 ID 及其关联对象:

cpp 复制代码
int idr_for_each(struct idr *idr, 
                 int (*fn)(int id, void *p, void *data), 
                 void *data);
  • fn:回调函数,每个 ID 会触发一次调用(id 为当前 ID,p 为关联的指针,data 为用户传入的参数)。
  • 返回值:回调函数返回非 0 时终止遍历,否则返回 0。

三、并发安全处理

idr 内部通过 RCU 和自旋锁保证并发安全:

  • 读操作(idr_find :需在 rcu_read_lock()/rcu_read_unlock() 保护下执行(无锁,高效)。
  • 写操作(分配 / 释放):需通过自旋锁保护(避免并发修改)。
cpp 复制代码
#include <linux/spinlock.h>
#include <linux/rculist.h>

static DEFINE_SPINLOCK(idr_lock); // 保护写操作的自旋锁

// 分配 ID 并关联对象(带锁)
int alloc_id_and_obj(struct my_obj *obj) {
    int id;
    unsigned long flags;

    spin_lock_irqsave(&idr_lock, flags); // 加锁(禁止中断)
    id = idr_alloc(&my_idr, obj, 1, 0, GFP_ATOMIC); // 从 ID=1 开始分配
    spin_unlock_irqrestore(&idr_lock, flags); // 解锁

    return id;
}

// 通过 ID 查找对象(无锁读)
struct my_obj *find_obj_by_id(int id) {
    struct my_obj *obj;

    rcu_read_lock(); // 进入 RCU 读临界区
    obj = idr_find(&my_idr, id);
    rcu_read_unlock(); // 退出读临界区

    return obj;
}

// 释放 ID 与对象(带锁)
void free_id_and_obj(int id) {
    struct my_obj *obj;
    unsigned long flags;

    spin_lock_irqsave(&idr_lock, flags);
    obj = idr_find(&my_idr, id); // 先查找对象
    if (obj) {
        idr_remove(&my_idr, id); // 释放 ID
        kfree(obj); // 手动释放对象(idr 不管理对象内存)
    }
    spin_unlock_irqrestore(&idr_lock, flags);
}

四、完整示例(模块中使用)

cpp 复制代码
#include <linux/module.h>
#include <linux/idr.h>
#include <linux/spinlock.h>
#include <linux/slab.h>

// 定义需要用 ID 标识的对象
struct my_obj {
    int value;
};

static struct idr my_idr;
static DEFINE_SPINLOCK(idr_lock);

// 遍历回调函数:打印 ID 和对象值
static int print_id_obj(int id, void *p, void *data) {
    struct my_obj *obj = p;
    printk("ID: %d, value: %d\n", id, obj->value);
    return 0; // 继续遍历
}

static int __init idr_demo_init(void) {
    int id1, id2;
    struct my_obj *obj1, *obj2;

    // 初始化 IDR
    idr_init(&my_idr);

    // 创建对象
    obj1 = kzalloc(sizeof(*obj1), GFP_KERNEL);
    obj2 = kzalloc(sizeof(*obj2), GFP_KERNEL);
    if (!obj1 || !obj2)
        return -ENOMEM;
    obj1->value = 100;
    obj2->value = 200;

    // 分配 ID 并关联对象
    spin_lock_irq(&idr_lock);
    id1 = idr_alloc(&my_idr, obj1, 1, 0, GFP_ATOMIC);
    id2 = idr_alloc(&my_idr, obj2, 1, 0, GFP_ATOMIC);
    spin_unlock_irq(&idr_lock);
    printk("Allocated IDs: %d, %d\n", id1, id2);

    // 遍历所有 ID 和对象
    idr_for_each(&my_idr, print_id_obj, NULL);

    return 0;
}

static void __exit idr_demo_exit(void) {
    // 释放 ID 和对象(简化示例,实际需保存 id1、id2)
    spin_lock_irq(&idr_lock);
    idr_remove(&my_idr, id1);
    idr_remove(&my_idr, id2);
    kfree(idr_find(&my_idr, id1)); // 假设退出前仍能找到对象
    kfree(idr_find(&my_idr, id2));
    spin_unlock_irq(&idr_lock);

    // 销毁 IDR
    idr_destroy(&my_idr);
    printk("IDR demo exited\n");
}

module_init(idr_demo_init);
module_exit(idr_demo_exit);
MODULE_LICENSE("GPL");
相关推荐
King's King4 小时前
自动化仓库规划设计方案
运维·自动化
码农-小林4 小时前
使用leaflet库加载服务器离线地图瓦片(这边以本地nginx服务器为例)
运维·服务器·nginx
噜啦噜啦嘞好4 小时前
Linux:库制作与原理
linux·运维·服务器
androidstarjack4 小时前
知乎服务器崩溃!
运维·服务器
---学无止境---4 小时前
Linux中将EFI从物理模式切换到虚拟模式efi_enter_virtual_mode函数的实现
linux
刘某的Cloud5 小时前
磁盘-IO
linux·运维·系统·磁盘io
我狸才不是赔钱货5 小时前
容器:软件世界的标准集装箱
linux·运维·c++·docker·容器
云知谷5 小时前
【嵌入式基本功】单片机嵌入式学习路线
linux·c语言·c++·单片机·嵌入式硬件
zxsz_com_cn5 小时前
设备健康管理大数据平台:工业智能化的核心数据引擎
运维·人工智能