System Calls

Last time we saw some example models of OS structures, why their components were formatted the ways they were, and consequently, how OS's serve as the bridge between the user and hardware.

In this lecture, we'll examine the component of the bridge between the user space and the kernel: system calls.

Recall: A system call is a software interrupt that is generated by a user process requesting some resource or function of the operating system.

System Calls are functional and can be viewed as the OS's Application Programming Interface (API).

As an interrupt, we know that the procedure is to have an associated handler that knows what to do with the interrupt.

Review: By what mechanism are interrupts mapped to their associated programmatic handlers?

The interrupt vector!

For Syscalls (how you say System Calls if you want to sound 1337), we have the exact same behavior wherein each system call is mapped to a unique identifier:

An OS's system call interface maps a unique numerical ID to each system call implementation in the kernel, which is maintained in a table indexed according to these values.

Once invoked in the kernel, each syscall returns its status (whether it went off without error or not) as well as any data that was requested by that syscall.

Pictorially, the syscall process looks like the following (for the open() syscall, which opens a file from which to read data):

Diagram taken from your textbook.

Types of System Calls


Naturally, we might expect system calls to expose basic elements of the parts of the computer that the OS is responsible for managing, and this is indeed the case:

System calls are broken down into 6 primary categories:

Category

Description

Win32 Examples

Unix Examples

Process Control

Creating, ending, getting info about processes.

CreateProcess(), ExitProcess()

fork(), exit()

File Manipulation

Creating and deleting files, reading and writing to them, getting attributes.

CreateFile(), ReadFile()

open(), read()

Device Manipulation

Reading / writing from devices, mounting and unmounting.

ReadConsole(), WriteConsole()

read(), write()

Information Maintenance

Getting time, date, system data, attributes.

GetCurrentProcessID(), SetTimer()

getpid(), alarm()

Communication

Sending messages between processes.

CreatePipe()

pipe()

Protection

Security and restriction.

SetFileSecurity()

chmod()

You can find a complete list of Unix System Calls here:
List of System Calls

Notice, in the above, you will see the # corresponding to the given system call, and expectations for the parameters, which are C types to pass into the CPU's registers.

Most syscall parameters can be passed into the registers directly, but in case arguments are too large to fit (e.g., large strings), we pass a pointer in instead.

We'll look at some examples of the above in but a moment!


System Call APIs


Note: the programmer need not know anything about how the syscall is implemented, but simply uses the syscalls based on their exposed behavior and useage.

That said, application programmers rarely use system calls in their raw form for a variety of reasons:

  • It would be a pain to have to remember the unique ID associated with each syscall rather than associate a name to it.

  • Different OS's have different IDs for different syscalls, and so applications invoking syscalls must be written on a per-OS basis.

  • Syscalls may involve a variety of required parameters that are rarely of interest in application programming.

Of course, this is all starting to sound familiar, whenever faced with similar circumstances, programmers prefer to use:

Application Programming Interfaces (APIs) that assign a syscall name, parameter, and return type to all OS-specific system calls are available for all major OS's.

The OS-specific APIs include: Win32 for Windows systems, POSIX for Unix systems (Linux and Mac), and Java for the JVM.

In other words, if system calls are the OS's API, then we have OS-specific APIs for those APIs.

The C Standard Library provides wrappers for many Unix system calls, which are then invoked in the library's implementation.

Pictorially, this appears as:

Diagram once again shamelessly lifted from your textbook -- why reinvent the wheel?

Note above that the C stdio library's printf function is merely a wrapper that employs the write() syscall.


So now that we've seen some examples in theory, let's C them enacted!



Examples in C

Since we probably haven't exactly been programming in C recently, it'd probably pay to have a very brisk review of the language mechanics, and notable differences from variants you've seen recently, like C++.

We'll lead off with a small review and then demonstrate some simple C programs that use some POSIX syscalls.


It-C Bit-C Review


Since you've recently had a class in C++, I'll brush over some of the basics about C and merely strip away some of the conveniences that C++ brought to the table.


Main Method

In C programs, the main method has the format: int main (int argc, char** argv).

To unpack this:

  • The return type is the exit code (0 for "everything OK", and anything but 0 for some error code). 0 is returned by default.

  • Main is parameterized by arguments that can be provided when the executable is run (typically from the terminal):

    • argc is the number of space-separated arguments provided.

    • argv is an array of an array (pointers to pointers) of characters (i.e., an array of cstrings) containing each entered argument. NOTE: argv[0] is always the name of the executable.

We can try a simple Hello-Worldish demonstration:

  /**
    * Small program to demonstrate the arguments to main
    */
  #include <stdio.h>
  
  int main (int argc, char** argv) {
      printf("Number of args: %i\n", argc);
      for (int i = 0; i < argc; i++) {
          printf("Arg %i is: %s\n", i, argv[i]);
      }
  }

To compile in the terminal, use the Gnu Compiler Collection gcc to compile C source into an executable: gcc ShowArgs.c

Obviously, there are many different parameters possible for gcc, including setting the name of the executable, but we won't bother with those for now (nor will we bother with header files).

To run the resulting executable from the terminal, merely enter ./a.out (the name of the executable). Provide variant numbers of arguments (words after ./a.out) to experiment.


Variable Allocation

Recall: what is the difference between local and dynamic allocation?

Local allocation [Stored in Stack]: memory freed when out of scope. Dynamic allocation [Stored in Heap]: memory freed by programmer, explicitly.

Take Care: failing to free memory that you have allocated will result in a memory leak, and possibly (with C), the dreaded segmentation fault.

For dynamic memory allocation, whereas C++ had new, new[], delete, delete[], C has malloc, calloc, free (memory and contiguous allocation, respectively).

We'll see more about those later.


C-Strings

Recall that in C, there is no pervasive String class -- text is merely treated as arrays of null terminated characters.

Null termination means that the null byte \0 indicates the end of a string in some memory block reserved for it. This might mean that memory is reserved for the text after the null byte, but this space is essentially ignored.


POSIX Syscalls in C


Let's try our first syscalls in C! Getting the current working directory, and then writing it to stdout.

Use the link to the Unix System calls below and then use the SYS_getcwd syscall to retrieve the current working directory, and then print it to stdout using SYS_write.
List of System Calls

  #include <unistd.h>
  #include <sys/syscall.h>
  #include <string.h>
  
  int main (int argc, char** argv) {
      // Create a "buffer", i.e., a local pointer to an array
      // of characters reserved with enough room for 1024
      char buffer[1024];
      
      // Make the SYS_getcwd system call -- see what parameters
      // it expects in the link above!
      syscall(SYS_getcwd, buffer);
      
      // Clean up the output to have a newline at the end:
      int buffLen = strlen(buffer); // strlen = # of chars until \0
      buffer[buffLen]   = '\n';
      buffer[buffLen+1] = '\0';     // Remember to null terminate!
      
      // Use the write syscall to print it!
      // (fd = 1 for stdout)
      syscall(SYS_write, 1, buffer, strlen(buffer));
  }

Next time, we'll do some more practice together in class! Until then...



  PDF / Print