☰
深入解析 Luma3DS 的 pm 系统模块:Arm11 PM 开源替代的实现与构建指南
2026/10/15 16:11:56 网站建设 项目流程
  • 固件
  • 操作系统
  • 嵌入式
  • 调试器

【免费下载链接】Luma3DS

Nintendo 3DS "Custom Firmware"

项目地址:https://gitcode.com/gh_mirrors/lu/Luma3DS
点击查看免费下载

Luma3DS 仓库中的 sysmodules/pm 是一个用 C 语言从零实现的Arm11 PM(Process Manager,进程管理器)系统模块的开源替代品,项目代号3ds_pm。本文以该模块为主体,完整讲解它的构建安装方法、整体架构、pm:app/pm:dbg两大服务接口、应用生命周期管理与 firmlaunch 机制,并结合 sysmodules/pm/source 下的源码逐层拆解其实现原理。读完本文,你将掌握如何在 Luma3DS 环境中编译并部署该模块,并理解 3DS 系统侧进程管理的关键设计。

一、模块定位:为什么要替换 Arm11 PM

PM 是 3DS 系统固件中的关键系统模块,负责启动、终止、监控 Arm11 侧的进程,并协调应用与系统模块之间的依赖关系、资源限制(Resource Limit)与 firmlaunch 重启流程。官方 PM 的闭源实现存在若干边界问题,而3ds_pm的目标是以完全开源、可审计的方式提供等价能力:

  • 官方 PM 在依赖加载、终止清理等路径上存在未充分处理的失败场景(源码注释中多处点明,见 launch.c);
  • 官方 PM 使用同一块runningApplicationData兼作调试数据存储,3ds_pm则拆分出独立的debugData字段(见 manager.h);
  • 官方 PM 不允许非应用进程被调试,3ds_pm通过自定义命令放宽了该限制;
  • 模块额外提供了若干 Luma3DS 特有的自定义 IPC 命令与通知,服务于 Rosalina 菜单、SAFE_FIRM 下的 hbmenu 链式加载等场景。

该模块以MIT 许可证发布(见 README),与 Luma3DS 主体采用的 GPLv3 不同,属于独立的宽松许可组件。

二、构建与安装:把 pm.cxi 放进 /luma/sysmodules

2.1 构建流程

按 README 的说明,要运行这个系统模块,需要基于较新的 Luma3DS 版本或提交,构建本项目并将生成的 CXI 文件复制到设备 SD 卡的/luma/sysmodules/pm.cxi。构建本身由 Makefile 驱动,环境要求如下:

  • 设置DEVKITARM环境变量(指向 devkitARM 工具链安装路径),Makefile 会先做检查:ifeq ($(strip $(DEVKITARM)),)$(error "Please set DEVKITARM ...");
  • 通过include $(DEVKITARM)/3ds_rules引入 3DS 平台规则;
  • 需要 ctrulib(LIBDIRS := $(CTRULIB))与makerom工具。

核心构建参数(Makefile):

  • TARGET := $(notdir $(CURDIR)),即在sysmodules/pm目录下构建,输出名为pm;
  • 编译架构:-march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft;
  • 常规优化:-O2 -fomit-frame-pointer;若设置BUILD_FOR_GDB=1则降为-O0并链接-lctrud以配合调试器;
  • 编译选项含-Wall -Wextra -Werror,保证告警即错误;
  • 链接参数含-wrap,exit(配合源码中的__wrap_exit防止模块意外退出);
  • 最终产物由makerom -f ncch -rsf pm.rsf -nocodepadding生成pm.cxi(见 Makefile)。

在仓库根目录执行顶层 Makefile 或直接进入sysmodules/pm执行make,即可得到pm.cxi,随后将其拷贝到 3DS 的 SD 卡/luma/sysmodules/pm.cxi。需要说明的是:该 CXI 必须由 Luma3DS 的 sysmodules 加载机制在开机时装载并运行,单独拷贝文件无法生效,需配合相应版本的 Luma3DS 环境使用。

2.2 模块身份与权限(pm.rsf)

生成 CXI 时使用的 pm.rsf 定义了模块的 NCCH 身份与权限,是理解模块能力边界的第一手资料:

配置项值说明
Title / ProductCodepm/lennybuilder模块名与产品码
UniqueId0x12唯一 ID,Category: Base,Version: 2
EnableCompresstrue压缩 exefs 代码
Priority / StackSize20/0x1000进程优先级与主线程栈大小
ServiceAccessControlfs:REG、fs:USER注:注释明确指出 PM 实际还会用到srv:pm与 Loader,但未在此列出
FileSystemAccessDirectSdmc可直接访问 SDMC(自定义项,配合forceMountSdCard使用)
SystemCallAccess约 70 个 SVC含OpenProcess、TerminateProcess、SetProcessResourceLimits、KernelSetState、ReplyAndReceive等

此外 rsf 将CanWriteSharedPage置为true(注释标记"changed",为 Luma3DS 自定义项)、RunnableOnSleep为true,ResourceLimitCategory为Other(与 BASE sysmodules 同属一类资源限制)。

三、整体架构:入口、线程与初始化时序

模块入口在 main.c。在main()之前,initSystem()完成前置初始化:

  1. __sync_init()初始化同步原语;
  2. readLumaConfig()通过自定义svcGetSystemInfo读取 Luma3DS 的 config / multiConfig / bootConfig,供后续功能判断(见 luma.c);
  3. 以 500ms 间隔轮询等待sm服务就绪(srvPmInit返回0xD88007FA时继续等待);
  4. loaderInit()与fsRegInit()建立对 Loader、fs:REG 的 IPC 连接;
  5. Manager_Init初始化进程列表(预分配0x40个ProcessData槽位)、ExHeaderInfoHeap_Init预分配 6 个 ExHeader 信息节点、TaskRunner_Init初始化任务队列。

随后main()依次执行:

initializeReslimits(); // 创建 4 个资源限制对象 Manager_RegisterKips(); // 登记内核内置进程(KIP) mapFirmlaunchParameters(); // 映射 firmlaunch 参数区(0x12000000) // 创建两条工作线程:进程监控线程与任务队列线程 // (优先级 0x17,亲和性 -2) autolaunchSysmodules(); // 自动启动 NS 系统模块 ServiceManager_Run(services, notifications, NULL); // 进入 IPC 服务主循环

其中两条核心线程分别是:

  • processMonitor(process_monitor.c):维护进程句柄数组,通过svcWaitSynchronizationN同时等待"新进程事件"与所有进程句柄,任一进程退出即触发清理:终止其依赖、注销SRVPM_UnregisterProcess/FSREG_Unregister/LOADER_UnregisterProgram、恢复 APPMEMALLOC、通知订阅者(通知号0x110 + variation);
  • taskRunner(task_runner.c):基于两个 LightEvent 组成的单槽任务队列,串行执行LaunchTitleAsync、TerminateProcessOrTitleAsync、PrepareForRebootAsync、LaunchFirmAsync、ChainloadHomebrewDirtyAsync等耗时任务,避免阻塞 IPC 主循环。

服务主循环由 service_manager.c 提供:它注册srvEnableNotification、订阅通知、用svcReplyAndReceive复用单一线程处理所有会话(pm:app4 个会话、pm:dbg2 个会话),收到通知0x100时请求退出。

四、双服务接口:pm:app 与 pm:dbg

模块对外暴露两个服务端口,命令处理分别位于 pmapp.c 与 pmdbg.c。

4.1 pm:app(进程管理主服务)

命令功能实现
1LaunchTitle(异步)launch.c
2LaunchFirm(firmlaunch 重启)firmlaunch.c
3TerminateApplicationtermination.c
4TerminateTitletermination.c
5TerminateProcesstermination.c
6PrepareForReboottermination.c
7GetFirmlaunchParamsfirmlaunch.c
8GetTitleExHeaderFlagsinfo.c
9SetFirmlaunchParamsfirmlaunch.c
10SetAppResourceLimitreslimit.c
11GetAppResourceLimitreslimit.c
12UnregisterProcessmanager.c
13LaunchTitleUpdate(带更新版本的启动)launch.c

所有命令均通过读取线程命令缓冲区(getThreadCommandBuffer)解析 IPC 头并分发,非法命令返回错误码0xD9001830。

4.2 pm:dbg(调试辅助服务)

命令功能说明
1LaunchAppDebug以调试模式启动应用,返回 debug 句柄
2LaunchApp普通启动应用
3RunQueuedProcess启动已排队的调试进程
0x100GetCurrentAppInfo自定义:返回当前应用的程序信息、PID 与启动标志
0x101DebugNextApplicationByForce自定义:强制下一次应用启动进入调试队列
0x102LaunchTitleDebug自定义:调试启动任意 title(不限应用)
0x103PrepareToChainloadHomebrew已移除(注释标明 removed)

命令 1–3 与官方 pm:dbg 保持一致;0x100–0x102 为 Luma3DS 扩展(DebugNextApplicationByForce实现见 launch.c),0x103 已废弃。

五、应用生命周期:启动、依赖与终止

5.1 启动路径

所有启动最终汇聚到 launch.c 的launchTitleImpl,其流程可概括为:

  1. 判断isTitleLaunchPrevented(当前禁启动 ErrDisp,见 luma.c);
  2. 通过registerProgram向 Loader 注册程序(N3DS 下自动处理N3DS_TID_BIT位,见 info.c);
  3. 校验 ExHeader 的core_version与当前内核版本一致(不一致返回0xC8A05800);
  4. 按启动标志调整 APPMEMALLOC 内存上限(O3DS 兼容模式:64/80/96 MB 等档位,见 launch.c);
  5. 选择加载依赖(PMLAUNCHFLAG_LOAD_DEPENDENCIES)或不加载依赖的路径,最终svcRun启动进程,并将运行中的应用登记到g_manager.runningApplicationData并发布通知0x10C。

LaunchTitle对外是异步语义:普通应用启动请求被投递到 TaskRunner 队列后立即返回 0(outPid置为0xFFFFFFFF);若已有应用在运行,则返回0xC8A05BF0(PM_ALREADY_LAUNCHED)。调试队列启动(PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION)则同步执行。

5.2 依赖管理

3DS 的 ExHeader 中声明了最多 48 个依赖 title,3ds_pm的依赖处理集中在 info.c 与 launch.c:

  • listDependencies读取依赖列表并剔除 N3DS 专属 title 位;
  • listMergeUniqueDependencies对依赖去重并维护引用计数refcount;
  • loadWithDependencies递归启动未加载的依赖(标记PROCESSFLAG_AUTOLOADED),官方 PM 在此处的多处缺陷(漏 incref、48 项缓冲溢出等)在源码注释中有详细说明(launch.c);
  • 自定义标志PMLAUNCHFLAGEXT_FAKE_DEPENDENCY_LOADING(BIT(24),见 launch.h)可跳过依赖加载,供 hbmenu 链式加载等场景使用。

ProcessData的关键状态位定义在 process_data.h:NOTIFY_TERMINATION(BIT0)、KIP(BIT1)、DEPENDENCIES_LOADED(BIT2)、AUTOLOADED(BIT3)、NOTIFY_TERMINATION_TERMINATED(BIT4)、NORMAL_APPLICATION(BIT5,官方 PM 没有该标志)。

5.3 终止路径

终止逻辑集中在 termination.c,其核心是一个先通知后强制的两阶段模型:

  1. 先向目标进程发送终止通知0x100(SRVPM_PublishToProcess);
  2. commitPendingTerminations等待allNotifiedTerminationEvent直到超时(超时后调用svcTerminateProcess强制结束,见 termination.c);
  3. TerminateApplication/TerminateTitle/TerminateProcess分别面向应用、titleId、pid 三种粒度;
  4. terminateAllProcesses(firmlaunch 前使用)会终止除调用者依赖之外的全部进程,并专门给 Rosalina(PID 5)发送自定义通知0x2000以便其释放非 KIP 服务句柄、停止调试器,随后终止 PXI(PID 4),见 termination.c。

5.4 firmlaunch 机制

firmlaunch 相关实现在 firmlaunch.c:

  • 参数区固定映射在物理地址0x12000000(mapFirmlaunchParameters通过svcKernelSetState(3, ...)完成,最多 0x1000 字节);
  • GetFirmlaunchParams/SetFirmlaunchParams用于读写参数;
  • LaunchFirm(firmTidLow, params, size)先写入参数,再异步执行:terminateAllProcesses终止全部进程(含强制挂载 SD 卡以便读取boot.firm,见 termination.c),随后按 N3DS 机型修正 TID 位,最终svcKernelSetState(0, firmTid)触发内核侧重启;
  • PrepareForReboot则通过terminateAllProcesses+ 通知0x179告知调用者"可重启"。

此外 termination.c 的ChainloadHomebrewDirty是 Luma3DS 特有的"脏补丁":用于在 SAFE_FIRM 下重启 hbmenu 时避免通知 NS/APT,并兼容 libctru 2023 年 1 月之前的svcExitProcess/svcTerminateProcess死锁问题(源码注释说明了该竞态)。

六、资源限制与 Core1 CPU 调度

3DS 按 ExHeader 的reslimit_category将进程划入 APPLICATION / SYS_APPLET / LIB_APPLET / OTHER 四类资源限制桶。初始化逻辑在 reslimit.c:

  • initializeReslimits为四类各创建一组svcCreateResourceLimit,并以固定顺序(COMMIT、PRIORITY、THREAD、EVENT、MUTEX、SEMAPHORE、TIMER、SHAREDMEMORY、ADDRESSARBITER、CPUTIME)写入 10 项限额;
  • O3DS 与 N3DS 使用两套基准值(如 APPLICATION 类可分配内存分别为0x4000000与0x7C00000,见 reslimit.c);
  • fixupReslimitValues会依据OS_KernelConfig的内存分区实际大小动态修正各桶的 COMMIT 值(并假装 KExt/Rosalina 不存在以避免破坏兼容性),见 reslimit.c;
  • setAppMemLimit通过写 COMMIT 限额同时更新 APPMEMALLOC(高 32 位),用于 O3DS 兼容模式与启动后复位。

CPU 时间(CPUTIME)调度方面,源码注释完整记录了 3DS 内核的两套抢占模式(Mode0 "multi" / Mode1 "single")与取值语义(0 禁用、1–89 为 Core1 百分比、1000 标记 sysmodule、10000+ 豁免抢占),并说明了 3DS 上进程亲和掩码并非访问限制器(reslimit.c)。setAppCpuTimeLimitAndSchedModeFromDescriptor按 ExHeader 的 cputime 描述符与一张硬编码的 1.0 版老游戏覆盖表(如《塞尔达传说 时之笛 3D》取 30,《星之卡比》等取 10000,见 reslimit.c)决定 Core1 调度模式与限额。若启用了 Luma3DS 的REDIRECTAPPTHREADS配置,setAppCpuTimeLimit会返回 0 从而禁止抢占(内核会把应用线程引导到 Core2,见 reslimit.c)。

SetAppResourceLimit/GetAppResourceLimit仅接受 CPUTIME 类别,且数值不得超过maxAppCpuTime,否则返回0xD8E05BF4。

七、与官方 PM 的实现差异小结

源码注释系统性地记录了与官方 PM 的差异,可归纳为以下几点(均有注释佐证):

  1. 简化合并:官方 PM 中"系统模块"与"普通应用"两套启动函数被合并为单一launchTitleImpl(launch.c);
  2. 错误清理:启动/依赖加载失败时明确svcTerminateProcess并清理依赖,修复官方 PM 的泄漏与空指针隐患(launch.c);
  3. 数据拆分:debugData独立于runningApplicationData,并新增PROCESSFLAG_NORMAL_APPLICATION标志(manager.h、process_data.h);
  4. 能力扩展:允许调试非应用进程、强制下一次应用调试、查询当前应用信息(pm:dbg 0x100–0x102);
  5. Luma3DS 集成:读取 Luma 配置、支持REDIRECTAPPTHREADS、为 Rosalina 预留自定义通知(0x2000)、KIP 登记时给 PM 与 Rosalina 授予全部 fs 存档访问权(fs_access_info = 0xFFFFFFFF,见 manager.c);
  6. hbmenu 支持:ChainloadHomebrewDirty与 SAFE_FIRM 场景处理(main.c)。

八、许可证与致谢

模块以MIT 许可证发布(见 README),源码文件头部同时保留 Luma3DS 的 GPLv3 版权声明(如 service_manager.c),共享配置头 luma_shared_config.h 则使用宽松的 zlib 式许可。README 中列出的主要作者为@fincs与@Stary。

九、延伸阅读

  • 模块根目录:sysmodules/pm(含 Makefile、pm.rsf、README.md)
  • 进程数据结构与链表:process_data.h、intrusive_list.h
  • 服务框架(其他 sysmodule 共用):service_manager.c
  • Luma3DS 顶层构建入口:Makefile
  • 若需在完整 Luma3DS 环境中查看本模块如何被加载与协同工作,可参考仓库根目录 README.md 与sysmodules目录下其余模块(如 loader、pxi、rosalina)的说明。
  • 固件
  • 操作系统
  • 嵌入式
  • 调试器

【免费下载链接】Luma3DS

Nintendo 3DS "Custom Firmware"

项目地址:https://gitcode.com/gh_mirrors/lu/Luma3DS
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询