launchd 是什么?
launchd
是 macOS
下一个服务管理工具,用于启动、停止和管理守护进程、应用程序、进程和脚本。
我们可以将 launchd
看作是 mac 下的 systemd
或者是 supervisor
,如果我们想要在 mac 下启动守护进程,用
launchd
就可以了。
守护进程是在后台运行的不需要用户输入的程序。比如我们常用的
MySQL,往往是以守护进程的方式启动的。 需要注意的是:虽然本文多次提到了
守护进程,但是准确来说,launchd
可以启动的不仅仅是守护进程,还可以启动应用程序、进程和脚本。
如何写 launchd 配置文件?
launchd
的配置文件是通过一个 plist
文件来定义的(plist
是 property list
的缩写),一个典型的格式如下:
1 |
|
说明:
- 配置文件中,除了
dict
里面的那一部分,其他的都是固定的,不需要修改。 - 三个字段说明:
Label
:也就是服务的名字,可以随便取,但是不能重复。我们通过launchctl list
来查看的时候,列出的就是这个名字。上述例子是com.example.app
。Program
:要启动的程序的路径,需要填写绝对路径。上述例子是/Users/Me/Scripts/cleanup.sh
。如果需要加参数的话,需要使用ProgramArguments
来代替Program
,详情参考下文。RunAtLoad
:是否在配置被加载的时候就运行,默认是false
,如果需要在启动的时候就运行,需要设置为true
。上述例子是true
。
- 标签说明:
key
就是属性的名称,紧跟着key
的下一行就是属性的值,属性的值的类型通过其标签反映出来,比如上面的<string>
表示包裹的是一个字符串类型,而<true/>
表示是一个布尔类型,而且它的值是true
。
mac 中很多配置都是通过
plist
来定义的,
launchd 配置文件放哪里?
macOS
中有两种类型的守护进程,一种是系统级别的(Daemons
),一种是用户级别的(Agents
),它们的配置文件放的位置是不一样的。
系统级别的守护进程就是不管你用户是谁,都会启动的,而用户级别的守护进程就是只有在对应的用户登录的时候才会启动的(所以会保存在用户主目录下)。
简单来说,就是如果没有用户登录进系统中,那么用户级别的守护进程(
Agents
)就不会启动。mac 其实跟 linux 一样,都是多用户系统,没有任何用户登录的时候,它依然是在运行的。
下面是 launchd
配置文件的路径:
~/Library/LaunchAgents
:用户级别的守护进程配置文件路径。这里保存特定用户的Agents
配置。(一般情况都是放这里)/Library/LaunchAgents
:用户级别的守护进程配置文件路径。这里保存所有用户共用的Agents
配置。/Library/LaunchDaemons
:全局的Daemons
配置。/System/Library/LaunchAgents
:所有登录用户共用的Agents
配置。/System/Library/LaunchDaemons
:全局的Daemons
配置。
可能大家看得有点迷,会有一种想法就是,那我的配置文件应该放哪里?这个问题的答案很简单:如果你的电脑只有你一个人用,那么你就把配置文件放在
~/Library/LaunchAgents
下面就行了。
如何让 launchd 开机启动我们配置的守护进程?
在我们添加了配置文件之后,还有一件事需要做的就是,修改配置文件的权限,我们可以参考一下上面几个文件夹中文件的权限,然后修改成相同的就可以了。
比如 ~/Library/LaunchAgents
文件夹中的配置文件权限都是
xx:staff
(xx
是当前登录的用户),那么我们也把我们的配置文件权限修改成
xx:staff
就可以了。
接下来,我们只需要执行
launchctl load ~/Library/LaunchAgents/com.example.app.plist
就可以了。
如何移除守护进程呢?
如果我们想要移除守护进程,只需要执行
launchctl unload ~/Library/LaunchAgents/com.example.app.plist
就可以了。
如果要执行的命令有参数怎么配置?
在很多时候,我们都是需要加上某些参数来启动我们的命令的,要实现这种效果可以使用
ProgramArguments
配置,下面是另外一个例子:
1 |
|
这个例子中,我们添加了更多的元素进去,比如日志、工作目录等。
说明:
ProgramArguments
属性的作用是指定要执行的命令以及其参数,它的值是数组类型。StandardErrorPath
配置了错误输出的日志路径。StandardOutPath
配置了标准输出的日志路径。WorkingDirectory
设置了我们程序运行时候所在的工作目录。
launchd 配置项说明
Label - 指定名字
1 | <key>Label</key> |
Program、ProgramArguments - 指定要执行的程序
这两个二选一,如果不需要指定参数,用
Program
。如果需要指定参数,那么使用
ProgramArguments
。
1 | <key>Program</key> |
1 | <key>ProgramArguments</key> |
EnvironmentVariables - 环境变量
我们可以为 Program
设置环境变量,比如下面这个例子:
1 | <key>EnvironmentVariables</key> |
这样我们就可以在程序运行的时候读取到这些环境变量。
StandardInPath、StandardOutPath、StandardErrorPath - 重定向输入输出
1 | <key>StandardInPath</key> |
具体含义:
StandardInPath
标准输入的路径。StandardOutPath
标准输出的路径。StandardErrorPath
标准错误输出的路径。
大多时候我们可能不需要,但是如果我们的服务跑不起来,加上
StandardErrorPath
和 StandardOutPath
我们就可以看到错误信息了。
WorkingDirectory - 指定工作目录
1 | <key>WorkingDirectory</key> |
这样我们可以在程序运行的时候,直接使用相对路径。
RunAtLoad、StartInterval、StartCalendarInterval - 指定什么时候启动
这三个属性我们可以在配置中选择一个来配置:
RunAtLoad
:如果设置为true
,那么Program
会在系统启动的时候执行。(对于Daemons
来说就是系统启动的时候启动,对于Agents
来说就是用户登录的时候启动)StartInterval
:指定启动的时间间隔,单位是秒。也就是每隔多少秒执行一次。StartCalendarInterval
:指定启动的时间,可以指定每天的某个时间启动。(类似定时任务),可以指定多个时间。
1 | <key>RunAtLoad</key> |
上面两行的作用是,在系统启动或者用户登进的时候执行命令。
1 | <key>StartInterval</key> |
上面两行的作用是,每隔 3600 秒执行一次。
1 | <key>StartCalendarInterval</key> |
上面的例子中,我们指定了每天的 3 点执行一次。
还有些不太常用的选项:
StartOnMount
、WatchPaths
、QueueDirectories
,通过这几个配置也可以指定命令执行的时机,本文不做介绍。
KeepAlive - 指定是否保持运行
这个默认其实是 true
,我们可以测试一下:kill
掉我们 launchd
启动的进程,我们会发现那个进程马上又会被启动。
这个选项中,我们可以配置一些额外的条件来让 launchd
知道什么时候需要重启进程:
SuccessfulExit
如果上一次退出是正常退出,那么就在进程退出的时候启动它。
1 | <key>KeepAlive</key> |
Crashed
如果上一次退出是异常退出,那么就在进程退出的时候启动它。
1 | <key>KeepAlive</key> |
NetworkState
如果网络连接断开,那么就在网络连接恢复的时候启动它。
1 | <key>KeepAlive</key> |
KeepAlive
的其他不常用选项:PathState
、OtherJobEnabled
、AfterInitialDemand
,本文不做介绍。
UserName、GroupName - 指定运行的用户和用户组
我们可以指定以什么用户、用户组来运行这个命令:
1 | <key>UserName</key> |
RootDirectory - 指定根目录
这允许我们在一个 jail root
中执行我们的命令。
1 | <key>RootDirectory</key> |
AbandonProcessGroup - 进程被终止的时候是否终止其子进程
当我们给 launchd
启动的进程发送 SIGTERM
信号的时候,这个 SIGTERM
信号也会同时被发送给它的子进程。
我们可以将 AbandonProcessGroup
设置为 true
来禁止这种行为:
1 | <key>AbandonProcessGroup</key> |
ExitTimeOut - 优雅终止
在我们停止 launchd
启动的进程的时候,会先发送一个
SIGTERM
信号,我们的进程可以在接收到这个信号后做一些清理操作。 直到
ExitTimeOut
秒后,如果进程还没退出,那么就会发送一个
SIGKILL
信号来强行终止进程的运行:
1 | <key>ExitTimeOut</key> |
ThrottleInterval - 命令调用的时间间隔
可与 KeepAlive
配合使用,在进程异常退出之后,间隔
ThrottleInterval
秒后再尝试启动。
launchd 常用操作
launchctl list - 列出所有 launchd 管理的服务
1 | ➜ launchctl list | grep my-ss-local |
输出的第一列是进程 id
,如果是 0
说明没有在运行状态。第二列的 0
表示的是进程上一次的退出状态码,0
一般表示成功。第三列表示的是我们在 plist
配置文件中配置的
Label
的值。
加载一个 plist(服务/job)
我们可以通过下面的命令加载一个 plist
:
1 | launchctl load ~/Library/LaunchAgents/com.example.app.plist |
移除一个 plist(服务/job)
我们可以通过下面的命令来移除 launchd
配置:
1 | launchctl unload ~/Library/LaunchAgents/com.example.app.plist |
启动一个 job
下面的 com.example.app
是我们在 plist
中配置的 Label
的值:
1 | launchctl start com.example.app |
停止一个 job
下面的 com.example.app
是我们在 plist
中配置的 Label
的值:
1 | launchctl stop com.example.app |
一些实例
下面是个人使用中的一些配置文件,供大家参考。下面的例子涵盖了常用的一些配置,我们复制改改就可以用了。
frpc
文件 ~/Library/LaunchAgents/frp.plist
:
1 |
|
这个配置文件的作用是,以守护进程的方式来启动
/usr/local/bin/frpc -c /etc/frpc.ini
这个命令。启用方式为:
1 | launchctl load ~/Library/LaunchAgents/frp.plist |
我们可以通过
ps
来查看进程是否成功启动。
ss-local
文件 ~/Library/LaunchAgents/ss-local.plist
1 |
|
这个配置文件的作用是,以守护进程的方式来启动
/Users/ruby/Code/proxy/ss-local -c ss-local-config.json
这个命令, 并且将标准错误输出和标准输出都重定向到
/Users/ruby/Library/Logs/my-ss-local.log
文件中。
ss-local
进程的工作目录是
/Users/ruby/Code/proxy/
。
启用方式为:
1 | launchctl load ~/Library/LaunchAgents/ss-local.plist |
homebrew 的 service - MySQL
文件
~/Library/LaunchAgents/homebrew.mxcl.mysql@5.7.plist
:
1 |
|
我们在使用 mac 的时候最常用的 homebrew
也是通过
launchd
来管理服务的。我们可以通过
brew services list
来查看当前启动的服务。 brew
管理的服务的配置文件也会被放在 ~/Library/LaunchAgents
目录中。
上面的配置文件中,做了以下配置:
KeepAlive
- 设置为true
,那么当进程退出的时候,launchd
会自动重启这个进程。Label
- 这个配置文件的唯一标识。我们除了通过brew services list
查看到MySQL
服务,还可以通过launchctl list | grep mysql
来看到这个进程的状态。ProgramArguments
- 指定要执行的命令。这里的完整命令是/usr/local/opt/mysql@5.7/bin/mysqld_safe --datadir=/usr/local/var/mysql
。RunAtLoad
- 设置为true
,那么当用户进入系统的时候,会自动启动这个进程。WorkingDirectory
- 指定MySQL
进程的工作目录。
总结
- mac 下可以使用
launchd
来配置一些守护进程、定时任务等。类似的工具有systemd
和supervisor
等,但是launchd
是 mac 自带的,不用安装其他依赖就能使用。 launchd
配置文件类型为plist
,也就是property list
,用户级别的配置文件一般存放在~/Library/LaunchAgents
目录中。launchd
配置的几个关键属性:Label
- 用来标识这个job
的唯一标识符。Program
- 指定要执行的命令。(如果有参数,我们需要使用ProgramArguments
来代替Program
)StandardErrorPath
、StandardOutPath
- 指定标准错误输出和标准输出的路径。WorkingDirectory
- 指定命令执行的工作目录。
launchd
可以通过launchctl
命令来管理:launchctl list
- 列出所有launchd
管理的服务。launchctl load
- 加载一个plist
。launchctl unload
- 移除一个plist
。launchctl start
- 启动一个job
。launchctl stop
- 停止一个job
。
launchd
的使用还是挺简单的,命令也就那么几个,所以如果想在你的 mac
中启动一些守护进程的话,可以尝试一下。