Coming from Windows, you may be used to uberAgent running as a background service. Technically, the same is true for macOS but, of course, things work differently on both platforms. To give you a broad overview, this article shines a light on how background services work on macOS in general, how they are managed by the system and how uberAgent plugs itself into the machinery. Let’s start by examining the, not only in this context, most relevant system component, launchd.
In many ways, launchd is special. For starters, it not only has its own Wikipedia article but also a Twitter persona. While people have become used to the idea that on the Internet all sorts of things can become famous, or at least infamous, acquiring even a mild degree of Internet fame is quite uncommon for something as hidden away as a nearly invisible operating system component. But it gets better. Dare type “launchd” into your Internet search engine of choice and chances are high that one of the first things you read is that launchd “manages daemons and agents”. While this may sound like launchd was a somewhat bureaucratic super villain of an upcoming movie franchise somewhere between The Lord of the Rings and The Matrix, its actual character is a little less sensational – but pivotal to the system, nonetheless.
Let’s begin with an observation that you can make when running
ps -e from Terminal.
ps is short for “process status” and with the
-e option it lists all processes currently running on the system. The very first entry in the table of processes that
ps prints is always the process with PID 1, its executed binary being none other than
/sbin/launchd. In fact, launchd is not only the very first process that gets launched from the kernel after boot-up, making it process number 1. It also is the very last process that, in a manner of speaking, switches off the lights after everyone left the building, before shutdown. In case you suffered some disappointment over launchd’s lack of cinematic qualities, this should at least technically qualify launchd as the equivalent to an overtowering end boss. And the system is very clear about this VIP status of launchd. Try to run launchd manually and all you earn is a bleak “launchd cannot be run directly.” At this point, it is pretty safe to conclude that launchd could be regarded as the one system entity that emerges from the kernel space to open up and manage the userspace.
And indeed, when you study the output of
ps -ef the parent process ID (PPID) of the vast majority of processes will very likely be “1” which, as we know by now, references launchd. Apparently, most processes are created by launchd. Why is that? Since we are dealing with a low-level system component, there are a multitude of reasons. And while all of them may legitimately spark your interest, for now, let us stick to the headline and pass the spotlight on to background services.
As a Mac admin, your one-stop shop for managing background services usually is
launchctl, the control CLI for launchd. As if
launchctl was not packed with enough functionality already, it actually provides some nifty additional features that are unrelated to managing background services. While the
reboot command may come in handy from time to time,
asuser allows you to run a command as if it were run from within any given user session.
procinfo is especially noteworthy because it provides a plethora of information about any process on the system, probably much more information than you’ll ever need.
As nefarious as the terms may sound at first, on macOS, daemons and agents are simply monikers for background services. While a daemon is a process that runs with root privileges and no user login required, an agent runs per login with user privileges and is therefore often called a user agent. Daemons and agents both usually stay invisible to the user with the only difference being that agents, in contrast to daemons, do have access to the graphics subsystem and are therefore capable to present a GUI to the user. When and how daemons or agents of any flavor are started and how long they stay active is subject to their respective configuration. As always on macOS, this configuration is stored as a plist file that is explained in detail by the
launchd.plist man page. In order to not widen the scope of this article too much, let’s just keep on record that there are many interesting configuration options available.
As you would expect, uberAgent runs as a system-wide daemon with root privileges. As such, its configuration plist file can be found in
/Library/LaunchDaemons. uberAgent is configured to start as early as possible, meaning shortly after boot-up and before any user session logins can occur. Should uberAgent be terminated for another reason than shutdown, launchd will restart it instantly. The greater goal here is, of course, to keep uberAgent running from system-start to shutdown.
Daemons do have limitations, though. Many subsystems associated with user sessions, especially anything related to the graphics context like apps and windows, can only be accessed from within the user context. Alas, the uberAgent daemon runs in the root context. uberAgent evades this complication by running a dedicated helper process in every user session. At the time of writing, this includes only GUI login sessions, regardless of whether initiated locally or remotely but rest assured that SSH sessions are on our roadmap. This means, of course, that the helper process must run as a user agent with its configuration plist file located in
/Library/LaunchAgents are the usual filesystem locations for admin-provided configurations and third-party software like uberAgent. Configuration plist files for system-provided daemons and agents reside in subdirectories of the same names inside the
/System directory. Just in case you were wondering how Finder manages to automatically restart, you can find its configuration file right in there, as well. Launch agents that are meant to only apply to specific users must provide a configuration plist file in those users’ home directory, i.e.
When it comes to privileges and helpers you usually want to aim very, very low. But as the name suggests, these specific helper tools do come with privileges. You may have stumbled over the
/Library/PrivilegedHelperTools directory already, or heard about privileged helper tools in security-related news, specifically regarding CVE-2019-13013. Staying the course in this little tour of background services, let it suffice to say that privileged helper tools are essentially daemons. The main difference is that macOS provides an API that allows apps to “bless” an executable from their bundle to become a launchd job. You can think of this mechanism as a way to register a configuration plist file with launchd so that the executable in question can be run as a background service. Since the executable eventually becomes a system-wide daemon running with root privileges, the system forces the blessing app to ask for the user’s consent before initiating the installation.
uberAgent itself does not make use of this mechanism. Rather, the system admin uses our installer package to install the necessary configuration plist files directly to their respective filesystem locations. Although uberAgent packages most of its contents as a bundle, it is not an app bundle. And since uberAgent also does not run as a GUI app there is neither opportunity nor practicability for the user interaction needed to initiate the blessing procedure.
While we can be pretty sure that the P stands for process and C stands for communication, the Apple documentation never explains the meaning of X. While it could stand for something marketable like “eXtreme” or “eXtraordinary”, it is slightly more reasonable to read the X as “cross” as another way to say “interprocess communication” like this completely unrelated paper. Because that is what XPC actually does. But for all we know, XPC could also be a recursive acronym meaning nothing less than “XPC process communication”.
Let’s get the elephant out of the room first. XPC per se is not another flavor of background services. Apple provides only programmatic interfaces in C, Objective-C, and Swift for XPC. So, unless you’re a software developer you will never come in direct contact with it. Since it is Apple’s preferred, or at least most advertised, solution for dealing with interprocess communication, XPC is still interesting enough to be discussed here. Why should interprocess communication concern you? Apple puts it this way:
There are two main reasons to use XPC services: privilege separation and stability.
Putting vulnerable or otherwise potentially unreliable code into a separate process and talking to it only when absolutely necessary is one of the cornerstones of sandboxing. It also makes apps more stable since satellite processes may crash for whatever reason but the main process they orbit stays alive as if nothing happened. And this is exactly the functionality that XPC services provide: spawn a new process, execute some code in it while maintaining communication channels to the invoking process and manage regular as well as irregular process exits. What’s the difference between XPC and XPC services, I hear you ask. It’s simple, really. XPC services use XPC technology as a means to an end which is becoming a proper, sandboxed background service that securely stays in contact with its responsible process.
As a POSIX-compliant operating system, macOS supports also Unix domain sockets, a technology that is commonly used for inter-process communication. In that regard, XPC offers essentially the same capabilities – but also more. While both technologies basically implement communication between processes, XPC has safety, robustness, and performance at its heart. Here are some advantages that stick out the most:
- XPC APIs favor structured messages over raw bytes, so they are easier and safer to use for developers.
- XPC is deeply integrated with launchd which makes it secure, e.g. through supporting credentials that identify connected processes.
- XPC is built directly on top of Mach messaging, the IPC technology of the Mach microkernel that is the centerpiece of macOS, which is deemed very fast.
Who else would be better suited to provide XPC services to the system if not launchd. Upon an incoming XPC request, launchd looks up the respective service and creates it in the guise of a new process. It then dials the privileges of the new process down to an absolute minimum, sparing only the privileges the requested XPC service may be allowed to have due to certain entitlements. Finally, it executes the actual service code in a now heavily restricted process environment with little to no access to the outside world. Because launchd acts as a proxy that sets the stage for the XPC service it automatically becomes the parent of that new process. That last part is also what makes XPC services somewhat intransparent. launchd might be the parent of the new process but it is not the one that is responsible for its creation. It would therefore be unreasonable to make launchd accountable for the performance load the XPC service puts on the system.
To make the issue a little more three-dimensional, let us take a closer look at Safari in Activity Monitor, the utility app that comes with macOS. When you select View > All Processes, Hierarchically, then select View > Filter Processes and enter “Safari” you will find several processes grouped as belonging to the Safari app. Their names should read familiar since most of them will simply be the URLs of open Safari tabs. But when you select View > Inspect Process for any of these processes, the Parent Process field will read launchd.
This means every time you ask Safari to create another tab, in the background Safari will initiate the appropriate XPC request that makes launchd create a new process called
com.apple.WebKit.WebContent for the tab. launchd then hands Safari the direct communication line to the process and from then on, Safari will forward your URL requests for the browser tab to the XPC service.
uberAgent makes use of XPC services itself. Admittedly, here and there we might not be one hundred percent confident with specific API calls that the system provides. In order to mitigate any risk to uberAgent’s stability that we sense, certain API calls are restricted to a sandbox process that may crash as much as it wants. As always, we put our very best efforts into ensuring that this never happens. But for us, the uberAgent XPC service is simply the manifestation of the “better safe than sorry” mantra, just as Apple designed it (in California) to be.
Also, just in case you were wondering, uberAgent of course knows the responsible process for every XPC service and is thus able to identify the application the respective performance load should be attributed to.
More often than not, there are several ways to do the same thing on macOS. In the case of background services, system admins usually stick to the Unix heirloom which is mainly daemons and agents, while software developers usually make use of the modern, more sophisticated XPC services API. Nevertheless, whatever role you find yourself in, it is worth knowing about the similarities and differences of both technologies. What both have in common is their manager, launchd, which has many responsibilities and plays a very important, even vital role on macOS.
If you haven’t had enough yet, here’s your ticket for a ride further down the rabbit hole. Note that these links will take you out of the nuts-and-bolts comfort zone very quickly and into the nitty-gritty technical details of background services on macOS.
- Launchd – At Your Service!, Jonathan Levin dissecting launchd at the MacSysAdmin conference
- launchd.info, outdated but still useful regarding debugging of launchd jobs
- Daemons and Services Programming Guide, Apple technical documentation
- TN 2083: Daemons and Agents, Apple technical documentation
- Introducing XPC, Apple first introducing XPC as their new IPC technology