leiiwang


1. Launchd Tutorial

1.1. 什么是launchd

A unified, open-source service management framework for starting, stopping and managing daemons, applications, processes, and scripts. Written and designed by Dave Zarzycki at Apple, it was introduced with Mac OS X Tiger and is licensed under the Apache License.

1.2. 为什么要launchd

可以替代init,rc,init.d script,rc.d script,SystemStarter (OS X),inetd / xinetd,crond / atd,watchdogd 而且使用更方便1

1.3. Daemons and Agents

launchd 根据运行权限的不同,区分daemons(rootor the user specified with the key User)和agent(user)。

不同的级别的daemon/agent存放位置也不同,其中/System/Library/为系统目录,不应该在此创建任何daemon/agent。第三方Library的目录在/Library/。而某个特别用户的目录在~/Library/

Type Location Run on behalf of
User Agents ~/Library/LaunchAgents Currently logged in user
Global Agents /Library/LaunchAgents Currently logged in user
Global Daemons /Library/LaunchDaemons root or the user specified with the key User
System Agents /System/Library/LaunchAgents Currently logged in user
System Daemons /System/Library/LaunchDaemons root or the user specified with the key User

1.4. 行为配置

daemon/agent的行为由一个property list定义。launchd提供了30多种配置项,full list请参考apple文档和 http://launchd.info/ ,这里简单介绍其中的几个。(可以用一个第三方工具LaunchControl配置)

Key Type Description
Label String job名字. 一般和plist名字相同,Required.
Program String 执行程序路径. 如/Users/Me/Scripts/cleanup.sh.
ProgramArguments Array of strings Program 参数
UserName String 执行user名(defaults to root or current user)
RunAtLoad Boolean (defaults to NO) 标志任务是否要load进launchd立即启动
StartOnMount Boolean (defaults to NO) 标志任务是否要启动when a new filesystem is mounted.
QueueDirectories Array of strings Watch a directory for new files
WatchPaths Array of strings Watch a filesystem path for changes. Can be a file or folder.
StartInterval Integer Schedules job to run on a repeating schedule. Indicates number of seconds to wait between runs.
StartCalendarInterval Dictionary of integers or Array of dictionaries of integers Job scheduling. The syntax is similar to cron.

1.5. 操作

所有行为都可以用一个命令行工具launchctl操作, 但使用LaunchControl更简单.下面列出了几个常见的操作。 ####获取当前运行的所有demon/agent

host:~ user$ launchctl list 

####Loading a Job

launchctl load ~/Library/LaunchAgents/com.leiiwang.firsttest.plist

com.leiiwang.firsttest.plist的内容:一个crontab任务,每分钟执行一次crontab-test.s,这个脚本只有一句话date.

<?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.leiiwang.firsttest</string>

        <key>ProgramArguments</key>
        <array>
                <string>/Users/wangleo/bin/crontab-test.sh</string>
        </array>

        <key>KeepAlive</key>
        <false/>

        <key>Nice</key>
        <integer>1</integer>

        <key>StartInterval</key>
        <integer>60</integer>

        <key>StandardErrorPath</key>
        <string>/tmp/leiiwang.firsttest.err</string>

        <key>StandardOutPath</key>
        <string>/tmp/leiiwang.firsttest.out</string>
</dict>
</plist>

输出/tmp/leiiwang.firsttest.out

Sat Dec 12 16:58:02 CST 2015
Sat Dec 12 16:59:02 CST 2015
Sat Dec 12 17:00:02 CST 2015
...

####Unloading a Job

launchctl unload ~/Library/LaunchAgents/com.leiiwang.firsttest.plist

1.6. recipes(例子)

1.6.1. 例1 app自动重启

这里用了macos 的open -W 命令.

<?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>KeepAlive</key>
		<true/>
		<key>Label</key>
		<string>local.Safari.keepAlive</string>
		<key>ProgramArguments</key>
		<array>
			<string>/usr/bin/open</string>
			<string>-W</string>
			<string>/Applications/Safari.app</string>
		</array>
	</dict>
</plist>

1.6.2. 例2 一个crontab任务(同时监控文件state)

一个备份任务,在database运行的时候进行备份 使用PathState检测PID file是否存在确定任务特定任务database是否在执行 使用KeepAlive使得PID file存在时保持运行. 使用ThrottleInterval,任务终止后多久后重启

<?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.example.app</string>
		<key>Program</key>
		<string>/Users/Me/Scripts/backup.sh</string>
		<key>KeepAlive</key>
		<dict>
			<key>PathState</key>
			<dict>
				<key>/var/log/mysql.pid</key>
				<true/>
			</dict>
		</dict>
		<key>ThrottleInterval</key>
		<integer>3600</integer>
	</dict>
</plist>

1.6.3. 例3 一个inetd任务

使用Sockets创建一个server监听13117

<plist version="1.0">
<dict>
  <key>Label</key><string>my.greeter</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/bin/ruby</string>
    <string>-e</string>
    <string>puts "Hi #{gets.match(/(\w+)\W*\z/)[1]}, happy #{Time.now.strftime("%A")}!"</string>
  </array>
  <key>inetdCompatibility</key><dict><key>Wait</key><false/></dict>
  <key>Sockets</key>
  <dict>
    <key>Listeners</key>
    <dict>
      <key>SockServiceName</key><string>13117</string>
    </dict>
  </dict>
</dict>
</plist>

测试

mac% launchctl load ~/Library/LaunchAgents/my.greeter.plist
mac% echo "My name is Leiiwang." | nc localhost 13117
Hi Leiiwang, happy Saturday!

2. Launchd Sourcecode

整体感受是典型大公司代码,很多人修改的痕迹,各种bug修复和打补丁,搞得有点难看,但从整体上来看质量还算可以。

2.1. 准备知识

mach port: macos 的一种ipc类似一种消息队列和单向pipe的结合(macos在大部分版本中并不支持消息队列,大都时候mach port已经够好用了),参见Ports, Port Rights, Port Sets, and Port Namespaces2 kqueue: free bsd版本的epoll

2.2. 命令launchctl list的运行流程

launchctl是launchd提供的命令行工具,和其他类似的工具一样,launchctl作为一个server-client模式的client运行,client和server直接使用unix域套接字进行通信

launchd中数据传输几乎只依赖一种数据结构_launch_data这种数据做array,dict,string…作用,效率是很低的,但是也可以满足launchd的业务需求了.

struct _launch_data {
	uint64_t type;
	union {
		struct {
			union {
				launch_data_t *_array;
				char *string;
				void *opaque;
				int64_t __junk;
			};
			union {
				uint64_t _array_cnt;
				uint64_t string_len;
				uint64_t opaque_size;
			};
		};
		int64_t fd;
		uint64_t  mp;
		uint64_t err;
		int64_t number;
		uint64_t boolean;
		double float_num;
	};
};

client端流程 1. 在launchctl.c定义了各种的命令的对应运行函数,list的运行函数为list_cmd(..) 2. 构造一个msg,其key为LAUNCH_KEY_GETJOB 3. session相关 4. launchd_msg -> launchd_msg_internal ->launchd_msg_send:pack成一个launch_t结构 5. sendmsg通过socket发送命令结构体到server端 6. recvmsg通过socket读取返回

server端流程 1. 在ipc.c创建了server并且通过kevent监听 2. kevent返回read事件,读取命令ipc_callback 3. launch_msg_recv 4. ipc_read_msg -> ipc_read_msg2 5. core.c:job_export_all ->job_export_all2 6. 从rootmgr开始遍历,把所有job打包进一个dict并返回 7. launch_msg_send返回给客户端数据

2.3. 命令launchctl load的运行流程

这里我们特别的分析一种类似1.5 例子的简单任务的处理流程

client端流程 1. 在launchctl.c定义了各种的命令的对应运行函数,load的运行函数为load_and_unload_cmd(..) 2. readfile:读取job配置的plist,检查几个必填key,读取到一个load_unload_state的结构体中 3. distill_job:对于watchpath/pathstate/queuedirectories任务特别处理,加入在watch job中 4. submit_job_pass,设置命令字为LAUNCH_KEY_SUBMIT_JOB 5. launchd_msg -> launchd_msg_internal ->launchd_msg_send:pack成一个launch_t结构 6. sendmsg通过socket发送命令结构体到server端 7. recvmsg通过socket读取返回

server端流程 1. 前面步骤略 2. ipc_read_msg -> ipc_read_msg2 3. core.c:job_import ->job_import_2 : 检查关键字,session相关… 4. job_new:创建job,填充一个job_t结构,注意一些默认配置,设置callback 5. job_dispatch->job_start 注意并不是所有的任务都会立即dispatch,start的,对于我们的这个任务同时根据配置加入了一个timer到kqueue,timer回调之后会再次dispatch->start 6. job_start -> runtime_fork创建子进程,创建成功后父进程更新一些管理数据结构如actic_jobs等,并且把进程加入监听,监听进程的fork,exit等状态变化 7. 子进程运行startchild->jobsetupattribute提取命令参数等,调用posix_spawn命令执行命令

3. 参考


  1. The Ubuntu Linux distribution considered using launchd in 2006. launchd was rejected as an option because it was released under the Apple Public Source License [return]
  2. Ports, Port Rights, Port Sets, and Port NamespacesWith the exception of the task’s virtual address space, all other Mach resources are accessed through a level of indirection known as a port. A port is an endpoint of a unidirectional communication channel between a client who requests a service and a server who provides the service. If a reply is to be provided to such a service request, a second port must be used. This is comparable to a (unidirectional) pipe in UNIX parlance.In most cases, the resource that is accessed by the port (that is, named by it) is referred to as an object. Most objects named by a port have a single receiver and (potentially) multiple senders. That is, there is exactly one receive port, and at least one sending port, for a typical object such as a message queue.The service to be provided by an object is determined by the manager that receives the request sent to the object. It follows that the kernel is the receiver for ports associated with kernel-provided objects and that the receiver for ports associated with task-provided objects is the task providing those objects.For ports that name task-provided objects, it is possible to change the receiver of requests for that port to a different task, for example by passing the port to that task in a message. A single task may have multiple ports that refer to resources it supports. For that matter, any given entity can have multiple ports that represent it, each implying different sets of permissible operations. For example, many objects have a name port and a control port (sometimes called the privileged port). Access to the control port allows the object to be manipulated; access to the name port simply names the object so that you can obtain information about it or perform other non-privileged operations against it.Tasks have permissions to access ports in certain ways (send, receive, send-once); these are called port rights. A port can be accessed only via a right. Ports are often used to grant clients access to objects within Mach. Having the right to send to the object’s IPC port denotes the right to manipulate the object in prescribed ways. As such, port right ownership is the fundamental security mechanism within Mach. Having a right to an object is to have a capability to access or manipulate that object.Port rights can be copied and moved between tasks via IPC. Doing so, in effect, passes capabilities to some object or server.One type of object referred to by a port is a port set. As the name suggests, a port set is a set of port rights that can be treated as a single unit when receiving a message or event from any of the members of the set. Port sets permit one thread to wait on a number of message and event sources, for example in work loops.Traditionally in Mach, the communication channel denoted by a port was always a queue of messages. However, OS X supports additional types of communication channels, and these new types of IPC object are also represented by ports and port rights. See the section Interprocess Communication (IPC), for more details about messages and other IPC types.Ports and port rights do not have systemwide names that allow arbitrary ports or rights to be manipulated directly. Ports can be manipulated by a task only if the task has a port right in its port namespace. A port right is specified by a port name, an integer index into a 32-bit port namespace. Each task has associated with it a single port namespace.Tasks acquire port rights when another task explicitly inserts them into its namespace, when they receive rights in messages, by creating objects that return a right to the object, and via Mach calls for certain special ports (mach_thread_self, mach_task_self, and mach_reply_port.) [return]
comments powered by Disqus