This section explains how system calls are used with C and other languages and presents some guidelines for using them correctly.
ptg11539634 20 Chapter 1: Fundamental Concepts
1.3.1 C (and C++) Bindings
How does a C or C++ programmer actually issue a system call? Just like any other function call. For example, read might be called like this:
amt = read(fd, buf, numbyte);
The implementation of the function read varies with the UNIX implementation.
Usually it’s a small function that executes some special code that transfers con-trol from the user process to the kernel and then returns the results. The real work is done inside the kernel, which is why it’s a system call and not just a library rou-tine that can do its work entirely in user space, such as qsort or strlen.
Remember, though, that since a system call involves two context switches (from user to kernel and back), it takes much longer than a simple function call within a process’s own address space. So it’s a good idea to avoid excessive system calls.
This point will be emphasized in Section 2.12 when we look into buffered I/O.
Every system call is defined in a header file, and you have to ensure that you’ve included the correct headers (sometimes more than one is needed) before you make the call. For instance, read requires that we include the header unistd.h, like this:
#include <unistd.h>
A single header typically defines more than one call (unistd.h defines about 80), so a typical program needs only a handful of includes. Even with a bunch more for Standard C11 functions (e.g., string.h), it’s still manageable. There’s no harm in including a header you don’t actually need, so it’s easiest to collect the most common headers into a master header and just include that. For this book, the master header is defs.h, which I present in Section 1.6. I won’t show the include for it in the example code in this book, but you should assume it’s included in every C file I write.
Unfortunately, there are header-file conflicts caused by ambiguities in the various standards or by misunderstandings on the part of implementors, so you’ll some-times find that you need to jigger the order of includes or play some tricks with the C preprocessor to make everything come out OK. You can see a bit of this in defs.h, but at least it hides the funny business from the code that includes it.
11. By Standard C, I mean the original 1989 standard, the 1995 update, or the latest, known as C99. When I mean C99 specifically, I’ll say so.
ptg11539634 Using System Calls 21
No standard requires that a given facility be implemented as a system call, rather than a library function, and I won’t make much of the distinction between the two when I introduce one or when I show one being used in an example program.
Don’t take my use of the term “system call” too literally, either; I just mean that the facility is usually implemented that way.
1.3.2 Other Language Bindings
While the main UNIX standards documents, POSIX and SUS, describe the inter-faces in C, they recognize that C is only one of many languages, and so they’ve defined bindings for other standardized languages as well, such as Fortran and Ada. No problem with easy functions like read, as long as the language has some way of passing in integers and addresses of buffers and getting an integer back, which most do (Fortran was a challenge). It’s tougher to handle structures (used by stat, for instance) and linked lists (used by getaddrinfo) . Still, language experts usually find a way.12
Lots of languages, like Java, Perl, and Python, have standard facilities that sup-port some POSIX facilities. For instance, here’s a Python program that uses the UNIX system called fork to create a child process. (I’ll get to fork in Chapter 5;
for now all you need to know is that it creates a child process and then returns in both the child and the parent, with different values, so both the if and else are true, in the parent and child, respectively.)
import os pid = os.fork() if pid == 0:
print 'Parent says, "HELLO!"' else:
print 'Child says, "hello!"'
This is the output:
Parent says, "HELLO!"
Child says, "hello!"
There won’t be any further examples using languages other than C and C++ in this book other than in Appendix C, which talks about Java and Jython (a version of Python that runs in a Java environment).
12. For example, Jtux, described in Appendix C, makes extensive use of the Java Native Interface (JNI).
ptg11539634 22 Chapter 1: Fundamental Concepts
1.3.3 Guidelines for Calling Library Functions
Here are some general guidelines for calling library functions (from C or C++), which apply to system calls or to any other functions:
• Include the necessary headers (as I discussed previously).
• Make sure you know how the function indicates an error, and check for it unless you have a good reason not to (Section 1.4). If you’re not going to check, document your decision by casting the return value to void, like this:
(void)close(fd);
We violate this guideline consistently for printf, but with few other excep-tions I follow it in our example programs.
• Don’t use casts unless absolutely necessary, because they might hide a mis-take. Here’s what you shouldn’t do:
int n;
struct xyz *p;
...
free((void *)p); /* gratuitous cast */
The problem is that if you mistakenly write n instead of p, the cast will sup-press a compiler warning. You don’t need the cast when the function’s prototype specifies void*. If you’re not sure whether the type you want to use is OK, leave the cast off so the compiler can tell you. This would apply to the case when the function calls, say, for an int and you want to give it a pid_t (the type for a process ID). Better to get the warning if there will be one than to shut the compiler up before it has a chance to speak. If you do get a warning, and you’ve determined that a cast is the fix, you can put the cast in then.
• If you’ve got more than one thread, know whether the function you want to call is thread safe—that is, whether it handles global variables, mutexes (semaphores), signals, etc., correctly for a multithreaded program. Many functions don’t.
• Try to write to the standard interfaces rather than to those on your particu-lar system. This has a lot of advantages: Your code will be more portable, the standardized functionality is probably more thoroughly tested, it will be easier for another programmer to understand what you’re doing, and your code is more likely to be compatible with the next version of the operating system. In practice, what you can do is keep a browser open to the Open
ptg11539634 Using System Calls 23
Group Web site (Section 1.9 and [SUS2002]), where there’s a terrific alpha-betized reference to all standard functions (including the Standard C library) and all header files. This is a much better approach than, say, using the man pages on your local UNIX system. Sure, sometimes you do need to look up particulars for your system, and occasionally you need to use something nonstandard. But make that the exception rather than the rule, and docu-ment that it’s nonstandard with a comdocu-ment. (Lots more about what I mean by “standard” in Section 1.5.)
1.3.4 Function Synopses
I’ll formally introduce each system call or function that I discuss in detail with a brief synopsis of it that documents the necessary headers, the arguments, and how errors are reported. Here’s one for atexit, a Standard C function, that we’re going to need in Section 1.4.2:
atexit—register function to be called when process exits
#include <stdlib.h>
int atexit(
void (*fcn)(void) /* function to be called */
);
/* Returns 0 on success, non-zero on error (errno not defined) */
(If you’re not already familiar with errno, it’s explained in the next section.) The way atexit works is that you declare a function like this:
static void fcn(void) {
/* work to be done at exit goes here */
}
and then you register it like this:
if (atexit(fcn) != 0) { /* handle error */
}
Now when the process exits, your function is automatically called. You can regis-ter more than 1 function (up to 32), and they’re all called in the reverse order of their registration. Note that the test in the if statement was for non-zero, since that’s exactly what the wording in the synopsis said. It wasn’t a test for 1, or greater than zero, or –1, or false, or NULL, or anything else. That’s the only safe
ptg11539634 24 Chapter 1: Fundamental Concepts
way to do it. Also, it makes it easier later if you’re looking for a bug and are try-ing to compare the code to the documentation. You don’t have to solve a little puzzle in your head to compare the two.