【Mac】使用 launchd 管理系统后台服务

在 Mac 上安装了 Adobe Photoshop 2021 后,我发现一个代表 Adobe Creative Cloud 服务的图标,出现在 Mac 系统右上角的菜单栏(Menu Bar)上。如果点击这个图标,就会弹出一个窗口,展示 Adobe 的登录表单。这个图标很容易误点,在图标上点击右键,菜单里也没有“退出”选项。打开“系统偏好设置”,“用户与群组”,在“登录项”选项卡上,也没有看到 Adobe 注册什么登录项。到底怎么才能退出这个恼人的程序呢?唯有了解了 launchd 这个系统服务管理机制,才能做到。

Image result for adobe creative cloud icon

Adobe Creative Cloud 的图标

基本概念

launchd 是 MacOS 上用于管理系统级或者用户级后台服务进程的管理工具。也是官方推荐的系统后台进程管理工具,就好像在 Linux 系统里,我们使用 systemd 去管理后台服务进程一样。

launchd 是一个程序,以系统常驻进程的形态运转,是 MacOS 系统启动后的第一个进程,在 Terminal 终端,键入命令 ps aux 可以看到,launchd 的进程ID(PID)是 1。也即这是系统的第一个进程

launchd 交互的工具,叫 launchctl。可以认为是它的管理客户端程序。通过该命令,我们可以发送指令给 launchd 完成对系统服务或后台进程的管理。

launchd 的管理对象都是后台进程,这些后台进程使用一种特定格式的配置文件叫 launchd.plist 来描述被管理的对象。这种文件是 XML 格式的,根据不同的运行权限,放在不同的目录里面,请看下面的表格。

目录说明
~/Library/LaunchAgents用户自己提供的用户级 Agent。
/Library/LaunchAgents管理员提供的用户级 Agent。
/Library/LaunchDaemons管理员提供的系统级 Daemon。
/System/Library/LaunchAgents苹果官方提供的用户级 Agent。
/System/Library/LaunchDaemons苹果官方提供的系统级 Daemon。

以上是存放 launchd 配置文件的常用目录

通过 launchd 管理的进程,人为被分为了几个种类:

  • 服务(Services) —— 在后台运行,用以支持图形界面应用(GUI App)运行的服务进程,比如响应系统全局快捷键,或者进行网络通信等;
  • 守护进程(Daemons) —— 理论上,不属于服务的后台进程,都归为守护进程一类,不过这里特指运行在后台,且不能与用户交互图形界面产生联系的进程;
  • 代理(Agents) —— 以用户的名义,在后台运行的进程,可以和用户图形界面产生联系,比如呼起一个软件的界面,不过官方不推荐这么用。

基本操作

通过对基本概念的理解,我们已经知道了这个系统工具的原理。从这个原理层面来理解,Adobe 公司这样设计自己的软件是不道德的。它使用权限很高的系统后台服务进程,只是为了拉起一个常驻在系统菜单栏的小图标,而这个小图标也不提供几乎任何有效的功能,只是一个登录入口,有点大炮打蚊子的感觉。关键是,这个小图标很容易误点,一旦点到就会弹出一个巨大的登录表单,骚扰了用户,对于不懂技术的用户来说,还无法简单退出。

1
2
3
4
5
6
7
8
9
10
11
12
# 罗列系统当前运行的进程清单
launchctl list

# 查看特定服务的配置信息
launchctl list com.adobe.AdobeCreativeCloud

# 加载特定的服务配置
launchctl load <file_path>

# 卸载特定的服务配置
launchctl unload -w /Library/LaunchAgents/com.adobe.AdobeCreativeCloud.plist
# 特此说明,-w 参数的作用是,如果自动执行了 load 命令尝试去恢复服务注册,则让其无效

使用命令launchctl list 可以罗列出,系统当前在运行的服务清单,赫然可以看到 com.adobe.AdobeCreativeCloud 在列,正在运行,进程 ID 513。这个 com.adobe.AdobeCreativeCloudlaunchd 系列里的概念叫 Label。

使用命令 launchctl list com.adobe.AdobeCreativeCloud 可以查看这个服务的一些配置信息,可以里面有这个服务启动的程序所在的目录,启动参数等。

如果我想把一个服务注册到系统服务里,使用命令 launchctl load 就可以做到,当然,还需要编写一个配置文件,在 Mac 系统里,是一种叫 plist 的格式(满足 XML 规范)。

如果我想要把一个服务注销掉,使用命令 launchctl unload <plist_path> 就可以做到。在上面,我们通过 launchctl list 命令找到了 com.adobe.AdobeCreativeCloud 这个 Label,怎么才能找到其配置文件呢?使用 locate com.adobe.AdobeCreativeCloud 命令,这是 *nix 系列系统的常见命令,相当于搜索。当然,首次使用的时候,还有一些麻烦,就是要建立索引,不过,Mac 系统的 Terminal 执行此命令,一般会提示如何建立索引的。照着执行便可。个人觉得比 Spotlight 要好用一点。

1
launchctl unload -w /Library/LaunchAgents/com.adobe.AdobeCreativeCloud.plist

找到那个 com.adobe.AdobeCreativeCloud 所在的 plist 配置文件的路径后,我们就可以用 launchctl unload <plist_path> 来卸载这个服务了。以后也不会开机自动启动了。注意,上面的例子里用了 -w 参数,如果不用此参数的话,你会发现一旦系统重启,那个恼人的小图标就又会出现。因为另有别的服务进程,或者应用程序,重新注册了这个系统服务。而使用 -w 参数,就是为了杜绝此种情况,使 unload 命令被持久化,让我们意图注销的“流氓”服务无法被重新加载。

自己创建一个

同理,自己也可以利用 launchd 来创建守护进程,这里我给出一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.sexywp.shadowsocks</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/sslocal</string>
<string>-c</string>
<string>/Users/charles/.shadowsocks/config.json</string>
</array>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>UserName</key>
<string>charles</string>
<key>GroupName</key>
<string>staff</string>
</dict>
</plist>

将个文件放到 /Library/LaunchAgents 目录,然后用 launchctl load 命令就可以加入到守护的列表里,系统发现没有这个进程,就会自动启动一个。可以代替 cronjob 或者 supervisord 等的功能。