首页 > 代码库 > Mac服务管理-Launchd(转)
Mac服务管理-Launchd(转)
背景:
在Mac下没有像Linux那样有很多的关于init方面的工具,从init的发展历史https://en.wikipedia.org/wiki/Init上可以知道,Mac使用的是Launchd作为init管理工具,对应的命令工具为launchctl。
如果在Linux下创建一个自启动服务可以使用Upstart、Systemd、Sysvinit,其中最简单和最古老的方式应该是Sysvinit,毕竟其支持Shell脚本,非常方便。而在Mac下,与Linux的做法不太一样,采用Launchd进行管理,其设置服务采用了plist文件进行对服务来描述,并通过配置好后放在/System/Library/LaunchDaemons
或者/Library/LaunchDaemons,最后通过launchctl命令行使其生效,期间也可以直接通过launchctl来对服务进行操作,比如启动、停止等。
详细的plist编写规范及介绍,参考:https://en.wikipedia.org/wiki/Launchd
以下是关于Launchd的详细解释:
Launchd是什么?
Mac OS X从10.4开始,采用Launchd来管理整个作业系统的Services及Processes 。传统的UNIX会使用/etc/rc.*或其他的机制来管理开机时要启动的Startup Services,而现在的Mac OS X使用launchd来管理,它的Startup Service叫做Launch Daemon和Launch Agents 。而视为Service的程序,就该是Background Process,不应该提供GUI,也不应该跳到(Console的)Foreground 。当然有些例外,例如听快速键之后跳出视窗的程序。
Launchd管理的Background Process有四种:
- Launch Daemon:在开机时加载(Load)。
- Launch Agent:在使用者登录时加载。
- XPC Service:好像是10.7才有的。
- Login Items:在User登录时执行。有两种方法可以用程序新增项目到Login Item:(Shared File List:会出现在Account偏好设定的Login Item清单。Service Management Framework:这个就不会出现在Login Item清单。)
(以下把重点放在Launch Daemon/Agent 。至于XPC和Login Item先不做解释。)
Launch Daemon & Launch Agent
Launch Daemon和Launch Agent是同一种东西在不同Scopes的异名。Launch Daemon是System-Wide(系统级别)的Service,称为Daemon,Launch Agent是Per-User(用户级别)的Service ,称为Agent,前者在开机时会加载(Load),后者在使用者登录时(才)会加载。
如果你打开Activity Monitor,并切换到Hierarchy View,你会发现有个Launchd会在最上层,跟它同层的只有kernel_task,它下面有很多Child Processes的User都是root,其中还有一个Launchd,启动的User是你自己,它底下的Child Processes的User也几乎都是你自己。当这些Processes是由Launchd加载Launchd Property List File来执行的时候,前者由root执行的称为Launch Daemons,后者由使用者执行的称为Launch Agents 。
Launchd Property List File就是你会在LaunchDaemon或LaunchAgents目录中看到的*.plist档案(以下统称plist档)。它是XML格式。
launchd Service Process Lifecycle
由Launchd所管理的Services(Launch Daemon、Launch Agent)是要先由Launchd加载(load)以后才会执行(run),但加载之后并不一定马上执行。在苹果的官方文件说明了kernel加载完成后会发生的事,用来说明Launch Daemons、Launch Agents及其Processes的生命周期。
开机时,会先加载OS Kernel,加载完成后就执行Launchd,用来加载System-Wide Services(Daemons)。这个System-Wide Launchd在开机时会做这些事:
- 加载(load)存放在这些目录下的plist:(/System/Library/LaunchDaemons,/Library/LaunchDaemons)
- 注册那些plist里面设定的sockets(port)和file descriptors
- 执行(run)KeepAlive = true的Daemons,当然RunAtLoad = true的也会启动。
Run好后,Loginwindow就出现了,提示使用者登录。有设定自动登录的话,就会跳过这关。
在使用者登录以后,会执行属于该使用者的Launchd,负责处理Launch Agent,做的事跟上面加载Launch Daemon很像,差别在于它从以下的目录加载plist:
- /System/Library/LaunchAgents
- /Library/LaunchAgents
- ~/Library/LaunchAgents
由使用者执行的任何程序也都是Launchd来执行的,所以Launchd也是该使用者的所有Processes之母。
在使用者登出、关机或重新开机时,会触发Termination Event。接受登出、关机、重新开机使用者指令的Process是Loginwindow。它会先向使用者确认,一但确认,就会对每个由该使用者的Launchd所启动的Processes送出Termination Signal,如果是Cocoa则送出Cocoa API的Event,其他的就送出SIGTERM要他们自我了断,45秒之后,除了Cocoa的应用程式可以丢出某个Error来取消这整个Termination Process,其他还没结束的都会被kill掉。
这就是为什么Loginwindow这个Process会一直存在,它要负责把该使用者执行起来的Processes统统清掉。而Per-User Services都关掉以后,就回到Loginwindow,或是执行关机、重新开机的流程,后两者就是照着差不多的流程去关掉所有System-Wide Services。
Launchd-Compatible Daemon Programming Guide
以下是该文件中提及关于配合Launchd开发Daemon时应注意的事,提到关于plist的key就请参考man 5 launchd.plist。以下的Daemon指的是Launch Daemon所要运行的Process,所以Launch Agent也一并适用。
Listen to SIGTERM
如上文所提及的,由于Loginwindow这个Process在要关掉你的Daemon时会先送SIGTERM,要你自我了断,等太久没关掉才会SIGKILL。如果你的程式需要在结束之前做什么事,一定要听SIGTERM这个Signal。
On-Demand Daemon
Launch Daemon/Agent默认不会让某个Process一直执行,当它的设定没有KeepAlive = true时,它会根据被执行的Process的CPU Usage和Requests(如TCP/IP Service)来决定要不要送出SIGTERM叫他自尽。
当该Service需要被使用时,而相对应的Program没有跑成Process时,会自动把该Service给跑起来。例如某个TCP/IP Service听某个Port,当这个Port有封包进来时,Launchd会把相对应的Service给启动,这种行为叫做on-demand 。
当然,也有non-on-demand daemon,其实也就是keep-alive daemon,这也是传统意义上的Daemon ,比如一直躲在墙角默默执行,直到有人找他,他才跳出来回一下话,回完了以后又继续躲在墙角。只要把KeepAlive这个key设成true,它就会在plist被Launchd加载(load)时执行(run)起来。要是那个Process死掉,Launchd会知道,马上再把它开起来。所以如果你试着去Activity Monitor砍掉这种Daemon,它就马上会复活。
No fork or exec
传统的System Programming会教你用exec、fork等等的POSIX API来做Daemon,但配合Launchd时,由于Daemon的生命周期是由Launchd来控制的,除非强制要求Kepp-Alive,否则要生要死是Launchd决定,更何况Keep-Alive还要考虑Daemon Process在结束以后自动重新执行,所以在配合Launchd写Daemon时,苹果建议你不要用传统的fork和exec*。当然,plist文件中的ProgramArguments就是exec*系列subroutine的参数。
当一个Process跑起来10秒内就死掉,Launchd会判定为Crash,然后试着重新执行。要是你用传统的fork-exec style,就可能会造成无限循环。
No setuid / setgid / chroot / chdir etc
为了安全性的考虑,苹果强烈建议你不要自己调用setupd、setgid、chroot、chdir等等System Subroutines,而是透过plist文件的设定值来让Launchd帮你完成,参考UserName、GroupName、RootDirectory、WorkingDirectory的keys 。
No pipe redirection hell for fd 0, 1 or 2
在写Log或输出信息时可以设定StandardOutPath、StandardErrorPath,只管输出到stdout或stderr就好了。而StandardInPath也可以让你的Process一执行就从stdin指定path的内容。也就是说,Launchd帮你把fd = 0, 1, 2的东西都传了一遍。
其他应用
定时任务
Launch Daemon/Agent的设置项可以指定该Service的执行周期及执行时间,也就是说,它可以替代传统的at、periodic和cron。这些设定值的key请参考StartInterval和StartCalendarInterval。
搭配LaunchOnlyOnce的话可以模拟at,但如果要用Launchd只临时做一件事,还不如直接at方便。
监视文件或目录异动
Launch Daemon/Agent可以监视某个path的异动,设定在WatchPaths这个key。这里所说的path可以是Directory或是某个特定的文件,只要该path有异动,就会执行你的Job。
也可以用来清Queue,只要Directory里面有东西,就会执行Job直到空为止,可以用来做Mail Server或Notification。设定在QueueDirectories这个key。
参考:
https://en.wikipedia.org/wiki/Init
https://en.wikipedia.org/wiki/Launchd
https://stackoverflow.com/questions/15735320/osx-s-etc-init-d-equivalent
https://nathangrigg.com/2012/07/schedule-jobs-using-launchd#launchctl
https://blog.yorkxin.org/2011/08/04/osx-launch-daemon-agent(以上内容转自此篇博客,由于繁体翻译成简体,有些地方可能语义存在问题)
https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/launchd.plist.5.html
https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/launchctl.1.html
https://developer.apple.com/library/content/technotes/tn2083/_index.html
Mac服务管理-Launchd(转)