7 System Calls

System Calls

Objective

  • Perform the exec system call for replacing process execution code.
  • Execute the exit system call for process termination.
  • Differentiate between wait and waitpid system calls.
  • Employ the learned system calls to control processes.
  • Understand the concept of orphan and zombie processes.

exec() exit() system() wait() waitpid()

System Calls

Linux Kernel Structure

exec

  • A process may replace its current code, data, and stack with those of another executable by using one of the exec() family of system calls.

  • When a process executes an exec(), its PID and PPID numbers stay the same; only the code that the process is executing changes.

  • exec transform the calling process by loading a new program into its memory space.

  • Unlike fork, there is no return from a successful call to exec.

  • We will cover only one of the exec family, which is execl.

  • All parameters for execl are character pointers. A general execl prototype is:

    execl(char *path, char *arg0, char *arg1, ..., char *argn, NULL)
  • execl requires the absolute or relative path of the executable.

  • Because the argument list is of arbitrary length, it must be terminated by a null pointer to mark the end of the list.

Example 1

Compile and run the following program, which demonstrates the use of the execl system call:

example-01.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
int main(void) {
  printf("Process %d with parent %d\n", getpid(), getppid());
  printf("Executing 'ps' using execl()\n\n");
 
  execl("/bin/ps", "ps", NULL);
 
  perror("execl failed to run 'ps'"); /* call has failed */
  return EXIT_SUCCESS;
}

execl stands for execute and leave, which means that the process will be executed and then terminated. Both 0 and NULL can be used to end the list of arguments to the execl() system call, as they are both equivalent to the null pointer.

Exercise 1

Write a program to show that when a process executes an exec(), its PID and PPID numbers remain the same, however, only the process executing the code will change.

exit

A process may terminate at any time by executing the exit() system call. When a child process terminates with exit(), it sends

  • SIGCHLD signal to wait for its termination status to be accepted by its parent.
  • A process that is waiting for its parent process to accept its return code is called zombie process.
  • A parent accepts the termination code of its child by executing wait().
  • A status of 0 means normal termination, any other value indicates an error or abnormal termination.

Example 2

Compile and run the following program, which demonstrates the use of the exit() system call:

example-02.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
int main(void) {
  printf("Just one process so far\n");
  printf("Forking...\n");
 
  pid_t pid = fork();
 
  if (pid < 0) {
    printf("fork failed\n");
    exit(1); /* terminating with error */
  } else if (pid == 0) {
    printf("I'm the child\n");
    exit(0); /* terminating normally */
  } else {
    printf("I'm the parent with child ID = %d\n", pid);
  }
 
  printf("%d terminated\n", getpid());
  printf("List of processes:\n");
  system("ps -f");
  return EXIT_SUCCESS;
}

Exercise 2

Compile and run the following program, which demonstrates the use of the exit() system call:

exercise-02.c
#include <stdio.h>
#include <stdlib.h>
 
int main(void) {
  printf("I am exiting with return code 42\n");
  exit(42);
}

Note: The shell stores the termination code of the the last command in $?:

> echo $?
42

wait

  • Causes a process to wait until one of its children terminates.
  • wait() returns the PID of the child that terminated.
  • If a process executes wait() and has no children, wait() returns -1.
  • wait() takes one argument, int *status, a pointer to an integer. If the pointer is NULL then the argument is simply ignored. If, however, wait() is passed a valid pointer, status will contain useful status information when wait() returns. Normally this information will be the exit-status of the child passed through exit().

Example 3

Compile and run the following program, which demonstrates the use of the wait() system call:

example-03.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
 
int main(void) {
  pid_t pid = fork();
 
  if (pid == 0) {
    /* child */
    printf("Child: sleeping...\n");
    sleep(5);
    printf("Child: waking... and exiting\n");
    exit(0);
  } else if (pid > 0) {
    printf("Parent: waiting for child\n");
    wait(NULL);
    printf("Parent: child woke up\n");
  }
 
  return EXIT_SUCCESS;
}

Exercise 3

Write a C program to demonstrate that the wait() system call returns the PID of the child that terminated.

Exercise 4

Write a C program that forks multiple children and waits for all of them to terminate.

Exercise 5

Modify the program from Exercise 4 to perform the following:

  • The program will request the user to enter the number of times a process will fork.
  • The parent process will wait for all its own child processes, displaying the PID and termination code for each child.
  • The termination codes must be generated randomly using rand().

If we fork a process n times in sequence, we should produce 2n - 1 child processes. Why does the output for the above modified program show only n child processes?

waitpid

To wait for a particular child, we can use the following waitpid() system call:

pid_t waitpid(pid_t pid, int *status, int options);
  • pid holds the process id.
  • status holds the status of the child.
  • options takes a variety of options. The most useful of these options is WNOHANG, this puts waitpid() in a loop. If WNOHANG is set, waitpid() will return 0 if the child has not yet terminated.

Example 4

Compile and run the following program, which demonstrates the use of the waitpid() system call:

example-04.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
 
int main(void) {
  pid_t pid = fork();
 
  if (pid < 0) {
    printf("fork failed\n");
    exit(1);
  } else if (pid == 0) {
    printf("Child %d is sleeping...\n", getpid());
    sleep(5);
    exit(0);
  }
 
  /* this is the parent side */
  int status;
  while (waitpid(pid, &status, WNOHANG) == 0) {
    printf("%d Still waiting...\n", getpid());
    sleep(1);
  }
 
  /* test to see how the child died */
  if (WIFEXITED(status)) {
    int exit_status = WEXITSTATUS(status);
    printf("Exit status from %d was %d \n", pid, exit_status);
  }
 
  return EXIT_SUCCESS;
}

The value returned to the parent via exit() is stored in the higher-order eight bits of the integer status. Therefore the lower-order eight bits must be zero, WIFEXITED() tests to see if this is the case.

The WEXITSTATUS() macro returns the value stored in the higher-order bits of status. If WIFEXITED() returns 0 then it means that the child was stopped by another process by a communication method called signal, which will be discussed later. WEXITSTATUS() and WIFEXITED() are defined in <sys/wait.h>.

Note that the call wait(&status) is equivalent to waitpid(-1, &status, 0).

Function Calls

The main difference between system calls and function calls is that:

  • A system call is a request for the kernel to access a resource, for instance, open(), close(), exec(), fork(), read(), write(), and other calls.
  • A function/library call is a request made by a program to perform a specific task, for example, printf(), scanf(), fopen(), system(), and other functions.

system

  • The system() function will invoke the command processor to execute a command; it will call the shell. If the command execution is terminated, the processor will give the control back to the program that has called the system command.
  • system’s signature is int system(const char *command);
  • The function returns the exit code value of the last executed command.
  • system() uses a combination of system calls under the hood. It causes a child process to be created, while the exec() family does not.

Exercise 6

  1. Compile and run the following program:

    exercise-06.c
    #include <stdio.h>
    #include <stdlib.h>
     
    int main(void) {
      printf("Executing the command 'ls'...\n");
      int value = system("ls");
     
      printf("The returned value is: %d\n", value);
      return 0;
    }
  2. Run your program and save its output to a file.

  3. Test the program with a non-valid command passed to the system() function. What value will it return?

Process Status

Orphan Processes

If a parent process dies earlier than its child, the child’s process is adopted by the parent process with PID 1 (the init process). Usually, the PPID of system processes running directly after the system boots up is 1.

Exercise 7

To demonstrate the creation of an orphan process, perform the following:

  1. Modify the code from 6 Process Management: Example 4 by adding sleep(15) as the last statement in the parent’s block. Also modify the sleep(5) in the child’s block to sleep(500), that is, keep the child running for 500 seconds, even after its parent dies. Compile and run the program as a background job using ./exercise-7 &:
    • Using ps -f: What are the child’s PID and PPID?
    • Based on your program: What are the child’s PID and PPID?
  2. When you get a message that the parent process terminated, again issue the ps -f command and note the new parent of the child process. What is the new PPID of the child?
  3. Now, kill the child process.

Zombie Processes

The process that terminates cannot leave the system until its parent accepts its return code. If the child process’s parent is alive but never executes a wait, the process’s return code will not be accepted and the child process will return a zombie.

Example 5

Compile and run the following program, which demonstrates the creation of a zombie process:

example-05.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
 
int main(void) {
  pid_t pid = fork();
 
  if (pid != 0) {
    printf("Parent will never stop\n");
    while (1) {
      sleep(1000);
    }
    /* never terminate and never execute a wait() */
    wait(NULL);
  } else {
    printf("Child is waiting for the parent \n");
    exit(42);
  }
 
  return EXIT_SUCCESS;
}

Hint: List zombies with ps -e -o stat,pid,ppid,cmd | grep '^Z'.

Resources