What happens when you run an app?

One of the commonest everyday mysteries of Macs is how they run apps.

If you are content with the one word answer “Because”, then when something goes wrong – an app crashes or quits immediately that you try to start it – you will be unable to know what to do next. Understanding how apps run also gives you insight into what you see in Activity Monitor, and how to use that and other tools to work out whether you have sufficient memory, or whether adding another 8 GB might be worth your investment.

Launching

Some apps are launched by OS X itself, but those which are most visible you launch by double-clicking the app icon, through Launchpad or the Dock, or opening a file which is currently associated with that specific app. Also remember that recent versions of OS X will try to restore all apps which were open when the Mac was last shut down, when you next start it up.

When you go to open a document which is associated with an app which is not running, OS X looks up which app is associated with that file or with that document type, and then tries to launch that app, sending it a message to open the document which you tried to open.

Inside the app

Apps are not just lumps of code, but contain set structures which you can browse by selecting the app in the Finder, and using the contextual menu to Show Package Contents. You will next see, at the top level of its folder structure, a single folder named Contents, within which are several other standard folders.

The structure of Preview.app, showing its main executable program code in the MacOS folder.
The structure of Preview.app, showing its main executable program code in the MacOS folder.

The folder _CodeSignature contains the code signature used by Gatekeeper to validate the app and its integrity. The executable code, the program itself, is located in the folder named MacOS. The folder named Resources contains all manner of components most commonly supporting the interface of the app, stored in different types of files such as .nib (nibs), .icns (icons), various graphics files containing other items for display, and folders with language-specific localisation details such as text strings.

There may also be named folders for other code resources, in particular Frameworks and Libraries, and Plugins. Larger and more complex apps may have many such code resources, and they can also be placed in locations other than the .app itself. These include the dreaded dynamic libraries (dylibs) which may be open to hacking.

Get set and go

Before trying to run an app, OS X checks to see whether it has been flagged as requiring checking by Gatekeeper, because it has been downloaded from a remote source and its signature has yet to be verified. If the app has already cleared Gatekeeper’s scrutiny, or once it has passed that, the instruction to launch the app is passed to OS X.

OS X responds to your instruction to launch an app when the kernel creates a process for the program, calling two functions, afork/a and aexecve/a. afork/a creates the process itself, then aexecve/a loads the code inside the MacOS folder and executes it. As part of that the dynamic linker shared library (usually /usr/lib/dyld) is loaded. These are put into the program’s address space (allocated memory).

As most programs call on various code libraries, the dynamic linker loads those libraries which the program needs. As each code file is loaded, the kernel checks that it has the correct format (known as Mach-O), and binds the symbols used so that the program can start running.

Inside the app’s process, the main thread of the app is created, and that is used to begin executing the code from within memory. The app’s code then takes over, and the app is now running.

Memory

During launching, OS X’s virtual memory manager creates a virtual address space (allocated virtual memory) for each process, and divides each up into equal chunks known as pages. These are mapped to hardware addresses in physical memory (RAM) in a page table. Whenever an app’s code needs to access memory, these page tables are used to look up the real address in RAM.

So the running program sees an almost limitless (for 64-bit apps) area of memory, which the memory management hardware and software then converts into real, physical memory.

Because this huge virtual memory cannot all fit into physical memory, there will be times when a running program tries to access some memory which is not currently held in real, physical memory: that is known as a page fault, and causes the code to stop running whilst the memory manager loads the data from disk into an available page in physical memory. This is known as paging in, and once complete the code is allowed to carry on running.

If there are no free pages in real, physical memory, an existing page must be released to create a free page; this normally requires the data currently held in that page to be written out into the backing store on disk, a process known as paging out (or swapping out).

Paging in and out stop the program from running, and thus slow the app down. If a lot of page faults occur and there are no free pages, this results in disk thrashing, which is slow and has a major performance hit.

OS X kernel maintains three system-wide lists of real, physical memory, and uses thresholds to manage pages as efficiently as possible, to minimise paging in and out. This ensures that pages that contain data that has not been used recently may be paged out to provide free pages when needed, for example. The memory manager works hard to ensure optimal memory use, but works best when there are plenty of free pages.

The app itself

Apps have a single main entry point, called when they are launched, and most use this main function very little. All it normally does is give control to the AppKit framework, which then takes over.

The AppKit framework creates the application object itself, loads the nib file specified in the app’s Info.plist file, and sets that up for the app. The app itself then takes control, initialising all that it needs, including setting up the app menu bar, opening any files (such as when you launch the app by double-clicking an associated document), and tidies up.

The app then sits in its Main Event Loop, an endless code loop which responds to your actions. For instance, if you then click on the menu bar, the menu is displayed, and the app waits for you to select a menu item. Once you do, the app is informed as to which item you selected, and the Main Event Loop processes that selection by choosing the code which it needs to execute – this is the process of event handling, which invokes all the different code functions built into the app.

There are two main types of event which are passed to the app: key and mouse events. Key events are sent to the window in which the entry point is currently set, and mouse events to the window in which the event occurred. So if you click on a button, the window containing that button is told of your action, so it can respond appropriately.

Clues and more

Some of the above steps may be recorded in the logs, giving you clues as to what is going on. Sometimes these entries are informative, telling you that certain code libraries have been loaded, or they may warn you of errors, which could cause problems with the app, perhaps even resulting in it suddenly quitting.

Understanding the normally hidden steps in the process of launching an app helps you discover what has gone wrong, and devise strategies for trying to deal with it.

Understanding how OS X manages memory enables you to work out whether your Mac has enough physical memory, and how you can improve performance.

There is further information and detail in the documentation which accompanies Apple’s huge but free development kit, Xcode, available in the App Store.