Deno 系统信息 API 的平台适配实现:以 deno_os 扩展为例解析 loadavg、hostname、mem_info 等跨平台系统调用
本篇围绕 Deno 仓库中的 deno_os 扩展(crate 名为 deno_os)展开:先完整继承其 README 中"每个系统信息 API 在各平台分别调用哪个系统调用"的对照表,再结合 lib.rs、sys_info.rs 与 JS 侧 30_os.js 的源码,讲清这些 API 从 Deno 命名空间一路落到 libc/Win32 调用的完整链路、权限校验规则与错误处理细节,读完可以复现 Deno 跨平台系统 API 的实现方式并自行扩展类似能力。
1. deno_os 是什么:一个 Rust 扩展 + 两段懒加载 JS
deno_os crate 的职责在 Cargo.toml 中一句话概括:OS specific APIs for Deno。它通过 deno_core::extension! 宏声明为一组 op 的集合,注册入口在 lib.rs:
- op 清单:
op_env、op_exec_path、op_exit、op_delete_env、op_get_env、op_gid、op_hostname、op_loadavg、op_network_interfaces、op_os_release、op_os_uptime、op_set_env、op_set_exit_code、op_system_memory_info、op_uid,以及信号三件套op_signal_bind/op_signal_poll/op_signal_unbind(实现在 ops/signal.rs); - JS 侧:
lazy_loaded_js = ["30_os.js", "40_signals.js"],即两段脚本按需加载,分别承载Deno系统信息/环境/退出相关方法与信号监听; - 依赖:核心依赖为
deno_core、deno_permissions(权限检查)、deno_signals(信号注册)、libc(Unix 系统调用),Windows 目标额外引入windows-sys,启用了Wdk_System_SystemServices(RtlGetVersion)、Win32_Networking_WinSock(GetHostNameW)、Win32_System_ProcessStatus(内存信息)等 feature,这些 feature 与后文各平台实现一一对应。
这种"Rust 实现 op + JS 薄封装 + 权限容器"的分层,是 Deno 所有扩展的标准形态;下文以 README 点名的五个 API 为主线逐一对比各平台的真实实现。
2. loadavg:负载平均值的三种取法
README 给出的平台对照表如下(完全继承原文档):
| 目标平台族 | 使用的系统调用 | 说明 |
|---|---|---|
| Linux | sysinfo |
- |
| Windows | - | 返回 DEFAULT_LOADAVG,Windows 上不存在 loadavg 概念 |
| macOS、BSD | getloadavg |
参见 FreeBSD 的 getloadavg 手册 |
源码印证在 sys_info.rs 的 loadavg() 中,三条编译分支与表格严格对应:
- Linux/Android:
libc::sysinfo()成功后,把info.loads[0..3]分别除以1 << SI_LOAD_SHIFT还原为浮点负载值(内核结构体里是以移位整数存储的); - Apple 与 FreeBSD/OpenBSD:
libc::getloadavg(&mut l, 3),返回值小于 3 时回退DEFAULT_LOADAVG; - Windows:直接返回
DEFAULT_LOADAVG = (0.0, 0.0, 0.0),不做任何系统调用。
op 层包装见 lib.rs:op_loadavg 先通过 PermissionsContainer::check_sys("loadavg", "Deno.loadavg()") 做权限检查,再调用 sys_info::loadavg(),最终由 30_os.js 的 loadavg() 直接透传,暴露为 Deno.loadavg(),返回长度为 3 的数组(1 分钟、5 分钟、15 分钟负载)。
值得注意的边界行为:任何系统调用失败都会静默回退为 (0,0,0) 而非抛错,这使得在受限环境(如部分容器)中 API 仍然"可用",但取到的可能是兜底值。
3. os_release:内核/系统版本的四个分支
README 对照表(继承原文档):
| 目标平台族 | 使用的系统调用 | 说明 |
|---|---|---|
| Linux | 读取 /proc/sys/kernel/osrelease |
- |
| Windows | RtlGetVersion |
返回 dwMajorVersion.dwMinorVersion.dwBuildNumber |
| macOS | sysctl([CTL_KERN, KERN_OSRELEASE]) |
- |
实际实现在 sys_info.rs 的 os_release(),比表格还多一个 Android 分支:
- Linux:
std::fs::read_to_string("/proc/sys/kernel/osrelease"),成功则pop()掉末尾换行,失败返回空串; - Android(表格未单列,源码存在该分支):
libc::uname()取utsname.release; - macOS/BSD:
sysctl([CTL_KERN, KERN_OSRELEASE]),固定 256 字节缓冲(注释说明"256 is enough"),失败返回字符串"Unknown"; - Windows:调用 WDK 的
RtlGetVersion填充OSVERSIONINFOEXW(先初始化dwOSVersionInfoSize字段),失败返回空串,成功则格式化为主版本.次版本.构建号。
op 入口 op_os_release(lib.rs)对应的权限项是 osRelease。JS 侧 30_os.js 将其暴露为 Deno.osRelease()。
4. hostname:缓冲区大小如何决定
README 对照表(继承原文档):
| 目标平台族 | 使用的系统调用 | 说明 |
|---|---|---|
| Unix | gethostname(sysconf(_SC_HOST_NAME_MAX)) |
- |
| Windows | GetHostNameW |
- |
sys_info.rs 的 hostname() 展示了两种截然不同的缓冲区策略:
- Unix:先
libc::sysconf(libc::_SC_HOST_NAME_MAX)向系统询问主机名的最大长度,按buf_size + 1分配缓冲区再调gethostname,最后强制 NUL 终止并做 lossy 转码——即"先问系统上限,再精确分配"; - Windows:固定 256 个宽字符(
WCHAR)缓冲区调用GetHostNameW。更隐蔽的一点是:调用前必须通过WINSOCKET_INIT: Once先执行一次WSAStartup(0x0202, ...)(MAKEWORD(2,2)),否则GetHostNameW行为不正确;启动失败会直接panic!。
op 入口 op_hostname(lib.rs)对应 Deno.hostname(),权限项为 hostname。
5. mem_info:系统内存信息的 MemInfo 结构
Deno.systemMemoryInfo() 返回的结构在 sys_info.rs 定义为 7 个 u64 字段:total、free、available、buffers、cached、swap_total、swap_free,通过 ToV8 派生直接转为 JS 对象。
README 对照表(继承原文档):
| 目标平台族 | 使用的系统调用 | 说明 |
|---|---|---|
| Linux | sysinfo 和 /proc/meminfo |
- |
| Windows | sysinfoapi::GlobalMemoryStatusEx |
- |
| macOS | sysctl([CTL_HW, HW_MEMSIZE]);sysctl([CTL_VM, VM_SWAPUSAGE]);host_statistics64(mach_host_self(), HOST_VM_INFO64) |
- |
各分支实现(sys_info.rs 的 mem_info())细节:
- Linux:
libc::sysinfo()提供 totalram/freeram/bufferram/totalswap/freeswap,并且所有值要乘以info.mem_unit(较新内核中字段单位是mem_unit而非字节);available不能从sysinfo得到,需要额外读取/proc/meminfo的MemAvailable:行(单位 kB,乘 1024 转为字节)——这正是 README 里"sysinfo 和 /proc/meminfo"两个来源的原因; - macOS:物理内存来自
sysctl([CTL_HW, HW_MEMSIZE]);swap 总量/空闲来自sysctl([CTL_VM, VM_SWAPUSAGE])的xsw_usage;available与free由 Mach 的host_statistics64(HOST_VM_INFO64)结合页大小计算,其中available = (free_count + inactive_count) × pagesize、free = (free_count - speculative_count) × pagesize; - Windows:
GlobalMemoryStatusEx只给出物理内存(ullTotalPhys/ullAvailPhys);swap 相关字段因MEMORYSTATUSEX.ullTotalPageFile不可靠,源码转而调用GetPerformanceInfo,用PageSize × (CommitLimit - PhysicalTotal)计算swap_total、再减去PhysicalAvailable得到swap_free。
op 入口 op_system_memory_info(lib.rs)对应权限项 systemMemoryInfo,返回 Option<MemInfo>(成功时为 Some)。
6. cpu_usage:进程 CPU 时间的两个来源
README 对照表(继承原文档):
| 目标平台族 | 使用的系统调用 | 说明 |
|---|---|---|
| Linux | getrusage |
- |
| Windows | processthreadsapi::GetProcessTimes |
- |
| macOS | getrusage |
- |
这段描述对应的是运行时(进程级) CPU 用量,实现在 lib.rs 的 op_runtime_cpu_usage + get_cpu_usage():
- Unix(含 Linux/macOS):
libc::getrusage(libc::RUSAGE_SELF, ...),把ru_stime、ru_utime的秒/微秒分别合成Duration; - Windows:
GetProcessTimes(GetCurrentProcess(), ...)取 kernel/userFILETIME,再经FileTimeToSystemTime转换,且对两个转换结果做了四象限容错(任一失败则该侧回零); - 其他平台(源码中的兜底分支)返回默认零值。
op 以 #[buffer] out: &mut [f64] 的形式把 [sys, user] 两个微秒值写回 JS 侧缓冲区,避免了每次调用的对象分配。另有一个姊妹 op op_runtime_memory_usage(lib.rs)以同样方式写回 4 个值:RSS、堆总量、已用堆、外部内存,其中 RSS 按平台分别来自 /proc/self/statm(Linux)、task_info(macOS)、KERN_PROC_PID sysctl(OpenBSD)、GetProcessMemoryInfo(Windows)。
7. 从 op 到 Deno API:权限检查与 JS 封装
所有系统信息 op 都遵循同一模式:先查 sys 权限子项,再执行系统调用。以 op_loadavg 为例(lib.rs),第二个参数是用于报错提示的 API 名 "Deno.loadavg()"。源码中可见的全部 sys 权限子项为:loadavg、hostname、osRelease、networkInterfaces、systemMemoryInfo、gid、uid、osUptime,它们都通过 check_sys(子项, 提示) 检查,检查逻辑位于 runtime/permissions/lib.rs。因此用户可以以 --allow-sys=hostname,osRelease 这类粒度授权,而不是整包放开系统信息。
JS 侧 30_os.js 只是对每个 op 的一层薄封装(loadavg()、hostname()、osRelease()、osUptime()、systemMemoryInfo()、networkInterfaces()、gid()、uid() 等),并在 lib.rs 的 lazy_loaded_js 中声明为懒加载脚本——只有真正触碰相关 API 时脚本才会被求值。
8. 同 crate 的配套能力:env、exit 与信号
README 聚焦五个只读 API,但同一个 crate 还承载了进程环境与生命周期管理,简单补充:
Deno.env:op_env/op_get_env/op_set_env/op_delete_env(lib.rs)实现了 key 校验(空 key、含=或 NUL 的 key、含 NUL 的 value 均抛TypeError类错误,错误变体见OsError)、权限检查,以及一个静态互斥锁ProcessEnvGuard协调运行时对进程环境的访问;修改TZ时会顺带调用tzset/_tzset并通知 V8 重新检测时区。tests/unit/os_test.ts 中的avoidEmptyNamedEnv、envToObjectKeysAreValid用例逐条验证了这些规则;Deno.exit:JS 侧先op_set_exit_code,再派发unload事件,最后op_exit()走 Rust 的exit(code)(lib.rs);在--watch模式下op_exit会改为终止当前 isolate 而非杀进程(WatcherExitHandle);- 信号监听:40_signals.js 以
addSignalListener/removeSignalListener管理监听集合,首个监听者注册时才通过op_signal_bind创建信号资源,最后一个移除时op_signal_unbind解绑;Rust 侧 ops/signal.rs 用 tokiowatchchannel 把信号回调投递给异步的op_signal_poll,资源关闭时调用deno_signals::unregister。
9. 验证方式
- 行为测试集中在 tests/unit/os_test.ts,覆盖 env 的成功/缺省/删除/权限拒绝、非法 key/value 抛错、
toObject()与get()的一致性(含 Windows 下=C:这类隐藏变量的回归测试)以及系统信息 API 的调用; - Rust 侧自带单元测试:lib.rs 用多线程 +
mpsc验证了ProcessEnvGuard在时区通知回调期间全程持有PROCESS_ENV_LOCK,lib.rs 则保证 Node 环境变量白名单数组有序(因为它依赖二分查找跳过权限检查); - 权限矩阵测试可参考 tests/unit/permissions_test.ts 中对
--allow-sys相关场景的断言。
小结
deno_os 是 Deno 中"把 OS 特定行为收敛到单一 crate"的样板:README 的五张平台×系统调用对照表(loadavg / os_release / hostname / mem_info / cpu_usage)在 sys_info.rs 与 lib.rs 中都有精确到 cfg 分支的对应实现,且每个 API 都统一挂接 sys 权限子项检查与懒加载 JS 封装。理解这条"JS API → op → 平台系统调用"的链路后,扩展或排查 Deno 的系统信息类 API 时,可以直接按权限子项定位到对应的 op_* 函数与 sys_info 模块。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00