PowerVR RA

2025-10-24 53 0

Resource Allocator (RA) 代码详细分析

整体架构概述

Resource Allocator 是一个通用的资源分配器,最初用于地址空间管理,但实际上可以管理任意的整数集合。

核心架构图

┌─────────────────────────────────────────────────────────────┐
│                      RA_ARENA (资源竞技场)                    │
├─────────────────────────────────────────────────────────────┤
│  - 名称、量子大小、策略标志                                    │
│  - 导入/释放回调函数                                          │
│  - 锁机制                                                     │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌────────────────┐    ┌──────────────────┐                │
│  │ Segment List   │    │  Free Table      │                │
│  │ (双向链表)      │    │  (Splay Tree +   │                │
│  │                │    │   Buckets)       │                │
│  └────────────────┘    └──────────────────┘                │
│         │                       │                           │
│         │                       │                           │
│         ▼                       ▼                           │
│  ┌──────────────────────────────────┐                       │
│  │    Boundary Tags (BT)            │                       │
│  │  - base: 资源基址                 │                       │
│  │  - uSize: 资源大小                │                       │
│  │  - type: free/live               │                       │
│  │  - flags: 标志                    │                       │
│  └──────────────────────────────────┘                       │
│                                                              │
│  ┌────────────────┐                                         │
│  │ Segment Hash   │  (快速查找已分配资源)                    │
│  └────────────────┘                                         │
└─────────────────────────────────────────────────────────────┘

核心数据结构

Boundary Tag (BT) 结构

struct _BT_ {
    enum bt_type {
        btt_free,    /* 空闲资源段 */
        btt_live     /* 已分配资源段 */
    } type;

    unsigned int is_leftmost;   /* 是否为导入区间的最左边 */
    unsigned int is_rightmost;  /* 是否为导入区间的最右边 */
    unsigned int free_import;   /* 是否可以释放导入 */

    RA_BASE_T base;            /* 资源基址 */
    RA_LENGTH_T uSize;         /* 资源大小 */

    /* 双向有序链表 - 按地址排序的所有段 */
    struct _BT_ *pNextSegment;
    struct _BT_ *pPrevSegment;

    /* 双向无序链表 - 相同标志的空闲段 */
    struct _BT_ *next_free;
    struct _BT_ *prev_free;

    IMG_HANDLE hPriv;          /* 用户私有数据 */
    RA_FLAGS_T uFlags;         /* 标志位 */
};

BT 生命周期图示

分配过程:
┌─────────┐      ┌─────────┐      ┌─────────┐
│  Free   │ ───> │  Split  │ ───> │  Live   │
│  BT     │      │  if     │      │  BT     │
│         │      │  needed │      │         │
└─────────┘      └─────────┘      └─────────┘

释放过程:
┌─────────┐      ┌─────────┐      ┌─────────┐
│  Live   │ ───> │ Merge   │ ───> │  Free   │
│  BT     │      │  with   │      │  BT     │
│         │      │Neighbors│      │         │
└─────────┘      └─────────┘      └─────────┘

Arena 结构

struct _RA_ARENA_ {
    IMG_CHAR name[RA_MAX_NAME_LENGTH];  /* 竞技场名称 */

    RA_LENGTH_T uQuantum;               /* 量子大小(对齐粒度) */

    /* 导入回调接口 */
    union {
        void* pImportAlloc;
        PFN_RA_IMPORT_ALLOC_SINGLE pImportAllocSingle;
        PFN_RA_IMPORT_ALLOC_MULTI pImportAllocMulti;
    };
    enum RA_IMPORT_ALLOC_METHOD eImportAllocMethod;
    PFN_RA_IMPORT_FREE pImportFree;

    void *pImportHandle;                /* 导入句柄 */

    IMG_PSPLAY_TREE per_flags_buckets;  /* 按标志分类的桶 */

    BT *pHeadSegment;                   /* 段链表头 */

    HASH_TABLE *pSegmentHash;           /* 段地址哈希表 */

    POS_LOCK hLock;                     /* 锁 */

    RA_POLICY_T ui32PolicyFlags;        /* 策略标志 */

    IMG_UINT32 ui32LockClass;           /* 锁类 */

    IMG_UINT64 ui64TotalArenaSize;      /* 总大小 */
    IMG_UINT64 ui64FreeArenaSize;       /* 空闲大小 */
};

核心算法详解

Free Table 组织结构

Free Table 使用 Splay Tree + Bucket Array 的混合结构:

Splay Tree (按 flags 组织)
        │
        ▼
┌──────────────────┐
│  Flags Bucket    │
│  (flags=0x123)   │
├──────────────────┤
│  buckets[0]  ──> BT(size: 1~1)
│  buckets[1]  ──> BT(size: 2~3)
│  buckets[2]  ──> BT(size: 4~7)
│  buckets[3]  ──> BT(size: 8~15)
│  buckets[4]  ──> BT(size: 16~31)
│  ...
│  buckets[n]  ──> BT(size: 2^n ~ 2^(n+1)-1)
└──────────────────┘

每个 bucket 内的 BT 链表:
buckets[n] ──> BT1 ──> BT2 ──> BT3 ──> NULL
// 对于大小为 uSize 的段,其桶索引为:
uIndex = pvr_log2(uSize);  // floor(log2(uSize))

// 例如:
// size=5  ──> index=2 (bucket[2]: 4~7)
// size=16 ──> index=4 (bucket[4]: 16~31)

分配算法流程

RA_Alloc() 流程:
┌──────────────────────────────────────┐
│ 1. 参数验证与对齐                     │
│    - 对齐到 Arena 量子大小            │
│    - 验证对齐要求(必须是2的幂)        │
└──────────────────┬───────────────────┘
                   │
                   ▼
┌──────────────────────────────────────┐
│ 2. _AttemptAllocAligned()            │
│    尝试从现有资源分配                 │
└──────────────────┬───────────────────┘
                   │
         ┌─────────┴─────────┐
         │                   │
    成功 │                   │ 失败
         ▼                   ▼
    ┌─────────┐    ┌──────────────────┐
    │  返回   │    │ 3. 导入新资源     │
    │  基址   │    │ _AttemptImportSpan│
    └─────────┘    └────────┬──────────┘
                            │
                            ▼
                   ┌──────────────────┐
                   │ 4. 重试分配       │
                   │ _AttemptAlloc...  │
                   └──────────────────┘

_AttemptAllocAligned 详细流程

static IMG_BOOL
_AttemptAllocAligned(RA_ARENA *pArena,
                     RA_LENGTH_T uSize,
                     RA_FLAGS_T uFlags,
                     RA_LENGTH_T uAlignment,
                     RA_BASE_T *base,
                     RA_PERISPAN_HANDLE *phPriv)
{
    // 1. 查找匹配 flags 的 bucket
    pArena->per_flags_buckets = PVRSRVSplay(uFlags, pArena->per_flags_buckets);
    if (!bucket_found) return IMG_FALSE;

    // 2. 计算搜索范围
    index_low = pvr_log2(uSize);
    if (uAlignment)
        index_high = pvr_log2(uSize + uAlignment - 1);
    else
        index_high = index_low;

    // 3. 根据策略选择搜索方式
    if (policy == BEST_FIT) {
        // 从最小满足条件的桶开始搜索
        for (i = index_low; i < FREE_TABLE_LIMIT; i++) {
            pBT = find_chunk_in_bucket(...);
            if (pBT) break;
        }
    } else {
        // 默认策略: 从稍大的桶开始
        // 先找 index_high+1 及以上的桶
        // 如果没找到,再向下搜索到 index_low
    }

    // 4. 如果找到合适的 BT,进行分割和分配
    if (pBT)
        return _AllocAlignSplit(pArena, pBT, uSize, uAlignment, base, phPriv);

    return IMG_FALSE;
}

搜索策略图示:

请求: size=20, alignment=16

计算桶范围:
index_low  = log2(20)  = 4   (bucket 4: 16~31)
index_high = log2(35)  = 5   (bucket 5: 32~63)

默认策略搜索顺序:
┌─────────────────────────────────┐
│ 1. 搜索 bucket[6] (64~127)      │ ← 优先大块
│ 2. 搜索 bucket[7] (128~255)     │
│ 3. ... 更大的桶                  │
│ 4. 搜索 bucket[5] (32~63)       │ ← 回退
│ 5. 搜索 bucket[4] (16~31)       │
└─────────────────────────────────┘

段分割算法

static BT *_SegmentSplit(BT *pBT, RA_LENGTH_T uSize)
{
    // 创建新的右邻居 BT
    pNeighbour = _BuildBT(pBT->base + uSize, 
                          pBT->uSize - uSize, 
                          pBT->uFlags);

    // 插入到段链表
    _SegmentListInsertAfter(pBT, pNeighbour);

    // 更新边界标志
    pNeighbour->is_leftmost = 0;
    pNeighbour->is_rightmost = pBT->is_rightmost;
    pBT->is_rightmost = 0;

    // 继承属性
    pNeighbour->free_import = pBT->free_import;
    pNeighbour->hPriv = pBT->hPriv;
    pBT->uSize = uSize;

    return pNeighbour;
}

分割示意图:

分割前:
┌───────────────────────────────────┐
│       BT (base=100, size=64)      │
│       type=free                    │
└───────────────────────────────────┘

分割操作: _SegmentSplit(pBT, 20)

分割后:
┌─────────────────┐┌────────────────┐
│  BT1            ││  BT2           │
│  base=100       ││  base=120      │
│  size=20        ││  size=44       │
│  is_rightmost=0 ││  is_rightmost=1│
└─────────────────┘└────────────────┘

_AllocAlignSplit 处理对齐和尾部分割

static IMG_BOOL
_AllocAlignSplit(RA_ARENA *pArena,
                 BT *pBT,
                 RA_LENGTH_T uSize,
                 RA_LENGTH_T uAlignment,
                 RA_BASE_T *pBase,
                 RA_PERISPAN_HANDLE *phPriv)
{
    // 1. 从 free list 移除
    _FreeListRemove(pArena, pBT);

    // 2. 计算对齐后的基址
    aligned_base = PVR_ALIGN(pBT->base, uAlignment);

    // 3. 如果需要,分割前面的部分
    if (aligned_base > pBT->base) {
        pNeighbour = _SegmentSplit(pBT, aligned_base - pBT->base);
        _FreeListInsert(pArena, pBT);  // 前面部分重新插入 free list
        pBT = pNeighbour;
    }

    // 4. 如果段太大,分割后面的部分
    if (pBT->uSize > uSize) {
        pNeighbour = _SegmentSplit(pBT, uSize);
        _FreeListInsert(pArena, pNeighbour);  // 后面部分插入 free list
    }

    // 5. 标记为已分配并加入哈希表
    pBT->type = btt_live;
    HASH_Insert_Extended(pArena->pSegmentHash, &aligned_base, pBT);

    *pBase = aligned_base;
    return IMG_TRUE;
}

对齐和分割示意图:

初始状态:
┌─────────────────────────────────────────────┐
│  Free BT (base=100, size=100)               │
└─────────────────────────────────────────────┘

请求: size=30, alignment=32

步骤1: 对齐分割
aligned_base = ALIGN(100, 32) = 128

┌──────────────┐┌──────────────────────────────┐
│  Free BT     ││  Working BT                  │
│  base=100    ││  base=128                    │
│  size=28     ││  size=72                     │
└──────────────┘└──────────────────────────────┘

步骤2: 大小分割
┌──────────────┐┌─────────────┐┌──────────────┐
│  Free BT     ││  Live BT    ││  Free BT     │
│  base=100    ││  base=128   ││  base=158    │
│  size=28     ││  size=30    ││  size=42     │
└──────────────┘└─────────────┘└──────────────┘
                 ↑
                 返回此基址

释放算法流程

void RA_Free(RA_ARENA *pArena, RA_BASE_T base)
{
    // 1. 从哈希表中查找并移除 BT
    pBT = HASH_Remove_Extended(pArena->pSegmentHash, &base);

    // 2. 更新统计
    pArena->ui64FreeArenaSize += pBT->uSize;

    // 3. 执行释放和合并
    _FreeBT(pArena, pBT);
}

_FreeBT 详细流程

static void _FreeBT(RA_ARENA *pArena, BT *pBT)
{
    // 1. 尝试与左邻居合并
    pNeighbour = pBT->pPrevSegment;
    if (!pBT->is_leftmost && pNeighbour->type == btt_free) {
        _FreeListRemove(pArena, pNeighbour);
        _SegmentListRemove(pArena, pNeighbour);
        pBT->base = pNeighbour->base;
        pBT->uSize += pNeighbour->uSize;
        pBT->is_leftmost = pNeighbour->is_leftmost;
        OSFreeMem(pNeighbour);
    }

    // 2. 尝试与右邻居合并
    pNeighbour = pBT->pNextSegment;
    if (!pBT->is_rightmost && pNeighbour->type == btt_free) {
        _FreeListRemove(pArena, pNeighbour);
        _SegmentListRemove(pArena, pNeighbour);
        pBT->uSize += pNeighbour->uSize;
        pBT->is_rightmost = pNeighbour->is_rightmost;
        OSFreeMem(pNeighbour);
    }

    // 3. 如果形成完整的导入段,尝试释放
    if (!_RemoveResourceSpan(pArena, pBT)) {
        // 否则插入 free list
        _FreeListInsert(pArena, pBT);
    }
}

合并示意图:

释放前:
┌──────────┐┌──────────┐┌──────────┐┌──────────┐
│  Free    ││  Live    ││  Live    ││  Free    │
│  (BT1)   ││  (BT2)   ││  (BT3)   ││  (BT4)   │
└──────────┘└──────────┘└──────────┘└──────────┘
              ↑ 释放此BT

释放 BT2 后:
┌──────────┐┌──────────┐┌──────────┐
│  Free    ││  Free    ││  Free    │
│  (BT1)   ││  (BT2)   ││  (BT4)   │
└──────────┘└──────────┘└──────────┘

左合并:
┌────────────────────┐┌──────────┐
│  Free (BT1+BT2)    ││  Free    │
│                    ││  (BT4)   │
└────────────────────┘└──────────┘

释放 BT3 后:
┌────────────────────┐┌──────────┐┌──────────┐
│  Free (BT1+BT2)    ││  Free    ││  Free    │
│                    ││  (BT3)   ││  (BT4)   │
└────────────────────┘└──────────┘└──────────┘

右合并:
┌────────────────────┐┌─────────────────────┐
│  Free (BT1+BT2)    ││  Free (BT3+BT4)     │
└────────────────────┘└─────────────────────┘

最终状态(如果 BT1+BT2 和 BT3+BT4 相邻):
┌──────────────────────────────────────────┐
│  Free (BT1+BT2+BT3+BT4)                  │
└──────────────────────────────────────────┘

Ghost Base 机制 (稀疏分配)

Ghost Base 用于处理稀疏内存分配,减少 BT 数量。

物理布局:
┌─────────────────────────────────────────┐
│     Real Base (base=0x1000)             │
│     size=4096*4 (4 pages)               │
└─────────────────────────────────────────┘

逻辑表示 (Ghost Bases):
aBaseArray[0] = 0x1000      (Real)
aBaseArray[1] = 0x2000|GHOST_BIT (Ghost)
aBaseArray[2] = 0x3000|GHOST_BIT (Ghost)
aBaseArray[3] = 0x4000|GHOST_BIT (Ghost)

每个数组元素代表一个页面,但只有一个真实的 BT

Ghost Base 转换图:

// 生成 Ghost Bases
static IMG_UINT32
_GenerateGhostBases(RA_BASE_T uiRealBase,
                    RA_LENGTH_T uiBaseSize,
                    RA_LENGTH_T uiChunkSize,
                    RA_BASE_ARRAY_T aBaseArray)
{
    IMG_UINT32 ui32Index = 0;
    aBaseArray[ui32Index] = uiRealBase;  // Real base

    for (ui32Index = 1; uiRemaining != 0; ui32Index++) {
        aBaseArray[ui32Index] = RA_BASE_SET_GHOST_BIT(uiCurrentBase);
        uiCurrentBase += uiChunkSize;
        uiRemaining -= uiChunkSize;
    }

    return ui32Index;
}

Ghost 转 Real 流程:

┌──────────────────────────────────────┐
│  原始: [R][G][G][G]                  │
│        0  1  2  3                    │
└──────────────────┬───────────────────┘
                   │
        释放 index=2 的 Ghost
                   │
                   ▼
┌──────────────────────────────────────┐
│  步骤1: 找到 Real Base (index=0)     │
│  步骤2: 分割 BT 在 index=2           │
│  步骤3: 新 Real 插入 index=2         │
└──────────────────┬───────────────────┘
                   │
                   ▼
┌──────────────────────────────────────┐
│  结果: [R][G][R][G]                  │
│        0  1  2  3                    │
│                                      │
│  两个独立的 BT:                      │
│  BT1: covers index 0,1              │
│  BT2: covers index 2,3              │
└──────────────────────────────────────┘

完整的分配/释放流程图

┌─────────────────────────────────────────────────────────────┐
│                    用户调用 RA_Alloc                         │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
        ┌────────────────────────────────┐
        │  锁定 Arena                     │
        │  对齐参数到量子大小             │
        └────────────┬───────────────────┘
                     │
                     ▼
        ┌────────────────────────────────┐
        │  _AttemptAllocAligned          │
        │  1. Splay 查找 flags bucket    │
        │  2. 计算桶索引范围              │
        │  3. 搜索合适的 BT               │
        └────┬───────────────────┬───────┘
             │                   │
        成功 │                   │ 失败
             │                   │
             ▼                   ▼
    ┌─────────────┐   ┌──────────────────────┐
    │返回分配结果  │   │ _AttemptImportSpan   │
    └─────────────┘   │ 1. 回调导入新资源     │
                      │ 2. 创建 span BT      │
                      │ 3. 重试分配           │
                      └──────────┬───────────┘
                                 │
                                 ▼
                      ┌──────────────────────┐
                      │  成功/失败返回        │
                      │  解锁 Arena          │
                      └──────────────────────┘

关键优化技术

Splay Tree 优化

// Splay 操作将最近访问的节点移到根
pArena->per_flags_buckets = PVRSRVSplay(uFlags, pArena->per_flags_buckets);

优势:

  • 频繁访问的 flags 保持在树根附近
  • 自适应平衡,无需显式平衡操作
  • 时间复杂度: O(log n) 均摊

位图加速 (PVR_CTZLL)

#if defined(PVR_CTZLL)
    // 使用 CTZ (Count Trailing Zeros) 快速找到第一个非空桶
    i = PVR_CTZLL(
        (~(((IMG_ELTS_MAPPINGS)1 << (index_high + 1)) - 1)) 
        & pArena->per_flags_buckets->bHasEltsMapping
    );
#else
    // 线性搜索
    for (i = index_high + 1; 
         i < FREE_TABLE_LIMIT && buckets[i] == NULL; 
         i++);
#endif

bHasEltsMapping 示意:

位图: 0000000010100110
       │││││││││││││││└─ bucket[0] 非空
       │││││││││││││└── bucket[1] 非空
       │││││││││││└─── bucket[2] 空
       │││││││││└──── bucket[5] 非空
       ...

查找操作: O(1) vs O(n)

策略标志

// 分配策略
RA_POLICY_ALLOC_NODE_SELECT_MASK
    - RA_POLICY_ALLOC_OPTIMAL     // 桶内按大小排序
    - Default                      // 桶内不排序

// 桶选择策略
RA_POLICY_BUCKET_MASK
    - RA_POLICY_BUCKET_BEST_FIT   // 从小桶开始
    - Default                      // 从大桶开始

// 其他策略
RA_POLICY_NO_SPLIT                // 禁止分割
RA_POLICY_ALLOC_ALLOW_NONCONTIG   // 允许非连续分配

多重分配 (Multi-Alloc)

RA_AllocMulti() 流程:
┌────────────────────────────────────────┐
│ 输入:                                   │
│  - uRequestSize: 总大小                │
│  - uiLog2ChunkSize: 块大小(log2)       │
│  - aBaseArray: 输出数组                │
└────────────┬───────────────────────────┘
             │
             ▼
┌────────────────────────────────────────┐
│ _AttemptAllocAlignedAssured            │
│                                        │
│ 尝试1: 寻找单个大块                    │
│   - 搜索 >= uRequestSize 的 BT        │
│   - 成功: 生成 Ghost Bases            │
│                                        │
│ 尝试2: Scoop (舀取) 多个小块           │
│   - 从高桶到低桶遍历                   │
│   - 累积多个 BT 直到满足总大小        │
│   - 每个 BT 生成 Ghost Bases          │
└────────────┬───────────────────────────┘
             │
             ▼
┌────────────────────────────────────────┐
│ 输出:                                   │
│  aBaseArray 填充 Real + Ghost Bases    │
│  bPhysContig 标记是否物理连续          │
└────────────────────────────────────────┘

Scoop 示意图:

请求: 100 pages, 每页 4KB

初始 Free List:
bucket[6] (64~127 pages): [BT1: 80 pages]
bucket[5] (32~63 pages):  [BT2: 40 pages]
bucket[4] (16~31 pages):  [BT3: 20 pages]

Scoop 过程:
1. 分配 BT1 (80 pages)
   aBaseArray[0..79] = Real + 79 Ghosts

2. 分配 BT2 (20 pages, 剩余20返回 free list)
   aBaseArray[80..99] = Real + 19 Ghosts

总计: 100 pages, 2个 Real Base, 98个 Ghost Base
物理上: 非连续 (bPhysContig = false)

稀疏分配 (Sparse Allocation)

稀疏分配原理

稀疏分配允许在虚拟地址空间中只映射部分物理页面,适用于大块虚拟内存但只需要部分物理内存的场景。

IMG_INTERNAL PVRSRV_ERROR
RA_AllocMultiSparse(RA_ARENA *pArena,
                     IMG_UINT32 uiLog2ChunkSize,
                     IMG_UINT8 uImportMultiplier,
                     RA_FLAGS_T uImportFlags,
                     const IMG_CHAR *pszAnnotation,
                     RA_BASE_ARRAY_T aBaseArray,
                     RA_BASE_ARRAY_SIZE_T uiBaseArraySize,
                     IMG_UINT32 *puiAllocIndices,
                     IMG_UINT32 uiAllocCount)

稀疏分配流程图

输入场景:
┌──────────────────────────────────────────────────┐
│ 虚拟数组: [][][][][][][][][]                      │
│ 索引:      0  1  2  3  4  5  6  7  8            │
│                                                  │
│ 需要分配的索引: [1, 2, 3, 5, 7]                  │
└──────────────────────────────────────────────────┘

分配过程:
┌──────────────────────────────────────────────────┐
│ 步骤1: 索引合并优化                               │
│   - 检测连续索引: [1,2,3], [5], [7]             │
│   - 合并为3个分配请求                            │
└───────────────────┬──────────────────────────────┘
                    │
                    ▼
┌──────────────────────────────────────────────────┐
│ 步骤2: 分配连续块                                │
│   块1: indices[1-3] -> 3 chunks                 │
│        调用 _RA_AllocMultiUnlocked()            │
│        生成: [R][G][G]                          │
│                                                  │
│   块2: indices[5] -> 1 chunk                    │
│        调用 _RA_AllocMultiUnlocked()            │
│        生成: [R]                                 │
│                                                  │
│   块3: indices[7] -> 1 chunk                    │
│        调用 _RA_AllocMultiUnlocked()            │
│        生成: [R]                                 │
└───────────────────┬──────────────────────────────┘
                    │
                    ▼
┌──────────────────────────────────────────────────┐
│ 最终结果:                                         │
│ aBaseArray[0] = INVALID_BASE_ADDR               │
│ aBaseArray[1] = 0x1000      (Real)              │
│ aBaseArray[2] = 0x2000|GHOST (Ghost)            │
│ aBaseArray[3] = 0x3000|GHOST (Ghost)            │
│ aBaseArray[4] = INVALID_BASE_ADDR               │
│ aBaseArray[5] = 0x5000      (Real)              │
│ aBaseArray[6] = INVALID_BASE_ADDR               │
│ aBaseArray[7] = 0x7000      (Real)              │
│ aBaseArray[8] = INVALID_BASE_ADDR               │
└──────────────────────────────────────────────────┘

索引合并代码分析

for (i = 0; i < uiAllocCount;)
{
    IMG_UINT32 j;
    IMG_UINT32 uiConsolidate = 1;

    // 检测连续索引
    for (j = i;
         j + 1 != uiAllocCount &&
         puiAllocIndices[j + 1] == puiAllocIndices[j] + 1;
         j++)
    {
        uiConsolidate++;
    }

    // 分配合并后的块
    eError = _RA_AllocMultiUnlocked(pArena,
                                     IMG_PAGES2BYTES64(uiConsolidate, uiLog2ChunkSize),
                                     uiLog2ChunkSize,
                                     uImportMultiplier,
                                     uImportFlags,
                                     pszAnnotation,
                                     &aBaseArray[puiAllocIndices[i]],
                                     uiConsolidate,
                                     IMG_TRUE, /* Sparse alloc */
                                     &bPhysContig);

    i += uiConsolidate;  // 跳过已处理的索引
}

合并优化示意:

输入索引: [0, 1, 2, 5, 6, 7, 8, 10, 15]

检测连续段:
┌────────┐    ┌──────────┐    ┌────┐    ┌────┐
│[0,1,2] │    │[5,6,7,8] │    │[10]│    │[15]│
│3个连续  │    │4个连续   │    │单个│     │单个│
└────────┘    └──────────┘    └────┘    └────┘

分配调用:
1. _RA_AllocMultiUnlocked(size=3*chunk)  -> 1次分配
2. _RA_AllocMultiUnlocked(size=4*chunk)  -> 1次分配
3. _RA_AllocMultiUnlocked(size=1*chunk)  -> 1次分配
4. _RA_AllocMultiUnlocked(size=1*chunk)  -> 1次分配

总计: 4次分配 vs 9次(如果不合并)
优化比率: 55%

稀疏释放 (Sparse Free)

IMG_INTERNAL PVRSRV_ERROR
RA_FreeMultiSparse(RA_ARENA *pArena,
                    RA_BASE_ARRAY_T aBaseArray,
                    RA_BASE_ARRAY_SIZE_T uiBaseArraySize,
                    IMG_UINT32 uiLog2ChunkSize,
                    IMG_UINT32 *puiFreeIndices,
                    IMG_UINT32 *puiFreeCount)

释放流程详解

释放前状态:
aBaseArray: [R][G][G][R][G][I][R][I][R][G]
索引:        0  1  2  3  4  5  6  7  8  9

释放索引: [1, 2, 4, 6]

处理过程:
┌──────────────────────────────────────────────────┐
│ 索引 1,2: 连续的 Ghost 基址                       │
│   - 找到 Real base: index 0                      │
│   - 调用 _ConvertAndFreeStartFromGhost()         │
│   - 操作:                                         │
│     1. 转换 index[1] 为 Real (分割 BT)           │
│     2. 检查 index[3] (已是 Real, 无需处理)       │
│     3. 释放 index[1-2]                           │
└──────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────┐
│ 索引 4: 单个 Ghost 基址                          │
│   - 找到 Real base: index 3                      │
│   - 转换并释放                                    │
└──────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────┐
│ 索引 6: Real 基址                                │
│   - 直接释放                                      │
└──────────────────────────────────────────────────┘

释放后状态:
aBaseArray: [R][I][I][R'][G][I][I][I][R][G]
             │       │                │
             │       └─ 新的 Real    │
             └─ 可能合并              └─ 未改变

BT 变化:
原: BT0(0-2), BT3(3-4), BT6(6), BT8(8-9)
新: BT0(0),   BT3(3),   BT4(4), BT8(8-9)

Ghost 转 Real 详细过程

static PVRSRV_ERROR
_ConvertGhostBaseToReal(RA_ARENA *pArena,
                        RA_BASE_ARRAY_T aBaseArray,
                        RA_BASE_T uiRealBase,
                        IMG_UINT32 ui32RealBaseArrayIndex,
                        IMG_UINT32 ui32GhostBaseArrayIndex,
                        RA_LENGTH_T uiChunkSize)
{
    // 1. 从哈希表获取原 Real BT
    pOrigRealBT = HASH_Retrieve_Extended(pArena->pSegmentHash, &uiRealBase);

    // 2. 计算分割位置并分割
    IMG_UINT32 offset = ui32GhostBaseArrayIndex - ui32RealBaseArrayIndex;
    pNewRealBT = _SegmentSplit(pOrigRealBT, uiChunkSize * offset);

    // 3. 将新 BT 加入哈希表
    HASH_Insert_Extended(pArena->pSegmentHash, &pNewRealBT->base, pNewRealBT);

    // 4. 更新数组
    aBaseArray[ui32GhostBaseArrayIndex] = pNewRealBT->base;

    return PVRSRV_OK;
}

转换示意图:

初始状态:
Array:  [R][G][G][G]
Index:   0  1  2  3
BT:     └─BT0────┘
        base=0x1000
        size=16K

转换 index[2] 为 Real:
┌──────────────────────────────────────┐
│ 1. 查找 Real base: index[0]          │
│ 2. 计算偏移: offset = 2 - 0 = 2       │
│ 3. 分割位置: 0x1000 + 2*4K = 0x3000   │
└──────────────────────────────────────┘

分割后:
Array:  [R][G][R'][G]
Index:   0  1  2   3
BT:     └BT0┘└BT2─┘
        0x1000 0x3000
        8K     8K

哈希表更新:
Before: {0x1000 -> BT0}
After:  {0x1000 -> BT0, 0x3000 -> BT2}

三种释放场景

static PVRSRV_ERROR
_ConvertAndFreeBaseArraySlice(RA_ARENA *pArena,
                             RA_BASE_ARRAY_T aBaseArray,
                             RA_BASE_ARRAY_SIZE_T uiBaseArraySize,
                             RA_LENGTH_T uiChunkSize,
                             IMG_UINT32 uiFreeStartIndex,
                             IMG_UINT32 uiFreeCount,
                             RA_CONVERT_AND_FREE_BEHAVIOUR eBehaviour)

场景1: 完整 Real Base 释放

Array:  [R][G][G][G]  全部释放
Result: [I][I][I][I]

操作: 直接调用 _FreeSingleBaseArray()

场景2: 部分从 Real 开始释放

Array:  [R][G][G][G][G]  释放 index[0-2]
Before: └────BT0──────┘
After:  [I][I][I][R'][G]
             └─BT1─┘

步骤:
1. 转换 index[3] 为 Real (分割 BT0)
2. 释放 BT0 (index[0-2])
3. 保留 BT1 (index[3-4])

场景3: Ghost 段释放

Array:  [R][G][G][G][G]  释放 index[1-3]
Before: └────BT0──────┘
After:  [R'][I][I][I][R'']
        └BT0┘     └BT1┘

步骤:
1. 转换 index[1] 为 Real (分割原 BT)
2. 转换 index[4] 为 Real (再次分割)
3. 释放中间的 BT (index[1-3])
4. 保留两端

内存交换 (Swap) 机制

RA_SwapSparseMem 流程

IMG_INTERNAL PVRSRV_ERROR
RA_SwapSparseMem(RA_ARENA *pArena,
                  RA_BASE_ARRAY_T aBaseArray,
                  RA_BASE_ARRAY_SIZE_T uiBaseArraySize,
                  IMG_UINT32 uiLog2ChunkSize,
                  IMG_UINT32 *puiXIndices,
                  IMG_UINT32 *puiYIndices,
                  IMG_UINT32 uiSwapCount)

交换算法详解

目标: 交换 X 和 Y 索引位置的内容

输入示例:
Array:   [R0][G][G][R1][G][G][R2][G]
Index:    0   1  2  3   4  5  6   7
X索引:   [1, 2, 3]
Y索引:   [5, 6, 7]

算法流程:
┌──────────────────────────────────────────────────┐
│ 步骤1: 索引合并                                   │
│   X: [1,2,3] -> 连续3个                          │
│   Y: [5,6,7] -> 连续3个                          │
│   取最小: min(3,3) = 3                           │
└───────────────────┬──────────────────────────────┘
                    │
                    ▼
┌──────────────────────────────────────────────────┐
│ 步骤2: 修剪边界 (Trim Block)                      │
│   X块: [1-3]                                     │
│     - 检查 index[1]: 如果是 Ghost, 转为 Real      │
│     - 检查 index[4]: 如果是 Ghost, 转为 Real      │
│   Y块: [5-7]                                     │
│     - 检查 index[5]: 如果是 Ghost, 转为 Real      │
│     - 检查 index[8]: 如果存在且是Ghost,转为Real    │
└───────────────────┬──────────────────────────────┘
                    │
                    ▼
┌──────────────────────────────────────────────────┐
│ 步骤3: 执行交换                                   │
│   SWAP(aBaseArray[1], aBaseArray[5])            │
│   SWAP(aBaseArray[2], aBaseArray[6])            │
│   SWAP(aBaseArray[3], aBaseArray[7])            │
└──────────────────────────────────────────────────┘

修剪边界函数

static PVRSRV_ERROR
_TrimBlockMakeReal(RA_ARENA *pArena,
                   RA_BASE_ARRAY_T aBaseArray,
                   RA_BASE_ARRAY_SIZE_T uiBaseArraySize,
                   IMG_UINT32 uiLog2ChunkSize,
                   IMG_UINT32 uiStartIndex,
                   IMG_UINT32 uiEndIndex)
{
    // 1. 确保起始索引是 Real
    if (RA_BASE_IS_GHOST(aBaseArray[uiStartIndex])) {
        _FindRealBaseFromGhost(...);
        _ConvertGhostBaseToReal(...);
    }

    // 2. 确保结束索引+1是 Real (如果不是数组末尾)
    if (uiEndIndex + 1 != uiBaseArraySize &&
        RA_BASE_IS_GHOST(aBaseArray[uiEndIndex + 1])) {
        _FindRealBaseFromGhost(...);
        _ConvertGhostBaseToReal(...);
    }

    return PVRSRV_OK;
}

修剪示意图:

交换前准备:
Array: [R0][G][G][G][R1][G][G][G]
Index:  0   1  2  3  4   5  6  7

交换范围: X=[1-3], Y=[5-7]

修剪 X 块 [1-3]:
┌──────────────────────────────────────┐
│ 起始: index[1] 是 Ghost              │
│   找到 Real: index[0]                │
│   分割: 创建 index[1] 为 Real        │
│ 结束: index[4] 是 Real, 无需处理      │
└──────────────────────────────────────┘

修剪后: [R0][R1'][G][G][R1][G][G][G]
          └─────X块────┘

修剪 Y 块 [5-7]:
┌──────────────────────────────────────┐
│ 起始: index[5] 是 Ghost              │
│   找到 Real: index[4]                │
│   分割: 创建 index[5] 为 Real        │
│ 结束: index[8] 不存在, 无需处理       │
└──────────────────────────────────────┘

修剪后: [R0][R1'][G][G][R1][R2'][G][G]
                        └─────Y块────┘

现在可以安全交换 X 和 Y 块

完整交换示例

初始状态:
Array: [R0][G][G][R1][G][G]
Index:  0   1  2  3   4  5
值:    A0  A1 A2 B0  B1 B2

交换操作: X=[1,2], Y=[4,5]

步骤1: 修剪
Array: [R0][R'][G][R1][R''][G]
         A0  A1  A2 B0  B1   B2

步骤2: 交换
SWAP([1],[4]): A1 <-> B1
SWAP([2],[5]): A2 <-> B2

最终状态:
Array: [R0][R'][G][R1][R''][G]
         A0  B1  B2 B0  A1   A2

BT 结构:
- BT(A0): index[0]
- BT(B1-B2): index[1-2]
- BT(B0): index[3]
- BT(A1-A2): index[4-5]

Import/Export 机制

资源导入流程

导入时机: 当 Arena 资源不足时

单次导入 (SINGLE):
┌─────────────────────────────────────────┐
│ pImportAllocSingle(handle, size, flags) │
└────────────────┬────────────────────────┘
                 │
                 ▼
        ┌────────────────┐
        │   RA_IMPORT    │
        │ - base         │
        │ - uSize        │
        │ - hPriv        │
        └────────┬───────┘
                 │
                 ▼
        ┌────────────────┐
        │ _InsertResource│
        │ Span(...)      │
        └────────┬───────┘
                 │
                 ▼
        ┌────────────────┐
        │  创建 BT       │
        │  标记 free_    │
        │  import=1      │
        └────────────────┘

多次导入 (MULTI):

eError = pArena->pImportAllocMulti(
    pArena->pImportHandle,
    uRequestSize,
    uImportFlags,
    uAlignment,
    pszAnnotation,
    puiImportCount,      // [in/out] 导入数量
    (RA_IMPORT**)ppsImports  // [out] 导入数组
);

// 可能返回多个不连续的内存段
// 例如: 请求 1MB, 可能返回:
//   - Import[0]: 512KB @ 0x1000
//   - Import[1]: 256KB @ 0x5000
//   - Import[2]: 256KB @ 0x9000

导入倍数优化

// uImportMultiplier 参数允许预分配
RA_Alloc(..., uImportMultiplier=2, ...)

计算导入大小:
uRequestSize *= uImportMultiplier;
uRequestSize = PVR_ALIGN(uRequestSize, pArena->uQuantum);

// 如果请求 64KB, multiplier=2
// 实际导入: 128KB
// 额外的 64KB 保留在 free list 中供未来使用

导入倍数示意:

场景: 频繁小额分配

策略1: multiplier = 1 (按需导入)
┌────────┐ ┌────────┐ ┌────────┐
│ 导入1  │ │ 导入2   │ │ 导入3  │
│ 4KB    │ │ 4KB    │ │ 4KB    │
└────────┘ └────────┘ └────────┘
3次导入调用, 3次系统调用

策略2: multiplier = 4 (预分配)
┌──────────────────────────────┐
│         导入1                 │
│         16KB                  │
├──────┬──────┬──────┬──────────┤
│ 用1  │ 用2  │ 用3  │   Free   │
│ 4KB  │ 4KB  │ 4KB  │   4KB    │
└──────┴──────┴──────┴──────────┘
1次导入调用, 1次系统调用

优势: 减少系统调用开销, 改善局部性

资源释放与回收

static INLINE IMG_BOOL
_RemoveResourceSpan(RA_ARENA *pArena, BT *pBT)
{
    // 仅当满足以下所有条件时才释放导入:
    // 1. free_import = 1 (标记为可释放)
    // 2. is_leftmost = 1 (整个导入段的左端)
    // 3. is_rightmost = 1 (整个导入段的右端)

    if (pBT->free_import &&
        pBT->is_leftmost &&
        pBT->is_rightmost)
    {
        // 从段列表移除
        _SegmentListRemove(pArena, pBT);

        // 更新统计
        pArena->ui64TotalArenaSize -= pBT->uSize;
        pArena->ui64FreeArenaSize -= pBT->uSize;

        // 调用释放回调
        pArena->pImportFree(pArena->pImportHandle, 
                           pBT->base, 
                           pBT->hPriv);

        // 释放 BT
        OSFreeMem(pBT);

        return IMG_TRUE;
    }

    return IMG_FALSE;
}

释放条件示意:

导入段: [0x1000, 16KB]

情况1: 部分使用
┌────────┬────────┬────────┐
│  Live  │  Free  │  Free  │
│  4KB   │  4KB   │  8KB   │
└────────┴────────┴────────┘
is_leftmost=1  is_rightmost=1
但有 Live 段 -> 不释放

情况2: 完全空闲但已分割
┌────────┬──────────────────┐
│  Free  │      Free        │
│  4KB   │      12KB        │
└────────┴──────────────────┘
左BT: is_rightmost=0 -> 不释放
右BT: is_leftmost=0 -> 不释放

情况3: 完全空闲且未分割
┌──────────────────────────┐
│         Free             │
│         16KB             │
└──────────────────────────┘
is_leftmost=1 && is_rightmost=1
free_import=1 -> 释放!

迭代器 (Iterator) 机制

迭代器结构

struct _RA_ARENA_ITERATOR_ {
    RA_ARENA *pArena;              // 目标 Arena
    BT *pCurrent;                  // 当前 BT
    IMG_BOOL bIncludeFreeSegments; // 是否包含空闲段
};

迭代模式

模式1: 仅 Live 段

pIter = RA_IteratorAcquire(pArena, IMG_FALSE);
while (RA_IteratorNext(pIter, &sData)) {
    // 只访问已分配的段
    // sData.uiAddr, sData.uiSize
}
RA_IteratorRelease(pIter);

模式2: 所有段 (含 Free)

pIter = RA_IteratorAcquire(pArena, IMG_TRUE);
while (RA_IteratorNext(pIter, &sData)) {
    if (sData.bFree) {
        // 空闲段
    } else {
        // 已分配段
    }
}

模式3: 按 Span 迭代

pIter = RA_IteratorAcquire(pArena, IMG_TRUE);
while (RA_IteratorNextSpan(pIter, &sData)) {
    // 每次返回一个完整的导入 span
    // 包含该 span 内的所有段 (Live + Free)
}

段合并逻辑

IMG_BOOL RA_IteratorNext(RA_ARENA_ITERATOR *pIter, RA_ITERATOR_DATA *pData)
{
    pData->uiAddr = pIter->pCurrent->base;
    pData->uiSize = pIter->pCurrent->uSize;
    pData->bFree = pIter->pCurrent->type == btt_free;

    // 合并相同类型的连续段
    while ((pNext = pNext->pNextSegment) != NULL &&
            pNext->type == pNext->pPrevSegment->type &&
            pNext->type == btt_live &&  // 只合并 Live 段
            pNext->base == pData->uiAddr + pData->uiSize)
    {
        pData->uiSize += pNext->uSize;
    }

    // 跳到下一个 Live 段 (如果 !bIncludeFreeSegments)
    ...
}

合并示意:

Segment List:
┌─────┬─────┬─────┬─────┬─────┐
│Live │Live │Free │Live │Live │
│ 4K  │ 4K  │ 8K  │ 4K  │ 4K  │
└─────┴─────┴─────┴─────┴─────┘

迭代结果 (bIncludeFreeSegments=False):
1. {addr=..., size=8K, free=false}  // 合并前两个
2. {addr=..., size=8K, free=false}  // 合并后两个

迭代结果 (bIncludeFreeSegments=True):
1. {addr=..., size=8K, free=false}
2. {addr=..., size=8K, free=true}
3. {addr=..., size=8K, free=false}

Block Dump 可视化

RA_BlockDump 功能

生成 Arena 内存布局的可视化图,用于调试和分析。

IMG_INTERNAL PVRSRV_ERROR
RA_BlockDump(RA_ARENA *pArena, 
             void (*pfnLogDump)(void*, IMG_CHAR*, ...), 
             void *pPrivData)

可视化输出示例

~~~ 'GPU_VM' Resource Arena Block Dump
    Block Size: 4096B
    Span Memory Usage: 268435456B
    Free Span Memory: 134217728B
    Largest Free Region Size: 67108864B
    Percent Fragmented 66%
===============================================================================
| 0x00000000 | ################################################################
| 0x00010000 | ################................................................
| 0x00020000 | ................................................................
| 0x00030000 | ................................################################
     ....
| 0x0FFF0000 | ################################################................

图例:
# = 已分配
. = 空闲

实现原理

数据结构:

papRegionArray[region_idx][chunk_idx]
│
├─ region: 代表一段连续地址范围
│   - 每个 region = uiRegionSize = 128 blocks (默认)
│
└─ chunk: 每个 chunk 用32位整数表示
    - chunk_idx 范围: 0 ~ uiChunkCount (通常4个)
    - 每个 chunk 代表 32 个 blocks
    - 每个 bit 代表一个 block 的分配状态

可视化映射:
┌─────────────────────────────────────────────────┐
│ Region 0                                        │
├─────────────────────────────────────────────────┤
│ Chunk[0]  Chunk[1]  Chunk[2]  Chunk[3]          │
│ 32bits    32bits    32bits    32bits            │
│ ────────  ────────  ────────  ────────          │
│ 0-31      32-63     64-95     96-127            │
└─────────────────────────────────────────────────┘

输出为两行 64 字符:
Line 1: Chunk[0] 和 Chunk[1] 的可视化 (64字符)
Line 2: Chunk[2] 和 Chunk[3] 的可视化 (64字符)

代码详解

// 1. 确定需要多少 regions
uiRecognisedQuantum = pArena->uQuantum > 0 ? pArena->uQuantum : 4096;

// 找到最后一个分配的地址
while (RA_IteratorNext(pIter, &sIterData)) {
    if (!sIterData.bFree && sIterData.uiAddr >= uiLastBase) {
        uiLastBase = sIterData.uiAddr;
        uiLastSize = sIterData.uiSize;
    }
}

// 计算 region 数量
uiRegionCount = (uiLastBase + uiLastSize) / uiRecognisedQuantum / uiRegionSize;

// 2. 填充位图
while (RA_IteratorNext(pIter, &sIterData)) {
    if (sIterData.bFree) {
        uiLargestFreeSegmentSize = max(uiLargestFreeSegmentSize, sIterData.uiSize);
        continue;  // 跳过空闲段
    }

    // 计算分配在哪个 region 和 chunk
    uiDataDivRecQuant = sIterData.uiAddr / uiRecognisedQuantum;
    uiAddrRegionIdx = uiDataDivRecQuant / uiRegionSize;
    uiAddrRegionOffset = uiDataDivRecQuant % uiRegionSize;

    uiAddrChunkIdx = uiAddrRegionOffset / uiChunkSize;
    uiAddrChunkOffset = uiAddrRegionOffset % uiChunkSize;

    // 计算分配占用多少个 blocks
    uiQuantisedSize = sIterData.uiSize / uiRecognisedQuantum;
    if (sIterData.uiSize % uiRecognisedQuantum != 0) {
        uiQuantisedSize++;
    }

    // 在位图中标记
    // ... (设置对应的 bits)
}

// 3. 打印可视化
for (i = 0; i < uiRegionCount; i++) {
    if (papRegionArray[i] != NULL) {
        for (j = 0; j < uiChunkCount; j+=2) {
            // 将两个 chunks (64 bits) 转换为 64 个字符
            for (k = 1 << 31; k != 0; k >>= 1) {
                pszLine[uiBit] = papRegionArray[i][j] & k ? '#' : '.';
                pszLine[32 + uiBit] = papRegionArray[i][j+1] & k ? '#' : '.';
                uiBit++;
            }

            // 计算并打印地址
            IMG_UINT64 uiLineAddress = 
                (i * uiRegionSize + (j >> 1) * uiLineWidth) * uiRecognisedQuantum;

            pfnLogDump(pPrivData, "| 0x%08llx | %s", uiLineAddress, pszLine);
        }
    }
}

位图填充详细示例

示例分配:
- 地址: 0x1000
- 大小: 0x5000 (20KB)
- Block大小: 4KB
- 量化大小: 5 blocks

计算过程:
uiDataDivRecQuant = 0x1000 / 0x1000 = 1
uiAddrRegionIdx = 1 / 128 = 0 (region 0)
uiAddrRegionOffset = 1 % 128 = 1
uiAddrChunkIdx = 1 / 32 = 0 (chunk 0)
uiAddrChunkOffset = 1 % 32 = 1

需要设置的 bits:
Chunk[0], bits [1-5] (5 个连续 bits)

位图操作:
初始: 00000000000000000000000000000000 (32 bits)
      │││││││││││││││││││││││││││││││└─ bit 0

设置 bits 1-5:
结果: 00000000000000000000000000111110
                                 │││││└─ bit 1
                                 ││││└── bit 2
                                 │││└─── bit 3
                                 ││└──── bit 4
                                 │└───── bit 5

转换为字符:
.####.............................. (32 字符)

碎片化计算

// 碎片化百分比计算
if (pArena->ui64FreeArenaSize && uiLargestFreeSegmentSize) {
    uiFragPercentage = 
        100 * pArena->ui64FreeArenaSize / 
        (pArena->ui64FreeArenaSize + uiLargestFreeSegmentSize);
}

碎片化示意:

场景1: 低碎片化
┌──────────────────────────────────────────────┐
│ Live │ Live │      Free (Large)              │
│ 10MB │ 10MB │      180MB                     │
└──────────────────────────────────────────────┘
总空闲: 180MB
最大连续空闲: 180MB
碎片化: 100 * 180 / (180 + 180) = 50%

场景2: 高碎片化
┌──────────────────────────────────────────────┐
│ Live │ Free │ Live │ Free │ ... │ Free       │
│ 5MB  │ 5MB  │ 5MB  │ 5MB  │ ... │ 5MB        │
└──────────────────────────────────────────────┘
总空闲: 90MB (18个5MB块)
最大连续空闲: 5MB
碎片化: 100 * 90 / (90 + 5) = 94%

策略标志详解

RA_POLICY_T 标志位

// 分配节点选择策略
RA_POLICY_ALLOC_NODE_SELECT_MASK = 0x00000001
    RA_POLICY_ALLOC_OPTIMAL = 0x00000001  // 桶内按大小排序

// 桶选择策略
RA_POLICY_BUCKET_MASK = 0x00000002
    RA_POLICY_BUCKET_BEST_FIT = 0x00000002  // 最佳适配

// 分割策略
RA_POLICY_NO_SPLIT_MASK = 0x00000004
    RA_POLICY_NO_SPLIT = 0x00000004  // 禁止分割

// 非连续分配策略
RA_POLICY_ALLOC_ALLOW_NONCONTIG_MASK = 0x00000008

策略对比

OPTIMAL vs 默认 (桶内排序)

默认策略 (无排序):
Bucket[5] (32~63 pages):
  -> BT1(40 pages) -> BT2(35 pages) -> BT3(60 pages) -> NULL

插入顺序: 先进先出 (FIFO)
查找: 返回第一个满足条件的

OPTIMAL 策略 (按大小排序):
Bucket[5] (32~63 pages):
  -> BT2(35 pages) -> BT1(40 pages) -> BT3(60 pages) -> NULL
     ↑小                                              大↑

插入: 按大小顺序插入
查找: 返回最小满足条件的

优势: 减少碎片化,保留大块供后续大分配使用

BEST_FIT vs 默认 (桶选择)

请求: 20 pages

默认策略:
1. 查找 bucket[5] 及以上 (32+ pages)
2. 如果没找到,回退到 bucket[4] (16~31 pages)
优势: 快速,优先使用大块

BEST_FIT 策略:
1. 查找 bucket[4] (16~31 pages)
2. 如果没找到,向上查找更大的桶
优势: 节省内存,减少浪费

示例:
可用: bucket[4]: BT(25 pages)
      bucket[6]: BT(80 pages)

默认: 选择 80 pages,浪费 60 pages
BEST_FIT: 选择 25 pages,浪费 5 pages

NO_SPLIT 策略

if ((pArena->ui32PolicyFlags & RA_POLICY_NO_SPLIT_MASK) == RA_POLICY_NO_SPLIT) {
    goto nosplit;
}

// 跳过所有分割逻辑
nosplit:
    pBT->type = btt_live;
    HASH_Insert_Extended(pArena->pSegmentHash, &aligned_base, pBT);
    *pBase = aligned_base;
    return IMG_TRUE;

使用场景:

场景: 硬件要求整块分配

请求: 20 pages, 找到 BT(64 pages)

默认策略:
┌────────┬──────────────────────────┐
│ 20 pg  │   44 pg (返回 Free)      │
└────────┴──────────────────────────┘
返回: 20 pages

NO_SPLIT 策略:
┌────────────────────────────────────┐
│          64 pg (全部分配)           │
└────────────────────────────────────┘
返回: 64 pages

优势: 避免分割开销,硬件可能有整块要求
劣势: 浪费内存

线程安全与锁机制

锁类 (Lock Class)

struct _RA_ARENA_ {
    POS_LOCK hLock;           // Arena 锁
    IMG_UINT32 ui32LockClass; // 锁类标识
};

// 获取锁时使用锁类
OSLockAcquireNested(pArena->hLock, pArena->ui32LockClass);

锁类层次:

RA_LOCKCLASS_0: 顶层 Arena,不依赖其他 Arena
RA_LOCKCLASS_1: 依赖 LOCKCLASS_0 的 Arena
RA_LOCKCLASS_2: 依赖 LOCKCLASS_1 的 Arena

嵌套规则:
允许: LOCKCLASS_0 -> LOCKCLASS_1 -> LOCKCLASS_2
禁止: LOCKCLASS_1 -> LOCKCLASS_0 (死锁风险)

示例层次:
┌──────────────────────┐
│ Physical Memory Arena│ <- LOCKCLASS_0
│ (不依赖其他Arena)     │
└──────────┬───────────┘
           │
           ▼
┌──────────────────────┐
│ GPU VM Arena         │ <- LOCKCLASS_1
│ (导入物理内存)        │
└──────────┬───────────┘
           │
           ▼
┌──────────────────────┐
│ Device Memory Arena  │ <- LOCKCLASS_2
│ (导入GPU VM)          │
└──────────────────────┘

临界区保护

// 所有公共 API 都遵循此模式
IMG_INTERNAL PVRSRV_ERROR RA_Alloc(...)
{
    OSLockAcquireNested(pArena->hLock, pArena->ui32LockClass);

    // 临界区操作
    // - 修改 Segment List
    // - 修改 Free Table
    // - 修改 Hash Table

    OSLockRelease(pArena->hLock);
    return eError;
}

保护的数据结构:

Arena 锁保护:
├─ pHeadSegment (段链表头)
├─ per_flags_buckets (Splay Tree)
├─ pSegmentHash (哈希表)
├─ ui64TotalArenaSize (统计)
└─ ui64FreeArenaSize (统计)

每个 BT 的链表指针:
├─ pNextSegment / pPrevSegment
├─ next_free / prev_free
└─ 由 Arena 锁隐式保护

回调函数与锁

// 导入回调可能会获取外部锁
eError = pArena->pImportAllocSingle(
    pArena->pImportHandle,
    uRequestSize,
    uImportFlags,
    uAlignment,
    pszAnnotation,
    psImport
);

// 注意: 
// 1. 回调内部可能锁定其他 Arena (必须遵循锁类层次)
// 2. 当前 Arena 锁仍然持有
// 3. 回调不应尝试递归锁定当前 Arena

死锁预防示例:

正确的嵌套:
Thread 1:
  Lock(PhysArena, CLASS_0)
    -> ImportCallback()
       -> Lock(VMArena, CLASS_1)  ✓ 允许

错误的嵌套:
Thread 1:
  Lock(VMArena, CLASS_1)
    -> ImportCallback()
       -> Lock(PhysArena, CLASS_0)  ✗ 违反层次

Thread 2:
  Lock(PhysArena, CLASS_0)
    -> ...
       -> Lock(VMArena, CLASS_1)

结果: 潜在死锁

性能分析与优化建议

时间复杂度分析

操作              最坏情况    平均情况    说明
─────────────────────────────────────────────────
Alloc (查找)     O(log n)    O(log n)    Splay Tree + 桶搜索
Alloc (分割)     O(1)        O(1)        常数时间创建 BT
Free (查找)      O(1)        O(1)        哈希表查找
Free (合并)      O(1)        O(1)        最多检查2个邻居
Insert Hash      O(1)        O(1)        哈希表插入
Splay            O(n)        O(log n)    均摊 O(log n)

其中 n = 不同 flags 的数量(通常很小)

空间复杂度

每个 Arena:
- 固定开销: sizeof(RA_ARENA) ≈ 128 bytes
- Splay Tree: n * sizeof(PSPLAY_TREE) ≈ n * 1KB
  (n = 不同 flags 数量)
- Hash Table: m * sizeof(hash_entry) 
  (m = 已分配段数量)

每个 BT:
- sizeof(BT) ≈ 80 bytes

示例计算:
1000 个分配,10 种不同 flags:
- Arena: 128 bytes
- Splay: 10 * 1024 = 10KB
- Hash: 1000 * 32 = 32KB
- BTs: 约 2000 个 (包括 free) * 80 = 160KB
总计: ≈ 202KB 元数据开销

优化建议

导入倍数优化

// 频繁小分配场景
RA_Alloc(pArena, 4KB, 
         4,  // multiplier=4, 预分配16KB
         ...);

优势:
- 减少导入次数
- 改善内存局部性
- 减少系统调用开销

代价:
- 可能浪费内存
- 增加初始分配延迟

策略选择

// 场景1: 内存充足,追求速度
RA_Create(..., 
          0);  // 默认策略

// 场景2: 内存紧张,追求利用率
RA_Create(...,
          RA_POLICY_BUCKET_BEST_FIT | 
          RA_POLICY_ALLOC_OPTIMAL);

量子大小选择

// 小量子: 精细管理,但开销大
RA_Create(..., 0);  // 4KB 量子

// 大量子: 粗粒度管理,减少 BT 数量
RA_Create(..., 4);  // 64KB 量子

权衡:
量子大小 | BT数量 | 碎片化 | 管理开销
─────────────────────────────────────
4KB      | 多     | 低     | 高
64KB     | 少     | 高     | 低

稀疏分配优化

// 避免: 逐个分配
for (i = 0; i < 1000; i++) {
    RA_Alloc(pArena, PAGE_SIZE, ...);
}

// 优化: 批量稀疏分配
IMG_UINT32 indices[1000];
for (i = 0; i < 1000; i++) indices[i] = i;

RA_AllocMultiSparse(pArena,
                    LOG2_PAGE_SIZE,
                    ...,
                    aBaseArray,
                    1000,
                    indices,
                    1000);

优势:
- 单次锁获取
- 索引合并优化
- 减少 BT 数量 (Ghost Base)

常见性能陷阱

陷阱1: 频繁小额分配

问题:
for (i = 0; i < 10000; i++) {
    RA_Alloc(pArena, 4KB, 1, ...);  // multiplier=1
}

影响:
- 10000 次锁操作
- 10000 次哈希插入
- 可能触发 10000 次导入

解决:
- 使用 multiplier > 1
- 使用 AllocMulti
- 使用内存池

陷阱2: 碎片化累积

场景:
重复: Alloc(8KB) -> Free(4KB) -> Alloc(4KB)

结果:
┌────┬────┬────┬────┬────┬────┐
│4KB │Free│4KB │Free│4KB │Free│
└────┴────┴────┴────┴────┴────┘

问题: 无法分配 > 4KB 的连续块

解决:
- 定期压缩 (如果支持)
- 使用更大的量子
- 分离不同大小的 Arena

陷阱3: 锁竞争

多线程场景:
Thread 1: RA_Alloc(ArenaA) 
Thread 2: RA_Alloc(ArenaA)  // 等待
Thread 3: RA_Alloc(ArenaA)  // 等待

解决:
- 使用多个 Arena 分片
- 线程本地缓存
- 批量操作减少锁持有时间

使用示例

基本用法

// 1. 创建 Arena
RA_ARENA *pArena = RA_Create(
    "MyArena",           // 名称
    0,                   // log2 量子 (4KB)
    RA_LOCKCLASS_0,      // 锁类
    MyImportAlloc,       // 导入回调
    MyImportFree,        // 释放回调
    pvImportHandle,      // 导入句柄
    0                    // 默认策略
);

// 2. 分配资源
RA_BASE_T base;
RA_LENGTH_T actualSize;
PVRSRV_ERROR eError;

eError = RA_Alloc(
    pArena,
    SIZE_64KB,          // 请求大小
    2,                  // 导入倍数
    0,                  // 标志
    SIZE_4KB,           // 对齐
    "TestAlloc",        // 注释
    &base,              // 输出基址
    &actualSize,        // 实际大小
    NULL                // 私有数据
);

// 3. 使用资源
if (eError == PVRSRV_OK) {
    // 使用 [base, base+actualSize)
}

// 4. 释放资源
RA_Free(pArena, base);

// 5. 销毁 Arena
RA_Delete(pArena);

稀疏分配示例

// 分配虚拟地址空间但只映射部分物理页

#define VIRT_SIZE_PAGES  1024
#define PHYS_SIZE_PAGES  256

RA_BASE_ARRAY_T aBaseArray[VIRT_SIZE_PAGES];
IMG_UINT32 aiMapIndices[PHYS_SIZE_PAGES];
IMG_BOOL bPhysContig;

// 1. 分配虚拟地址范围
eError = RA_AllocMulti(
    pVMArena,
    VIRT_SIZE_PAGES * PAGE_SIZE,
    LOG2_PAGE_SIZE,
    1,  // multiplier
    0,  // flags
    "SparseVM",
    aBaseArray,
    VIRT_SIZE_PAGES,
    &bPhysContig
);

// 2. 选择要映射的页面
for (i = 0; i < PHYS_SIZE_PAGES; i++) {
    aiMapIndices[i] = rand() % VIRT_SIZE_PAGES;
}

// 3. 分配物理内存到选定页面
eError = RA_AllocMultiSparse(
    pPhysArena,
    LOG2_PAGE_SIZE,
    1,
    0,
    "SparsePhys",
    aBaseArray,          // 同一数组
    VIRT_SIZE_PAGES,
    aiMapIndices,
    &PHYS_SIZE_PAGES
);

// 现在 aBaseArray 包含:
// - Real bases 在 aiMapIndices 指定的位置
// - Ghost bases 用于虚拟连续性
// - INVALID_BASE_ADDR 在未映射位置

按需映射示例

// 初始: 全虚拟分配,无物理映射
RA_BASE_ARRAY_T aBaseArray[NUM_PAGES];
eError = RA_AllocMulti(pVMArena, TOTAL_SIZE, ...);

// 所有页面初始为 INVALID_BASE_ADDR 或 Ghost

// 页面错误处理: 按需分配物理页
void HandlePageFault(IMG_UINT32 uiPageIndex)
{
    IMG_UINT32 aiIndices[1] = {uiPageIndex};
    IMG_UINT32 uiCount = 1;

    // 分配一个物理页到指定索引
    RA_AllocMultiSparse(
        pPhysArena,
        LOG2_PAGE_SIZE,
        1,
        0,
        "OnDemand",
        aBaseArray,
        NUM_PAGES,
        aiIndices,
        &uiCount
    );

    // 建立页表映射
    MapPage(aBaseArray[uiPageIndex], uiPageIndex);
}

// 释放不常用页面
void EvictPage(IMG_UINT32 uiPageIndex)
{
    IMG_UINT32 aiIndices[1] = {uiPageIndex};
    IMG_UINT32 uiCount = 1;

    // 取消映射
    UnmapPage(uiPageIndex);

    // 释放物理内存
    RA_FreeMultiSparse(
        pPhysArena,
        aBaseArray,
        NUM_PAGES,
        LOG2_PAGE_SIZE,
        aiIndices,
        &uiCount
    );

    aBaseArray[uiPageIndex] = INVALID_BASE_ADDR;
}

总结

核心设计思想

1. 分层管理
   ┌─────────────┐
   │ Splay Tree  │ <- flags 分类
   └──────┬──────┘
          │
   ┌──────▼──────┐
   │   Buckets   │ <- 大小分类 (log2)
   └──────┬──────┘
          │
   ┌──────▼──────┐
   │  BT Chains  │ <- 具体段
   └─────────────┘

2. 延迟合并
   - 只在必要时合并相邻空闲段
   - 避免过度合并/分割

3. Ghost Base
   - 减少元数据
   - 支持稀疏分配
   - 按需转换为 Real

4. 策略可配置
   - 适应不同使用场景
   - 平衡速度与利用率

适用场景

✓ GPU 虚拟地址空间管理
✓ 物理内存分配
✓ 设备内存管理
✓ 大范围稀疏资源分配
✓ 需要对齐的资源分配

✗ 频繁极小分配 (< 4KB)
✗ 实时系统 (锁开销)
✗ 严格 O(1) 要求的场景

关键优势

  1. 通用性: 可管理任意整数资源
  2. 可扩展: 支持多种导入方式
  3. 高效: 对数时间复杂度,位图加速
  4. 灵活: 丰富的策略选项
  5. 稀疏支持: Ghost Base 机制
  6. 线程安全: 完整的锁保护
  7. 可调试: Block Dump 可视化

架构图总览

                    ┌──────────────────────────┐
                    │      User API            │
                    │ Alloc / Free / Iterator  │
                    └────────────┬─────────────┘
                                 │
                    ┌────────────▼─────────────┐
                    │      RA_ARENA            │
                    │  - Lock                  │
                    │  - Policy                │
                    │  - Statistics            │
                    └────────────┬─────────────┘
                                 │
                 ┌───────────────┼───────────────┐
                 │               │               │
        ┌────────▼────────┐ ┌───▼────┐ ┌───────▼────────┐
        │  Segment List   │ │ Splay  │ │  Segment Hash  │
        │  (Address Order)│ │  Tree  │ │  (Live Segs)   │
        └────────┬────────┘ └───┬────┘ └────────────────┘
                 │              │
                 │     ┌────────▼────────┐
                 │     │   Buckets       │
                 │     │  (Size Classes) │
                 │     └────────┬────────┘
                 │              │
                 └──────┬───────┘
                        │
                ┌───────▼────────┐
                │  Boundary Tags │
                │   (BT)         │
                │  - base        │
                │  - size        │
                │  - type        │
                │  - flags       │
                └────────────────┘

这个 Resource Allocator 是一个精心设计的内存管理系统,通过多层索引、智能分割/合并、Ghost Base 等技术,在通用性、性能和内存效率之间取得了良好的平衡。

相关文章

PowerVR RGX Sync-Fence
PowerVR Rogue ZS Buffer
PowerVR RGX Parameter Buffer And Ring Buffer
PowerVR Rogue FreeList
PowerVR RGX Firmware Utils
PowerVR RGX BVNC

发布评论