• 问题: 文件删掉了,只要还有进程开着它,空间就一直被占着。Finder、储存空间面板和 du 都看不到。
  • 我们发现: 一个叫 chronod 的后台进程攥着 174 份已删除的系统数据库副本。重启它,不到三秒就拿回了 2.03 GB。
  • 我们做了什么: Mint 1.0.82 把这类文件列在 Safe to clean 里,只算结束这个 macOS 后台进程后应该拿回的部分。这一行永远不会替你勾上。

删了文件,Mac 的可用空间却没涨,可能是有正在运行的进程还开着它们。macOS 会一直保留已删除文件的数据,直到最后一个占着它的进程放手;按文件夹统计的工具都看不到这部分空间,访达、“储存空间”和 du 都一样。我们的一台 Mac 上,一个叫 chronod 的后台进程占着一份系统数据库的 174 个已删除副本。重启它之后,不到三秒就回来了 2.03 GB。

三天前我们第一次量这件事时,结论是错的。这个错在哪台 Mac 上都容易犯,所以错在哪里,下面也会写。

删除文件,删掉的是名字

删除一个文件时,macOS 把它的名字从文件夹里拿掉。如果还有进程开着这个文件,或者把它映射进了内存,数据就一直留在磁盘上,直到那个进程放手。这部分空间仍然算“已用”,却不在任何文件夹里,所以把文件夹加起来的工具都会少算。

lsof 能列出这些文件。它的 +L1 选项只显示已经没有名字、但还开着的文件。下面这条命令按进程汇总,从大到小列出每个进程占着多少:

lsof +c 0 -nP +L1 |
  awk 'NR>1 && $5=="REG" && !seen[$9 FS $1]++ {s[$1]+=$7}
       END {for (p in s) if (s[p]>=1e7) printf "%8.2f GB  %s\n", s[p]/1e9, p}' |
  sort -rn

它只读,不用输密码。不加 sudo 只能看到以你的身份运行的进程,加上才能看到以 root 运行的那些。显示的大小是文件长度;两个进程同时开着的文件,会在两个进程下各算一次,所以结束一个进程拿回的空间可能比它那一行少。后面会讲到。

一台 Mac 上占着多少

10 月 4 日太平洋时间 02:22,这台 Mac 上次开机将近五天,lsof 列出 324 个已删除但还开着的文件,按长度算 2.88 GB。大部分在一个进程手里:

  • chronod:172 个,2.24 GB
  • linkd:17 个,0.21 GB
  • 一个 Insta360 摄像头扩展:2 个,0.07 GB
  • fileproviderd:6 个,0.06 GB

chronod 的文件除了一个,全是 LaunchServices 数据库的旧副本。这个数据库记录了 Mac 上有哪些应用、每个应用打开哪些文件和链接。每份副本约 13 MB。chronod 是负责更新小组件的系统进程,这时它从 10 月 2 日 15:50 起已经连续运行了 34.5 小时。

重启 chronod

macOS 的后台进程由 launchd 启动,chronod 被结束后,有需要时 launchd 会再把它拉起来。所以我们结束了它,同时盯着可用空间:

02:26  chronod 占着 174 个已删除副本,按长度 2.276 GB
       可用空间,10 秒内读 6 次:   39.834 至 39.879 GB
       给 chronod 发 SIGTERM
+2.5 秒 可用空间:                  41.910 GB
+30 秒  可用空间,读 12 次:         41.899 至 41.912 GB

2.5 秒内可用空间涨了 2.03 GB,之后一直稳定。不到一分钟,新的 chronod 已经在运行。

2.03 GB 是 lsof 所列 2.28 GB 的 89%。差的那部分是共享造成的:chronod 的副本里有 20 个、共 0.25 GB,同时被别的进程开着,要等最后一个持有者放手才会释放。2.28 减 0.25 正好是实测的 2.03。所以长度没有虚报;重启一个进程能拿回的,是只有它一个在占的那些文件的长度。

我们第一次量错了

10 月 1 日,Spotlight 索引正把同一台 Mac 的可用空间吃光(那件事写在这里),我们第一次注意到 chronod 攒下的这堆副本。当时 chronod 从 9 月 29 日开机起一直在运行,占着 827 个已删除副本,按长度 5.49 GB;13 分钟后变成 836 个。

接着在一分钟之内发生了三件事:关掉 Spotlight 索引,抹掉索引,重启 chronod。可用空间的读数(太平洋时间):

12:46:22  可用 57 GiB   (关掉索引之后)
12:46:40  新的 chronod 启动
12:46:43  可用 61 GiB   (索引已抹掉,chronod 已重启)
12:46:57  可用 61 GiB

那天下午的记录把这 4 GiB 算在了抹掉索引头上。判断 chronod 时,我们比较的是 12:46:43 和 12:46:57 两个读数,可它们都在 chronod 重启之后,于是看起来 chronod 几乎什么都没还。我们当时写下:这些副本大概彼此共享磁盘块,lsof 高估了空间。

10 月 4 日的测试只改一样东西,前后都有读数,结论正好相反。如果 10 月 1 日 chronod 也能拿回约 89%,它那 5.49 GB 应该回来约 4.9 GB,那次 4 GiB 的跳升(约 4.3 GB,读数取整到 GiB)对得上。我们现在认为那次跳升大部分来自 chronod,并已更正 Spotlight 那篇:关掉索引至少拿回了 44 GiB,约 47 GB,而不是我们最初写的 51 GB。

能避开这个错的两个习惯很普通,也很容易省掉:两次读数之间只改一样东西;进程是什么时候重启的,用 ps -o lstart= -p <pid> 查,别按自己以为的时间算。

chronod 为什么留着旧副本

LaunchServices 更新数据库的方式,是写一个新文件,再删掉旧的。已经映射了旧文件的进程,在映射新文件之前会一直留着旧的。chronod 在开机后的 51 小时里攒了 827 个副本,约每小时 16 个;之后的 34.5 小时里攒了 174 个,约每小时 5 个。别的进程留着的副本少得多,所以这看起来是 chronod 自己的问题,不是每个读这个数据库的进程都会这样。

我们猜是编译应用推高了速度:每次编译都会把新应用注册到 LaunchServices,而这台 Mac 一天要编译很多次。一个快速测试没能得出结论:两分钟内注册、注销一个应用各三次,多了两个副本;什么都不做的 30 秒里,也多了一个。编译也许会让速度变快,但不是唯一的原因。chronod 为什么放任旧副本越积越多,我们还不知道。

你可以怎么做

  • 跑一下上面的检查命令。如果某个 macOS 后台进程占得太多,就重启它,比如 killall chronod,launchd 会再把它拉起来。从 10 月 1 日起,我们在这台 Mac 上结束过三次 chronod(包括上面这次测试),没发现有东西坏掉,小组件会重新绘制。
  • 如果占着文件的是某个应用,退出那个应用就行。10 月 1 日,Spotify 占着 281 个已删除的缓存文件。
  • 重启 Mac 会释放全部。
  • 不用去访达里找这些文件,它们已经没有名字了。

Mint 怎么处理

Mint 1.0.82(10 月 2 日)会在 Safe to clean 里列出进程还占着的已删除文件。它只统计没有别的进程占着的文件,所以那一行的数字就是结束这个进程应该能拿回的量。它只为 launchd 会重新拉起的 macOS 后台进程提供这一项;应用占着的文件放在 Keep 里,并提示退出那个应用。这一行不会替你勾选,Auto Care 也从不处理它。

常见问题

这部分空间在“储存空间”里显示在哪儿? 它不在任何文件夹里,没法归到哪个类别。我们推测它算进了“系统数据”,也就是储存空间面板拆不开的那一块,但还没有拿面板本身核实过。

结束 chronod 安全吗? launchd 会在一分钟内把它重新拉起来。我们在一台 Mac 上做过三次,没看到问题。样本很小;如果小组件对你的工作很重要,重启 Mac 更稳妥。

它还会再攒起来吗? 会。每次重启之后,chronod 马上又开始攒副本。