Three fundamental responsibilities of the Mac OS X kernel are to control processes, manage the filesystem, and operate peripheral devices. As a programmer you have access to these kernel operations through system calls and library functions. This section discusses system calls at a general level; a detailed treatment is beyond the scope of this book.
As the name implies, a system call instructs the system (kernel) to perform some work directly on your behalf. The request tells the kernel what work needs to be done and includes the necessary arguments. For example, a system call to open a file includes the name of the file. A library routine is indirect; it issues system calls for you. The advantages of a library routine are that it may insulate you from the low-level details of kernel operations and that it has been written carefully to make sure that it performs efficiently.
For example, it is straightforward to use the standard I/O library function fprintf() to send text to standard output or standard error. Without this function, you would need to issue several system calls to achieve the same result. The calls to the library routines putchar() and getchar() in Figure 12-1 on page 481 ultimately use the write() and read() system calls to perform the I/O operations.
kTRace: traces System Calls
The ktrace utility is a debugging tool that displays a trace of all UNIX system calls made by a process or program. Because you do not need to recompile the program that you want to trace, you can use kTRace on binaries that you do not have source for.
System calls are events that take place at the interface (boundary) between user code and kernel code. Examining this boundary can help you isolate bugs, track down race conditions, and perform sanity checking. The ktrace utility records only UNIX system calls, not direct Mach system calls. The kTRace utility creates a log file named ktrace.out. Because the log file is not in human-readable format, you must use kdump to look at the system calls recorded by ktrace.
You can use the p pid option to kTRace to trace a running process. Include the i option to cause kTRace to trace child processes if the process you are tracing calls other programs. You must have root privileges to use ktrace to trace a setuid program.
When you enter a command at a shell prompt, the shell process calls the fork() system call to create a copy of itself (spawn a child) and then uses an exec() system call to overlay that copy in memory with a different program (the command you asked it to run). Table 12-3 lists system calls that affect processes.
Accessing the Filesystem
Many operations take place when a program reads from or writes to a file. The program needs to know where the file is located; so the filename must be converted to an inode number on the correct filesystem. Your access permissions must be checked not only for the file itself but also for all intervening directories in the path to the file. Because the file is not stored in one continuous piece on the disk, all disk blocks that contain pieces of the file must be located. The appropriate kernel device driver must be called to control the operation of the disk. Once the file has been found, the program may need to find a particular location within the file rather than working with it sequentially from beginning to end. Table 12-4 lists some of the most common system calls for filesystem operations.
Access to peripheral devices on a Mac OS X system is often handled through the filesystem interface. Peripheral devices are represented by one or more special files, usually located under /dev. When you read from or write to one of these special files, the kernel passes your request to the appropriate kernel device driver. As a result you can use the standard system calls and library routines to interact with these devices; you do not need to learn a new set of specialized functions. This ability is one of the most powerful features of a UNIX system because it allows users to use the same basic utilities on a wide range of devices. Under OS X, only some devices (those with BSD kernel interfaces) are accessible through files in /dev. Devices that use IOKit drivers are accessible through the Platform Expert kernel component.
The availability of standard system calls and library routines is the key to the portability of UNIX tools. For example, as an applications programmer, you can rely on the read() and write() system calls working the same way on different versions of UNIX and UNIX-like systems, and on different types of computers. The systems programmer who writes a device driver or ports the kernel to run on a new computer, however, must understand the details at their lowest level.