A
while back, I mentioned that
launchd was pretty cool. But what’s so great about it?
Simply put,
launchd is what makes it easy to get tasks launched on-demand on Mac OS X 10.4 and later. It obviates lots of different archaic Unix infrastructure —
init.d,
cron,
inetd — in favor of a single self-consistent and easy to use mechanism. Dave Zarzycki’s post
Where to begin? describes the
launchd design philosophy in some depth.
Processes launched at startup on Mac OS X are managed by
launchd. There’s no careful balancing of
init.d or
SystemStarter scripts on modern releases of the operating system. Instead,
launchd jobs have property list entries in the
LaunchAgents and
LaunchDaemons directories in the system and local domains. Some specify that
launchd should keep them alive indefinitely, others simply provide conditions under which they should be launched.
For instance, the standard finger daemon indicates that it should be run whenever something connects to the socket corresponding to the
finger service; it doesn’t run all the time, nor does it rely upon a separate “network service daemon” to launch. Similarly, the Bluetooth daemon is only run only when a particular Mach port is connected to; it doesn’t need to be running all the time, and a framework interacting with it can easily just connect to it and start using it, without worrying about the mechanics of launching it, ensuring only one instance is running, and so on.
Where
launchd really shines is in the level of integration it lets developers offer between frameworks and daemons or agents when it comes to managing shared state.
Let’s say you’re creating a framework that manages access to a device (or any other shared resource). Only one process should actually actually interact with the device at the IOKit level with the device itself at a time. And besides, you want to provide a nice high-level Cocoa API anyway. So you need to create some sort of background process — a daemon — to actually talk to the device, and a framework that can talk to the daemon on behalf of any number of applications.
The most straightforward way to do this is to put your daemon executable within your framework, and install your framework in
/Library/Frameworks. Then you can install a
property list describing when
launchd should launch the daemon, and in what type of environment to launch it. Since everything is going to be local to a single machine, you can use a Unix-domain socket — which, if you don’t already know, is represented via a path in the filesystem — by describing it in the
Sockets key in the property list. That way, any time the framework wants to communicate with the daemon, it can just connect to the socket at that path and it will have a channel.
(You can even use the
SecureSocketWithKey option to have
launchd generate the path for you, and pass it to all processes via the specified environment variable. See
the ssh-agent property list for an example; it's how
ssh-agent is launched on-demand in Leopard.)
Your framework and daemon can then use Distributed Objects over that socket (thanks to
NSSocketPort) to communicate by sending standard Objective-C messages back and forth. Just be sure to support
NSCoding for any objects they exchange, and to annotate your methods with
oneway,
bycopy,
byref and so on as appropriate to keep communication efficient. The daemon can even distinguish between clients by keeping track of which socket it’s accepted their connection on.
Wait, though — how does the daemon actually
get that Unix-domain socket if
launchd is what’s actually listening on it? There’s a process that the daemon needs to go through when it first starts up called
launchd check-in. This process lets launchd pass the daemon information from the property list that was used to start the daemon. You just have to use the
launch_msg() API described in <launch.h> to send a
LAUNCH_KEY_CHECKIN string:
- (void)checkInWithLaunchd {
launch_data_t checkinRequest = launch_data_new_string(LAUNCH_KEY_CHECKIN);
launch_data_t checkinResponse = launch_msg(checkinRequest);
switch (launch_data_get_type(checkinResponse)) {
// launchd will return an errno if an error occurs
case LAUNCH_DATA_ERRNO: {
int error = launch_data_get_errno(checkinResponse);
// handle it
}
break;
// launchd will return your job's dictionary for successful checkin
case LAUNCH_DATA_DICTIONARY: {
// The dictionary isn't an exact copy of your plist.
// It has file descriptors substituted for socket descriptions,
// Mach ports substituted for Mach port descriptions, and so on.
}
break;
// launchd returned some other bad response
default: {
// handle it
}
break;
}
launch_data_free(checkinRequest);
launch_data_free(checkinResponse);
}
The trick is that the dictionary given back by
launchd upon check-in is
not simply a literal copy of what’s specified in the job’s property list! Instead, where you had specified in the property list how you wanted
launchd to listen on a socket, upon check-in
launchd will actually pass the socket itself as a file descriptor. You can retrieve it easily using the
launch_data_get_fd() function, accept the connection (on another socket), and continue listening.
For a bunch more detailed information on how to leverage
launchd in your own designs, see the
launchd(8) and
launchd.plist(5) manual pages, as well as
Tech Note TN2083: Daemons and Agents and the
System Startup Programming Topics guide. There’s an example daemon called
SampleD which illustrates how
launchd check-in works, and of course you can
browse the source code to launchd — it’s
under the
Apache License, Version 2.0 — at the
launchd site on
Mac OS forge.