Skip to Main Content
Mac Chrome ReOpenBack to Top

Mac Chrome ReOpen

14 minutes

问题描述:

Chrome 用 Command+Q 强制关闭程序后,程序在 Dock 栏自己弹了弹又重新弹起来了。

每次都要强制关闭两次以上才能真正把 Chrome 关掉。

Mac 本身的日志功能要多使用,有时能够反应出一定的问题

查看系统的终端报错日志(揪出幕后黑手)

log stream --predicate 'eventMessage CONTAINS[c] "launch"' | grep -i "Chrome"

运行后,终端会进入监听状态。此时你去对 Chrome 执行 Command + Q。 * 观察 Chrome 自动弹起的那一瞬间,终端里跳出来的日志。

2026-06-23 09:21:30.454905+0800 ... loginwindow: [com.apple.loginwindow.logging:TAL] -[PersistentAppsSupport appShouldBeRelaunched:] | entered. checking app: Google Chrome
2026-06-23 09:21:30.454921+0800 ... loginwindow: [com.apple.loginwindow.logging:TAL] -[PersistentAppsSupport saveLogoutPersistentState:finalSnapshot:] |            Adding to relaunchArray: Google Chrome

解释: loginwindow 是 macOS 的登录窗口及会话管理进程。这里的 TAL (Transparent App Lifecycle)PersistentAppsSupport 是 macOS 的“窗口恢复/持久化应用”机制。

翻译成白话: 在 09:21:30 时,macOS 的系统级进程 loginwindow 介入,检测了 Chrome 的状态。它认为 Chrome “应该被重新拉起(appShouldBeRelaunched)”,并直接把它塞进了重启队列(Adding to relaunchArray)里。

随后在 09:21:34 左右,系统底层的 duetexpertd(苹果的智能预测/行为建议服务)和 runningboardd 协同,再次把 Chrome 给捞了起来。

日志时间线(08:41:01) 时间 事件 含义 .637 checkAgainstApp: Google Chrome TAL 开始评估 Chrome 是否该被恢复 .638 appShouldBeRelaunched → Adding to relaunchArray 系统判定:Chrome 退出后应被重新拉起 .827 自动填充 (Google Chrome) state 3→4, quit Chrome 密码自动填充助手先退出 .855 appDeath for com.google.Chrome Chrome 主进程死亡 .855–.848 Google Chrome state 3→4, quit 主程序走完退出流程 .850(+6s) previouslyRunningApps 仍含 Chrome TAL 快照里 Chrome 未被清除 关键:relaunchArray 写入发生在 Chrome 真正退出之前(.638 vs .855)。说明 macOS 在退出流程早期就决定了要恢复 Chrome,不是事后误判。

两个直接原因

  1. TAL 持久列表里已有 Chrome 你的 ~/Library/Preferences/ByHost/com.apple.loginwindow.*.plist 中:

BundleID = “com.google.chrome” BackgroundState = 3 Path = “/Applications/Google Chrome.app” BackgroundState = 2:前台运行、有窗口 → 应恢复 BackgroundState = 3:后台/hidden 状态 → 仍会被记录

每次 CMD+Q,loginwindow 都会重新评估并再次写入 relaunchArray。

  1. NSQuitAlwaysKeepsWindows 未设置 defaults read com.google.Chrome NSQuitAlwaysKeepsWindows

→ key not found

未设置时,macOS 默认行为是 退出时保留窗口状态(Resume)。有窗口的 Chrome 退出 → appShouldBeRelaunched 返回 YES → 进 relaunchArray。

exit_type: Normal 已修复,但 TAL 仍会因「有窗口 + 保留状态」而恢复。

  1. 自动填充 (Google Chrome) 是什么? 这是 Chrome 的密码自动填充系统组件,在 loginwindow 里被当作独立 Application 跟踪。退出时它先于主进程退出,可能让 TAL 认为 Chrome 生态尚未完全结束,加剧恢复判定。

针对这份日志的修复(按顺序) 第一步:强制 Chrome 退出时不保留窗口 Chrome 必须正在运行时执行:

defaults write com.google.Chrome NSQuitAlwaysKeepsWindows -bool false 然后 按住 Option 再按 CMD+Q(或菜单:Chrome → 退出时按住 Option 变为「退出并保留窗口关闭」)。

第二步:从 TAL 持久列表移除 Chrome killall “Google Chrome” 2>/dev/null python3 << ‘PY’ import plistlib, os, subprocess, re uuid = re.search(r’“IOPlatformUUID” = “([^”]+)“’, subprocess.check_output([“ioreg”,“-rd1”,“-c”,“IOPlatformExpertDevice”], text=True)).group(1) path = os.path.expanduser(f“~/Library/Preferences/ByHost/com.apple.loginwindow.{uuid}.plist“) with open(path, “rb”) as f: d = plistlib.load(f) before = len(d.get(“TALAppsToRelaunchAtLogin”, [])) d[“TALAppsToRelaunchAtLogin”] = [ a for a in d.get(“TALAppsToRelaunchAtLogin”, []) if “chrome” not in a.get(“BundleID”, “”).lower() ] with open(path, “wb”) as f: plistlib.dump(d, f) print(f“Removed Chrome from TAL: {before} -> {len(d[‘TALAppsToRelaunchAtLogin’])} apps“) PY 第三步:关闭 Chrome 密码自动填充系统扩展 系统设置 → 密码 → 密码选项 → 自动填充 取消勾选 Google Chrome(或 Chrome 相关自动填充项)。

这会让 自动填充 (Google Chrome) 不再作为独立进程被 loginwindow 跟踪。

第四步:验证 log stream –predicate ‘process == “loginwindow” AND eventMessage CONTAINS[c] “Chrome”’ –style compact CMD+Q 后,不应再出现 Adding to relaunchArray: Google Chrome。

与之前分析的关系 项目 当前状态 Yandex Mail.app 已删除 ✓ exit_type 已是 Normal ✓ 系统后台项 已关闭 ✓ TAL 持久列表 仍有 Chrome ← 当前主因 NSQuitAlwaysKeepsWindows 未设置 ← 触发 appShouldBeRelaunched 自动填充助手 日志中可见,建议关闭 这份日志说明:不是 Chrome 崩溃重启,而是 macOS TAL 正常的窗口恢复机制在 CMD+Q 时主动标记 Chrome 为「应 relaunch」。根因是 TAL 列表 + 退出时保留窗口,而非后台项或 flags。

macOS 排查常用 CLI 速查

排查 Chrome 自动拉起时用到的主要是 sfltool、log、pgrep。下面按工具整理常见用法。

  1. sfltool — 后台项 / 登录项 注意:不是 stltool,正确命令是 sfltool(Shared File List tool)。 主要用于查看 Login Items 和 Allow in the Background(后台允许) 注册信息。

基本命令

sfltool list          # 列出 shared file list
sfltool list-info     # 详细信息
sfltool csinfo        # codesign 信息
sfltool resetbtm      # ⚠️ 重置全部 BTM,慎用

典型排查场景

  1. log — 统一日志系统(Unified Logging) macOS 10.12+ 的日志不再散落在文本文件里,主要靠 log stream(实时)和 log show(历史)。
实时监听 log stream
 基础:监听 loginwindow 所有日志
log stream --predicate 'process == "loginwindow"' --style compact
排查 Chrome TAL 拉起(我们用的)
log stream --predicate 'process == "loginwindow" AND eventMessage CONTAINS[c] "Chrome"' --style compact
更宽:任何含 launch 的日志,再 grep Chrome
log stream --predicate 'eventMessage CONTAINS[c] "launch"' | grep -i "Chrome"
监听 TAL 子系统
log stream --predicate 'subsystem == "com.apple.loginwindow.logging"' --style compact
多进程联合监听
log stream --predicate 'process == "loginwindow" OR process == "runningboardd" OR process == "duetexpertd"' \
  --style compact | grep -i chrome
历史查询 log show
最近 2 小时 loginwindow 里含 Chrome 的日志
log show --last 2h \
  --predicate 'process == "loginwindow" AND eventMessage CONTAINS[c] "Chrome"' \
  --style compact
最近 7 天:quit / relaunch / unexpectedly 相关
log show --last 7d \
  --predicate 'eventMessage CONTAINS "Google Chrome" AND (eventMessage CONTAINS "quit" OR eventMessage CONTAINS "relaunch" OR eventMessage CONTAINS "unexpectedly")' \
  --style compact
只看错误级别
log show --last 1h --predicate 'process == "loginwindow"' --level error --style compact

–predicate 语法要点 写法 说明 process == “loginwindow” 精确匹配进程名 eventMessage CONTAINS[c] “Chrome” 消息含 Chrome,[c] = 忽略大小写 subsystem == “com.apple.talagent” 按子系统过滤 messageType == error 只看 error AND / OR 组合条件 实用技巧 输出到文件,避免刷屏 log stream –predicate ‘process == “loginwindow” AND eventMessage CONTAINS[c] “Chrome”’
–style compact > ~/Desktop/chrome-quit.log

时间范围(历史)

log show –start “2026-06-25 08:40:00” –end “2026-06-25 08:42:00”
–predicate ‘process == “loginwindow”’ –style compact 我们抓到的关键日志模式 appShouldBeRelaunched: Google Chrome Adding to relaunchArray: Google Chrome applicationQuit: for app:Google Chrome appDeath for com.google.Chrome previouslyRunningApps: ( … com.google.chrome … ) 3. pgrep — 按名称查进程 比 ps aux | grep 更干净(不会 grep 到自己)。

基本用法

# 进程是否存在(静默,适合脚本判断)
pgrep -x "Google Chrome" && echo "Chrome 在运行"
# 列出 PID + 完整命令行(排查最有用)
pgrep -lf "Google Chrome"
# 不区分大小写
pgrep -lf -i 'chrome|google chrome'
# 只输出 PID
pgrep "Google Chrome"
常见组合
# CMD+Q 后确认是否还有残留
pgrep -lf "Google Chrome" || echo "已完全退出"
# 看 Chrome 各 Helper 进程
pgrep -lf "Chrome Helper"
# 统计 Chrome 相关进程数
pgrep -lf -i chrome | wc -l
# 配合 killall 做彻底退出
killall "Google Chrome" 2>/dev/null
sleep 1
pgrep -lf "Google Chrome"   # 应无输出

pgrep 常用选项 选项 含义 -l 显示进程名 -f 匹配完整命令行(而不只是进程名) -i 忽略大小写 -x 精确匹配进程名 -u $USER 只查当前用户的进程 4. 配套命令(同一套排查里常用) TAL 持久恢复列表

# 读取「注销/重启时重新打开的应用」列表
UUID=$(ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}')
defaults -currentHost read com.apple.loginwindow TALAppsToRelaunchAtLogin
# 或直接看 plist
plutil -p ~/Library/Preferences/ByHost/com.apple.loginwindow.${UUID}.plist | grep -A5 chrome
launchctl 启动项 / 服务
# 当前用户已加载的服务,筛 Chrome/Google
launchctl list | grep -i -E 'chrome|google'
# 查看 LaunchAgent 内容
plutil -p ~/Library/LaunchAgents/com.google.GoogleUpdater.wake.plist
defaults 应用偏好
# Chrome 是否保留退出时的窗口
defaults read com.google.Chrome NSQuitAlwaysKeepsWindows
# 强制关闭保留窗口(Chrome 运行中执行)
defaults write com.google.Chrome NSQuitAlwaysKeepsWindows -bool false
# 全局:退出时是否关闭窗口
defaults read NSGlobalDomain NSQuitAlwaysKeepsWindows
pluginkit 系统扩展 / 自动填充
# 查看 Chrome 相关系统插件(如自动填充)
pluginkit -m -A -v | grep -i -E 'chrome|google|autofill'
5. 一套完整排查流程(可复制)
# === 1. 静态状态 ===
sfltool dumpbtm | grep -i -A10 "Google Chrome"
defaults -currentHost read com.apple.loginwindow TALAppsToRelaunchAtLogin | grep -i chrome
pgrep -lf "Google Chrome"
# === 2. 开终端 A:实时日志 ===
log stream --predicate 'process == "loginwindow" AND eventMessage CONTAINS[c] "Chrome"' --style compact
# === 3. 对 Chrome 执行 CMD+Q ===
# === 4. 终端 B:确认进程 ===
pgrep -lf "Google Chrome" || echo "OK: 无残留"
# === 5. 历史补查 ===
log show --last 5m --predicate 'process == "loginwindow" AND eventMessage CONTAINS[c] "relaunch"' --style compact
  1. 常见坑 问题 说明 log stream 刷屏 必须加 –predicate,否则 I/O 日志量极大 log show 很慢 务必加 –last 1h 或 –start/–end 限定范围 pgrep 匹配过宽 -i chrome 会匹配到 Cursor 的 chrome_crashpad_handler,必要时加 |Google Chrome 精确匹配 sfltool 与系统设置不一致 UI 显示 Google Chrome.app,dumpbtm 可能还有独立的 background tasks 条目 defaults read 报 key not found 不等于 false,可能意味着「未设置、走系统默认」

系统日志 很好用

需求 1:macOS 系统日志功能(Unified Logging)使用要点整理 macOS 自 10.12 引进 统一日志系统 (Unified Logging System) 后,日志不再零散存储于各个文本文件中,而是由 diagnosticd 统一收集并二进制压缩存储。

  1. 核心命令工具:log 实时流监控 (log stream):

要点:它类似于传统 Linux 的 tail -f,但必须配合 –predicate(谓词过滤器)使用,否则海量的系统级 I/O 日志会瞬间刷屏,导致终端卡死。

历史日志检索 (log show):

要点:用于查看过去发生的事情。必须限定时间范围,否则检索极其缓慢。

示例:查看过去 10 分钟内关于 Chrome 的报错:

log show --last 10m --predicate 'eventMessage CONTAINS[c] "Chrome"' --level error
  1. 谓词过滤 (–predicate) 的高级语法 谓词基于 Apple 的 NSPredicate 架构,大小写敏感(除非加 [c] 表示忽略大小写):

process == “loginwindow”:精准匹配进程名。

processImageUUID CONTAINS[c] “Chrome”:匹配进程路径或动态库中包含 Chrome 的行为(对抓取 Helper 应用极有效)。

messageType == error 或 messageType == fault:只看错误或严重故障(fault)。

  1. 日志等级的隐蔽陷阱 macOS 日志分为五个等级:Default、Info、Debug、Error、Fault。

痛点:出于性能和隐私考虑,Info 和 Debug 等级的日志默认是不写入磁盘,也不在实时流中展示的。

解决办法:如果你在调自建脚本(如你的自定义自动化脚本)发现看不到详细日志,需要手动为该进程开启 Debug 级写入:

sudo log config --mode "level:debug" --subsystem com.your.app

需求 2:开源 macOS (Darwin xnu) 代码架构与设计思路 你拉取的仓库实际上是 Darwin(macOS 的开源核心),其内核名为 XNU(X is Not Unix)。XNU 是一个独特的混合内核 (Hybrid Kernel),它融合了 Mach 的灵活性和 BSD 的成熟稳定性。

  1. 核心代码架构(目录结构解密) 在官方开源的 xnu 仓库中,核心架构主要由以下四大板块交织而成:
xnu/
├── mach/               # Mach 微内核层(核心调度与 IPC)
│   ├── kern/           # 线程调度、时钟、任务管理
│   └── vm/             # 虚拟内存管理 (VM Object, Pager)
├── bsd/                # BSD 层(提供 POSIX 标准与上层抽象)
│   ├── kern/           # 进程管理 (PID)、credentials、sysent
│   ├── vfs/            # 虚拟文件系统切换 (Virtual File System)
│   └── netinet/        # TCP/IP 网络协议栈
├── iokit/              # I/O Kit(C++ 驱动框架,负责硬件交互)
│   └── Drivers/        # 磁盘、网络、USB 等基础驱动抽象
├── libkern/            # 内核基础库(C++ 运行时支持、容器类)
└── pexpert/            # 平台专家层(处理不同硬件架构如 ARM64/Intel 的引导初始化)
  1. 核心设计思路 ⚙️ 思路一:微内核与宏内核的“双管齐下”(混合内核) Mach(微内核):只负责最底层、最极致的单一任务——进程间通信 (IPC/Mach Ports)、进程隔离、底层线程调度。Mach 认为万物皆为 “Port”(端口)。

BSD(宏内核):由于微内核频繁进行上下文切换(Context Switch)会导致性能低下,苹果在内核空间里直接嵌套了一个改版的 BSD。BSD 把 Mach 的底层概念包装成 POSIX 标准。

逻辑关系:系统调用进来,上层看到的是 BSD 的 PID(进程 ID),但 BSD 底层其实映射的是 Mach 的 Task。

🔒 思路二:严格的权限与资源断言机制(RunningBoard 的由来) 为什么你会在前面的日志里频繁看到 runningboardd、ttl 和 assertion(断言)?

在 XNU 的设计进化中(特别是引入 iOS 和 Apple Silicon 移动架构后),内核极其看重功耗与生命周期管理。

XNU 引入了 Assertion(资源断言) 机制。一个应用(如 Chrome)在前台时,runningboardd 会向内核申请一个 frontmost 强断言,内核据此分配高优先级 CPU 和能效核心(E-cores/P-cores)。当应用转入后台或准备退出时,断言被 Invalidating(作废),内核会瞬间限制或冻结其 Helper 进程,这也是导致多页面 Chrome 回收假死而被系统强制拉起的底层诱因。

💻 思路三:面向对象的驱动安全(I/O Kit & DriverKit) 传统的 Unix 驱动是用 C 语言写的,一死全死。XNU 独辟蹊径,在内核中实现了一个受限的 C++ 运行时 (libkern)。

设计目的:利用 C++ 的面向对象特性(如依托 OSMetaClass 管理生命周期),让驱动程序对象化。如今苹果正逐步将 I/O Kit 迁移到用户空间(DriverKit),让绝大多数驱动在内核外运行,即使崩溃也不会导致 Mac 蓝屏/紫屏。

通过结合这两点,你会发现:你用 log stream 抓到的每一行状态,其实都是 XNU 内核中 Mach Task、BSD 进程状态以及 RunningBoard 断言在系统层面的具象化表现。