[shad-PS4] docs | 内核/系统服务 | HLE-高等级模拟
链接:https://github.com/shadps4-emu/shadPS4
docs:shadPS4
shadPS4 是一款**模拟器**,旨在通过计算机平台运行*PlayStation 4游戏
*。
其核心原理是将PS4特有的硬件指令和系统调用,转换为
计算机硬件与操作系统可理解的操作,涵盖*内存管理
、图形渲染
、输入处理
和文件系统访问
*等关键环节。
通过本项目的学习,可以更好的理解 兼容性的实现,和软硬件间的处理。
架构图
章节
- 内核/系统服务(HLE)
- 内存管理器
- 文件系统
- 模块链接器
- 输入处理器
- Vulkan渲染器
- 着色器重编译器
- Qt图形界面
Github & 配置
C++ 编写,适用于 Windows、Linux 和 macOS 的早期 PlayStation 4 模拟器。
它可以成功运行
Bloodborne、Dark Souls Remastered、Red Dead Redemption 等多款游戏。
主要功能点
- 支持 Windows、Linux 和 macOS 平台
- 可以运行多款 PlayStation 4 游戏
- 提供键盘和鼠标映射功能
- 支持 Xbox 和 DualShock 手柄
技术栈
- C++
- Vulkan
- SDL3
- ImGui
配置
1. 系统要求
确保系统满足以下要求:
- 操作系统:Windows、Linux 或 macOS
- 依赖库:
- C++ 编译器(如 GCC 或 Clang)
- CMake
- Vulkan SDK
- SDL3
- ImGui
2. 克隆代码库
在终端中运行以下命令以克隆 shadPS4 的代码库:
git clone git@github.com:shadps4-emu/shadPS4.git
cd shadPS4
3. 创建构建目录
建议在项目目录中创建一个单独的构建目录:
mkdir build
cd build
4. 使用 CMake 配置项目
运行 CMake 以配置项目:
cmake ..
根据你的系统和需求,你可能需要添加一些选项。例如,如果你想启用 Vulkan 支持,可以使用:
cmake .. -DVULKAN=ON
5. 构建项目
使用以下命令构建项目:
cmake --build .
6. 安装
构建完成后,可以选择安装(可选):
sudo cmake --install .
7. 运行模拟器
构建完成后,在 build
目录中找到可执行文件,运行模拟器即可。
注意事项
- 确保已安装所有依赖项,并根据需要配置环境变量。
- 在运行模拟器之前,确保系统支持
Vulkan
。
第一章:内核/系统服务(HLE)
欢迎来到shadPS4教程系列!这是开篇章节,我们将首先探索shadPS4如何让PlayStation 4游戏相信它正运行在真实PS4上的核心机制——内核/系统服务,也称为**高等级模拟(High-Level Emulation, HLE)**。
假设我们是PS4游戏。
-
我们已经习惯了PlayStation 4操作系统提供的特定环境:创建新任务(想象这些任务是帮助游戏处理不同工作的助手,如管理AI或加载数据)、管理内存、处理控制器输入或在屏幕上绘制图像等基础服务。
-
我们的游戏并不直接执行这些操作,而是通过调用PS4操作系统来实现。
-
这些请求就像给操作系统拨打电话。在PS4上,这些"电话"被称为系统调用或**syscall**。
当尝试在计算机上运行PS4游戏时,游戏仍然期望与PS4操作系统交互。
但计算机运行的是Windows、Linux或macOS等不同操作系统,它们无法理解PS4特有的系统调用。
-
这时**shadPS4的内核/系统服务(HLE)**层便发挥了作用。它如同一位精通双语的翻译专家,每当游戏发起PS4系统调用时,模拟器会
拦截
该调用,并将其转换为
计算机操作系统能够理解和执行的操作。 -
这种转换
并非
对PS4操作系统内部工作原理的逐行仿真
,而是采用**高等级模拟
(HLE)**策略。这意味着我们理解PS4系统调用的意图(例如"创建线程
"、“分配内存
”),然后利用计算机操作系统的资源达成相同结果。
为何选择HLE?
因为对PS4操作系统内核进行精确的低等级模拟(Low-Level Emulation, LLE)极其复杂且效率低下。HLE通过模仿游戏期望的行为而非精确复制PS4操作系统的内部实现细节,能够更快地让游戏运行。
让我们通过一个简单案例理解这个过程:
当游戏需要向控制台输出信息时会发生什么?
案例:游戏输出日志
假设游戏需要打印调试信息"Initializing game world…"。
在真实PS4上,游戏会发起特定的控制台写入系统调用。
在shadPS4
中,这个系统调用被拦截
后,内核/系统服务层会进行处理:
识别出文本输出请求,获取游戏提供的文本内容,最终调用计算机
操作系统的标准输出或日志系统显示信息。
游戏收到操作成功的信号后继续运行,完全感知不到它并未运行在真实PS4上。
通过时序图可直观展现该交互过程:
(相当于是在游戏和启动之间,又套了一层,及在电脑上模拟PS4环境,转化实现成相同的调用)
该流程图展示了完整的请求处理链:
游戏发起请求
→模拟器CPU捕获
→内核服务解析
→宿主系统执行
→结果返回游戏
。
代码实现
这种"翻译"与"模拟"在代码中如何体现?我们通过几个核心代码片段来解析。
首先观察模拟器如何管理游戏可能访问的各类"设备"资源(如控制台输出设备):
— 文件: src/core/devices/base_device.h
—
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later#pragma once// ... 头文件包含 ...namespace Libraries::Kernel {
struct OrbisKernelStat;
struct SceKernelIovec;
} // namespace Libraries::Kernelnamespace Core::Devices {class BaseDevice {
public:explicit BaseDevice();virtual ~BaseDevice() = 0; // 声明为抽象基类virtual int ioctl(u64 cmd, Common::VaCtx* args) {return ORBIS_KERNEL_ERROR_ENOTTY; // 默认返回"操作不支持"}virtual s64 write(const void* buf, size_t nbytes) {return ORBIS_KERNEL_ERROR_EBADF; // 默认返回"错误文件描述符"}virtual size_t readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) {return ORBIS_KERNEL_ERROR_EBADF;}// ... 其他虚拟函数定义 ...
};} // namespace Core::Devices
BaseDevice
作为抽象基类,定义了设备操作的标准接口。
任何需要模拟PS4设备(如控制台、存储设备)的组件都必须继承此类并实现这些virtual
函数(如write
、read
、ioctl
)。
默认错误返回值模拟了真实操作系统的行为——当设备未实现特定操作时返回标准错误码。
以控制台设备为例,观察具体实现:
— 文件: src/core/devices/console_device.h —
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later#pragma once
#include <memory>
#include "base_device.h"namespace Core::Devices {class ConsoleDevice final : BaseDevice { // 继承自BaseDeviceu32 handle; // 设备实例标识符public:static std::shared_ptr<BaseDevice> Create(u32 handle, const char*, int, u16);explicit ConsoleDevice(u32 handle) : handle(handle) {}~ConsoleDevice() override = default;// 重写基类函数int ioctl(u64 cmd, Common::VaCtx* args) override;s64 write(const void* buf, size_t nbytes) override; // 处理写入请求的核心函数size_t readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override;size_t writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override;// ... 其他函数重写 ...
};} // namespace Core::Devices
ConsoleDevice
作为具体实现类,重写了write
函数来处理控制台输出请求。
实际代码实现如下:
— 文件: src/core/devices/console_device.cpp
—
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later#include "common/logging/log.h" // 使用日志系统
#include "console_device.h"namespace Core::Devices {// ... Create函数实现 ...s64 ConsoleDevice::write(const void* buf, size_t nbytes) {// TODO: 实现宿主系统控制台输出LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); // 当前仅为桩函数return 0; // 模拟成功返回值
}// ... 其他函数实现 ...
} // namespace Core::Devices
当前实现中,write
函数作为桩函数(stub)记录调用信息。
完整实现时,此函数将把buf
中的游戏数据通过宿主系统的日志系统输出,并返回PS4系统预期的成功代码。
线程管理模拟
当游戏请求创建新线程时,模拟器需要映射到宿主系统的线程机制。
NativeThread
类展示了这一过程:
— 文件: src/core/thread.h —
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later#pragma once#include "common/types.h"namespace Libraries::Kernel {
struct PthreadAttr; // PS4线程属性
} // namespace Libraries::Kernelnamespace Core {using ThreadFunc = void (*)(void*); // 线程入口函数签名class NativeThread {
public:NativeThread();~NativeThread();// 创建宿主系统线程来模拟PS4线程int Create(ThreadFunc func, void* arg, const ::Libraries::Kernel::PthreadAttr* attr);void Exit(); // 终止当前线程// ... 辅助函数 ...
};} // namespace Core
Create
函数根据宿主系统类型调用不同的底层API:
— 文件: src/core/thread.cpp
—
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later// ... 平台相关头文件包含 ...int NativeThread::Create(ThreadFunc func, void* arg, const ::Libraries::Kernel::PthreadAttr* attr) {
#ifndef _WIN64pthread_t* pthr = reinterpret_cast<pthread_t*>(&native_handle);pthread_attr_t pattr;pthread_attr_init(&pattr);// 根据游戏请求设置栈属性pthread_attr_setstack(&pattr, attr->stackaddr_attr, attr->stacksize_attr);// 调用宿主系统线程创建函数return pthread_create(pthr, &pattr, (PthreadFunc)func, arg);
#else// Windows特定实现return NtCreateThread(&native_handle, THREAD_ALL_ACCESS, nullptr, GetCurrentProcess(),&clientId, &ctx, &teb, false);
#endif
}
该实现完美体现了HLE原则:
解析游戏的线程创建请求后,调用宿主系统的原生线程API
(如POSIX的pthread_create
或Windows的NtCreateThread
),同时尽量匹配游戏指定的线程属性。
线程本地存储(TLS)模拟
PS4通过特定处理器寄存器管理线程本地存储,模拟器通过Tls
类实现跨平台兼容:
— 文件: src/core/tls.h —
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later#pragma once#include <cstring>
#include "common/types.h"// ... 类型定义 ...namespace Core {struct Tcb {Tcb* tcb_self;// ... PS4 TLS数据结构 ...
};/// 设置TCB(线程控制块)基地址
void SetTcbBase(void* image_address);/// 获取当前线程TCB
Tcb* GetTcbBase();/// 确保线程初始化完成
void EnsureThreadInitialized();// ... 辅助函数 ...
} // namespace Core
具体实现根据宿主平台采用不同策略:
— 文件: src/core/tls.cpp —
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later// ... 平台相关实现 ...#ifdef _WIN32
void SetTcbBase(void* image_address) {TlsSetValue(GetTcbKey(), image_address); // Windows TLS API
}Tcb* GetTcbBase() {return reinterpret_cast<Tcb*>(TlsGetValue(GetTcbKey()));
}
#elif defined(__APPLE__) && defined(ARCH_X86_64)
void SetTcbBase(void* image_address) {asm volatile("mov %0, %%fs" ::"r"(new_selector)); // macOS x86_64段寄存器操作
}
#elif defined(ARCH_X86_64)
void SetTcbBase(void* image_address) {asm volatile("wrgsbase %0" ::"r"(image_address) : "memory"); // x86_64专用指令
}
#else
void SetTcbBase(void* image_address) {pthread_setspecific(GetTcbKey(), image_address); // 通用POSIX实现
}
#endif
这些实现确保游戏访问TLS时,能正确映射
到宿主系统管理的存储区域
,完美模拟PS4行为。
中断控制器模拟
IrqController
类模拟硬件中断处理机制:
— 文件: src/core/platform.h —
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later#pragma once// ... 类型定义 ...namespace Platform {enum class InterruptId : u32 {GfxEop = 0x40, // 图形硬件完成中断// ... 其他中断类型 ...
};using IrqHandler = std::function<void(InterruptId)>; // 中断处理函数struct IrqController {void RegisterOnce(InterruptId irq, IrqHandler handler) {// 注册单次中断处理}void Register(InterruptId irq, IrqHandler handler, void* uid) {// 注册持久中断处理}void Signal(InterruptId irq) {// 模拟硬件触发中断}// ... 其他方法 ...
};using IrqC = Common::Singleton<IrqController>; // 单例访问
} // namespace Platform
当模拟的硬件组件(如GPU)触发中断时
调用Signal
方法通知注册的处理函数
模拟PS4内核的中断分发机制。
总结
讲解了shadPS4的**内核/系统服务(HLE)**层如何通过高层行为模拟,将PS4游戏的系统调用转换为计算机操作系统的原生操作。
从基础IO(继承-重写write)
到线程管理(解析-调用原生API)
,从存储隔离(内存映射)
到中断处理(signal触发)
,HLE策略在保证兼容性的同时极大提升了运行效率。
下一章我们将深入探讨与内核紧密协作的核心组件——内存管理器。
下一章:内存管理器