Signup/Sign In

Introduction to System Calls

To understand system calls, first one needs to understand the difference between kernel mode and user mode of a CPU. Every modern operating system supports these two modes.

Modes supported by the operating system

Modes supported by the operating system


Kernel Mode

  • When CPU is in kernel mode, the code being executed can access any memory address and any hardware resource.
  • Hence kernel mode is a very privileged and powerful mode.
  • If a program crashes in kernel mode, the entire system will be halted.

User Mode

  • When CPU is in user mode, the programs don't have direct access to memory and hardware resources.
  • In user mode, if any program crashes, only that particular program is halted.
  • That means the system will be in a safe state even if a program in user mode crashes.
  • Hence, most programs in an OS run in user mode.

System Call

When a program in user mode requires access to RAM or a hardware resource, it must ask the kernel to provide access to that resource. This is done via something called a system call.

When a program makes a system call, the mode is switched from user mode to kernel mode. This is called a context switch.

Then the kernel provides the resource which the program requested. After that, another context switch happens which results in change of mode from kernel mode back to user mode.

Generally, system calls are made by the user level programs in the following situations:

  • Creating, opening, closing and deleting files in the file system.
  • Creating and managing new processes.
  • Creating a connection in the network, sending and receiving packets.
  • Requesting access to a hardware device, like a mouse or a printer.

In a typical UNIX system, there are around 300 system calls. Some of them which are important ones in this context, are described below.


Fork()

The fork() system call is used to create processes. When a process (a program in execution) makes a fork() call, an exact copy of the process is created. Now there are two processes, one being the parent process and the other being the child process.

The process which called the fork() call is the parent process and the process which is created newly is called the child process. The child process will be exactly the same as the parent. Note that the process state of the parent i.e., the address space, variables, open files etc. is copied into the child process. This means that the parent and child processes have identical but physically different address spaces. The change of values in parent process doesn't affect the child and vice versa is true too.

Both processes start execution from the next line of code i.e., the line after the fork() call. Let's look at an example:

// example.c
#include <stdio.h>
void main() 
{
    int val;  
    val = fork();   // line A
    printf("%d", val);  // line B
}

When the above example code is executed, when line A is executed, a child process is created. Now both processes start execution from line B. To differentiate between the child process and the parent process, we need to look at the value returned by the fork() call.

The difference is that, in the parent process, fork() returns a value which represents the process ID of the child process. But in the child process, fork() returns the value 0.

This means that according to the above program, the output of parent process will be the process ID of the child process and the output of the child process will be 0.


Exec()

The exec() system call is also used to create processes. But there is one big difference between fork() and exec() calls. The fork() call creates a new process while preserving the parent process. But, an exec() call replaces the address space, text segment, data segment etc. of the current process with the new process.

It means, after an exec() call, only the new process exists. The process which made the system call, wouldn't exist.

There are many flavors of exec() in UNIX, one being exec1() which is shown below as an example:

// example2.c
#include <stdio.h>
void main() 
{
    execl("/bin/ls", "ls", 0);      // line A
    printf("This text won't be printed unless an error occurs in exec().");
}

As shown above, the first parameter to the execl() function is the address of the program which needs to be executed, in this case, the address of the ls utility in UNIX. Then it is followed by the name of the program which is ls in this case and followed by optional arguments. Then the list should be terminated by a NULL pointer (0).

When the above example is executed, at line A, the ls program is called and executed and the current process is halted. Hence the printf() function is never called since the process has already been halted. The only exception to this is that, if the execl() function causes an error, then the printf() function is executed.