当前位置: 首页 > news >正文

[shad-PS4] docs | 内核/系统服务 | HLE-高等级模拟

链接:https://github.com/shadps4-emu/shadPS4

docs:shadPS4

shadPS4 是一款**模拟器**,旨在通过计算机平台运行*PlayStation 4游戏*。

其核心原理是将PS4特有的硬件指令和系统调用,转换为计算机硬件与操作系统可理解的操作,涵盖*内存管理图形渲染输入处理文件系统访问*等关键环节。

通过本项目的学习,可以更好的理解 兼容性的实现,和软硬件间的处理。

架构图

在这里插入图片描述

章节

  1. 内核/系统服务(HLE)
  2. 内存管理器
  3. 文件系统
  4. 模块链接器
  5. 输入处理器
  6. Vulkan渲染器
  7. 着色器重编译器
  8. 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函数(如writereadioctl)。

默认错误返回值模拟了真实操作系统的行为——当设备未实现特定操作时返回标准错误码。

控制台设备为例,观察具体实现:

— 文件: 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策略在保证兼容性的同时极大提升了运行效率。

下一章我们将深入探讨与内核紧密协作的核心组件——内存管理器

下一章:内存管理器

http://www.lryc.cn/news/581581.html

相关文章:

  • Spark流水线数据质量检查组件
  • UNet改进(16):稀疏注意力(Sparse Attention)在UNet中的应用与优化策略
  • Redis集群和 zookeeper 实现分布式锁的优势和劣势
  • 物联网实施与运维【路由器/网关配置】+智能楼道系统
  • python库 dateutil 库的各种案例的使用详解
  • 【Note】《Kafka: The Definitive Guide》第三章: Kafka 生产者深入解析:如何高效写入 Kafka 消息队列
  • Android studio在点击运行按钮时执行过程中输出的compileDebugKotlin 这个任务是由gradle执行的吗
  • 升级AGP(Android Gradle plugin)和gradle的版本可以提高kapt的执行速度吗
  • 【python】对纯二进制向量(仅包含 0 和 1,长度为 8 或 16)的检测和提取
  • 基于腾讯云开发与“人·事·财·物”架构理念的家政预约小程序设计与实现
  • 【Python练习】030. 编写一个函数,实现字符串的反转
  • Python 中 ffmpeg-python 库的详细使用
  • 一条 SQL 语句的内部执行流程详解(MySQL为例)
  • 2025 JuniorCryptCTF re 部分wp
  • 重力翻转者:原创趣味小游戏
  • 前端开发常见问题(从布局到性能优化)
  • 【libm】 10 rem_pio2函数 (rem_pio2.rs)
  • 人工智能之数学基础:线性回归算法的矩阵参数求导
  • 传统微商困境与开源链动2+1模式、AI智能名片及S2B2C商城小程序的转型破局
  • AUTOSAR进阶图解==>AUTOSAR_SWS_V2XFacilities
  • Hadoop MapReduce 入门
  • Hadoop高可用集群搭建
  • k8s-服务发布基础
  • 小菜狗的云计算之旅,学习了解rsync+sersync实现数据实时同步(详细操作步骤)
  • 【Linux网络编程】Socket - UDP
  • 儿童趣味记忆配对游戏
  • 【CSS-15】深入理解CSS transition-duration:掌握过渡动画的时长控制
  • Java的各种各样的生命周期——思考历程
  • 字符函数和字符串函数(下)- 暴力匹配算法
  • ASP.NET Web Pages 安装使用教程