文件概述与架构
内部标志位定义
/* 内部标志位 - 这些标志不暴露给外部API */
#define PMR_FLAG_INTERNAL_SPARSE_ALLOC (1 << 0) // 稀疏分配标记
#define PMR_FLAG_INTERNAL_NO_LAYOUT_CHANGE (1 << 1) // 禁止布局变更
#define PMR_FLAG_INTERNAL_DEFER_FREE (1 << 2) // 延迟释放
#define PMR_FLAG_INTERNAL_IS_ZOMBIE_PMR (1 << 3) // 僵尸PMR
#define PMR_FLAG_INTERNAL_IS_EXCLUSIVE (1 << 4) // 独占模式
#define PMR_FLAG_INTERNAL_IS_ZOMBIE_PMR_EMPTY (1 << 5) // 空僵尸PMR
标志位详解:
| 标志位 | 含义 | 使用场景 |
|---|---|---|
| SPARSE_ALLOC | 稀疏分配 | 逻辑地址空间 > 物理内存 |
| NO_LAYOUT_CHANGE | 布局固定 | 导出后不可修改 |
| DEFER_FREE | 延迟释放 | 等待GPU缓存刷新 |
| IS_ZOMBIE_PMR | 僵尸状态 | 引用计数为0,待清理 |
| IS_EXCLUSIVE | 独占模式 | 不与其他资源共享 |
系统架构图
┌─────────────────────────────────────────────────────────────┐
│ 应用层 (Application) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ OpenGL │ │ Vulkan │ │ OpenCL │ │ 应用 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ DeviceMemory API层 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ DevmemIntAllocatePMR / DevmemIntMapPMR │ │
│ │ DevmemIntExportPMR / DevmemIntImportPMR │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ PMR核心层 (pmr.c) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ PMR管理器 │ │ 引用计数 │ │ 地址转换 │ │
│ │ _PMRCreate │ │ _Ref/_Unref │ │ LogicalToPhys│ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 稀疏内存 │ │ 延迟释放 │ │ 多设备支持 │ │
│ │ ChangeSparse │ │ Zombie清理 │ │ CrossDevice │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ PMR实现层 (各种Factory) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ OS Pages │ │ DMA Buf │ │ ION │ │ Custom │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 物理内存层 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ PHYS_HEAP - 物理堆管理 │ │
│ │ ├─ UMA Heap (统一内存架构) │ │
│ │ ├─ LMA Heap (本地内存架构) │ │
│ │ └─ DMA Heap (DMA内存) │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 硬件层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 物理RAM │ │ GPU MMU │ │ 缓存系统 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
主要数据结构
全局上下文结构 (PMR_CTX)
static struct _PMR_CTX_
{
/* 序列号生成器 - 每个PMR的唯一标识 */
IMG_UINT64 uiNextSerialNum; // 从1开始递增
/* 安全密钥生成器 - 用于PMR访问验证 */
IMG_UINT64 uiNextKey; // 伪随机密钥
/* 活跃PMR统计 - 用于调试和资源跟踪 */
ATOMIC_T uiNumLivePMRs; // 原子变量,线程安全
/* 上下文保护锁 */
POS_LOCK hLock; // 保护串行号和密钥生成
/* 模块初始化标志 */
IMG_BOOL bModuleInitialised; // 防止重复初始化
} _gsSingletonPMRContext = {
1, // uiNextSerialNum初始值
0, // uiNextKey初始值
{0}, // uiNumLivePMRs初始值
NULL, // hLock未初始化
IMG_FALSE // bModuleInitialised
};
逐行解析:
- uiNextSerialNum = 1
- 从1开始,0保留为无效值
- 每创建一个PMR递增1
- 用于日志追踪和调试
- uiNextKey = 0
- 初始密钥为0
- 通过LCG算法生成后续密钥
- 密钥生成公式:
Key生成算法:
uiNextKey = (0x80200003 * uiNextKey) ^ (0xf00f0081 * ptr_address)
新Key = (0x80200003 × 旧Key) XOR (0xf00f0081 × 内存地址)
这是一个简单的伪随机数生成器,确保每个PMR有唯一标识。
- uiNumLivePMRs
- 使用原子类型ATOMIC_T
- 增加:OSAtomicIncrement(&uiNumLivePMRs)
- 减少:OSAtomicDecrement(&uiNumLivePMRs)
- 用于检测内存泄漏
- hLock
- 普通互斥锁(不是自旋锁)
- 保护序列号和密钥生成的原子性
- 初始化:OSLockCreate(&hLock)
这是一个全局单例上下文,管理所有PMR的基本状态。
全局上下文初始化流程
PVRSRV_ERROR PMRInit(void)
{
PVRSRV_ERROR eError;
/* 检查是否已经初始化 */
if (_gsSingletonPMRContext.bModuleInitialised)
{
// 防止重复初始化
PVR_GOTO_WITH_ERROR(eError, PVRSRV_ERROR_PMR_UNRECOVERABLE_ERROR, out);
}
/* 创建保护锁 */
eError = OSLockCreate(&_gsSingletonPMRContext.hLock);
PVR_LOG_GOTO_IF_ERROR(eError, "OSLockCreate", out);
/* 设置初始值 */
_gsSingletonPMRContext.uiNextSerialNum = 1;
_gsSingletonPMRContext.uiNextKey = 0x8300f001 * (uintptr_t)&_gsSingletonPMRContext;
_gsSingletonPMRContext.bModuleInitialised = IMG_TRUE;
OSAtomicWrite(&_gsSingletonPMRContext.uiNumLivePMRs, 0);
return PVRSRV_OK;
out:
return eError;
}
初始化流程图:
开始 PMRInit()
↓
检查 bModuleInitialised
↓ (未初始化)
创建全局锁 hLock
↓
设置 uiNextSerialNum = 1
↓
初始化 uiNextKey (使用地址混淆)
↓
设置 uiNumLivePMRs = 0
↓
标记 bModuleInitialised = TRUE
↓
返回 PVRSRV_OK
PMR结构体
PMR结构体完整定义
struct _PMR_
{
#if defined(SUPPORT_PMR_DEFERRED_FREE)
/* 延迟释放头部 - 用于僵尸列表管理 */
PMR_HEADER sHeader;
/*
* typedef struct _PMR_HEADER_ {
* DLLIST_NODE sZombieNode; // 双向链表节点
* PMR_ZOMBIE_TYPE eZombieType; // 僵尸类型
* } PMR_HEADER;
*/
#endif
/* ============ 物理内存管理 ============ */
/* 物理堆指针 - 指向这个PMR所属的物理内存堆 */
PHYS_HEAP *psPhysHeap;
/*
* 物理堆类型:
* - PHYS_HEAP_TYPE_UMA: 统一内存架构(CPU和GPU共享)
* - PHYS_HEAP_TYPE_LMA: 本地内存架构(GPU专用)
* - PHYS_HEAP_TYPE_DMA: DMA内存
*/
/* ============ 引用计数管理 ============ */
/* 主引用计数 - 为0时触发销毁 */
IMG_UINT32 uiRefCount;
/* 引用计数保护锁 - 自旋锁,快速操作 */
POS_SPINLOCK hRefCountLock;
/*
* 引用计数增加的场景:
* 1. PMR创建时初始为0,第一次Lock时变为1
* 2. PMRLockPhysAddresses() 每次+1
* 3. PMRExportPMR() 导出时+1
* 4. PMRImportPMR() 导入时+1
* 5. PMRRefPMR() 显式增加引用+1
*
* 引用计数减少的场景:
* 1. PMRUnlockPhysAddresses() 每次-1
* 2. PMRUnexportPMR() 取消导出-1
* 3. PMRUnimportPMR() 取消导入-1
* 4. PMRUnrefPMR() 显式减少引用-1
*/
/* ============ 映射计数 ============ */
/* 客户端CPU映射计数 - 用户空间映射次数 */
ATOMIC_T iClientCpuMapCount;
/*
* 增加:DevmemAcquireCpuVirtAddr()
* 减少:DevmemReleaseCpuVirtAddr()
* 作用:当>0时禁止稀疏内存布局变更
*/
#if defined(SUPPORT_LINUX_OSPAGE_MIGRATION)
/* 内核CPU映射计数 - 内核空间映射次数 */
ATOMIC_T iKernelCpuMapCount;
/*
* 增加:PMRAcquireKernelMappingData()
* 减少:PMRReleaseKernelMappingData()
* 作用:>0时禁止页面迁移
*/
/* GPU映射列表 - 所有GPU映射的链表 */
DLLIST_NODE sGpuMappingListHead;
/*
* 用于页面迁移时更新所有GPU页表
*/
/* PMR状态 */
PMR_STATE eState;
/*
* PMR_STATE_INIT: 初始化状态
* PMR_STATE_ACTIVE: 活跃状态
* PMR_STATE_PAGES_IN_MIGRATE: 页面迁移中
*/
#endif
/* GPU预留资源计数 - 关联的GPU映射数量 */
IMG_INT32 iAssociatedResCount;
/*
* 增加:PMRLinkGPUMapping()
* 减少:PMRUnlinkGPUMapping()
* 作用:跟踪有多少个GPU映射引用此PMR
*/
/* 物理地址锁定计数 */
ATOMIC_T iLockCount;
/*
* 增加:PMRLockPhysAddresses()
* 减少:PMRUnlockPhysAddresses()
*
* 特殊规则:
* - 如果是on-demand分配,初始值为0
* - 如果不是on-demand,初始值为1(已经backed)
*/
/* ============ 同步机制 ============ */
/* 主锁 - 保护PMR结构体的复杂操作 */
POS_LOCK hLock;
/*
* 使用场景:
* - 地址转换操作
* - 稀疏内存变更
* - 页面迁移
* - 延迟释放处理
*
* 锁类型:可睡眠的互斥锁
*/
/* 位图锁 - 保护标志位和位图操作 */
POS_SPINLOCK hBitmapLock;
/*
* 保护:
* - uiInternalFlags
* - uiDevImportBitmap
*
* 锁类型:不可睡眠的自旋锁
*/
#if defined(SUPPORT_PMR_DEVICE_IMPORT_DEFERRED_FREE) || defined(PVRSRV_ENABLE_XD_MEM)
/* 设备导入位图 - 记录哪些设备导入了此PMR */
IMG_UINT64 uiDevImportBitmap;
/*
* 每个bit代表一个设备:
* bit 0: Device 0
* bit 1: Device 1
* ...
* 最多支持64个设备
*/
#endif
#if defined(SUPPORT_PMR_DEVICE_IMPORT_DEFERRED_FREE)
/* 跨设备导入列表 */
DLLIST_NODE sXDeviceImports;
/*
* 链表元素类型:PMR_DEVICE_IMPORT
* 记录所有导入此PMR的设备信息
*/
#endif
/* ============ 标识和安全 ============ */
/* 全局唯一序列号 */
IMG_UINT64 uiSerialNum;
/*
* 从全局上下文获取,递增分配
* 用于:
* - 日志追踪
* - PDump符号名生成
* - 调试信息
*/
/* 安全密钥 - 用于验证PMR访问权限 */
PMR_PASSWORD_T uiKey;
/*
* 64位伪随机数
* 导出时传递给导入方
* 导入时必须提供正确的密钥
*/
/* ============ 函数表和类型信息 ============ */
/* 实现函数表指针 - 指向具体的PMR实现 */
const PMR_IMPL_FUNCTAB *psFuncTab;
/*
* 函数表包含:
* - pfnLockPhysAddresses: 锁定物理地址
* - pfnUnlockPhysAddresses: 解锁物理地址
* - pfnDevPhysAddr: 获取设备物理地址
* - pfnAcquireKernelMappingData: 获取内核映射
* - pfnReleaseKernelMappingData: 释放内核映射
* - pfnReadBytes: 读取数据
* - pfnWriteBytes: 写入数据
* - pfnChangeSparseMem: 修改稀疏内存布局
* - pfnFinalize: 销毁PMR
* - ... 更多函数
*/
/* 类型特定私有数据 */
PMR_IMPL_PRIVDATA pvFlavourData;
/*
* 不同类型PMR的私有数据:
* - OS Pages: 页面数组
* - DMA-BUF: DMA-BUF句柄
* - ION: ION缓冲区信息
* 由具体Factory分配和管理
*/
/* 环境特定数据 */
DECLARE_PMR_ENV_DATA(sEnvData)
/*
* 平台相关数据,如:
* - Linux: struct file指针
* - 其他平台的特定数据
*/
/* PMR类型标识 */
PMR_IMPL_TYPE eFlavour;
/*
* PMR_TYPE_OSPAGES: OS页面
* PMR_TYPE_DMABUF: DMA缓冲区
* PMR_TYPE_ION: ION内存
* PMR_TYPE_PHYSMEM: 物理内存
* ... 更多类型
*/
/* ============ PDump支持 ============ */
/* PDump默认内存空间名称 */
const IMG_CHAR *pszPDumpDefaultMemspaceName;
/*
* 从物理堆获取,如:
* - "SYSMEM": 系统内存
* - "FBMEM": Framebuffer内存
* 用于PDump脚本生成
*/
#if defined(PDUMP)
/* PDump分配句柄 */
IMG_HANDLE hPDumpAllocHandle;
/*
* 数组,每个逻辑块一个句柄
* 用于PDump记录和回放
*/
/* PDump块数量 */
IMG_UINT32 uiNumPDumpBlocks;
/*
* 等于逻辑块数量
* 用于管理PDump句柄数组
*/
#endif
/* ============ 内存布局信息 ============ */
/* 逻辑大小 - PMR的虚拟地址空间大小 */
PMR_SIZE_T uiLogicalSize;
/*
* 对于稀疏PMR:逻辑大小 >= 物理大小
* 对于连续PMR:逻辑大小 == 物理大小
* 单位:字节
*/
/* 映射表指针 */
PMR_MAPPING_TABLE *psMappingTable;
/*
* typedef struct {
* IMG_UINT32 ui32NumLogicalChunks; // 逻辑块数量
* IMG_UINT32 ui32NumPhysChunks; // 物理块数量
* PMR_SIZE_T uiChunkSize; // 块大小
* IMG_UINT32 aui32Translation[]; // 翻译数组
* } PMR_MAPPING_TABLE;
*
* 翻译数组:
* - 下标:逻辑块索引
* - 值:物理块索引(0xFFFFFFFF表示无效)
*/
/* 连续性保证 - log2(页面大小) */
PMR_LOG2ALIGN_T uiLog2ContiguityGuarantee;
/*
* 示例:
* - 12: 4KB页面 (1 << 12 = 4096)
* - 16: 64KB页面 (1 << 16 = 65536)
* - 21: 2MB页面 (1 << 21 = 2097152)
*
* 保证物理内存的最小连续单元大小
*/
/* ============ 标志位 ============ */
/* 公开标志 - 从PVRSRV_MEMALLOCFLAGS_T转换 */
PMR_FLAGS_T uiFlags;
/*
* 包含:
* - CPU可读/可写权限
* - GPU可读/可写权限
* - 缓存策略
* - On-demand分配标志
* - 安全内存标志
* ... 更多标志
*/
/* 内部标志 - 私有状态位 */
IMG_UINT32 uiInternalFlags;
/*
* 见第1.3节的标志位定义
*/
/* ============ 调试信息 ============ */
/* 注释字符串 - 用于调试和追踪 */
IMG_CHAR szAnnotation[DEVMEM_ANNOTATION_MAX_LEN];
/*
* 由应用设置,通常包含:
* - 缓冲区名称
* - 纹理名称
* - 用途描述
* 最大长度通常为64或128字节
*/
#if defined(PVRSRV_ENABLE_GPU_MEMORY_INFO)
/* RI (Resource Information) 句柄 */
void *hRIHandle;
/*
* 用于内存信息统计和查询
* 记录到全局RI数据库
*/
#endif
#if defined(XDXGPU_SMMU_SUPPORT)
/* SMMU映射列表 */
DLLIST_NODE SmmuMapList;
/*
* 记录所有SMMU映射
* 用于多GPU场景
*/
#endif
#if defined(XDXGPU)
/* DMA预留对象 - 用于同步 */
DMA_RESV *resv;
DMA_RESV _resv;
/*
* 用于DMA-BUF fence机制
* 同步CPU和GPU访问
*/
#endif
/* ============ 上下文引用 ============ */
/* 全局上下文指针 - 指向单例上下文 */
struct _PMR_CTX_ *psContext;
/*
* 通常指向 &_gsSingletonPMRContext
* 用于访问全局资源
*/
};
PMR结构体内存布局示意图
PMR结构体内存布局 (假设64位系统)
| 含义 | 地址 |
|---|---|
| sHeader (仅在DEFERRED_FREE时存在) | 0x00 - 0x18 |
| psPhysHeap (指针, 8字节) | 0x18 - 0x20 |
| uiRefCount (4字节) | 0x20 - 0x24 |
| padding (4字节对齐) | 0x24 - 0x28 |
| hRefCountLock (指针, 8字节) | 0x28 - 0x30 |
| iClientCpuMapCount (ATOMIC, 4字节) | 0x30 - 0x34 |
| iKernelCpuMapCount (ATOMIC, 4字节) | 0x34 - 0x38 |
| sGpuMappingListHead (双向链表, 16字节) | 0x38 - 0x48 |
| eState (4字节) | 0x48 - 0x4C |
| iAssociatedResCount (4字节) | 0x4C - 0x50 |
| iLockCount (ATOMIC, 4字节) | 0x50 - 0x54 |
| padding (4字节) | 0x54 - 0x58 |
| hLock (指针, 8字节) | 0x58 - 0x60 |
| hBitmapLock (指针, 8字节) | 0x60 - 0x68 |
| uiDevImportBitmap (8字节) | 0x68 - 0x70 |
| sXDeviceImports (双向链表, 16字节) | 0x70 - 0x80 |
| uiSerialNum (8字节) | 0x80 - 0x88 |
| uiKey (8字节) | 0x88 - 0x90 |
| psFuncTab (指针, 8字节) | 0x90 - 0x98 |
| pvFlavourData (指针, 8字节) | 0x98 - 0xA0 |
| eFlavour (4字节) | 0xA0 - 0xA4 |
| padding (4字节) | 0xA4 - 0xA8 |
| pszPDumpDefaultMemspaceName (指针, 8字节) | xA8 - 0xB0 |
| szAnnotation[64] (字符数组, 64字节) | 0xB0 - 0xF0 |
| hPDumpAllocHandle (指针, 8字节) | 0xF0 - 0xF8 |
| uiNumPDumpBlocks (4字节) | 0xF8 - 0xFC |
| padding (4字节) | 0xFC - 0x100 |
| uiLogicalSize (8字节) | 0x100 - 0x108 |
| psMappingTable (指针, 8字节) | 0x108 - 0x110 |
| uiLog2ContiguityGuarantee (4字节) | 0x110 - 0x114 |
| padding (4字节) | 0x114 - 0x118 |
| uiFlags (8字节) | 0x118 - 0x120 |
| uiInternalFlags (4字节) | 0x120 - 0x124 |
| padding (4字节) | 0x124 - 0x128 |
| psContext (指针, 8字节) | 0x128 - 0x130 |
| hRIHandle (指针, 8字节) | 0x130 - 0x138 |
总大小: 约 312 字节 (0x138)
PMR创建流程
_PMRCreate 函数逐行分析
/**
* @brief PMR内部创建函数
* @param uiLogicalSize 逻辑大小(虚拟地址空间大小)
* @param ui32NumPhysChunks 物理块数量
* @param ui32NumLogicalChunks 逻辑块数量
* @param pui32MappingTable 映射表数组
* @param uiLog2ContiguityGuarantee 连续性保证(log2页面大小)
* @param uiFlags PMR标志位
* @param ppsPMR 输出:创建的PMR指针
*/
static PVRSRV_ERROR
_PMRCreate(PMR_SIZE_T uiLogicalSize,
IMG_UINT32 ui32NumPhysChunks,
IMG_UINT32 ui32NumLogicalChunks,
IMG_UINT32 *pui32MappingTable,
PMR_LOG2ALIGN_T uiLog2ContiguityGuarantee,
PMR_FLAGS_T uiFlags,
PMR **ppsPMR)
{
void *pvPMRLinAddr; // PMR线性地址(包含PMR+映射表)
PMR *psPMR; // PMR指针
PMR_MAPPING_TABLE *psMappingTable; // 映射表指针
struct _PMR_CTX_ *psContext; // 全局上下文
IMG_UINT32 i, ui32Temp = 0;
PVRSRV_ERROR eError;
IMG_BOOL bSparse = IMG_FALSE; // 是否稀疏分配
PMR_SIZE_T uiChunkSize; // 块大小
psContext = &_gsSingletonPMRContext;
/* ========== 第1步:判断是否为稀疏分配 ========== */
/*
* 稀疏分配的判断条件:
* 1. 逻辑块数 != 物理块数(有些逻辑块没有对应物理内存)
* 2. 逻辑块数 > 1(分成多个块)
*/
if ( (ui32NumLogicalChunks != ui32NumPhysChunks) ||
(ui32NumLogicalChunks > 1) )
{
bSparse = IMG_TRUE;
// 稀疏分配时,块大小由连续性保证决定
uiChunkSize = 1ULL << uiLog2ContiguityGuarantee;
/*
* 示例:
* uiLog2ContiguityGuarantee = 12 (4KB)
* uiChunkSize = 1 << 12 = 4096 bytes
*/
}
else
{
// 连续分配时,块大小就是整个逻辑大小
uiChunkSize = uiLogicalSize;
}
/* ========== 第2步:稀疏分配额外验证 ========== */
if (bSparse)
{
/*
* 验证公式:逻辑大小 = 块大小 × 逻辑块数
*
* 示例:
* 逻辑大小 = 1MB = 1048576 bytes
* 块大小 = 4KB = 4096 bytes
* 逻辑块数 = 1048576 / 4096 = 256
*/
if (uiLogicalSize != (uiChunkSize * ui32NumLogicalChunks))
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Bad mapping size (uiLogicalSize = 0x%llx, "
"uiChunkSize = 0x%llx, ui32NumLogicalChunks = %d)",
__func__,
(unsigned long long)uiLogicalSize,
(unsigned long long)uiChunkSize,
ui32NumLogicalChunks));
return PVRSRV_ERROR_PMR_BAD_MAPPINGTABLE_SIZE;
}
}
/* ========== 第3步:一次性分配内存 ========== */
/*
* 内存布局:
* ┌─────────────┐
* │ PMR结构体 │ sizeof(*psPMR)
* ├─────────────┤
* │ 映射表结构 │ sizeof(*psMappingTable)
* ├─────────────┤
* │ 翻译数组 │ sizeof(IMG_UINT32) * ui32NumLogicalChunks
* └─────────────┘
*
* 优点:
* 1. 减少内存碎片
* 2. 提高缓存局部性
* 3. 简化释放流程(只需一次free)
*/
pvPMRLinAddr = OSAllocMem(sizeof(*psPMR) +
sizeof(*psMappingTable) +
IMG_FLEX_ARRAY_SIZE(sizeof(IMG_UINT32), ui32NumLogicalChunks));
PVR_RETURN_IF_NOMEM(pvPMRLinAddr);
// PMR结构体在起始位置
psPMR = (PMR *) pvPMRLinAddr;
// 映射表紧跟在PMR结构体后面
psMappingTable = IMG_OFFSET_ADDR(pvPMRLinAddr, sizeof(*psPMR));
/*
* IMG_OFFSET_ADDR宏展开:
* #define IMG_OFFSET_ADDR(ptr, offset) ((void*)((char*)(ptr) + (offset)))
*/
/* ========== 第4步:初始化映射表 ========== */
psMappingTable->uiChunkSize = uiChunkSize;
psMappingTable->ui32NumLogicalChunks = ui32NumLogicalChunks;
psMappingTable->ui32NumPhysChunks = ui32NumPhysChunks;
/*
* 初始化翻译数组为0xFF(TRANSLATION_INVALID)
*
* 为什么先全部设为无效?
* - 稀疏分配中,不是所有逻辑块都有对应物理块
* - 先标记为无效,再填充有效的映射
*/
OSCachedMemSet(psMappingTable->aui32Translation, 0xFF,
sizeof(psMappingTable->aui32Translation[0]) * ui32NumLogicalChunks);
/*
* 填充有效映射
*
* 示例:
* pui32MappingTable = [0, 2, 4] // 物理块0,2,4有效
* ui32NumPhysChunks = 3
*
* 循环后:
* aui32Translation[0] = 0 // 逻辑块0 -> 物理块0
* aui32Translation[1] = 0xFF (无效)
* aui32Translation[2] = 2 // 逻辑块2 -> 物理块2
* aui32Translation[3] = 0xFF (无效)
* aui32Translation[4] = 4 // 逻辑块4 -> 物理块4
*/
for (i=0; i<ui32NumPhysChunks; i++)
{
ui32Temp = pui32MappingTable[i]; // 获取逻辑索引
PVR_ASSERT(ui32Temp < ui32NumLogicalChunks);
psMappingTable->aui32Translation[ui32Temp] = ui32Temp;
/*
* 注意:这里假设逻辑索引==物理索引
* 实际可能需要独立的物理索引数组
*/
}
/* ========== 第5步:创建同步锁 ========== */
// 创建主锁(可睡眠)
eError = OSLockCreate(&psPMR->hLock);
PVR_GOTO_IF_ERROR(eError, ErrFreePMR);
// 创建位图锁(自旋锁)
eError = OSSpinLockCreate(&psPMR->hBitmapLock);
PVR_GOTO_IF_ERROR(eError, ErrFreePMRLock);
// 创建引用计数锁(自旋锁)
eError = OSSpinLockCreate(&psPMR->hRefCountLock);
PVR_GOTO_IF_ERROR(eError, ErrFreeBitmapLock);
/* ========== 第6步:初始化PMR基本字段 ========== */
psPMR->uiRefCount = 0; // 初始引用计数为0
// 初始化映射计数
OSAtomicWrite(&psPMR->iClientCpuMapCount, 0);
#if defined(SUPPORT_LINUX_OSPAGE_MIGRATION)
OSAtomicWrite(&psPMR->iKernelCpuMapCount, 0);
dllist_init(&psPMR->sGpuMappingListHead); // 初始化GPU映射列表
#endif
psPMR->iAssociatedResCount = 0; // GPU资源计数
/*
* 锁计数初始化逻辑:
* - On-demand分配:iLockCount = 0(未backing)
* On-demand 分配(按需分配) 指的是:
* 在创建或映射一个逻辑内存对象(例如 PMR 或 GPU 虚拟地址空间)时,不立即分配所有物理页,而是 在首次访问(或明确请求)时才分配物理页。也可以理解为:延迟分配 (lazy allocation) 或 稀疏分配 (sparse allocation)。
* - 立即分配:iLockCount = 1(已backing)
*/
OSAtomicWrite(&psPMR->iLockCount,
(PVRSRV_CHECK_ON_DEMAND(uiFlags) ? 0 : 1));
psPMR->psContext = psContext;
psPMR->uiLogicalSize = uiLogicalSize;
psPMR->uiLog2ContiguityGuarantee = uiLog2ContiguityGuarantee;
psPMR->uiFlags = uiFlags;
psPMR->psMappingTable = psMappingTable;
// 设置内部标志
psPMR->uiInternalFlags = bSparse ? PMR_FLAG_INTERNAL_SPARSE_ALLOC : 0;
// 清空注释
psPMR->szAnnotation[0] = '\0';
// 初始化环境数据
PMR_ENV_INITIALIZE(psPMR, sEnvData);
#if defined(SUPPORT_PMR_DEVICE_IMPORT_DEFERRED_FREE) || defined(PVRSRV_ENABLE_XD_MEM)
psPMR->uiDevImportBitmap = 0; // 清空设备导入位图
#endif
#if defined(SUPPORT_PMR_DEFERRED_FREE)
psPMR->sHeader.eZombieType = PMR_ZOMBIE_TYPE_PMR;
dllist_init(&psPMR->sHeader.sZombieNode);
#if defined(SUPPORT_PMR_DEVICE_IMPORT_DEFERRED_FREE)
dllist_init(&psPMR->sXDeviceImports);
#endif
#endif
#if defined(PVRSRV_ENABLE_GPU_MEMORY_INFO)
psPMR->hRIHandle = NULL; // RI句柄稍后设置
#endif
#ifdef XDXGPU
OSDmaResvInit(&psPMR->_resv); // 初始化DMA预留对象
PMRSetDmaReservation(psPMR, &psPMR->_resv);
#endif
/* ========== 第7步:生成唯一标识 ========== */
OSLockAcquire(psContext->hLock); // 获取全局锁
// 分配序列号
psPMR->uiKey = psContext->uiNextKey;
psPMR->uiSerialNum = psContext->uiNextSerialNum;
/*
* 密钥生成算法(线性同余生成器 + 地址混淆)
*
* 公式:
* next_key = (A * current_key) XOR (B * address)
*
* 其中:
* A = 0x80200003 (大质数)
* B = 0xf00f0081 (混淆因子)
* address = PMR内存地址
*/
psContext->uiNextKey = (0x80200003 * psContext->uiNextKey)
^ (0xf00f0081 * (uintptr_t)pvPMRLinAddr);
psContext->uiNextSerialNum++; // 序列号递增
*ppsPMR = psPMR; // 输出创建的PMR
OSLockRelease(psContext->hLock); // 释放全局锁
PVR_DPF((PVR_DBG_MESSAGE,
"%s: 0x%p, key:0x%016" IMG_UINT64_FMTSPECX ", numLive:%d",
__func__, psPMR, psPMR->uiKey,
OSAtomicRead(&psPMR->psContext->uiNumLivePMRs)));
/* ========== 第8步:增加活跃PMR计数 ========== */
OSAtomicIncrement(&psContext->uiNumLivePMRs);
return PVRSRV_OK;
/* ========== 错误处理路径 ========== */
ErrFreeBitmapLock:
OSSpinLockDestroy(psPMR->hBitmapLock);
ErrFreePMRLock:
OSLockDestroy(psPMR->hLock);
ErrFreePMR:
OSFreeMem(psPMR);
return eError;
}
PMR创建流程图
┌─────────────────────────────────────────────────────────────┐
│ 开始 _PMRCreate() │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第1步:判断内存类型 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ if (逻辑块数 != 物理块数 || 逻辑块数 > 1) │ │
│ │ bSparse = TRUE │ │
│ │ uiChunkSize = 1 << uiLog2ContiguityGuarantee │ │
│ │ else │ │
│ │ bSparse = FALSE │ │
│ │ uiChunkSize = uiLogicalSize │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第2步:验证参数 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ if (bSparse) │ │
│ │ 检查:uiLogicalSize == uiChunkSize * 逻辑块数 │ │
│ │ 失败 → 返回 PVRSRV_ERROR_PMR_BAD_MAPPINGTABLE_SIZE │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第3步:内存分配 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 分配大小 = sizeof(PMR) + sizeof(映射表) + │ │
│ │ sizeof(uint32) * 逻辑块数 │ │
│ │ │ │
│ │ 布局: │ │
│ │ ┌─────────────┐ │ │
│ │ │ PMR结构体 │ ← psPMR │ │
│ │ ├─────────────┤ │ │
│ │ │ 映射表头 │ ← psMappingTable │ │
│ │ ├─────────────┤ │ │
│ │ │ 翻译数组 │ ← aui32Translation[] │ │
│ │ └─────────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第4步:初始化映射表 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ psMappingTable->uiChunkSize = ... │ │
│ │ psMappingTable->ui32NumLogicalChunks = ... │ │
│ │ psMappingTable->ui32NumPhysChunks = ... │ │
│ │ │ │
│ │ // 先全部设为无效 │ │
│ │ memset(aui32Translation, 0xFF, ...) │ │
│ │ │ │
│ │ // 填充有效映射 │ │
│ │ for (i = 0; i < ui32NumPhysChunks; i++) │ │
│ │ aui32Translation[pui32MappingTable[i]] = i │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第5步:创建同步锁 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ OSLockCreate(&psPMR->hLock) // 主锁 │ │
│ │ OSSpinLockCreate(&psPMR->hBitmapLock) // 位图锁 │ │
│ │ OSSpinLockCreate(&psPMR->hRefCountLock) // 引用锁 │ │
│ │ │ │
│ │ 失败处理:逐级释放已创建的资源 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第6步:初始化PMR字段 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ uiRefCount = 0 │ │
│ │ iClientCpuMapCount = 0 │ │
│ │ iLockCount = (on-demand ? 0 : 1) │ │
│ │ uiLogicalSize = ... │ │
│ │ uiLog2ContiguityGuarantee = ... │ │
│ │ uiFlags = ... │ │
│ │ psMappingTable = ... │ │
│ │ uiInternalFlags = (bSparse ? SPARSE_ALLOC : 0) │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第7步:生成唯一标识 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ OSLockAcquire(psContext->hLock) │ │
│ │ │ │
│ │ psPMR->uiSerialNum = psContext->uiNextSerialNum++ │ │
│ │ psPMR->uiKey = psContext->uiNextKey │ │
│ │ │ │
│ │ // LCG密钥生成 │ │
│ │ psContext->uiNextKey = │ │
│ │ (0x80200003 * uiNextKey) ^ (0xf00f0081 * addr) │ │
│ │ │ │
│ │ OSLockRelease(psContext->hLock) │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第8步:更新统计 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ OSAtomicIncrement(&psContext->uiNumLivePMRs) │ │
│ │ │ │
│ │ 输出调试信息: │ │
│ │ "PMR: 0x%p, key:0x%016llx, numLive:%d" │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 返回 PVRSRV_OK │
└─────────────────────────────────────────────────────────────┘
内存分配策略对比
| 策略 | 连续分配 | 稀疏分配 |
|---|---|---|
| 逻辑块数 | 1 | > 1 |
| 物理块数 | 1 | ≤ 逻辑块数 |
| 块大小 | 等于逻辑大小 | 由连续性保证决定 |
| 映射表 | 简化(1:1映射) | 完整翻译数组 |
| 内存使用 | 逻辑 == 物理 |
逻辑≥物理 |
| 优势 | 性能最优 | 节省物理内存 |
| 劣势 | 可能浪费内存 | 地址转换开销 |
| 典型用途 | 大型纹理/缓冲区 | 虚拟内存管理 |
引用计数管理
引用计数增加 _Ref()
/**
* @brief 增加PMR引用计数
* @param psPMR PMR指针
* @param uiRefCount 增加的数量
* @return PVRSRV_OK 成功,其他值失败
*
* 线程安全:使用自旋锁保护
*/
static PVRSRV_ERROR
_Ref(PMR *psPMR, IMG_UINT32 uiRefCount)
{
OS_SPINLOCK_FLAGS uiFlags;
/* ========== 获取自旋锁 ========== */
/*
* 为什么使用自旋锁?
* - 引用计数操作非常快速
* - 不涉及睡眠操作
* - 需要严格的原子性
*/
OSSpinLockAcquire(psPMR->hRefCountLock, uiFlags);
/* ========== 安全检查1:引用计数为0 ========== */
/*
* 如果引用计数已经为0,说明PMR已经在销毁过程中
* 这时不应该再增加引用
*
* 可能的原因:
* - 竞态条件:一个线程正在释放,另一个线程试图获取
* - 程序错误:在对象销毁后仍然访问
*/
if (psPMR->uiRefCount == 0)
{
OSSpinLockRelease(psPMR->hRefCountLock, uiFlags);
PVR_DPF((PVR_DBG_ERROR,
"pmr.c: Ref Count == 0 PMR: @0x%p Annot: %s",
psPMR,
psPMR->szAnnotation));
OSWarnOn(1); // 触发内核警告(但不panic)
return PVRSRV_ERROR_REFCOUNT_OVERFLOW;
}
/* ========== 安全检查2:防止溢出 ========== */
/*
* 检查增加后是否会溢出
*
* 溢出场景:
* - 恶意攻击:故意大量增加引用导致永不释放
* - 程序错误:循环中重复引用但未释放
*
* 防御:
* - 检查 current + delta < MAX
* - 拒绝会导致溢出的操作
*/
if (psPMR->uiRefCount >= IMG_UINT32_MAX - uiRefCount)
{
OSSpinLockRelease(psPMR->hRefCountLock, uiFlags);
PVR_DPF((PVR_DBG_ERROR,
"pmr.c: Ref Count >= IMG_UINT32_MAX PMR: @0x%p "
"Annot: %s RefCount: %u",
psPMR,
psPMR->szAnnotation,
uiRefCount));
OSWarnOn(1);
return PVRSRV_ERROR_REFCOUNT_OVERFLOW;
}
/* ========== 安全增加引用计数 ========== */
/*
* 通过上面的检查后,可以安全地增加
* 此时仍在自旋锁保护下,保证原子性
*/
psPMR->uiRefCount += uiRefCount;
/* ========== 释放自旋锁 ========== */
OSSpinLockRelease(psPMR->hRefCountLock, uiFlags);
return PVRSRV_OK;
}
引用计数减少 _Unref()
/**
* @brief 减少PMR引用计数
* @param psPMR PMR指针
* @param uiRefCount 减少的数量
* @param pui32RefCount 输出:减少后的引用计数(可为NULL)
* @return PVRSRV_OK 成功,其他值失败
*/
static PVRSRV_ERROR
_Unref(PMR *psPMR, IMG_INT32 uiRefCount, IMG_UINT32 *pui32RefCount)
{
OS_SPINLOCK_FLAGS uiFlags;
/* ========== 获取自旋锁 ========== */
OSSpinLockAcquire(psPMR->hRefCountLock, uiFlags);
/* ========== 安全检查:防止下溢 ========== */
/*
* 如果当前引用计数为0,不能再减少
*
* 可能原因:
* - 多次Unref同一个引用
* - Unref次数多于Ref次数
* - 程序逻辑错误
*/
if (psPMR->uiRefCount == 0)
{
OSSpinLockRelease(psPMR->hRefCountLock, uiFlags);
PVR_DPF((PVR_DBG_ERROR,
"pmr.c: Unref Count = 0 PMR: @0x%p Annot: %s "
"RefCount: %u",
psPMR,
psPMR->szAnnotation,
uiRefCount));
OSWarnOn(1);
return PVRSRV_ERROR_REFCOUNT_OVERFLOW;
}
/* ========== 减少引用计数 ========== */
psPMR->uiRefCount -= uiRefCount;
/* ========== 返回当前引用计数 ========== */
/*
* 如果调用者提供了输出参数,返回减少后的计数
* 调用者可以根据这个值判断是否需要销毁PMR
*/
if (pui32RefCount != NULL)
{
*pui32RefCount = psPMR->uiRefCount;
}
/* ========== 释放自旋锁 ========== */
OSSpinLockRelease(psPMR->hRefCountLock, uiFlags);
return PVRSRV_OK;
}
引用计数生命周期管理
/**
* @brief 减少引用计数,必要时销毁PMR
* @param psPMR PMR指针
* @param uiRefCount 减少的数量
* @return PVRSRV_OK 成功
*
* 核心逻辑:
* 1. 减少引用计数
* 2. 如果计数归零,触发销毁流程
* 3. 如果启用延迟释放,进入僵尸列表
*/
static PVRSRV_ERROR
_UnrefAndMaybeDestroy(PMR *psPMR, IMG_UINT32 uiRefCount)
{
const PMR_IMPL_FUNCTAB *psFuncTable;
#if defined(SUPPORT_PMR_DEFERRED_FREE)
PVRSRV_DEVICE_NODE *psDevNode;
IMG_BOOL bQueuedDeviceImports = IMG_FALSE;
#endif
PVRSRV_ERROR eError;
PVR_ASSERT(psPMR != NULL);
psFuncTable = psPMR->psFuncTab;
/* ========== 获取Factory锁 ========== */
/*
* 为什么需要Factory锁?
* - PMR销毁时需要调用具体Factory的清理函数
* - Factory可能在管理多个PMR,需要同步
* - 避免在销毁过程中创建新的同类型PMR
*/
_FactoryLock(psFuncTable);
/* ========== 减少引用计数 ========== */
eError = _Unref(psPMR, uiRefCount, &uiRefCount);
PVR_LOG_GOTO_IF_ERROR(eError, "_Unref", ErrFactoryUnlock);
/* ========== 检查引用计数 ========== */
if (uiRefCount > 0)
{
/* 还有其他引用,不销毁 */
_FactoryUnlock(psFuncTable);
return PVRSRV_OK;
}
/* ========== 引用计数为0,开始销毁流程 ========== */
#if !defined(SUPPORT_PMR_DEFERRED_FREE)
/* ========== 不支持延迟释放:立即销毁 ========== */
/*
* 用于NoHW和PDump驱动
* 没有GPU缓存同步问题
*/
_PMRDestroy(psPMR);
#else
/* ========== 支持延迟释放:智能销毁策略 ========== */
psDevNode = PhysHeapDeviceNode(psPMR->psPhysHeap);
/*
* 决策树:是否需要延迟释放?
*
* 立即释放条件(任一满足):
* 1. 未设置DEFER_FREE标志(从未映射到GPU)
* 2. GPU电源OFF(缓存已失效)
* 3. PMR已标记为空僵尸
*
* 延迟释放条件:
* 1. 设置了DEFER_FREE标志
* 2. GPU电源ON或有设备导入
* 3. PMR不是空僵尸
*/
/* ========== 检查1:是否标记为延迟释放 ========== */
if (!_IntFlagIsSet(psPMR, PMR_FLAG_INTERNAL_DEFER_FREE))
{
/*
* 未标记延迟释放:
* - 可能是纯CPU内存
* - 可能从未映射到GPU
* - 可以安全立即销毁
*/
_PMRDestroy(psPMR);
goto exit_;
}
#if defined(SUPPORT_PMR_DEVICE_IMPORT_DEFERRED_FREE)
/* ========== 检查2:处理跨设备导入 ========== */
/*
* 如果其他设备导入了此PMR,也需要等待它们刷新缓存
* 将导入信息加入各个设备的僵尸列表
*/
bQueuedDeviceImports = _DeviceImportsEnqueueZombies(psPMR);
#endif
/* ========== 检查3:设备状态 ========== */
if (!bQueuedDeviceImports
&& !_IsDeviceOnAndOperating(psDevNode)
#if defined(SUPPORT_LINUX_OSPAGE_MIGRATION)
&& !PVRSRV_CHECK_OS_LINUX_MOVABLE(psPMR->uiFlags)
#endif
)
{
/*
* 设备已关闭且没有设备导入:
* - GPU缓存已失效(电源OFF)
* - 可以安全立即销毁
*/
_PMRDestroy(psPMR);
}
/* ========== 检查4:空僵尸PMR ========== */
else if (_IntFlagIsSet(psPMR, PMR_FLAG_INTERNAL_IS_ZOMBIE_PMR_EMPTY))
{
/*
* PMR已被标记为空僵尸:
* - 所有页面已释放
* - 只剩下PMR结构本身
* - 可以安全销毁
*/
_PMRDestroy(psPMR);
}
/* ========== 默认:加入僵尸列表延迟释放 ========== */
else
{
/*
* 需要延迟释放:
* 1. 标记为僵尸PMR
* 2. 加入设备的僵尸列表
* 3. 等待清理线程处理
*/
_ZombieListLock(psDevNode);
/* 设置僵尸标志 */
_IntFlagSet(psPMR, PMR_FLAG_INTERNAL_IS_ZOMBIE_PMR);
/* 加入僵尸列表 */
dllist_add_to_tail(&psDevNode->sPMRZombieList,
&psPMR->sHeader.sZombieNode);
psDevNode->uiPMRZombieCount++;
/*
* 调用Factory的Zombify回调
*
* 目的:
* - 更新内存统计(将页面从"活跃"转为"僵尸")
* - 释放可以立即释放的资源
* - 标记哪些资源需要等待
*
* 需要持锁调用,防止清理线程并发访问
*/
if (psPMR->psFuncTab->pfnZombify != NULL)
{
eError = psPMR->psFuncTab->pfnZombify(psPMR->pvFlavourData, psPMR);
PVR_LOG_IF_ERROR(eError, "pfnZombify");
}
_ZombieListUnlock(psDevNode);
}
exit_:
#endif /* SUPPORT_PMR_DEFERRED_FREE */
_FactoryUnlock(psFuncTable);
return PVRSRV_OK;
ErrFactoryUnlock:
_FactoryUnlock(psFuncTable);
return eError;
}
引用计数状态转换图
PMR生命周期状态机
┌──────────────────────────────────────────────────────────────┐
│ 初始状态 │
│ uiRefCount = 0 │
│ PMR刚创建完成 │
└──────────────────────────────────────────────────────────────┘
│
│ PMRLockPhysAddresses()
│ PMRExportPMR()
│ PMRRefPMR()
↓
┌──────────────────────────────────────────────────────────────┐
│ 活跃状态 │
│ uiRefCount > 0 │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 可能的引用来源: │ │
│ │ - 物理地址锁定 (iLockCount) │ │
│ │ - CPU映射 (iClientCpuMapCount) │ │
│ │ - GPU映射 (iAssociatedResCount) │ │
│ │ - 导出/导入 (Export/Import) │ │
│ │ - 显式引用 (RefPMR) │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
│
│ PMRUnlockPhysAddresses()
│ PMRUnexportPMR()
│ PMRUnrefPMR()
│ 所有引用释放完毕
↓
┌──────────────────────────────────────────────────────────────┐
│ 引用计数归零 │
│ uiRefCount = 0 │
│ _UnrefAndMaybeDestroy() │
└──────────────────────────────────────────────────────────────┘
│
├─────────────┬─────────────┐
↓ ↓ ↓
┌─────────────────┐ ┌──────────┐ ┌───────────────┐
│ 未设DEFER_FREE │ │ GPU OFF │ │ 空僵尸PMR │
└─────────────────┘ └──────────┘ └───────────────┘
│ │ │
└─────────────┴─────────────┘
↓
┌─────────────────────────┐
│ 立即销毁 _PMRDestroy │
└─────────────────────────┘
│
↓
┌─────────────────────────┐
│ 释放物理内存 │
│ 销毁映射表 │
│ 销毁锁 │
│ 释放PMR结构体 │
└─────────────────────────┘
│
↓
┌─────────────────────────┐
│ 生命周期结束 │
└─────────────────────────┘
或者 ↓
┌─────────────────────────────────────┐
│ 设置DEFER_FREE && GPU ON │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 进入僵尸状态 │
│ 设置 IS_ZOMBIE_PMR 标志 │
│ 加入 sPMRZombieList │
│ 等待清理线程处理 │
└─────────────────────────────────────┘
│
│ 等待GPU缓存刷新
│ 或设备电源OFF
↓
┌─────────────────────────────────────┐
│ 清理线程 _PmrZombieCleanup │
│ 检查同步条件满足 │
│ 从僵尸列表移除 │
└─────────────────────────────────────┘
│
↓
┌─────────────────────────────────────┐
│ 最终销毁 _PMRDestroy │
└─────────────────────────────────────┘
│
↓
┌─────────────────────────────────────┐
│ 生命周期结束 │
└─────────────────────────────────────┘
引用计数操作汇总表
| 操作函数 | 引用变化 | 调用场景 | 注意事项 |
|---|---|---|---|
| PMRCreatePMR | 0→0 | PMR创建 | 初始为0,需要后续Lock |
| PMRLockPhysAddresses | +1 | 锁定物理地址 | 同时增加iLockCount |
| PMRUnlockPhysAddresses | -1 | 解锁物理地址 | 同时减少iLockCount |
| PMRExportPMR | +1 | 导出到其他进程 | 设置NO_LAYOUT_CHANGE |
| PMRUnexportPMR | -1 | 取消导出 | 对应Export操作 |
| PMRImportPMR | +1 | 从其他进程导入 | 验证密钥 |
| PMRUnimportPMR | -1 | 取消导入 | 对应Import操作 |
| PMRRefPMR | +1 | 显式增加引用 | 用于长期持有 |
| PMRUnrefPMR | -1 | 显式减少引用 | 对应RefPMR |
| PMRMakeLocalImportHandle | +1 | 创建本地导入句柄 | 同进程内使用 |
| PMRUnmakeLocalImportHandle | -1 | 销毁本地导入句柄 | 对应MakeLocal |
| DevmemIntMapPMR | +1 | 映射到GPU | 增加GPU映射计数 |
| DevmemIntUnmapPMR | -1 | 从GPU取消映射 | 减少GPU映射计数 |
地址转换引擎
核心转换函数 _PMRLogicalOffsetToPhysicalOffset
/**
* @brief 逻辑地址到物理地址转换
* @param psPMR PMR指针
* @param ui32Log2PageSize 页面大小(log2)
* @param ui32NumOfPages 要转换的页数
* @param uiLogicalOffset 逻辑偏移量
* @param puiPhysicalOffset 输出:物理偏移量数组
* @param pui32BytesRemain 输出:剩余字节数
* @param bValid 输出:页面有效性数组
* @return PVRSRV_OK 成功,其他值失败
*
* 功能说明:
* - 将PMR内的逻辑地址转换为实际物理地址
* - 支持单页和多页批量转换
* - 处理稀疏内存的无效页面
* - 返回第一个块的剩余字节数
*/
static PVRSRV_ERROR
_PMRLogicalOffsetToPhysicalOffset(const PMR *psPMR,
IMG_UINT32 ui32Log2PageSize,
IMG_UINT32 ui32NumOfPages,
IMG_DEVMEM_OFFSET_T uiLogicalOffset,
IMG_DEVMEM_OFFSET_T *puiPhysicalOffset,
IMG_UINT32 *pui32BytesRemain,
IMG_BOOL *bValid)
{
PMR_MAPPING_TABLE *psMappingTable = psPMR->psMappingTable;
IMG_DEVMEM_OFFSET_T uiPageSize = 1ULL << ui32Log2PageSize;
IMG_DEVMEM_OFFSET_T uiOffset = uiLogicalOffset;
IMG_UINT64 ui64ChunkIndex;
IMG_UINT32 ui32Remain;
IMG_UINT32 idx;
/* 断言:至少转换一页 */
PVR_ASSERT(ui32NumOfPages > 0);
/* ========== 决策:快速路径 vs 慢速路径 ========== */
/*
* 快速路径条件:
* ui32NumPhysChunks == ui32NumLogicalChunks
*
* 含义:
* - 物理块数等于逻辑块数
* - 意味着是连续内存或完全backing的稀疏内存
* - 逻辑地址 == 物理地址(1:1映射)
*
* 优势:
* - 无需查表
* - 无需复杂计算
* - 性能最优
*/
if (psMappingTable->ui32NumPhysChunks == psMappingTable->ui32NumLogicalChunks)
{
/* ========== 快速路径实现 ========== */
/*
* 第一页处理
*
* 计算剩余字节:
* 从当前偏移到PMR末尾的字节数
*/
*pui32BytesRemain = TRUNCATE_64BITS_TO_32BITS(psPMR->uiLogicalSize - uiOffset);
/*
* 直接赋值:逻辑偏移 == 物理偏移
*/
puiPhysicalOffset[0] = uiOffset;
bValid[0] = IMG_TRUE;
/* ========== 多页处理 ========== */
if (ui32NumOfPages > 1)
{
/*
* 对齐到页边界
*
* 示例:
* uiOffset = 0x1234 (初始偏移)
* uiPageSize = 0x1000 (4KB)
* uiOffset &= ~0xFFF // 结果:0x1000
*
* 原因:
* 后续页面都是整页,从页边界开始
*/
uiOffset &= ~(uiPageSize-1);
/*
* 循环处理剩余页面
*/
for (idx = 1; idx < ui32NumOfPages; idx++)
{
uiOffset += uiPageSize; // 下一页
puiPhysicalOffset[idx] = uiOffset;
bValid[idx] = IMG_TRUE;
}
}
}
/* ========== 慢速路径:稀疏内存 ========== */
else
{
/*
* 慢速路径需要:
* 1. 计算逻辑块索引
* 2. 查询映射表
* 3. 计算物理偏移
* 4. 处理无效页面
*/
for (idx = 0; idx < ui32NumOfPages; idx++)
{
IMG_UINT32 uiTranslation;
const IMG_UINT32 uiChunkSize = psMappingTable->uiChunkSize;
/* ========== 步骤1:计算逻辑块索引和块内偏移 ========== */
/*
* OSDivide64r64 函数:
* 将64位除法拆分为商和余数
*
* ui64ChunkIndex = uiOffset / uiChunkSize (商)
* ui32Remain = uiOffset % uiChunkSize (余数)
*
* 示例:
* uiOffset = 0x5234 (21044 bytes)
* uiChunkSize = 0x1000 (4096 bytes)
* ui64ChunkIndex = 5 (第5个块)
* ui32Remain = 0x234 (564 bytes块内偏移)
*/
ui64ChunkIndex = OSDivide64r64(
uiOffset,
TRUNCATE_64BITS_TO_32BITS(uiChunkSize),
&ui32Remain);
/* ========== 步骤2:边界检查 ========== */
/*
* 防止越界访问
*
* 可能原因:
* - 用户空间传入的参数错误
* - 内存布局发生变化但参数未更新
*/
if (ui64ChunkIndex >= psMappingTable->ui32NumLogicalChunks)
{
return PVRSRV_ERROR_BAD_MAPPING;
}
/* ========== 步骤3:查询映射表 ========== */
/*
* aui32Translation[逻辑块索引] = 物理块索引
*
* 特殊值:
* TRANSLATION_INVALID (0xFFFFFFFF) 表示该逻辑块未映射
*/
uiTranslation = psMappingTable->aui32Translation[ui64ChunkIndex];
/* ========== 步骤4:处理映射结果 ========== */
if (uiTranslation == TRANSLATION_INVALID)
{
/*
* 无效映射:
* - 该逻辑块没有分配物理内存
* - 稀疏内存的"洞"
* - 标记为无效,返回特殊值
*/
bValid[idx] = IMG_FALSE;
puiPhysicalOffset[idx] = IMG_UINT64_C(0xffffffffffffffff);
}
else
{
/*
* 有效映射:
* 计算物理地址
*
* 公式:
* 物理偏移 = 物理块索引 × 块大小 + 块内偏移
*
* 示例:
* uiTranslation = 3 (物理块3)
* uiChunkSize = 0x1000
* ui32Remain = 0x234
* 物理偏移 = 3 * 0x1000 + 0x234 = 0x3234
*/
bValid[idx] = IMG_TRUE;
puiPhysicalOffset[idx] = uiTranslation * uiChunkSize + ui32Remain;
}
/* ========== 步骤5:计算第一页剩余字节 ========== */
if (idx == 0)
{
/*
* 剩余字节 = 块大小 - 块内偏移
*
* 示例:
* uiChunkSize = 0x1000 (4096)
* ui32Remain = 0x234 (564)
* 剩余 = 4096 - 564 = 3532 bytes
*
* 用途:
* - 告诉调用者在当前块内还能访问多少字节
* - 用于IO操作的边界检查
*/
*pui32BytesRemain = TRUNCATE_64BITS_TO_32BITS(uiChunkSize - ui32Remain);
/*
* 对齐到页边界
* 后续页面从整页边界开始
*/
uiOffset &= ~(uiPageSize-1);
}
/* ========== 步骤6:移动到下一页 ========== */
uiOffset += uiPageSize;
}
}
return PVRSRV_OK;
}
地址转换示意图
连续内存(快速路径)
连续内存地址转换
逻辑地址空间:
┌─────────────────────────────────────────────────────────┐
│ 0x0000 │ 0x1000 │ 0x2000 │ 0x3000 │ 0x4000 │
|────────────────────────────────────────────────────────|
│ Page0 │ Page1 │ Page2 │ Page3 │ Page4 │
└─────────────────────────────────────────────────────────┘
↓ (1:1映射)
物理地址空间:
┌─────────────────────────────────────────────────────────┐
│ 0x0000 │ 0x1000 │ 0x2000 │ 0x3000 │ 0x4000 │
|────────────────────────────────────────────────────────|
│ Page0 │ Page1 │ Page2 │ Page3 │ Page4 │
└─────────────────────────────────────────────────────────┘
转换过程:
1. 检测:NumPhysChunks == NumLogicalChunks
2. 直接赋值:PhysicalOffset = LogicalOffset
3. 无需查表,性能最优
时间复杂度:O(1)
稀疏内存(慢速路径)
稀疏内存地址转换
逻辑地址空间(连续):
┌────────┬────────┬────────┬────────┬────────┬────────┐
│ Chunk0 │ Chunk1 │ Chunk2 │ Chunk3 │ Chunk4 │ Chunk5 │
|─────────────────────────────────────────────────────|
│ 0x0 │ 0x1 │ 0x2 │ 0x3 │ 0x4 │ 0x5 │
└────────┴────────┴────────┴────────┴────────┴────────┘
↓ ↓ ↓ ↓ ↓ ↓
映射表 aui32Translation[]:
┌────────┬────────┬────────┬────────┬────────┬────────┐
│ 0 │ 0xFF │ 2 │ 0xFF │ 4 │ 5 │
|─────────────────────────────────────────────────────|
│ (有效) │ (无效) │ (有效) │ (无效) │ (有效) │ (有效) │
└────────┴────────┴────────┴────────┴────────┴────────┘
↓ ↓ ↓ ↓
物理地址空间(不连续):
┌────────┐ ┌────────┐ ┌────────┬────────┐
│ Block0 │ │ Block2 │ │ Block4 │ Block5 │
│ Phys │ │ Phys │ │ Phys │ Phys │
└────────┘ └────────┘ └────────┴────────┘
转换示例:
输入:逻辑偏移 0x2500 (在Chunk2中)
步骤1:计算块索引
ChunkIndex = 0x2500 / 0x1000 = 2
Remain = 0x2500 % 0x1000 = 0x500
步骤2:查询映射表
Translation = aui32Translation[2] = 2 (有效)
步骤3:计算物理偏移
PhysicalOffset = 2 * 0x1000 + 0x500 = 0x2500
bValid = TRUE
**无效访问示例:**
输入:逻辑偏移 0x1234 (在Chunk1中)
步骤1:ChunkIndex = 1, Remain = 0x234
步骤2:Translation = aui32Translation[1] = 0xFF (无效)
步骤3:PhysicalOffset = 0xFFFFFFFFFFFFFFFF
bValid = FALSE
时间复杂度:O(n) 其中n为页数
页对齐向下取整
一次性分配内存包含:
1. sizeof(*psPMR): PMR结构体大小
2. sizeof(*psMappingTable): 映射表结构体大小
3. IMG_FLEX_ARRAY_SIZE(sizeof(IMG_UINT32), ui32NumLogicalChunks): 翻译数组大小
内存布局:
┌─────────────┬─────────────────┬─────────────────────────┐
│ PMR struct │ MappingTable │ Translation Array │
│ │ struct │ [ui32NumLogicalChunks] │
└─────────────┴─────────────────┴─────────────────────────┘
在慢速路径中,代码需要处理多个连续页面的地址转换。关键问题是:用户传入的逻辑偏移量可能不是页对齐的,所以需要页对齐向下取整。
示例说明
假设:
- 页大小 = 4096 字节 (0x1000)
- 用户传入逻辑偏移 = 0x1234 (不是页对齐)
- 需要转换 3 个页面
不进行向下取整的情况:
页面0: 偏移 = 0x1234 (用户指定的起始位置)
页面1: 偏移 = 0x1234 + 0x1000 = 0x2234
页面2: 偏移 = 0x2234 + 0x1000 = 0x3234
问题:这些偏移量都不是页边界对齐的!
进行向下取整后:
uiOffset &= ~(uiPageSize-1); // 0x1234 & ~0xFFF = 0x1000
页面0: 偏移 = 0x1234 (第一个页面保持用户指定的位置)
页面1: 偏移 = 0x1000 + 0x1000 = 0x2000 (页对齐)
页面2: 偏移 = 0x2000 + 0x1000 = 0x3000 (页对齐)
为什么只对后续页面对齐?
第一个页面 (idx == 0):
- 保持用户指定的确切偏移量:puiPhysicalOffset[0] = 转换后的实际地址
- 用户可能需要访问页面中间的某个位置,不能强制对齐
后续页面 (idx > 0):
- 必须页对齐:因为我们是在处理完整的页面
- 从包含用户偏移量的页面边界开始,逐页递增
具体代码流程
// 第一次迭代 (idx == 0)
if (idx == 0) {
// 1. 计算当前块中剩余的字节数
*pui32BytesRemain = TRUNCATE_64BITS_TO_32BITS(uiChunkSize - ui32Remain);
// 2. 为后续页面计算做准备,向下对齐到页边界
uiOffset &= ~(uiPageSize-1); // 清除低位,对齐到页边界
}
// 每次迭代结束都会执行
uiOffset += uiPageSize; // 移动到下一个页面
位运算解释
uiOffset &= ~(uiPageSize-1);
以4KB页为例:
- uiPageSize = 0x1000 (4096)
- uiPageSize-1 = 0xFFF (4095)
- ~(uiPageSize-1) = 0xFFFFFFFFFFFFF000 (掩码)
这个掩码会清除低12位,实现向下对齐到4KB边界。
内存布局示意
原始用户偏移: 0x1234
│
▼
┌────┴─────┬─────────┬─────────┬─────────┐
│ Page 0 │ Page 1 │ Page 2 │ Page 3 │
├──────────┼─────────┼─────────┼─────────┤
│ 0x1000 │ 0x2000 │ 0x3000 │ 0x4000 │
└──────────┴─────────┴─────────┴─────────┘
│ │ │
│ │ └── 0x3000 (对齐)
│ └── 0x2000 (对齐)
└── 0x1234 (用户指定,不对齐)
向下取整的目的是:
1. 第一个页面:保持用户精确指定的偏移量
2. 后续页面:确保从正确的页边界开始,逐页递增
3. 内存管理正确性:确保页面地址计算的准确性和一致性
这种设计既满足了用户对精确偏移的需求,又保证了内存管理系统的页面对齐要求。
PMR映射表工作原理详解
映射表概念
psMappingTable->aui32Translation[ui64ChunkIndex] 是一个逻辑块到物理块的映射数组。
基本概念:
- 逻辑块:用户看到的连续内存块编号
- 物理块:实际物理内存中的块编号
- 映射表:记录逻辑块到物理块映射关系的数组
映射表结构示意
逻辑内存视图 (用户看到的):
┌─────────┬─────────┬─────────┬─────────┐
│ Chunk 0 │ Chunk 1 │ Chunk 2 │ Chunk 3 │
│ (逻辑) │ (逻辑) │ (逻辑) │ (逻辑) │
└─────────┴─────────┴─────────┴─────────┘
映射表 aui32Translation[]:
Index: [0] [1] [2] [3]
Value: [5] [2] [7] [1]
│ │ │ │
▼ ▼ ▼ ▼
实际物理内存:
┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│Chunk 0 │Chunk 1 │Chunk 2 │Chunk 3 │Chunk 4 │Chunk 5 │Chunk 6 │Chunk 7 │
│(物理) │(物理) │(物理) │(物理) │(物理) │(物理) │(物理) │(物理) │
└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
代码详解
获取映射关系
uiTranslation = psMappingTable->aui32Translation[ui64ChunkIndex];
ui64ChunkIndex: 当前逻辑偏移所在的逻辑块索引aui32Translation[ui64ChunkIndex]: 返回对应的物理块索引uiTranslation: 存储物理块索引号检查映射有效性
if (uiTranslation == TRANSLATION_INVALID)
{
bValid[idx] = IMG_FALSE;
puiPhysicalOffset[idx] = IMG_UINT64_C(0xffffffffffffffff);
}
TRANSLATION_INVALID 表示这个逻辑块没有对应的物理内存,可能原因:
- 内存还未分配
- 内存已被释放
- 稀疏内存映射中的"洞"
计算物理偏移量
else
{
bValid[idx] = IMG_TRUE;
puiPhysicalOffset[idx] = uiTranslation * uiChunkSize + ui32Remain;
}
物理偏移计算公式详解
puiPhysicalOffset[idx] = uiTranslation * uiChunkSize + ui32Remain;
公式组成:
- uiTranslation: 物理块索引号
- uiChunkSize: 每个块的大小(字节)
- ui32Remain: 在块内的偏移量
计算过程:
1. uiTranslation * uiChunkSize: 计算物理块的起始地址
2. + ui32Remain: 加上块内偏移,得到最终物理地址
具体例子
假设:
- 块大小 uiChunkSize = 0x10000 (64KB)
- 用户访问逻辑偏移 0x25678
- 通过除法得到:ui64ChunkIndex = 2, ui32Remain = 0x5678
映射表查找:
uiTranslation = aui32Translation[2]; // 假设返回 7
物理地址计算:
puiPhysicalOffset[idx] = 7 * 0x10000 + 0x5678 = 0x70000 + 0x5678 = 0x75678
内存布局可视化
用户逻辑地址: 0x25678
↓ (通过映射表转换)
实际物理地址: 0x75678
逻辑内存:
┌──────────┬──────────┬──────────┬──────────┐
│ Block 0 │ Block 1 │ Block 2 │ Block 3 │
│ 0x00000 │ 0x10000 │ 0x20000 │ 0x30000 │
└──────────┴──────────┴────┬─────┴──────────┘
│
0x25678 (逻辑)
│
▼ (映射)
物理内存:
┌──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
│ Block 0 │ Block 1 │ Block 2 │ Block 3 │ Block 4 │ Block 5 │ Block 6 │ Block 7 │
│ 0x00000 │ 0x10000 │ 0x20000 │ 0x30000 │ 0x40000 │ 0x50000 │ 0x60000 │ 0x70000 │
└──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴────┬─────┘
│
0x75678 (物理)
为什么需要映射表?
内存碎片处理
物理内存可能不连续,映射表允许逻辑上连续的内存映射到分散的物理块。
动态内存管理
- 延迟分配:逻辑空间预留,物理内存按需分配
- 内存回收:释放不用的物理块,映射表标记为无效
稀疏内存
大的虚拟地址空间中只有部分区域有对应的物理内存。
内存重映射
可以重新组织物理内存布局而不影响逻辑地址。
- 映射表:逻辑块索引 → 物理块索引的查找表
- 物理地址计算:物理块起始地址 + 块内偏移 = 最终物理地址
- 无效映射处理:标记为无效并设置特殊值,避免访问错误
这种设计是现代操作系统和GPU驱动中虚拟内存管理的核心机制!
获取设备物理地址 PMR_DevPhysAddr
/**
* @brief 获取设备物理地址
* @param psPMR PMR指针
* @param ui32Log2PageSize 页面大小(log2)
* @param ui32NumOfPages 页数
* @param uiLogicalOffset 逻辑偏移,即需求内存大小
* @param psDevAddrPtr 输出:设备物理地址数组
* @param pbValid 输出:有效性数组
* @param uiPMRUsage PMR使用模式(CPU/DEVICE/MAPPING)
* @return PVRSRV_OK 成功
*
* 必须先调用 PMRLockPhysAddresses()
*/
PVRSRV_ERROR
PMR_DevPhysAddr(const PMR *psPMR,
IMG_UINT32 ui32Log2PageSize,
IMG_UINT32 ui32NumOfPages,
IMG_DEVMEM_OFFSET_T uiLogicalOffset,
IMG_DEV_PHYADDR *psDevAddrPtr,
IMG_BOOL *pbValid,
PMR_PHYSADDRMODE_TYPE uiPMRUsage)
{
IMG_UINT32 ui32Remain;
PVRSRV_ERROR eError = PVRSRV_OK;
IMG_DEVMEM_OFFSET_T auiPhysicalOffset[PMR_MAX_TRANSLATION_STACK_ALLOC];
IMG_DEVMEM_OFFSET_T *puiPhysicalOffset = auiPhysicalOffset;
#if defined(SUPPORT_STATIC_IPA)
IMG_UINT32 ui32FlagsIPAPolicy; /* IPA策略标志 */
IMG_UINT32 ui32IPAHeapShift; /* 物理堆位移值 */
IMG_UINT32 ui32IPAHeapPolicyValue; /* 物理堆默认策略 */
IMG_UINT32 ui32IPAHeapClearMask; /* 物理堆清除掩码 */
IMG_UINT64 ui64IPAPolicy; /* 应用到地址的IPA策略 */
IMG_UINT64 ui64IPAClearMask; /* 应用到地址的清除掩码 */
#endif
PVR_ASSERT(psPMR != NULL);
PVR_ASSERT(ui32NumOfPages > 0);
PVR_ASSERT(psPMR->psFuncTab->pfnDevPhysAddr != NULL);
/* ========== 前置检查:物理地址必须已锁定 ========== */
/*
* iLockCount 检查:
* - On-demand分配:必须 > 0
* - 非On-demand:必须 > 1(初始值为1)
*
* 为什么需要锁定?
* - 防止物理页面被换出
* - 防止页面迁移
* - 保证地址有效性
*/
if (OSAtomicRead(&psPMR->iLockCount) <= 0)
{
PVR_DPF((PVR_DBG_ERROR,"Attempt to obtain DevPhysAddr of non-backed PMR"));
return PVRSRV_ERROR_PMR_NOT_PERMITTED;
}
/* ========== 内存分配优化:栈 vs 堆 ========== */
/*
* PMR_MAX_TRANSLATION_STACK_ALLOC 通常为 32 或 64
*
* 策略:
* - 小量页面:使用栈数组(快速)
* - 大量页面:动态分配(避免栈溢出)
*/
if (ui32NumOfPages > PMR_MAX_TRANSLATION_STACK_ALLOC)
{
puiPhysicalOffset = OSAllocMem(ui32NumOfPages * sizeof(IMG_DEVMEM_OFFSET_T));
PVR_RETURN_IF_NOMEM(puiPhysicalOffset);
}
/* ========== 步骤1:逻辑地址转物理偏移 ========== */
eError = _PMRLogicalOffsetToPhysicalOffset(psPMR,
ui32Log2PageSize,
ui32NumOfPages,
uiLogicalOffset,
puiPhysicalOffset,
&ui32Remain,
pbValid);
PVR_LOG_GOTO_IF_ERROR(eError, "_PMRLogicalOffsetToPhysicalOffset", FreeOffsetArray);
#if defined(SUPPORT_STATIC_IPA)
/* ========== 步骤2:IPA(中间物理地址)处理 ========== */
/*
* IPA (Intermediate Physical Address):
* 在某些系统中,需要对物理地址进行额外转换
*
* 使用场景:
* - IOMMU系统
* - 多级地址转换
* - 安全内存隔离
*
* 三层地址转换:
* CPU地址 → IPA → 设备物理地址
*/
PVR_ASSERT(psPMR->psPhysHeap != NULL);
/* 获取物理堆的IPA配置 */
ui32IPAHeapShift = PhysHeapGetIPAShift(psPMR->psPhysHeap);
ui32IPAHeapPolicyValue = PhysHeapGetIPAValue(psPMR->psPhysHeap);
ui32IPAHeapClearMask = PhysHeapGetIPAMask(psPMR->psPhysHeap);
#if defined(PVRSRV_INTERNAL_IPA_FEATURE_TESTING)
/* 测试模式:允许PMR覆盖物理堆的默认IPA策略 */
ui32FlagsIPAPolicy = PVRSRV_MEMALLOCFLAG_IPA_POLICY(psPMR->uiFlags);
if (ui32FlagsIPAPolicy == 0U)
{
ui32FlagsIPAPolicy = ui32IPAHeapPolicyValue;
}
#else
/* 生产模式:使用物理堆的默认策略 */
ui32FlagsIPAPolicy = ui32IPAHeapPolicyValue;
#endif
/*
* CPU使用模式:禁用IPA转换
*
* 原因:
* - CPU直接访问物理地址
* - 不需要IPA中间层
*/
if (unlikely(BITMASK_HAS(uiPMRUsage, CPU_USE)))
{
ui32IPAHeapClearMask = 0U;
ui32FlagsIPAPolicy = 0U;
}
/* 计算IPA转换参数 */
ui64IPAPolicy = (IMG_UINT64)ui32FlagsIPAPolicy << ui32IPAHeapShift;
ui64IPAClearMask = (IMG_UINT64)ui32IPAHeapClearMask << ui32IPAHeapShift;
#endif /* SUPPORT_STATIC_IPA */
#if defined(SUPPORT_LINUX_OSPAGE_MIGRATION)
/* ========== 步骤3:处理页面迁移 ========== */
/*
* 页面迁移场景:
* - Linux内核的页面回收
* - 内存碎片整理
* - NUMA节点间迁移
*/
if (BITMASK_HAS(uiPMRUsage, MAPPING_USE) && BITMASK_HAS(uiPMRUsage, CPU_USE))
{
/*
* CPU映射场景:需要等待迁移完成
*
* 策略:
* - 自旋等待迁移完成
* - 不设超时(必须完成)
* - 释放锁让其他线程运行
*/
PMRLockHeldAssert(psPMR);
while (1)
{
if (psPMR->eState == PMR_STATE_ACTIVE)
{
break; /* 迁移完成,可以获取地址 */
}
/*
* 释放锁,让迁移线程完成工作
* 避免死锁
*/
PMRUnlockPMR(psPMR);
OSReleaseThreadQuanta(); /* 主动让出CPU */
PMRLockPMR(psPMR);
}
/* 迁移完成,获取物理地址 */
eError = psPMR->psFuncTab->pfnDevPhysAddr(psPMR->pvFlavourData,
ui32Log2PageSize,
ui32NumOfPages,
puiPhysicalOffset,
#if defined(SUPPORT_STATIC_IPA)
ui64IPAPolicy,
ui64IPAClearMask,
#endif
pbValid,
psDevAddrPtr);
}
else if (BITMASK_HAS(uiPMRUsage, MAPPING_USE) && BITMASK_HAS(uiPMRUsage, DEVICE_USE))
{
/*
* GPU映射场景:如果在迁移中,返回重试
*
* 原因:
* - GPU映射持有更高层的锁
* - 不能长时间等待
* - 返回RETRY让上层重试
*/
PMRLockHeldAssert(psPMR);
if (psPMR->eState != PMR_STATE_ACTIVE)
{
eError = PVRSRV_ERROR_RETRY;
goto FreeOffsetArray;
}
eError = psPMR->psFuncTab->pfnDevPhysAddr(psPMR->pvFlavourData,
ui32Log2PageSize,
ui32NumOfPages,
puiPhysicalOffset,
#if defined(SUPPORT_STATIC_IPA)
ui64IPAPolicy,
ui64IPAClearMask,
#endif
pbValid,
psDevAddrPtr);
}
else
#endif /* SUPPORT_LINUX_OSPAGE_MIGRATION */
{
/* ========== 步骤4:标准路径 - 调用Factory函数获取物理地址 ========== */
/*
* 调用具体PMR实现的函数
*
* 不同类型PMR的实现:
* - OS Pages: 查询页面结构体
* - DMA-BUF: 从sg_table获取
* - ION: 从ION缓冲区获取
* - 物理内存: 直接返回物理地址
*/
eError = psPMR->psFuncTab->pfnDevPhysAddr(psPMR->pvFlavourData,
ui32Log2PageSize,
ui32NumOfPages,
puiPhysicalOffset,
#if defined(SUPPORT_STATIC_IPA)
ui64IPAPolicy,
ui64IPAClearMask,
#endif
pbValid,
psDevAddrPtr);
PVR_GOTO_IF_ERROR(eError, FreeOffsetArray);
}
#if defined(PVR_PMR_TRANSLATE_UMA_ADDRESSES)
/* ========== 步骤5:UMA地址转换 ========== */
/*
* UMA (Unified Memory Architecture):
* CPU和GPU共享同一物理地址空间
*
* 某些系统需要CPU物理地址→GPU设备地址转换
*
* 注意:默认关闭,因为大多数系统不需要
*/
if (PhysHeapGetType(psPMR->psPhysHeap) == PHYS_HEAP_TYPE_UMA ||
PhysHeapGetType(psPMR->psPhysHeap) == PHYS_HEAP_TYPE_DMA)
{
IMG_UINT32 i;
IMG_DEV_PHYADDR sDevPAddrCorrected;
/* 逐页转换地址 */
for (i = 0; i < ui32NumOfPages; i++)
{
PhysHeapCpuPAddrToDevPAddr(psPMR->psPhysHeap,
1,
&sDevPAddrCorrected,
(IMG_CPU_PHYADDR *) &psDevAddrPtr[i]);
psDevAddrPtr[i].uiAddr = sDevPAddrCorrected.uiAddr;
}
}
#endif
FreeOffsetArray:
/* ========== 清理:释放动态分配的内存 ========== */
if (puiPhysicalOffset != auiPhysicalOffset)
{
OSFreeMem(puiPhysicalOffset);
}
return eError;
}
地址转换完整流程图
┌─────────────────────────────────────────────────────────────┐
│ PMR_DevPhysAddr 完整流程 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 前置检查 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 1. 检查 iLockCount > 0 (物理地址已锁定) │ │
│ │ 2. 检查 psFuncTab->pfnDevPhysAddr != NULL │ │
│ │ 3. 检查 ui32NumOfPages > 0 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 内存分配优化 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ if (ui32NumOfPages <= 32) │ │
│ │ 使用栈数组 auiPhysicalOffset[32] │ │
│ │ else │ │
│ │ 动态分配 OSAllocMem() │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第1阶段:逻辑地址 → 物理偏移 │
│ _PMRLogicalOffsetToPhysicalOffset() │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 连续内存? │ │
│ │ ├─ 是 → 快速路径:PhysOffset = LogicalOffset │ │
│ │ └─ 否 → 慢速路径:查询映射表 │ │
│ │ │ │
│ │ 输出: │ │
│ │ - puiPhysicalOffset[]: 物理偏移数组 │ │
│ │ - pbValid[]: 有效性标记 │ │
│ │ - ui32Remain: 第一块剩余字节 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第2阶段:IPA转换准备 (可选) │
│ #if defined(SUPPORT_STATIC_IPA) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 获取物理堆IPA配置: │ │
│ │ - ui32IPAHeapShift: 位移值 │ │
│ │ - ui32IPAHeapPolicyValue: 策略值 │ │
│ │ - ui32IPAHeapClearMask: 清除掩码 │ │
│ │ │ │
│ │ if (CPU_USE) │ │
│ │ 禁用IPA转换 │ │
│ │ else │ │
│ │ 计算IPA参数 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第3阶段:处理页面迁移 (可选) │
│ #if defined(SUPPORT_LINUX_OSPAGE_MIGRATION) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ CPU映射 + 迁移中? │ │
│ │ ├─ 是 → 自旋等待迁移完成 │ │
│ │ │ while (eState != ACTIVE) │ │
│ │ │ 释放锁 → 让出CPU → 重新获取锁 │ │
│ │ │ │ │
│ │ GPU映射 + 迁移中? │ │
│ │ └─ 是 → 返回 PVRSRV_ERROR_RETRY │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第4阶段:调用Factory获取设备物理地址 │
│ psFuncTab->pfnDevPhysAddr() │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 根据PMR类型调用不同实现: │ │
│ │ │ │
│ │ OS Pages PMR: │ │
│ │ 从 struct page 获取 PFN │ │
│ │ PFN << PAGE_SHIFT → 物理地址 │ │
│ │ │ │
│ │ DMA-BUF PMR: │ │
│ │ 从 sg_table 遍历 scatter-gather 列表 │ │
│ │ 获取每个段的物理地址 │ │
│ │ │ │
│ │ ION PMR: │ │
│ │ 从 ION buffer 的 sg_table 获取 │ │
│ │ │ │
│ │ 物理内存PMR: │ │
│ │ 直接返回预先分配的物理地址 │ │
│ │ │ │
│ │ 应用IPA转换(如果启用) │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第5阶段:UMA地址转换 (可选) │
│ #if defined(PVR_PMR_TRANSLATE_UMA_ADDRESSES) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ if (PHYS_HEAP_TYPE_UMA || PHYS_HEAP_TYPE_DMA) │ │
│ │ for each page: │ │
│ │ PhysHeapCpuPAddrToDevPAddr() │ │
│ │ 转换 CPU物理地址 → GPU设备地址 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 清理和返回 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ if (动态分配的内存) │ │
│ │ OSFreeMem(puiPhysicalOffset) │ │
│ │ │ │
│ │ 返回: │ │
│ │ - psDevAddrPtr[]: 设备物理地址数组 │ │
│ │ - pbValid[]: 页面有效性数组 │ │
│ │ - PVRSRV_OK 或错误码 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
物理地址锁定
锁定物理地址 PMRLockPhysAddressesNested
/**
* @brief 锁定PMR的物理地址(支持嵌套)
* @param psPMR PMR指针
* @param uiLockCount 锁定次数
* @param ui32NestingLevel 嵌套级别(防止死锁)
* @return PVRSRV_OK 成功
*
* 作用:
* 1. 防止物理页面被换出
* 2. 防止页面迁移
* 3. 确保物理地址在使用期间有效
* 4. 增加引用计数
*/
PVRSRV_ERROR
PMRLockPhysAddressesNested(PMR *psPMR,
IMG_UINT32 uiLockCount,
IMG_UINT32 ui32NestingLevel)
{
PVRSRV_ERROR eError;
IMG_UINT32 uiCallbackValue;
PVR_ASSERT(psPMR != NULL);
/*
* 注意:这个锁不是用来保护引用计数的
* (引用计数由自旋锁保护)
*
* 这个锁的作用:
* - 保证物理地址锁定操作的原子性
* - 确保Lock和Factory回调之间没有其他操作
*/
/* ========== 步骤1:获取PMR主锁 ========== */
/*
* ui32NestingLevel用于防止死锁
*
* 场景:函数A持有锁0,调用函数B需要锁1
* 如果没有嵌套级别,可能死锁
*
* 嵌套级别越高,优先级越高
*/
OSLockAcquireNested(psPMR->hLock, ui32NestingLevel);
/* ========== 步骤2:增加引用计数 ========== */
/*
* 为什么在这里增加引用计数?
*
* 1. 锁定物理地址意味着要使用PMR
* 2. 必须保证PMR在使用期间不被销毁
* 3. 引用计数是生命周期管理的核心机制
*
* 注意:即使物理地址锁定失败,引用计数也会增加
* 失败时需要在错误路径中减少引用
*/
eError = _Ref(psPMR, uiLockCount);
if (eError != PVRSRV_OK)
{
OSLockRelease(psPMR->hLock);
return eError;
}
/* ========== 步骤3:增加锁计数 ========== */
/*
* iLockCount作用:
* 1. 跟踪Lock/Unlock配对
* 2. 调试检查(防止多unlock或少unlock)
* 3. 决定何时调用Factory的Lock/Unlock回调
*
* 计数规则:
* - On-demand PMR: 初始为0,第一次Lock时变为1
* - 非On-demand PMR: 初始为1(已backing),Lock后变为2
*/
/*
* uiCallbackValue: 需要调用Factory回调的阈值
*
* 计算逻辑:
* - On-demand: 0 → 1时调用(第一次锁定)
* - 非On-demand: 1 → 2时调用(第一次额外锁定)
*/
uiCallbackValue = uiLockCount + (PVRSRV_CHECK_ON_DEMAND(psPMR->uiFlags) ? 0 : 1);
if (OSAtomicAdd(&psPMR->iLockCount, uiLockCount) == uiCallbackValue)
{
/*
* 锁计数刚好达到阈值,调用Factory回调
*
* pfnLockPhysAddresses的作用:
* 1. 分配物理页面(如果是on-demand)
* 2. Pin住页面(防止被swap out)
* 3. 增加页面引用(防止被回收)
* 4. 刷新TLB(如果需要)
*/
if (psPMR->psFuncTab->pfnLockPhysAddresses != NULL)
{
/* 必须成对实现Lock和Unlock */
PVR_ASSERT(psPMR->psFuncTab->pfnUnlockPhysAddresses != NULL);
eError = psPMR->psFuncTab->pfnLockPhysAddresses(psPMR->pvFlavourData);
PVR_GOTO_IF_ERROR(eError, e1);
}
}
/* ========== 步骤4:释放PMR主锁 ========== */
OSLockRelease(psPMR->hLock);
return PVRSRV_OK;
/* ========== 错误处理 ========== */
e1:
/*
* Factory回调失败,需要回滚
* 1. 减少锁计数
* 2. 减少引用计数
* 3. 释放主锁
*/
OSAtomicSubtract(&psPMR->iLockCount, uiLockCount);
PVR_ASSERT(_GetRef(psPMR) != 0);
OSLockRelease(psPMR->hLock);
PVR_ASSERT(eError != PVRSRV_OK);
/*
* 减少引用计数必须在最后
* 因为可能触发PMR销毁
*/
(void) _Unref(psPMR, uiLockCount, NULL);
return eError;
}
解锁物理地址 PMRUnlockPhysAddressesNested
/**
* @brief 解锁PMR的物理地址(支持嵌套)
* @param psPMR PMR指针
* @param uiLockCount 解锁次数
* @param ui32NestingLevel 嵌套级别
* @return PVRSRV_OK 成功
*
* 注意:必须与PMRLockPhysAddresses配对使用
*/
PVRSRV_ERROR
PMRUnlockPhysAddressesNested(PMR *psPMR,
IMG_UINT32 uiLockCount,
IMG_UINT32 ui32NestingLevel)
{
PVRSRV_ERROR eError = PVRSRV_OK;
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
PMR_IMPL_ZOMBIEPAGES pvZombiePages = NULL;
PMR_ZOMBIE_PAGES* psPMRZombiePages = NULL;
#endif
PVR_ASSERT(psPMR != NULL);
/* ========== 步骤1:获取PMR主锁 ========== */
/*
* 获取锁的原因:
* 1. 保证锁计数归零和解锁操作的原子性
* 2. 防止竞态条件(一个线程unlock,另一个线程lock)
*/
OSLockAcquireNested(psPMR->hLock, ui32NestingLevel);
/* ========== 步骤2:检查锁计数 ========== */
/*
* 调试断言:确保锁定/解锁配对正确
*
* 如果断言失败,说明:
* - 多次unlock同一个lock
* - unlock了从未lock的PMR
* - 程序逻辑错误
*/
PVR_ASSERT(OSAtomicRead(&psPMR->iLockCount) >= uiLockCount);
/* ========== 步骤3:减少锁计数并检查是否需要调用Factory回调 ========== */
/*
* OSAtomicSubtract 返回减少前的值
*
* 判断条件:
* - On-demand: 1 → 0 时调用unlock回调
* - 非On-demand: 2 → 1 时调用unlock回调
*
* 原因:
* - On-demand: 0表示未backing
* - 非On-demand: 1表示基础backing状态
*/
if (OSAtomicSubtract(&psPMR->iLockCount, uiLockCount) ==
(PVRSRV_CHECK_ON_DEMAND(psPMR->uiFlags) ? 0 : 1))
{
if (psPMR->psFuncTab->pfnUnlockPhysAddresses != NULL)
{
PVR_ASSERT(psPMR->psFuncTab->pfnLockPhysAddresses != NULL);
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
/* 带延迟释放的解锁
*
* pvZombiePages输出参数:
* - NULL: 没有页面需要延迟释放
* - 非NULL: Factory返回需要延迟释放的页面信息
*
* 使用场景:
* - On-demand分配且需要释放部分页面
* - 稀疏内存布局变更后的旧页面
*/
eError = psPMR->psFuncTab->pfnUnlockPhysAddresses(psPMR->pvFlavourData, &pvZombiePages);
#else
/* 标准解锁(不支持页面延迟释放)
*
* pfnUnlockPhysAddresses的作用:
* 1. Unpin页面(允许被swap out)
* 2. 减少页面引用计数
* 3. 释放暂时不需要的物理内存
*/
eError = psPMR->psFuncTab->pfnUnlockPhysAddresses(psPMR->pvFlavourData);
#endif
PVR_LOG_IF_ERROR(eError, "pfnUnlockPhysAddresses");
}
}
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
/* ========== 步骤4:处理僵尸页面(如果有) ========== /
/
* 僵尸页面场景:
* 1. On-demand PMR释放部分页面
* 2. 稀疏内存缩减后的旧页面
* 3. 页面需要等待GPU缓存刷新后再释放
*/
if (pvZombiePages != NULL)
{
/* 预分配僵尸页面包装结构
*
* 为什么在这里分配?
* - 在持锁状态下分配
* - 简化错误处理
* - 如果分配失败,可以尝试重新lock
*/
psPMRZombiePages = OSAllocZMem(sizeof(PMR_ZOMBIE_PAGES));
PVR_GOTO_IF_NOMEM(psPMRZombiePages, eError, ErrRelockPhysAddresses);
}
#endif
/* ========== 步骤5:释放PMR主锁 ========== */
OSLockRelease(psPMR->hLock);
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
/* ========== 步骤6:处理僵尸页面(锁外) ========== /
/
* _ProcessZombiePages的作用:
* 1. 检查是否可以立即释放(设备已关闭)
* 2. 如果不能立即释放,加入僵尸列表
* 3. 如果没有僵尸页面,释放预分配的包装结构
*/
eError = _ProcessZombiePages(pvZombiePages, psPMRZombiePages, psPMR);
PVR_GOTO_IF_ERROR(eError, ErrReturn);
#endif
/* ========== 步骤7:减少引用计数,可能触发销毁 ========== */
/*
* 注意顺序:
* 1. 先完成unlock操作
* 2. 再减少引用计数
* 3. 如果引用计数归零,触发销毁流程
*/
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
(void) _UnrefAndMaybeDestroy(psPMR, uiLockCount);
return eError;
#else
return _UnrefAndMaybeDestroy(psPMR, uiLockCount);
#endif
/* ========== 错误处理:重新锁定物理地址 ========== */
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
ErrRelockPhysAddresses:
{
/* 分配僵尸页面结构失败
* 需要重新锁定物理地址,保持状态一致
*/
IMG_UINT32 uiCallbackValue =
uiLockCount + (PVRSRV_CHECK_ON_DEMAND(psPMR->uiFlags) ? 0 : 1);
/* 恢复锁计数 */
if (OSAtomicAdd(&psPMR->iLockCount, uiLockCount) == uiCallbackValue)
{
if (psPMR->psFuncTab->pfnLockPhysAddresses != NULL)
{
/*
* 重新调用Factory的锁定函数
* 恢复到unlock之前的状态
*/
eError = psPMR->psFuncTab->pfnLockPhysAddresses(psPMR->pvFlavourData);
PVR_LOG_IF_ERROR(eError, "pfnLockPhysAddresses");
}
}
}
OSLockRelease(psPMR->hLock);
ErrReturn:
return eError;
#endif
}
锁定/解锁状态转换图
┌─────────────────────────────────────────────────────────────┐
│ PMR物理地址锁定状态机 │
└─────────────────────────────────────────────────────────────┘
On-Demand PMR:
─────────────
创建时
│
↓
┌─────────────────┐
│ 未backing状态 │
│ iLockCount = 0 │
│ 物理页面未分配 │
└─────────────────┘
│
│ PMRLockPhysAddresses()
│ - 分配物理页面
│ - Pin页面
↓
┌─────────────────┐
│ 已锁定状态 │
│ iLockCount = 1 │
│ 物理页面已分配 │
└─────────────────┘
│
│ 可以多次Lock
│ iLockCount++
↓
┌─────────────────┐
│ 多次锁定状态 │
│ iLockCount > 1 │
│ 引用计数增加 │
└─────────────────┘
│
│ PMRUnlockPhysAddresses()
│ iLockCount--
↓
┌─────────────────┐
│ 已锁定状态 │
│ iLockCount = 1 │
│ 还有1个引用 │
└─────────────────┘
│
│ PMRUnlockPhysAddresses()
│ - Unpin页面
│ - 可能释放物理页面
↓
┌─────────────────┐
│ 未backing状态 │
│ iLockCount = 0 │
│ 物理页面已释放 │
└─────────────────┘
非On-Demand PMR:
───────────────
创建时
│
↓
┌─────────────────┐
│ 基础backing状态 │
│ iLockCount = 1 │
│ 物理页面已分配 │
└─────────────────┘
│
│ PMRLockPhysAddresses()
│ - Pin页面
↓
┌─────────────────┐
│ 已锁定状态 │
│ iLockCount = 2 │
│ 页面已Pin │
└─────────────────┘
│
│ 可以多次Lock
│ iLockCount++
↓
┌─────────────────┐
│ 多次锁定状态 │
│ iLockCount > 2 │
│ 多个引用 │
└─────────────────┘
│
│ PMRUnlockPhysAddresses()
│ iLockCount--
↓
┌─────────────────┐
│ 已锁定状态 │
│ iLockCount = 2 │
│ 还有额外引用 │
└─────────────────┘
│
│ PMRUnlockPhysAddresses()
│ - Unpin页面
↓
┌─────────────────┐
│ 基础backing状态 │
│ iLockCount = 1 │
│ 回到初始状态 │
│ 物理页面保留 │
└─────────────────┘
锁定机制关键点汇总
| 方面 | On-Demand PMR | 非On-Demand PMR |
|---|---|---|
| 初始iLockCount | 0 | 1 |
| 首次Lock效果 | 分配物理页面 | Pin已存在的页面 |
| iLockCount=0时 | 无物理backing | 不允许(最小为1) |
| 首次Unlock效果 | 释放物理页面 | Unpin页面 |
| 页面生命周期 | 按需分配和释放 | 创建时分配,销毁时释放 |
| 内存效率 | 高(按需使用) | 低(预先分配) |
| 访问延迟 | 首次访问高 | 一致性好 |
| 典型用途 | 虚拟内存、大型缓冲区 | 小型关键缓冲区 |
内存读写操作
读取内存 PMR_ReadBytes
/**
* @brief 从PMR读取数据
* @param psPMR PMR指针
* @param uiLogicalOffset 逻辑偏移量
* @param pcBuffer 输出缓冲区
* @param uiBufSz 缓冲区大小
* @param puiNumBytes 输出:实际读取字节数
* @return PVRSRV_OK 成功
*
* 特点:
* - 自动处理块边界
* - 支持稀疏内存(无效块填0)
* - 无需预先lock(函数内部处理)
*/
PVRSRV_ERROR
PMR_ReadBytes(PMR *psPMR,
IMG_DEVMEM_OFFSET_T uiLogicalOffset,
IMG_UINT8 *pcBuffer,
size_t uiBufSz,
size_t *puiNumBytes)
{
PVRSRV_ERROR eError = PVRSRV_OK;
IMG_DEVMEM_OFFSET_T uiPhysicalOffset;
size_t uiBytesCopied = 0;
/* ========== 步骤1:参数验证 ========== */
/*
* 检查整数溢出
*
* 恶意输入场景:
* uiLogicalOffset = 0xFFFFFFFFFFFFFFF0
* uiBufSz = 0x20
* 溢出后:0xFFFFFFFFFFFFFFF0 + 0x20 = 0x10 (回绕)
*
* 防御:检查加法是否回绕
*/
if (uiLogicalOffset + uiBufSz < uiLogicalOffset)
{
return PVRSRV_ERROR_INVALID_PARAMS;
}
/* ========== 步骤2:边界检查和调整 ========== */
/*
* 如果读取范围超出PMR大小,截断到PMR末尾
*
* 示例:
* PMR大小 = 1MB (0x100000)
* 请求读取:offset=0xFFF00, size=0x200
* 实际读取:offset=0xFFF00, size=0x100(到PMR末尾)
*/
if (uiLogicalOffset + uiBufSz > psPMR->uiLogicalSize)
{
uiBufSz = TRUNCATE_64BITS_TO_32BITS(psPMR->uiLogicalSize - uiLogicalOffset);
}
PVR_ASSERT(uiBufSz > 0);
PVR_ASSERT(uiBufSz <= psPMR->uiLogicalSize);
/*
* PMR必须实现以下之一:
* 1. pfnReadBytes: 自定义读取函数
* 2. pfnAcquireKernelMappingData: 提供内核映射
*
* 如果都没有实现,无法读取数据
*/
PVR_ASSERT(psPMR->psFuncTab->pfnAcquireKernelMappingData != NULL ||
psPMR->psFuncTab->pfnReadBytes != NULL);
/* ========== 步骤3:循环读取,处理跨块情况 ========== */
/*
* 为什么需要循环?
* - 读取可能跨越多个块
* - 每个块可能不连续
* - 稀疏PMR中某些块可能无效
*/
while (uiBytesCopied != uiBufSz)
{
IMG_UINT32 ui32Remain; // 当前块剩余字节
size_t uiBytesToCopy; // 本次要复制的字节数
size_t uiRead; // 实际读取的字节数
IMG_BOOL bValid; // 块是否有效
/* ========== 3.1:获取物理偏移 ========== */
/*
* 将当前逻辑偏移转换为物理偏移
*
* 输出:
* - uiPhysicalOffset: 物理偏移量
* - ui32Remain: 当前块剩余可读字节
* - bValid: 该块是否有效(是否有物理backing)
*/
eError = _PMRLogicalOffsetToPhysicalOffset(psPMR,
0, // ui32Log2PageSize=0表示字节级访问
1, // 只转换1个"页"
uiLogicalOffset,
&uiPhysicalOffset,
&ui32Remain,
&bValid);
PVR_LOG_RETURN_IF_ERROR(eError, "_PMRLogicalOffsetToPhysicalOffset");
/* ========== 3.2:计算本次读取字节数 ========== */
/*
* 取最小值:
* 1. 剩余要读取的字节数:uiBufSz - uiBytesCopied
* 2. 当前块剩余字节数:ui32Remain
*
* 示例:
* 总共要读 1000 bytes
* 已读 800 bytes
* 当前块剩余 150 bytes
* 本次读取 = MIN(1000-800, 150) = 150 bytes
*/
uiBytesToCopy = MIN(uiBufSz - uiBytesCopied, ui32Remain);
/* ========== 3.3:读取数据或填充零 ========== */
if (bValid)
{
/*
* 有效块:调用底层读取函数
*
* _PMR_ReadBytesPhysical会:
* 1. 锁定物理地址
* 2. 调用Factory的读取函数或映射内存
* 3. 复制数据
* 4. 解锁物理地址
*/
eError = _PMR_ReadBytesPhysical(psPMR,
uiPhysicalOffset,
&pcBuffer[uiBytesCopied],
uiBytesToCopy,
&uiRead);
if ((eError != PVRSRV_OK) || (uiRead != uiBytesToCopy))
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Failed to read chunk (eError = %s, uiRead = %zu, uiBytesToCopy = %zu)",
__func__,
PVRSRVGetErrorString(eError),
uiRead,
uiBytesToCopy));
/* 遇到错误,停止读取 */
break;
}
}
else
{
/*
* 无效块:填充零
*
* 稀疏内存场景:
* - 该逻辑块没有分配物理内存
* - 根据规范,读取应返回零
* - 类似于读取未初始化的虚拟内存
*/
PVR_DPF((PVR_DBG_WARNING,
"%s: Invalid phys offset at logical offset (0x%llx) logical size (0x%llx)",
__func__,
uiLogicalOffset,
psPMR->uiLogicalSize));
/* 填充零 */
OSCachedMemSet(&pcBuffer[uiBytesCopied], 0, uiBytesToCopy);
uiRead = uiBytesToCopy;
eError = PVRSRV_ERROR_FAILED_TO_GET_PHYS_ADDR;
}
/* ========== 3.4:更新偏移和计数 ========== */
uiLogicalOffset += uiRead;
uiBytesCopied += uiRead;
}
/* ========== 步骤4:返回实际读取字节数 ========== */
*puiNumBytes = uiBytesCopied;
return eError;
}
底层物理读取 _PMR_ReadBytesPhysical
/**
* @brief 从物理偏移读取数据
* @param psPMR PMR指针
* @param uiPhysicalOffset 物理偏移量
* @param pcBuffer 输出缓冲区
* @param uiBufSz 要读取的字节数
* @param puiNumBytes 输出:实际读取字节数
* @return PVRSRV_OK 成功
*
* 内部函数,由PMR_ReadBytes调用
*/
static PVRSRV_ERROR
_PMR_ReadBytesPhysical(PMR *psPMR,
IMG_DEVMEM_OFFSET_T uiPhysicalOffset,
IMG_UINT8 *pcBuffer,
size_t uiBufSz,
size_t *puiNumBytes)
{
PVRSRV_ERROR eError;
/* ========== 方式1:使用Factory的pfnReadBytes回调 ========== */
if (psPMR->psFuncTab->pfnReadBytes != NULL)
{
/*
* 某些PMR类型提供专门的读取函数
*
* 优点:
* - 可能更高效(避免映射开销)
* - 可以处理特殊硬件(如GPU寄存器)
* - 可以实现特殊读取策略
*/
/* 锁定物理地址 */
eError = PMRLockPhysAddresses(psPMR);
PVR_GOTO_IF_ERROR(eError, e0);
/* 调用Factory的读取函数 */
eError = psPMR->psFuncTab->pfnReadBytes(psPMR->pvFlavourData,
uiPhysicalOffset,
pcBuffer,
uiBufSz,
puiNumBytes);
/* 解锁物理地址 */
PMRUnlockPhysAddresses(psPMR);
PVR_GOTO_IF_ERROR(eError, e0);
}
/* ========== 方式2:默认处理 - 通过内核映射读取 ========== */
else if (psPMR->psFuncTab->pfnAcquireKernelMappingData)
{
/*
* 默认读取策略:
* 1. 获取内核虚拟地址映射
* 2. 通过memcpy复制数据
* 3. 释放映射
*
* 适用于大多数PMR类型
*/
IMG_HANDLE hKernelMappingHandle;
IMG_UINT8 *pcKernelAddress;
/* 获取内核映射 */
eError = psPMR->psFuncTab->pfnAcquireKernelMappingData(
psPMR->pvFlavourData,
(size_t) uiPhysicalOffset, // 映射起始偏移
uiBufSz, // 映射大小
(void **)&pcKernelAddress, // 输出:内核虚拟地址
&hKernelMappingHandle, // 输出:映射句柄
psPMR->uiFlags); // PMR标志(缓存策略等)
PVR_GOTO_IF_ERROR(eError, e0);
/*
* 使用保守的DeviceMemCopy
*
* 为什么不用普通memcpy?
* - PMR可能映射为非缓存内存
* - 需要特殊的内存屏障
* - DeviceMemCopy处理这些细节
*/
OSDeviceMemCopy(&pcBuffer[0], pcKernelAddress, uiBufSz);
*puiNumBytes = uiBufSz;
/* 释放内核映射 */
psPMR->psFuncTab->pfnReleaseKernelMappingData(psPMR->pvFlavourData,
hKernelMappingHandle);
}
else
{
/*
* 既没有pfnReadBytes也没有pfnAcquireKernelMappingData
* 这个PMR无法被读取
*/
OSPanic();
#ifndef __CHECKER__
PVR_LOG_GOTO_WITH_ERROR("psPMR->psFuncTab", eError, PVRSRV_ERROR_INVALID_PARAMS, e0);
#endif
}
return PVRSRV_OK;
e0:
PVR_ASSERT(eError != PVRSRV_OK);
*puiNumBytes = 0;
return eError;
}
写入内存 PMR_WriteBytes
/**
* @brief 向PMR写入数据
* @param psPMR PMR指针
* @param uiLogicalOffset 逻辑偏移量
* @param pcBuffer 输入缓冲区
* @param uiBufSz 要写入的字节数
* @param puiNumBytes 输出:实际写入字节数
* @return PVRSRV_OK 成功
*
* 注意:写入到无效块会被忽略(稀疏PMR)
*/
PVRSRV_ERROR
PMR_WriteBytes(PMR *psPMR,
IMG_DEVMEM_OFFSET_T uiLogicalOffset,
IMG_UINT8 *pcBuffer,
size_t uiBufSz,
size_t *puiNumBytes)
{
PVRSRV_ERROR eError = PVRSRV_OK;
IMG_DEVMEM_OFFSET_T uiPhysicalOffset;
size_t uiBytesCopied = 0;
/* ========== 参数验证和边界检查(同读取) ========== */
if (uiLogicalOffset + uiBufSz < uiLogicalOffset)
{
return PVRSRV_ERROR_INVALID_PARAMS;
}
if (uiLogicalOffset + uiBufSz > psPMR->uiLogicalSize)
{
uiBufSz = TRUNCATE_64BITS_TO_32BITS(psPMR->uiLogicalSize - uiLogicalOffset);
}
PVR_ASSERT(uiBufSz > 0);
PVR_ASSERT(uiBufSz <= psPMR->uiLogicalSize);
PVR_ASSERT(psPMR->psFuncTab->pfnAcquireKernelMappingData != NULL ||
psPMR->psFuncTab->pfnWriteBytes != NULL);
/* ========== 循环写入 ========== */
while (uiBytesCopied != uiBufSz)
{
IMG_UINT32 ui32Remain;
size_t uiBytesToCopy;
size_t uiWrite;
IMG_BOOL bValid;
/* 获取物理偏移 */
eError = _PMRLogicalOffsetToPhysicalOffset(psPMR,
0,
1,
uiLogicalOffset,
&uiPhysicalOffset,
&ui32Remain,
&bValid);
PVR_LOG_RETURN_IF_ERROR(eError, "_PMRLogicalOffsetToPhysicalOffset");
uiBytesToCopy = MIN(uiBufSz - uiBytesCopied, ui32Remain);
if (bValid)
{
/* 有效块:写入数据 */
eError = _PMR_WriteBytesPhysical(psPMR,
uiPhysicalOffset,
&pcBuffer[uiBytesCopied],
uiBytesToCopy,
&uiWrite);
if ((eError != PVRSRV_OK) || (uiWrite != uiBytesToCopy))
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Failed to write chunk",
__func__));
break;
}
}
else
{
/*
* 无效块:忽略写入
*
* 稀疏内存语义:
* - 写入到未分配的逻辑地址
* - 数据被丢弃
* - 不报错,假装成功
*
* 原因:
* - 允许应用写入整个逻辑地址空间
* - 简化应用逻辑(不需要检查每个地址)
*/
uiWrite = uiBytesToCopy;
}
uiLogicalOffset += uiWrite;
uiBytesCopied += uiWrite;
}
*puiNumBytes = uiBytesCopied;
return eError;
}
读写操作流程图
┌─────────────────────────────────────────────────────────────┐
│ PMR读写操作完整流程 │
└─────────────────────────────────────────────────────────────┘
读取操作 (PMR_ReadBytes):
═════════════════════════
┌─────────────────────────┐
│ 开始:PMR_ReadBytes │
│ 输入: │
│ - 逻辑偏移 │
│ - 缓冲区 │
│ - 大小 │
└─────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第1步:安全检查 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 检查整数溢出:offset + size < offset? │ │
│ │ 边界检查:offset + size > PMR.size? │ │
│ │ 调整大小:size = min(size, PMR.size - offset) │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第2步:循环读取(处理跨块) │
│ while (已读取 < 总大小) │
└─────────────────────────────────────────────────────────────┘
↓
┌───────────────┐
│ 2.1: 地址转换 │
└───────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ _PMRLogicalOffsetToPhysicalOffset() │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 输入:逻辑偏移 │ │
│ │ 输出: │ │
│ │ - 物理偏移 │ │
│ │ - 块剩余字节 │ │
│ │ - 有效性标志 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌───────────────┐
│ 2.2: 计算本次 │
│ 读取大小 │
└───────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 本次读取 = MIN(剩余要读, 块剩余) │
└─────────────────────────────────────────────────────────────┘
↓
├──────────────┬──────────────┐
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 块有效? │ │ 是 │ │ 否 │
└──────────┘ └──────────┘ └──────────┘
│ │
↓ ↓
┌─────────────────┐ ┌─────────────────┐
│ 读取物理数据 │ │ 填充零 │
└─────────────────┘ └─────────────────┘
│ │
↓ ↓
┌──────────────────────────────────┐
│ _PMR_ReadBytesPhysical() │
│ ┌────────────────────────────┐ │
│ │ 方式1: pfnReadBytes? │ │
│ │ ├─ 是 → 调用Factory读取 │ │
│ │ └─ 否 → 使用方式2 │ │
│ │ │ │
│ │ 方式2: 内核映射 │ │
│ │ ├─ 获取内核虚拟地址 │ │
│ │ ├─ memcpy复制数据 │ │
│ │ └─ 释放映射 │ │
│ └────────────────────────────┘ │
└──────────────────────────────────┘
│ │
└──────┬───────┘
↓
┌─────────────────┐
│ 更新偏移和计数 │
│ - 逻辑偏移 += │
│ - 已读字节 += │
└─────────────────┘
↓
┌─────────────────┐
│ 检查是否完成 │
└─────────────────┘
│ │
│ 否 │ 是
↓ ↓
(回到循环) (完成)
↓
┌─────────────────┐
│ 返回实际读取数 │
└─────────────────┘
写入操作 (PMR_WriteBytes):
══════════════════════════
┌─────────────────────────┐
│ 开始:PMR_WriteBytes │
│ (流程类似读取) │
└─────────────────────────┘
↓
(参数检查,同读取)
↓
(循环写入,逐块处理)
↓
┌──────────────┐
│ 块有效判断 │
└──────────────┘
│ │
有效 无效
│ │
↓ ↓
┌────────┐ ┌────────────┐
│写入数据│ │忽略写入 │
│ │ │(假装成功) │
└────────┘ └────────────┘
│ │
└──┬───┘
↓
┌──────────────┐
│ 更新偏移 │
└──────────────┘
↓
┌──────────────┐
│ 检查完成 │
└──────────────┘
↓
┌──────────────┐
│ 返回实际写入 │
└──────────────┘
读写操作对比表
| 方面 | 读取 (Read) | 写入 (Write) |
|---|---|---|
| 无效块处理 | 填充零 | 忽略写入 |
| 返回值 | 实际读取字节数 | 实际写入字节数 |
| 错误处理 | 立即停止 | 立即停止 |
| 锁定要求 | 内部自动锁定 | 内部自动锁定 |
| Factory回调 | pfnReadBytes(可选) | pfnWriteBytes(可选) |
| 默认实现 | 内核映射+memcpy | 内核映射+memcpy |
| 缓存一致性 | 使用DeviceMemCopy | 使用DeviceMemCopy |
| 典型用途 | 调试、数据导出 | 初始化、数据注入 |
| 性能考虑 | 可能触发页面锁定 | 可能触发页面锁定 |
稀疏内存管理
动态稀疏内存变更 PMR_ChangeSparseMemUnlocked
/**
* @brief 动态修改稀疏PMR的内存布局
* @param psPMR PMR指针
* @param ui32AllocPageCount 要分配的页数
* @param pai32AllocIndices 分配页索引数组
* @param ui32FreePageCount 要释放的页数
* @param pai32FreeIndices 释放页索引数组
* @param uiSparseFlags 稀疏操作标志
* @return PVRSRV_OK 成功
*
* 注意:调用前必须持有PMR锁
*/
PVRSRV_ERROR
PMR_ChangeSparseMemUnlocked(PMR *psPMR,
IMG_UINT32 ui32AllocPageCount,
IMG_UINT32 *pai32AllocIndices,
IMG_UINT32 ui32FreePageCount,
IMG_UINT32 *pai32FreeIndices,
IMG_UINT32 uiSparseFlags)
{
PVRSRV_ERROR eError;
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
PMR_IMPL_ZOMBIEPAGES pvZombiePages = NULL;
PMR_ZOMBIE_PAGES* psPMRZombiePages = NULL;
#endif
/* ========== 前置检查1:PMR是否允许布局变更 ========== */
/*
* 禁止变更的情况:
* 1. PMR_IsMemLayoutFixed() - 已导出的PMR
* 2. PMR_IsClientCpuMapped() - 有CPU映射存在
*
* 原因:
* - 导出后的PMR被其他进程使用,改变布局会导致不一致
* - CPU映射期间改变布局会导致访问错误
*/
if (PMR_IsMemLayoutFixed(psPMR) || PMR_IsClientCpuMapped(psPMR))
{
PVR_DPF((PVR_DBG_ERROR,
"%s: This PMR layout cannot be changed - "
"PMR_IsMemLayoutFixed()=%c, _PMR_IsClientCpuMapped()=%c",
__func__,
PMR_IsMemLayoutFixed(psPMR) ? 'Y' : 'n',
PMR_IsClientCpuMapped(psPMR) ? 'Y' : 'n'));
return PVRSRV_ERROR_PMR_NOT_PERMITTED;
}
/* ========== 前置检查2:PMR是否支持稀疏变更 ========== */
/*
* 不是所有PMR类型都支持动态布局变更
*
* 必须实现 pfnChangeSparseMem 回调
*
* 不支持的PMR类型:
* - 物理内存PMR(预分配)
* - 某些导入的PMR(如DMA-BUF)
* - 固定大小的硬件缓冲区
*/
if (NULL == psPMR->psFuncTab->pfnChangeSparseMem)
{
PVR_DPF((PVR_DBG_ERROR,
"%s: This type of sparse PMR cannot be changed.",
__func__));
return PVRSRV_ERROR_NOT_IMPLEMENTED;
}
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
/* ========== 预分配僵尸页面结构 ========== */
/*
* 如果要释放页面,预先分配僵尸包装结构
*
* 为什么提前分配?
* - 简化错误处理
* - 避免在关键路径中分配失败
* - 如果不需要(没有僵尸页面),稍后会释放
*/
if (uiSparseFlags & SPARSE_RESIZE_FREE)
{
psPMRZombiePages = OSAllocZMem(sizeof(PMR_ZOMBIE_PAGES));
PVR_GOTO_IF_NOMEM(psPMRZombiePages, eError, returnErr);
}
#endif
/* ========== 核心操作:调用Factory函数修改布局 ========== */
/*
* pfnChangeSparseMem 的职责:
* 1. 分配新页面(根据pai32AllocIndices)
* 2. 释放旧页面(根据pai32FreeIndices)
* 3. 更新映射表
* 4. 返回僵尸页面信息(如果有)
*
* 参数说明:
* - pvFlavourData: Factory私有数据
* - psPMR: PMR对象(用于访问映射表等)
* - ui32AllocPageCount: 要分配的页数
* - pai32AllocIndices: 逻辑页索引数组(在哪些位置分配)
* - ui32FreePageCount: 要释放的页数
* - pai32FreeIndices: 逻辑页索引数组(释放哪些位置)
* - pvZombiePages: 输出参数,返回需要延迟释放的页面
* - uiSparseFlags: 操作标志
*/
eError = psPMR->psFuncTab->pfnChangeSparseMem(
psPMR->pvFlavourData,
psPMR,
ui32AllocPageCount,
pai32AllocIndices,
ui32FreePageCount,
pai32FreeIndices,
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
&pvZombiePages,
#endif
uiSparseFlags);
/* ========== 错误处理:内存不足 ========== */
if (eError != PVRSRV_OK)
{
#if defined(PVRSRV_ENABLE_PROCESS_STATS)
/*
* 如果是内存不足错误,更新OOM统计
*
* 用途:
* - 系统监控
* - 调试内存问题
* - 触发内存回收策略
*/
if (eError == PVRSRV_ERROR_PMR_FAILED_TO_ALLOC_PAGES)
{
PVRSRVStatsUpdateOOMStat(NULL,
PMR_DeviceNode(psPMR),
PVRSRV_DEVICE_STAT_TYPE_OOM_PHYSMEM_COUNT,
OSGetCurrentClientProcessIDKM());
}
#endif
PVR_LOG_GOTO_ERROR(eError, "pfnChangeSparseMem", freePmrZombiePages);
}
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
/*
* 断言检查:
* - 如果设置了SPARSE_RESIZE_FREE标志,可能有僵尸页面
* - 如果没有设置,不应该有僵尸页面
*/
PVR_ASSERT((uiSparseFlags & SPARSE_RESIZE_FREE) ? IMG_TRUE : pvZombiePages == NULL);
/*
* 处理僵尸页面:
* 1. 如果有僵尸页面,加入僵尸列表或立即释放
* 2. 如果没有僵尸页面,释放预分配的包装结构
*/
eError = _ProcessZombiePages(pvZombiePages, psPMRZombiePages, psPMR);
PVR_LOG_GOTO_IF_ERROR(eError, "_ProcessZombiePages", returnErr);
#endif
#if defined(PDUMP)
/* ========== PDump支持:记录稀疏变更操作 ========== */
{
IMG_BOOL bInitialise = IMG_FALSE;
IMG_UINT8 ui8InitValue = 0;
/*
* 检查是否需要初始化新分配的页面
*
* ZERO_ON_ALLOC: 分配时清零
* POISON_ON_ALLOC: 填充特定值(用于检测未初始化访问)
*/
if (PVRSRV_CHECK_ZERO_ON_ALLOC(PMR_Flags(psPMR)))
{
bInitialise = IMG_TRUE;
}
else if (PVRSRV_CHECK_POISON_ON_ALLOC(PMR_Flags(psPMR)))
{
ui8InitValue = (IMG_UINT8)PVRSRV_POISON_ON_ALLOC_VALUE;
bInitialise = IMG_TRUE;
}
/*
* 生成PDump命令
*
* PDump用途:
* - 记录驱动操作
* - 回放测试场景
* - 调试问题
*/
PDumpPMRChangeSparsePMR(psPMR,
IMG_PAGE2BYTES32(psPMR->uiLog2ContiguityGuarantee),
ui32AllocPageCount,
pai32AllocIndices,
ui32FreePageCount,
pai32FreeIndices,
bInitialise,
ui8InitValue,
&psPMR->hPDumpAllocHandle);
}
#endif
return PVRSRV_OK;
/* ========== 错误处理路径 ========== */
freePmrZombiePages:
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
if (uiSparseFlags & SPARSE_RESIZE_FREE)
{
OSFreeMem(psPMRZombiePages);
}
returnErr:
#endif
return eError;
}
稀疏内存变更示意图
稀疏内存动态变更操作
═══════════════════
场景:16页的稀疏PMR,初始分配了4页
初始状态:
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │ 12 │ 13 │ 14 │ 15 │
├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤
│ ✓ │ │ ✓ │ │ │ ✓ │ │ │ │ │ ✓ │ │ │ │ │ │
│Phys│ │Phys│ │ │Phys│ │ │ │ │Phys│ │ │ │ │ │
│ 0 │ │ 1 │ │ │ 2 │ │ │ │ │ 3 │ │ │ │ │ │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
映射表 aui32Translation:
[0, 0xFF, 1, 0xFF, 0xFF, 2, 0xFF, 0xFF, 0xFF, 0xFF, 3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]
操作请求:
- 分配页面:[4, 7, 13] (分配3页)
- 释放页面:[2, 10] (释放2页)
执行 PMR_ChangeSparseMemUnlocked():
════════════════════════════════════
第1步:调用 pfnChangeSparseMem
↓
Factory执行:
1. 分配物理页面:
- 为逻辑页4分配物理页4
- 为逻辑页7分配物理页5
- 为逻辑页13分配物理页6
2. 释放物理页面:
- 释放逻辑页2的物理页1
- 释放逻辑页10的物理页3
3. 更新映射表
4. 返回僵尸页面信息(物理页1和3)
第2步:处理僵尸页面
↓
检查设备状态:
- 如果GPU OFF → 立即释放
- 如果GPU ON → 加入僵尸列表
第3步:PDump记录(如果启用)
结果状态:
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │ 12 │ 13 │ 14 │ 15 │
├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤
│ ✓ │ │ │ │ ✓ │ ✓ │ │ ✓ │ │ │ │ │ │ ✓ │ │ │
│Phys│ │(已 │ │Phys│Phys│ │Phys│ │ │(已 │ │ │Phys│ │ │
│ 0 │ │释放│ │ 4 │ 2 │ │ 5 │ │ │释放│ │ │ 6 │ │ │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
新映射表 aui32Translation:
[0, 0xFF, 0xFF, 0xFF, 4, 2, 0xFF, 5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 6, 0xFF, 0xFF]
物理内存变化:
- 原有:4页 (页0,1,2,3)
- 新增:3页 (页4,5,6)
- 释放:2页 (页1,3)
- 结果:5页 (页0,2,4,5,6)
内存使用:
- 逻辑大小:16页 (不变)
- 物理大小:5页 (从4页增加到5页)
- 利用率:31.25% (5/16)
稀疏内存操作约束表
| 约束条件 | 检查时机 | 失败原因 | 错误码 |
|---|---|---|---|
| 布局固定 | 函数开始 | PMR已导出或锁定 | PMR_NOT_PERMITTED |
| CPU映射存在 | 函数开始 | 有活跃的CPU映射 | PMR_NOT_PERMITTED |
| 不支持稀疏 | 函数开始 | PMR类型不支持 | NOT_IMPLEMENTED |
| 内存不足 | Factory调用 | 物理内存耗尽 | FAILED_TO_ALLOC_PAGES |
| 索引越界 | Factory内部 | 页索引超出范围 | INVALID_PARAMS |
| 页面冲突 | Factory内部 | 分配已存在的页 | INVALID_PARAMS |
延迟释放机制
僵尸清理函数 _PmrZombieCleanup
/**
* @brief 僵尸PMR清理函数(在清理线程中执行)
* @param pvData 清理项数据(PMR_ZOMBIE_CLEANUP_ITEM*)
* @return PVRSRV_OK 成功,PVRSRV_ERROR_RETRY 需要重试
*
* 作用:
* - 等待GPU缓存刷新完成
* - 释放僵尸PMR和僵尸页面
* - 处理跨设备导入
*/
static PVRSRV_ERROR
_PmrZombieCleanup(void *pvData)
{
PMR_ZOMBIE_CLEANUP_ITEM *psCleanupItem = pvData;
DLLIST_NODE *psNode;
DLLIST_NODE sRetryHead;
IMG_UINT32 uiRetryCount = 0;
PVRSRV_ERROR eError = PVRSRV_OK;
/* ========== 步骤1:检查是否可以释放僵尸 ========== */
/*
* _CanNotFreeZombies 检查两个条件:
* 1. GPU MMU缓存同步计数器
* 2. GPU电源状态计数器
*
* 只有当以下任一条件满足时才能释放:
* - MMU缓存已刷新(同步计数器已更新)
* - GPU已关闭电源(缓存已失效)
*/
if (_CanNotFreeZombies(psCleanupItem))
{
/*
* 条件未满足,返回RETRY
* 清理线程会稍后重试
*/
return PVRSRV_ERROR_RETRY;
}
/* ========== 步骤2:初始化重试列表 ========== */
/*
* 某些僵尸可能无法立即释放
* 将它们加入重试列表
*/
dllist_init(&sRetryHead);
/* ========== 步骤3:遍历僵尸列表 ========== */
do
{
/* 从僵尸列表获取下一个节点 */
psNode = dllist_get_next_node(&psCleanupItem->sZombieList);
if (psNode == NULL)
{
continue; /* 列表已空 */
}
/* 从列表移除(但还未释放) */
dllist_remove_node(psNode);
/* 更新统计 */
_ZombieListLock(psCleanupItem->psDevNode);
psCleanupItem->psDevNode->uiPMRZombieCountInCleanup--;
_ZombieListUnlock(psCleanupItem->psDevNode);
/* ========== 步骤4:根据僵尸类型处理 ========== */
switch (PMR_GetZombieTypeFromNode(psNode))
{
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
/* ────────────── 僵尸页面 ────────────── */
case PMR_ZOMBIE_TYPE_PAGES:
{
PMR_ZOMBIE_PAGES* psZombiePages = PMR_GetZombiePagesFromNode(psNode);
/*
* 调用Factory的僵尸页面释放函数
*
* pfnFreeZombiePages的作用:
* 1. 释放物理页面
* 2. 更新内存统计
* 3. 返回页面到OS
*/
eError = psZombiePages->pfnFactoryFreeZombies(psZombiePages->pvFactoryPages);
if (eError != PVRSRV_OK)
{
/*
* 释放失败(可能是暂时性错误)
* 加入重试列表
*/
PVR_DPF((PVR_DBG_ERROR,
"Cannot free zombie pages! Skipping object %p",
psZombiePages));
dllist_add_to_tail(&sRetryHead, psNode);
uiRetryCount++;
}
else
{
/* 成功释放,销毁包装结构 */
OSFreeMem(psZombiePages);
}
break;
}
#endif
#if defined(SUPPORT_PMR_DEVICE_IMPORT_DEFERRED_FREE)
/* ────────────── 设备导入 ────────────── */
case PMR_ZOMBIE_TYPE_DEVICE_IMPORT:
{
PMR_DEVICE_IMPORT *psImport = PMR_GetDeviceImportFromNode(psNode);
/*
* 释放跨设备导入记录
*
* 作用:
* 1. 清除设备导入位图中的对应位
* 2. 从PMR的导入列表移除
* 3. 释放导入结构体
*/
_DeviceImportFreeImportZombie(psImport);
break;
}
#endif
/* ────────────── PMR本体 ────────────── */
case PMR_ZOMBIE_TYPE_PMR:
{
PMR* psPMR = PMR_GetPMRFromNode(psNode);
const PMR_IMPL_FUNCTAB *psFuncTable = psPMR->psFuncTab;
/* 获取Factory锁(防止并发创建/销毁) */
_FactoryLock(psFuncTable);
#if defined(SUPPORT_PMR_DEVICE_IMPORT_DEFERRED_FREE)
/*
* 检查是否还有其他设备在等待缓存刷新
*
* uiDevImportBitmap != 0 表示:
* - 还有其他设备导入了此PMR
* - 那些设备可能还未刷新缓存
* - 不能释放PMR
/
if (_DeviceImportBitmapGet(psPMR) != 0)
{
/
* 还有依赖未解决
* 加入重试列表
/
dllist_add_to_tail(&sRetryHead, psNode);
uiRetryCount++;
_FactoryUnlock(psFuncTable);
continue; / 继续处理下一个 */
}
#endif
/*
* 所有条件满足,最终销毁PMR
*
* _PMRDestroy的作用:
* 1. 调用Factory的Finalize回调
* 2. 释放物理内存
* 3. 销毁映射表
* 4. 销毁锁
* 5. 更新统计
* 6. 释放PMR结构体
*/
_PMRDestroy(psPMR);
_FactoryUnlock(psFuncTable);
break;
}
}
} while (psNode != NULL);
/* ========== 步骤5:处理重试列表 ========== */
if (uiRetryCount)
{
/*
* 有项目需要重试
*
* 处理:
* 1. 设置返回值为RETRY
* 2. 将重试项目移回清理项的列表
* 3. 更新统计
* 4. 清理线程会重新调度这个清理项
*/
eError = PVRSRV_ERROR_RETRY;
/*
* 将重试列表插入到清理项列表头部
* 这样最老的项目在前面,符合FIFO原则
*/
dllist_insert_list_at_head(&psCleanupItem->sZombieList, &sRetryHead);
/* 更新统计 */
_ZombieListLock(psCleanupItem->psDevNode);
psCleanupItem->psDevNode->uiPMRZombieCountInCleanup += uiRetryCount;
_ZombieListUnlock(psCleanupItem->psDevNode);
}
else
{
/*
* 所有僵尸都已成功释放
* 释放清理项本身
*/
OSFreeMem(psCleanupItem);
}
return eError;
}
入队僵尸 PMRQueueZombiesForCleanup
/**
* @brief 将设备的僵尸列表入队到清理线程
* @param psDevNode 设备节点
* @return IMG_TRUE 成功入队,IMG_FALSE 失败或无需入队
*
* 调用时机:
* - GPU MMU缓存刷新后
* - GPU电源状态变化
* - 定期清理
*/
IMG_BOOL
PMRQueueZombiesForCleanup(PPVRSRV_DEVICE_NODE psDevNode)
{
PVRSRV_DATA *psPVRSRVData = PVRSRVGetPVRSRVData();
PMR_ZOMBIE_CLEANUP_ITEM *psCleanupItem;
/* ========== 检查1:驱动是否在卸载 ========== */
/*
* 如果驱动正在卸载,不再延迟释放
* 直接在卸载流程中同步释放所有资源
*/
if (psPVRSRVData->bUnload || psDevNode->psMMUCacheSyncPrim == NULL)
{
return IMG_FALSE;
}
/* ========== 检查2:僵尸列表是否为空 ========== */
_ZombieListLock(psDevNode);
if (dllist_is_empty(&psDevNode->sPMRZombieList))
{
_ZombieListUnlock(psDevNode);
return IMG_FALSE; /* 没有僵尸需要处理 */
}
/* ========== 步骤1:分配清理项 ========== */
psCleanupItem = OSAllocMem(sizeof(*psCleanupItem));
if (psCleanupItem == NULL)
{
_ZombieListUnlock(psDevNode);
return IMG_FALSE;
}
/* ========== 步骤2:初始化清理项 ========== */
/*
* 清理项结构:
* - sCleanupThreadFn: 清理线程回调
* - sZombieList: 僵尸列表(移动自设备节点)
* - psDevNode: 设备节点引用
* - psSync: MMU缓存同步原语
* - uiRequiredSyncValue: 期望的同步值
* - uiRequiredPowerOffCounter: 期望的电源关闭计数
*/
psCleanupItem->sCleanupThreadFn.pfnFree = _PmrZombieCleanup;
psCleanupItem->sCleanupThreadFn.pvData = psCleanupItem;
psCleanupItem->sCleanupThreadFn.bDependsOnHW = IMG_TRUE;
psCleanupItem->sCleanupThreadFn.eCleanupType = PVRSRV_CLEANUP_TYPE_PMR;
CLEANUP_THREAD_SET_RETRY_TIMEOUT(&psCleanupItem->sCleanupThreadFn,
CLEANUP_THREAD_RETRY_TIMEOUT_MS_DEFAULT);
psCleanupItem->psDevNode = psDevNode;
psCleanupItem->psSync = psDevNode->psMMUCacheSyncPrim;
psCleanupItem->uiRequiredSyncValue = psDevNode->ui32NextMMUInvalidateUpdate;
psCleanupItem->uiRequiredPowerOffCounter = psDevNode->uiPowerOffCounterNext;
/* ========== 步骤3:移动僵尸列表 ========== */
/*
* dllist_replace_head:
* - 将设备的僵尸列表移动到清理项
* - 设备的僵尸列表变为空
* - 原子操作,保证线程安全
*/
dllist_replace_head(&psDevNode->sPMRZombieList, &psCleanupItem->sZombieList);
/* 更新统计 */
psCleanupItem->psDevNode->uiPMRZombieCountInCleanup += psDevNode->uiPMRZombieCount;
psDevNode->uiPMRZombieCount = 0;
OSLockRelease(psDevNode->hPMRZombieListLock);
/* ========== 步骤4:提交到清理线程 ========== */
/*
* PVRSRVCleanupThreadAddWork:
* - 将清理项添加到清理线程队列
* - 清理线程会异步执行
* - 如果返回RETRY,会自动重新入队
*/
PVRSRVCleanupThreadAddWork(psDevNode, &psCleanupItem->sCleanupThreadFn);
return IMG_TRUE;
}
延迟释放完整流程图
┌─────────────────────────────────────────────────────────────┐
│ 延迟释放完整生命周期 │
└─────────────────────────────────────────────────────────────┘
第1阶段:标记为延迟释放
═════════════════════════
┌─────────────────────┐
│ PMR引用计数归零 │
│ _UnrefAndMaybeDestroy│
└─────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 检查释放条件 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ if (未设置DEFER_FREE) │ │
│ │ → 立即销毁 │ │
│ │ │ │
│ │ else if (GPU OFF && 无跨设备导入) │ │
│ │ → 立即销毁 │ │
│ │ │ │
│ │ else if (标记为空僵尸) │ │
│ │ → 立即销毁 │ │
│ │ │ │
│ │ else │ │
│ │ → 进入僵尸状态(延迟释放) │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓ (进入僵尸状态)
┌─────────────────────────────────────────────────────────────┐
│ 标记为僵尸 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 1. 设置 IS_ZOMBIE_PMR 标志 │ │
│ │ 2. 加入设备的 sPMRZombieList │ │
│ │ 3. 增加 uiPMRZombieCount │ │
│ │ 4. 调用 pfnZombify() 更新内存统计 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
第2阶段:等待同步条件
═════════════════════
┌─────────────────────┐
│ 僵尸在设备列表中等待 │
│ sPMRZombieList │
└─────────────────────┘
↓
等待以下任一事件:
┌─────────────────┐
│ GPU MMU缓存刷新 │
│ (同步计数器更新) │
└─────────────────┘
或
┌─────────────────┐
│ GPU电源关闭 │
│ (电源计数器更新) │
└─────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 触发入队 │
│ PMRQueueZombiesForCleanup() │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 检查条件: │ │
│ │ 1. 驱动未在卸载 │ │
│ │ 2. 僵尸列表非空 │ │
│ │ 3. 同步原语有效 │ │
│ │ │ │
│ │ 操作: │ │
│ │ 1. 分配清理项 │ │
│ │ 2. 记录同步条件 │ │
│ │ 3. 移动僵尸列表到清理项 │ │
│ │ 4. 提交到清理线程 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
第3阶段:清理线程处理
═════════════════════
┌─────────────────────────────────────────────────────────────┐
│ 清理线程执行 │
│ _PmrZombieCleanup() │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 检查同步条件 │
│ _CanNotFreeZombies() │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 检查1:MMU同步计数器 │ │
│ │ if (当前值 >= 期望值) │ │
│ │ → 缓存已刷新,可以释放 │ │
│ │ │ │
│ │ 检查2:电源关闭计数器 │ │
│ │ if (当前值 >= 期望值) │ │
│ │ → GPU已关闭,可以释放 │ │
│ │ │ │
│ │ 结果: │ │
│ │ 任一条件满足 → 继续清理 │ │
│ │ 都不满足 → 返回RETRY,稍后重试 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓ (条件满足)
┌─────────────────────────────────────────────────────────────┐
│ 遍历僵尸列表 │
│ for each zombie in list │
└─────────────────────────────────────────────────────────────┘
↓
┌───────────────┬───────────────┬───────────────┐
│ │ │ │
↓ ↓ ↓ ↓
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│僵尸页面 │ │设备导入 │ │ PMR本体 │ │ (其他) │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │
↓ ↓ ↓
┌────────────────────────────────────────────────────┐
│ pfnFreeZombiePages() │ FreeImport() │ _PMRDestroy()│
└────────────────────────────────────────────────────┘
│ │ │
├───────────────┴───────────────┤
↓ ↓
成功释放 释放失败
↓ ↓
从列表移除 加入重试列表
减少计数 保留在清理项中
释放内存 等待下次重试
│ │
└───────────────┬───────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 清理完成 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ if (所有僵尸已释放) │ │
│ │ 释放清理项 │ │
│ │ 返回 PVRSRV_OK │ │
│ │ │ │
│ │ else (有失败项) │ │
│ │ 将失败项移回清理项列表 │ │
│ │ 返回 PVRSRV_ERROR_RETRY │ │
│ │ 清理线程会重新调度 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
延迟释放时序图
时间轴:从PMR标记为僵尸到最终释放
T0: PMR引用归零
│ └─ _UnrefAndMaybeDestroy()
│ └─ 设置IS_ZOMBIE_PMR
│ └─ 加入sPMRZombieList
│
├─────── (等待中) ───────┐
│ │
T1: GPU执行渲染命令 │
│ - 访问PMR的物理内存 │
│ - 使用缓存的地址 │
│ │
├─────── (等待中) ───────┤
│ │
T2: GPU完成渲染 │
│ - FW发出中断 │
│ - 更新同步计数器 │
│ │
├─────── (等待中) ───────┤
│ │
T3: MMU缓存刷新 │
│ - FW更新同步原语 │
│ - ui32NextMMUInvalidate++ │
│ │
├─────── (触发点) ───────┤
│ │
T4: 入队清理 │
│ └─ PMRQueueZombiesForCleanup()
│ └─ 创建清理项
│ └─ 提交到清理线程
│
├─────── (排队中) ───────┤
│ │
T5: 清理线程执行 │
│ └─ _PmrZombieCleanup()
│ ├─ 检查同步条件 ✓
│ ├─ 遍历僵尸列表
│ └─ 释放资源
│
├─────── (完成) ─────────┤
│ │
T6: 资源完全释放 │
└─ 物理内存返回OS
└─ PMR结构体释放
总延迟时间:T6 - T0 = 数十到数百毫秒
完整内存处理流程汇总
PMR生命周期完整流程
┌═══════════════════════════════════════════════════════════════┐
║ PMR完整生命周期流程图 ║
╚═══════════════════════════════════════════════════════════════╝
阶段1:创建 (Creation)
══════════════════════
应用层请求
↓
DevmemIntAllocatePMR()
↓
┌─────────────────────────────────────────┐
│ PMRCreatePMR() │
│ ├─ _PMRCreate() │
│ │ ├─ 分配PMR结构 │
│ │ ├─ 初始化映射表 │
│ │ ├─ 创建锁 │
│ │ ├─ 生成序列号和密钥 │
│ │ └─ 设置初始状态 │
│ │ │
│ ├─ 调用Factory创建函数 │
│ │ └─ 分配物理内存或准备on-demand │
│ │ │
│ ├─ PDump记录(如果启用) │
│ │ │
│ └─ 返回PMR句柄 │
└─────────────────────────────────────────┘
↓
PMR状态:
- uiRefCount = 0
- iLockCount = 0 (on-demand) 或 1 (非on-demand)
- 未映射到GPU/CPU
阶段2:锁定 (Locking)
══════════════════════
DevmemIntMap() 或其他操作
↓
┌─────────────────────────────────────────┐
│ PMRLockPhysAddresses() │
│ ├─ 获取PMR主锁 │
│ ├─ _Ref() 增加引用计数 │
│ ├─ OSAtomicAdd(&iLockCount) │
│ │ │
│ └─ if (首次锁定) │
│ └─ pfnLockPhysAddresses() │
│ ├─ 分配物理页面(on-demand) │
│ ├─ Pin页面 │
│ └─ 更新页表 │
└─────────────────────────────────────────┘
↓
PMR状态:
- uiRefCount++
- iLockCount++
- 物理页面已分配并锁定
阶段3:使用 (Usage)
════════════════════
┌─────────────────────┬─────────────────────┬─────────────────────┐
│ GPU映射 │ CPU映射 │ 读写操作 │
└─────────────────────┴─────────────────────┴─────────────────────┘
│ │ │
↓ ↓ ↓
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ DevmemIntMapPMR │ │ DevmemAcquireCpu │ │ PMR_ReadBytes │
│ ├─ 获取物理地址 │ │ ├─ PMRAcquireKern│ │ ├─ 地址转换 │
│ ├─ 填充页表 │ │ ├─ 增加映射计数 │ │ ├─ 锁定物理地址 │
│ ├─ 刷新TLB │ │ └─ 返回虚拟地址 │ │ ├─ 复制数据 │
│ └─ 增加引用 │ └──────────────────┘ │ └─ 解锁物理地址 │
└──────────────────┘ └──────────────────┘
│ │ │
└──────────┬──────────┴─────────────────────┘
↓
PMR状态更新:
- iAssociatedResCount++ (GPU映射)
- iClientCpuMapCount++ (CPU映射)
- 设置DEFER_FREE标志(GPU映射时)
阶段4:解锁 (Unlocking)
════════════════════════
使用完毕后
↓
┌─────────────────────────────────────────┐
│ PMRUnlockPhysAddresses() │
│ ├─ 获取PMR主锁 │
│ ├─ OSAtomicSubtract(&iLockCount) │
│ │ │
│ └─ if (锁计数归零) │
│ └─ pfnUnlockPhysAddresses() │
│ ├─ Unpin页面 │
│ ├─ 可能释放页面(on-demand) │
│ └─ 返回僵尸页面信息(如有) │
│ │
│ ├─ 处理僵尸页面 │
│ └─ _UnrefAndMaybeDestroy() │
└─────────────────────────────────────────┘
↓
PMR状态:
- uiRefCount--
- iLockCount--
- 如果uiRefCount=0 → 进入销毁流程
阶段5:销毁 (Destruction)
══════════════════════════
引用计数归零
↓
┌─────────────────────────────────────────┐
│ _UnrefAndMaybeDestroy() │
│ ├─ 检查引用计数 == 0? │
│ │ │
│ └─ 决策:立即销毁 vs 延迟销毁? │
└─────────────────────────────────────────┘
│ │
立即销毁 延迟销毁
│ │
↓ ↓
┌─────────────────┐ ┌─────────────────────────┐
│ _PMRDestroy() │ │ 标记为僵尸PMR │
│ ├─ pfnFinalize()│ │ ├─ 设置IS_ZOMBIE标志 │
│ ├─ 释放物理内存 │ │ ├─ 加入僵尸列表 │
│ ├─ 销毁映射表 │ │ ├─ 调用pfnZombify() │
│ ├─ 销毁锁 │ │ └─ 等待清理线程 │
│ ├─ 释放PMR结构 │ └─────────────────────────┘
│ └─ 减少LivePMR │ │
└─────────────────┘ ↓
│ ┌─────────────────────────┐
│ │ 等待同步条件 │
│ │ - GPU缓存刷新 │
│ │ - GPU电源关闭 │
│ └─────────────────────────┘
│ │
│ ↓
│ ┌─────────────────────────┐
│ │ PMRQueueZombiesForCleanup│
│ │ - 创建清理项 │
│ │ - 提交到清理线程 │
│ └─────────────────────────┘
│ │
│ ↓
│ ┌─────────────────────────┐
│ │ _PmrZombieCleanup() │
│ │ - 检查同步条件 │
│ │ - 释放僵尸对象 │
│ │ - 调用_PMRDestroy() │
│ └─────────────────────────┘
│ │
└──────────┬───────────┘
↓
┌─────────────────┐
│ 资源完全释放 │
│ - 物理内存返回OS │
│ - PMR结构体释放 │
│ - 统计更新 │
└─────────────────┘
关键路径性能分析
| 操作 | 时间复杂度 | 关键因素 | 优化策略 |
|---|---|---|---|
| PMR创建 | O(n) | n=逻辑块数 | 预分配常用大小 |
| 地址转换(连续) | O(1) | 快速路径 | 内联函数 |
| 地址转换(稀疏) | O(m) | m=页数 | 缓存转换结果 |
| 物理地址锁定 | O(1) | 首次O(n)分配 | On-demand延迟 |
| 读取数据 | O(k) | k=块数 | 批量操作 |
| 稀疏变更 | O(p+q) | p=分配,q=释放 | 合并操作 |
| 延迟释放 | O(1)入队 | 异步清理 | 批处理 |
内存状态转换表
| 起始状态 | 操作 | 结束状态 | 副作用 |
|---|---|---|---|
| 未创建 | PMRCreatePMR | 已创建,未锁定 | 分配结构 |
| 已创建,未锁定 | PMRLockPhysAddresses | 已锁定 | 分配物理内存(on-demand) |
| 已锁定 | DevmemIntMapPMR | 已映射(GPU) | 更新页表,设置DEFER_FREE |
| 已锁定 | DevmemAcquireCpu | 已映射(CPU) | 增加映射计数 |
| 已映射 | PMRUnlockPhysAddresses | 已创建,未锁定 | 减少引用 |
| 已创建 | PMRUnrefPMR (ref=0) | 僵尸状态 | 加入僵尸列表 |
| 僵尸状态 | 同步条件满足 | 清理队列 | 创建清理项 |
| 清理队列 | _PmrZombieCleanup | 已销毁 | 释放所有资源 |
错误处理路径汇总
错误类型 处理策略 恢复可能性
══════════════════════════════════════════════════════════════════════
内存不足
├─ PMR创建失败 返回错误,不创建PMR 可重试
├─ 物理页面分配失败 返回错误,保持未锁定状态 可重试
└─ 僵尸结构分配失败 重新锁定物理地址,保持一致性 自动恢复
参数错误
├─ 逻辑大小不匹配 创建时拒绝 不可恢复
├─ 页索引越界 返回错误,不修改状态 不可恢复
└─ 无效的稀疏操作 返回错误,保持原有布局 不可恢复
权限错误
├─ 布局已固定 拒绝稀疏变更 不可恢复
├─ CPU映射存在 拒绝稀疏变更 需解映射
└─ 不支持的操作 返回NOT_IMPLEMENTED 不可恢复
并发冲突
├─ 页面迁移中 返回RETRY(GPU), 等待(CPU) 自动重试
├─ 引用计数异常 记录警告,拒绝操作 检测错误
└─ 双重释放 检测并拒绝 检测错误
硬件错误
├─ GPU超时 标记设备错误 需重置
├─ MMU错误 触发panic或恢复 部分恢复
└─ DMA错误 返回硬件错误 需重置
典型使用场景流程
场景1:纹理分配和使用
1. 应用分配纹理 (1MB, 稀疏)
DevmemIntAllocatePMR()
└─ PMRCreatePMR()
└─ OSPagesPMR创建 (on-demand)
- 逻辑大小: 1MB (256页 × 4KB)
- 物理大小: 0 (未分配)
- 映射表: 全部无效
2. 上传纹理数据 (实际使用512KB)
DevmemIntMapPMR() → GPU映射
└─ PMRLockPhysAddresses()
└─ pfnLockPhysAddresses()
- 分配128页物理内存
- 更新映射表: [0-127]=有效, [128-255]=无效
- Pin页面防止交换
DevmemAcquireCpuVirtAddr() → CPU映射
└─ 获取用户空间虚拟地址
└─ memcpy纹理数据
3. GPU渲染使用纹理
GPU访问纹理地址
└─ MMU查询页表
└─ 命中: 访问物理内存
└─ 未命中: 触发页错误
4. 释放纹理
DevmemReleaseCpuVirtAddr()
DevmemIntUnmapPMR()
PMRUnrefPMR()
└─ 引用计数 → 0
└─ _UnrefAndMaybeDestroy()
└─ 延迟释放 (有GPU映射)
└─ 等待GPU完成渲染
└─ 等待缓存刷新
└─ 清理线程释放
总时间: ~100-500ms (包括延迟)
内存效率: 50% (512KB/1MB)
场景2:大型缓冲区动态调整
1. 初始分配 (4MB, 稀疏)
PMRCreatePMR()
- 逻辑: 4MB (1024页)
- 物理: 256页 (1MB)
- 利用率: 25%
2. 使用一段时间后扩展
应用需要2MB
└─ PMR_ChangeSparseMemUnlocked()
- 分配: 256页 (索引256-511)
- 释放: 0页
- 结果: 2MB物理内存
- 利用率: 50%
3. 释放不用的部分
应用只需1.5MB
└─ PMR_ChangeSparseMemUnlocked()
- 分配: 0页
- 释放: 128页 (索引512-639)
- 僵尸页面: 128页进入延迟释放
- 结果: 1.5MB物理内存
- 利用率: 37.5%
4. 最终释放
PMRUnrefPMR()
└─ 所有页面进入延迟释放队列
└─ 分批释放回OS
优势:
- 逻辑地址空间固定 (4MB)
- 物理内存按需调整
- 避免重新映射开销
场景3:跨进程内存共享
进程A (导出方)
═════════════
1. 分配共享内存
PMRCreatePMR(1MB)
2. 写入数据
PMR_WriteBytes()
3. 导出PMR
PMRExportPMR()
└─ PMR_SetLayoutFixed(TRUE)
- 锁定布局,禁止变更
- 生成导出句柄和密钥
- 引用计数++
4. 传递句柄给进程B
(通过共享内存或socket)
进程B (导入方)
═════════════
1. 导入PMR
PMRImportPMR(handle, key)
└─ 验证密钥
└─ 引用计数++
2. 映射到GPU
DevmemIntMapPMR()
└─ 获取相同的物理地址
└─ GPU可以访问相同内存
3. 使用完毕
PMRUnimportPMR()
└─ 引用计数--
进程A清理
═════════
1. 取消导出
PMRUnexportPMR()
└─ 引用计数--
2. 释放PMR
PMRUnrefPMR()
└─ 引用计数--
└─ 如果B也释放了: 计数→0
└─ 真正释放物理内存
安全保证:
- 密钥验证防止非法访问
- 布局固定保证一致性
- 引用计数防止提前释放
性能优化建议
优化点 优化前 优化后 提升
═══════════════════════════════════════════════════════════════════════════
1. 栈vs堆分配
- 小量页面(<32) 堆分配 栈数组 2-3x
- 大量页面(>=32) 栈溢出风险 堆分配 安全
2. 快速路径检测
- 连续内存 O(n)遍历 O(1)直接返回 10-100x
- 稀疏内存 必须查表 优化映射表 1.5-2x
3. 批量操作
- 逐页操作 n次系统调用 1次批量操作 5-10x
- 单个PMR多次锁定 多次锁操作 保持锁定 2-3x
4. 延迟释放
- 同步释放 阻塞等待刷新 异步清理 消除阻塞
- 立即释放 频繁分配/释放 批量延迟 减少碎片
5. 缓存策略
- 地址转换结果 每次计算 缓存热点 2-5x
- 映射表访问 每次读取 预取批量 1.5-2x
6. 并发优化
- 粗粒度锁 全局锁 Per-PMR锁 线性扩展
- 自旋锁vs互斥锁 错误选择 场景匹配 避免睡眠
7. 内存对齐
- 未对齐访问 额外周期 缓存行对齐 1.2-1.5x
- 碎片化分配 浪费空间 连续分配 节省20%
8. PDump开销
- 生产环境启用 明显变慢 条件编译 恢复性能
- 调试环境 完整记录 按需记录 可控开销
内存使用统计
内存类型 典型大小 生命周期 优化方向
═══════════════════════════════════════════════════════════════
PMR结构体 ~500 bytes 长期 池化分配
映射表 n4 bytes 长期 嵌入PMR
翻译数组 n4 bytes 长期 压缩稀疏
物理页面 4KB-2MB 可变 按需分配
僵尸结构 ~100 bytes 短期 预分配
清理项 ~200 bytes 短期 批量处理
总开销 (1000个PMR):
- 结构开销: ~500KB
- 映射表: 取决于稀疏度
- 物理内存: 实际使用量
- 管理开销: <1%
总结
这份PMR.c的完整分析涵盖了:
- 核心数据结构 - PMR结构体、全局上下文、映射表
- 生命周期管理 - 创建、引用计数、锁定、销毁
- 地址转换引擎 - 逻辑到物理地址映射,快速/慢速路径
- 稀疏内存 - 动态布局变更,映射表管理
- 延迟释放 - 僵尸PMR处理,异步清理
- 读写操作 - 内核映射,数据传输
- 安全机制 - 引用计数溢出保护,密钥验证
- 性能优化 - 栈分配优化,批量操作,缓存策略
- 调试支持 - PDump集成,内存统计
JINHU