[shadPS4] 内存管理 | 权限管理 |文件系统 | 挂载和句柄
第二章:内存管理器
欢迎回到shadPS4项目!
在前一章:内核/系统服务(HLE)中,我们学习了shadPS4
如何作为PS4游戏系统调用的翻译器
,使游戏能够在计算机上运行。
任何游戏最基础的资源需求就是内存。正如PS4游戏期望操作系统提供线程创建或日志输出等特定服务,它同样要求操作系统以特定方式管理内存。
这正是内存管理器的核心职责。
-
我们可以将PS4的完整内存架构(RAM、显存等)想象成一座巨型图书馆。
-
PS4
操作系统
如同图书馆(内存)管理员
,负责书架分配、图书(数据)定位,并确保不同读者(游戏或系统的各个组件)能访问所需资源,同时遵守权限规则。
当游戏在shadPS4运行时,它仍认为
自己身处PS4的"图书馆"中。
它按照PS4的规范申请内存、访问特定地址数据,并设置内存区域的访问规则(如只读、可读写、可执行代码)。但实际的内存操作发生在*计算机的物理内存*
中,由计算机的操作系统管理。
shadPS4的内存管理器就是这个复杂的内存翻译与管理系统。其主要功能包括:
- 地址转换:游戏使用的地址存在于PS4
模拟内存
空间中,内存管理器将其转换为计算机物理内存的真实地址
。 - 区域管理:PS4将内存划分为不同功能区域(游戏代码、系统数据、显存),内存管理器维护这些模拟区域与计算机内存的映射关系。
- 权限控制:当游戏申请具有特定权限的内存时(如"只读区域"),内存管理器通过计算机操作系统的功能实现相同规则。
通过三级地址体系理解这个机制:
- PS4虚拟地址(VAddr):游戏可见的逻辑地址,如同图书馆的"
索书号
"。 - PS4物理地址(PAddr):模拟的物理存储位置,如同图书馆的"
实际架位
"。 - 宿主地址(Host Address):计算机内存的物理坐标,如同现实世界的"
建筑位置
"。
内存管理器通过三层映射体系(VAddr→PAddr→Host Address),在遵守PS4内存规范的同时实现物理内存操作。
典型案例:游戏申请内存
当游戏需要存储玩家背包或地图数据时,会通过系统调用(如sceKernelMmap
)申请指定大小的内存块。shadPS4的内存管理流程如下:
该流程图展示了完整处理链条:从游戏请求→内核服务转发
→内存管理器
决策→宿主系统执行
→结果返回游戏。
核心组件
内存管理系统由两大核心组件构成:
- 地址空间(AddressSpace):对接宿主操作系统的底层接口,通过
VirtualAlloc
(Windows)或mmap
(Linux/macOS)实现物理内存操作。 - 内存管理器(MemoryManager):维护PS4内存规范的中央控制器,管理虚拟内存区域表(
vma_map
)和物理内存区域表(dmem_map
)。
关键数据结构定义如下:
// 虚拟内存区域描述符
struct VirtualMemoryArea {VAddr base; // PS4虚拟地址基址u64 size; // 区域大小VMAType type; // 区域类型(代码/数据/堆栈等)MemoryProt prot; // 访问权限std::string name; // 区域标识bool is_exec; // 可执行标志
};// 物理内存区域描述符
struct DirectMemoryArea {PAddr base; // PS4物理地址基址u64 size;s32 memory_type; // 内存类型bool is_free; // 空闲状态
};
内存分配时的决策流程:
- 查询
vma_map
寻找合适的虚拟
地址空间 - 在
dmem_map
中分配对应的物理
内存区域 - 通过
AddressSpace
申请宿主系统内存 - 更新
内部映射
关系
内存映射实现
核心函数MapMemory
的实现逻辑:
s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size,MemoryProt prot, MemoryMapFlags flags, VMAType type) {std::scoped_lock lock{mutex}; // 线程安全锁// 地址决策逻辑VAddr mapped_addr = (flags & Fixed) ? virtual_addr : SearchFreeRegion(size);// 虚拟区域划分auto vma = CarveVMA(mapped_addr, size);vma->prot = prot;vma->type = type;// 物理内存映射if (type != Reserved) {*out_addr = AddressSpace::Map(mapped_addr, size, prot);}return ORBIS_OK; // PS4成功代码
}
该函数实现的关键步骤:
- 线程互斥保证数据一致性
scoped_lock lock{mutex};
- 根据
标志位
选择固定地址或动态分配 - 切割虚拟内存区域
- 通过AddressSpace对接宿主系统
权限管理机制
当游戏通过sceKernelMprotect
修改内存权限时:
s32 MemoryManager::Protect(VAddr addr, u64 size, MemoryProt prot) {auto vma = FindVMA(addr); // 定位目标区域// 权限验证if (vma->prot == prot) return ORBIS_OK;// 宿主系统权限设置AddressSpace::Protect(addr, size, prot);// 更新映射表vma->prot = prot;return ORBIS_OK;
}
该过程通过三级防护:
- 虚拟区域权限验证
- 宿主系统
内存保护
设置 - 内部状态
同步更新
总结
内存管理器通过精密的地址转换
和双层映射(PS4->VirtualMemoryArea->DirectMemoryArea
)体系,在宿主系统上构建符合PS4规范的虚拟内存环境。其核心价值体现在:
- 地址隔离:为游戏维护
独立的虚拟地址
空间 - 资源抽象:将异构硬件内存
统一抽象管理
- 安全沙盒:严格执行PS4内存访问规则
- 性能优化:通过
预分配
和缓存
策略提升效率
下一章我们将深入解析shadPS4如何模拟PS4文件系统,实现游戏资源的加载与访问。
下一章:文件系统
第三章:文件系统
欢迎回来!在上一章 第二章:内存管理器中,我们探讨了shadPS4如何管理游戏对内存的请求,将PS4的概念性内存布局
映射到我们PC的物理RAM
。
现在游戏已经将其代码和数据加载到内存中
,但它经常需要访问更多数据或保存进度。这就需要与存储设备
(PS4的硬盘或光盘)进行交互。
-
就像处理内存和系统服务一样,当PS4游戏需要访问文件时,其设计是与PS4操作系统通信。
-
它使用特定路径,比如
/app0/data/game.dat
表示游戏数据,/user/savedata/
保存存档文件,或/update/
用于游戏补丁。 -
这些路径指向PS4文件系统结构内的位置。
但当我们在PC上运行PS4游戏时,这些PS4路径不会神奇地出现在我们的硬盘上。PC拥有自己的文件系统(如Windows的C盘,或Linux的/home/user/
)。游戏需要找到其文件的方法,这些文件实际上位于PC的某个位置。
-
这就是shadPS4中文件系统组件的作用。可以将其视为关键桥梁或复杂的重定向服务。
-
它
拦截
游戏使用PS4路径打开、读取、写入或列出文件的请求,并将这些操作转换
为PC实际文件系统上的对应操作,使用
存储游戏文件的实际路径。
这类似于挂载网络驱动器:我们使用驱动器盘符(如Z:)和相对路径(Z:\Documents\report.doc)访问文件,但这些命令实际上被秘密地转换为访问网络上某处服务器的特定文件夹
。shadPS4在PS4路径和PC路径之间进行这种转换。
用例:游戏打开文件
假设游戏需要加载位于PS4路径/app0/config/settings.ini
的配置文件。
在真实的PS4上,它会通过系统调用打开该文件,获取文件句柄(用于标识已打开文件的数字),然后使用该句柄进行读取。
句柄
是系统分配给已打开文件的唯一数字标识,程序用它来操作文件(如读取)。
以下是该请求在shadPS4中的流程:
- 游戏通过
sceKernelOpenFile
等PS4系统调用请求打开路径/app0/config/settings.ini
- 内核/系统服务(HLE)拦截该调用。它识别这是一个文件操作,需要由文件系统组件处理
- 文件系统组件接收PS4路径
/app0/config/settings.ini
- 在其内部映射表(称为挂载点)中查找对应的PC文件夹。假设找到将
/app0
映射到C:\MyGames\PS4Game\GameData
的条目 - 构造完整的PC路径:
C:\MyGames\PS4Game\GameData\config\settings.ini
- 请求PC操作系统实际打开该真实路径下的文件
- PC操作系统打开文件并返回句柄(如文件指针或描述符)
- 文件系统组件在其句柄表中注册这个打开的文件,分配唯一编号(模拟器管理的文件描述符)
- 文件系统组件将模拟器文件描述符返回给内核/系统服务(HLE)
- 内核/系统服务(HLE)将该编号返回给游戏,模拟PS4系统调用的成功结果。游戏现在使用该编号进行后续读取或关闭文件等操作
流程图展示:
文件系统的核心概念
为了实现这种转换和管理,文件系统组件依赖几个核心概念:
- 挂载点 (
MntPoints
):这是路径转换的核心。它维护PS4风格"挂载点"(如/app0
、/data
、/user
)与实际PC目录之间的映射列表。当游戏请求PS4路径时,文件系统找到最长匹配的挂载点,并相应地重定向路径的其余部分 - 句柄表 (
HandleTable
):当游戏打开文件或设备时,PS4操作系统会给予文件描述符(小整数)。shadPS4需要做同样的事情。HandleTable
是一个简单列表或数组,每个条目对应这些整数文件描述符,存储有关已打开文件或设备的信息(如类型、主机路径和实际的主机OS文件句柄) - 文件/设备表示 (
File
结构和BaseDevice
):句柄表中的每个条目指向表示已打开内容的对象。对于常规文件,这通常是包含原生主机OS文件句柄的File
结构或类。对于控制台或内核接口等设备,它指向BaseDevice
对象(如第一章所见)。这些对象提供实际的read
、write
、seek
等操作 - 主机文件I/O (
Common::FS::IOFile
包装器):为了在PC上执行实际的文件操作,模拟器使用PC操作系统提供的函数(如open
、read
、write
、close
、seek
)。这些通常被封装在跨平台接口中(Common::FS::IOFile
或类似类/函数),因此模拟器的其他部分不需要知道Windows与Linux文件调用的具体细节
挂载点 (MntPoints
)
MntPoints
类跟踪映射规则
— 文件: src/core/file_sys/fs.h (简化版) —
class MntPoints {
public:struct MntPair {std::filesystem::path host_path; // PC上的实际文件夹std::string mount; // PS4路径 (如"/app0")bool read_only;};void Mount(const std::filesystem::path& host_folder, const std::string& guest_folder,bool read_only = false);void UnmountAll();// 查找与PS4路径对应的主机路径std::filesystem::path GetHostPath(std::string_view guest_directory,bool* is_read_only = nullptr, bool force_base_path = false);
private:std::vector<MntPair> m_mnt_pairs; // 所有活动挂载点的列表
};
文件和句柄表 (File
, HandleTable
)
当游戏打开文件(或访问设备)时,shadPS4需要使用整数标识符(文件描述符)进行跟踪。HandleTable
管理这些标识符,每个标识符指向File
对象(或BaseDevice
)
— 文件: src/core/file_sys/fs.h (简化版) —
struct File {std::atomic_bool is_opened{};std::atomic<FileType> type{FileType::Regular}; // 文件类型std::filesystem::path m_host_name; // 已打开的PC路径
};class HandleTable {
public:int CreateHandle(); // 创建新条目并返回索引void DeleteHandle(int d);File* GetFile(int d); // 获取文件描述符对应的File对象
private:std::vector<File*> m_files; // 文件描述符表
};
执行主机文件操作 (File::Open
, File::Read
等)
当文件系统获得主机路径(来自MntPoints
)并创建了带有待处理句柄的File
对象(来自HandleTable
)后,它需要实际在主机OS上打开文件。这由File
对象本身的方法处理,通常使用主机OS调用的包装器
— 文件: src/core/file_sys/file.cpp (Windows版简化) —
int File::Open(const std::filesystem::path& path, Common::FS::FileAccessMode f_access) {// 将通用访问模式转换为Windows特定标志DWORD access = GENERIC_READ; // 示例:读取访问handle = CreateFileW(path.native().c_str(), access, FILE_SHARE_READ, NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL);if (handle == INVALID_HANDLE_VALUE) {return ENOENT; // 文件未找到错误示例}m_host_name = path; // 存储主机路径is_opened = true; // 标记为已打开return 0; // 成功
}
结论
文件系统组件对于弥补PS4预期文件结构与PC实际文件系统之间的差距至关重要。它通过以下方式实现:
- 维护挂载点将
PS4路径转换为PC路径
- 使用句柄表为游戏
提供已打开文件
和设备的整数标识符 - 通过File对象(或设备)表示已打开资源,这些对象包含必要信息(包括来自主机OS的实际句柄)
调用主机OS函数
(包装为跨平台兼容)来执行PC存储上的实际文件操作
这个系统确保当PS4游戏试图访问/app0/something.psf
时,shadPS4能准确定位该文件在计算机上的位置,并实现无缝交互。
下一章:模块链接器