System Calls
Objective
- Perform the
exec
system call for replacing process execution code. - Execute the
exit
system call for process termination. - Differentiate between
wait
andwaitpid
system calls. - Employ the learned system calls to control processes.
- Understand the concept of orphan and zombie processes.
Summary
exec()
exit()
system()
wait()
waitpid()
System Calls
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 toexec
. -
We will cover only one of the
exec
family, which isexecl
. -
All parameters for
execl
are character pointers. A generalexecl
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:
#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:
#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:
#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 isNULL
then the argument is simply ignored. If, however,wait()
is passed a valid pointer,status
will contain useful status information whenwait()
returns. Normally this information will be the exit-status of the child passed throughexit()
.
Example 3
Compile and run the following program, which demonstrates the use of the wait()
system call:
#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 isWNOHANG
, this putswaitpid()
in a loop. IfWNOHANG
is set,waitpid()
will return0
if the child has not yet terminated.
Example 4
Compile and run the following program, which demonstrates the use of the waitpid()
system call:
#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 thesystem
command. system
’s signature isint 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 theexec()
family does not.
Exercise 6
-
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; }
-
Run your program and save its output to a file.
-
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:
- Modify the code from 6 Process Management: Example 4 by adding
sleep(15)
as the last statement in the parent’s block. Also modify thesleep(5)
in the child’s block tosleep(500)
, that is, keep the child running for500
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?
- Using
- 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? - 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:
#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'
.