复刻tmux的魔法Muxy的muxy-session守护进程如何实现持久化终端会话【免费下载链接】muxyLightweight and Memory efficient terminal for Mac built with SwiftUI and libghostty项目地址: https://gitcode.com/gh_mirrors/muxy1/muxyMuxy是一款用 SwiftUI 和 libghostty 构建的轻量 Mac 终端它的持久化终端会话能力由隐藏的muxy-session守护进程实现哪怕你直接关闭窗口甚至退出应用正在跑的编译、测试任务都不会中断重新打开 Muxy 即可续接原会话。这篇文章将拆解这套机制的完整原理。一、为什么终端会话需要活下来 用过 tmux 的人都有同感会话被托管在后台守护进程里窗口只是观察者所以关掉终端会话依然存活。Muxy 把同样的魔法搬进了原生应用但实现完全不同——没有外部依赖只有一个静态二进制muxy-session和一个 Unix 域套接字。它解决的真实痛点是编译到 90% 时不小心按了CmdQ一切前功尽弃想看一眼手机合上笔记本盖子回到工位发现任务已被杀掉想换一个窗口尺寸重开标签页又不想丢失当前运行状态。二、整体架构一个二进制两种身份muxy-session的入口在 main.swift它只做一件事——根据子命令分流子命令身份职责daemon --socket path守护进程监听本地 socket托管所有 PTY 会话attach前台客户端接管真实终端把按键/画面与守护进程双向桥接应用侧的接线员是 PersistentSessionService.swift开启持久化后Muxy 不再直接启动 shell而是启动muxy-session attach并通过环境变量注入会话身份MUXY_SESSION_ID、MUXY_SESSION_SOCKET、MUXY_SESSION_CWD等。于是真实的 shell 进程跑在守护进程名下应用窗口退化为一个显示器。守护进程与 attach 客户端之间的通信采用极简的二进制帧协议每种帧类型定义在 SessionFrame.swift 中attach / attached握手声明我要接管哪个会话input / output按键流与屏幕输出的双向搬运resize窗口尺寸同步SIGWINCH信号的转发kill / killAll / list / info会话管理与查询。三、守护进程的诞生与无人自动退场 ⚙️muxy-session的守护进程遵循惰性启动谁需要谁拉起。attach 客户端连接 socket 失败时会立刻用posix_spawn以POSIX_SPAWN_SETSID标志在后台生成守护进程见 SessionAttachClient.swift 中的launchDaemon随后进入重试连接循环直到握手成功。防止两个守护进程抢一个 socket的是一枚文件锁SessionSocket.swift 中的acquireLock用flock(LOCK_EX | LOCK_NB)锁定control.sock.lock抢不到锁的新进程会安静退出——这就是为什么守护进程全局唯一却无需任何注册中心。守护进程主循环 SessionDaemon.swift 是一个教科书级的poll事件循环同时盯着三类文件描述符监听 socket新客户端接入SIGCHLD 信号管道子进程退出通知各会话 PTY master 各客户端连接数据双向流动。最妙的是空闲自毁当会话和连接都为空时poll超时设为 10 秒idleTimeoutMilliseconds超时即shutdown()——清理所有描述符、删除 socket 与锁文件后退出。用户机器上不会留下任何僵尸服务。四、PTY让 shell 真正断线不断命的核心守护进程用forkpty在 SessionPTY.swift 中孵化每个会话父进程持有 PTY master子进程execve进入用户 shell。关键点在于子进程先把SIGPIPE / SIGHUP / SIGINT等信号全部复位为默认处理——这样它表现得像一个真实终端里运行的普通进程CtrlC、挂起等行为全部符合直觉。清理过程同样讲究采用三级递进策略先向整个进程组发SIGHUP模拟终端被拔出给进程体面退出的机会20 轮轮询后仍存活升级SIGTERM再坚持 50 轮直接SIGKILL。而且它通过sysctl枚举整个进程组的成员逐个通知避免vim的子进程、python的 fork 后代变成无主孤儿。五、重连看到历史256KB 环形回放缓冲 ️重新 attach 时凭什么能看到离开前的画面答案是 SessionReplayBuffer.swift 中的环形缓冲区守护进程为每个会话维护 256KB 滚动输出replayCapacity新客户端接入时按 32KB 分片重放随后再向 shell 进程组补发一次SIGWINCH请求全屏重绘requestRedraw。这块代码比想象中精细得多备用屏幕感知识别 ANSI 转义序列检测你当时是否在vim/htop的备用屏幕上——若是重放时返回空缓冲交给程序自己重绘避免花屏UTF-8 边界保护回放裁剪点永远不会把一个汉字或 emoji 从中间切开残缺转义序列清理头部若残留半个 CSI/OSC 序列会被安全丢弃。六、安全细节只有你自己能连进来 socket 是本地文件理论上同机其他用户也能尝试连接。Muxy 做了四层防护路径解析优先~/Library/Application Support/Muxy/sessions/control.sock超长时回退/tmp/muxy-uid/且回退目录必须属主校验通过见 PersistentSessionPaths.swift权限收紧umask(0o077) socketchmod 0o600锁文件同样 0600身份核验每个新连接用LOCAL_PEERCRED取出对端 UID与守护进程自身 UID 不符直接拒绝协议版本attach 请求携带版本号跨版本混用会明确报错而非静默错乱。会话的窗口尺寸策略则由 SessionWindowSizePolicy.swift 统一裁决保证0x0这类非法尺寸不会把 PTY 撑坏。七、如何在 Muxy 中体验持久化会话 打开设置 → 终端启用持久化会话偏好定义见 TerminalPersistentSessionPreferences.swift在任意项目里跑一个长任务比如make build直接关闭窗口或CmdQ退出 Muxy——任务在后台守护进程中继续跑重新打开 MuxyPersistentSessionRecovery.swift 会探测遗留的 socket 与control.sock把标签页、工作目录、甚至当时运行的命令一起恢复回来。如果想亲手验证可以在会话运行时于muxy-session日志旁观察 socket 文件或在另一个终端里手动执行守护进程二进制查看daemon / attach两种用法。总结小而美的系统设计muxy-session用不到几百个源文件实现了 tmux 最核心的持久化能力设计取舍非常清晰无依赖纯 POSIX API一个静态二进制塞进应用 Bundle自管理惰性启动、文件锁单例、空闲自毁不留后台负担体验优先环形回放 备用屏感知让重连几乎无缝安全默认本地 socket 的每一条缝隙都有对应防线。对开发者而言MuxySession/ 与 MuxySessionProtocol/ 两个目录本身就是一份关于 Unix 域 socket、PTY 与poll事件循环的优质教学材料——比读 tmux 源码友好得多。【免费下载链接】muxyLightweight and Memory efficient terminal for Mac built with SwiftUI and libghostty项目地址: https://gitcode.com/gh_mirrors/muxy1/muxy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考