3. Processes
- Each process is identified by its unique process ID
- Every process has a parent process.
- Processes are arranged in a tree, with the init process at its root
- A program can obtain the process ID with getpid() and can obtain the process ID of its parent process with the getppid().
#include <stdio.h>
#include <unistd.h>
int main ()
{
printf (“The process ID is %d\n”, (int) getpid ());
printf (“The parent process ID is %d\n”, (int) getppid ());
return 0;
}
- The ps command displays the processes that are running on your system.
- You can kill a running process with the kill command.
3.1 Creating Processes
3.1.1. Using system
- The system function provides an easy way to execute a command from within a program.
#include <stdlib.h>
int main ()
{
int return_value;
return_value = system (“ls -l /”);
return return_value;
}
3.1.2. Using fork and exec
- When a program calls fork, a duplicate process, called the child process, is created.
- The parent process continues executing the program from the point that fork was called.
- The child process, too, executes the same program from the same place.
- The return value in the parent process is the process ID of the child.
- The return value in the child process is zero.
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main ()
{
pid_t child_pid;
printf (“the main program process ID is %d\n”, (int) getpid ());
child_pid = fork ();
if (child_pid != 0) {
printf (“this is the parent process, with id %d\n”, (int) getpid ());
printf (“the child’s process ID is %d\n”, (int) child_pid);
}
else
printf (“this is the child process, with id %d\n”, (int) getpid ());
return 0;
}
- The exec functions replace the program running in a process with another program.
- Functions that contain the letter p in their names (execvp and execlp) accept a program name.
- Functions that don’t contain the p must be given the full path.
- Functions that contain the letter v in their names (execv, execvp, and execve) accept the argument list as a vector.
- Functions that contain the letter l (execl, execlp, and execle) accept the argument list as a list.
- Functions that contain the letter e in their names (execve and execle) accept an array of environment variables.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
/* Spawn a child process running a new program. PROGRAM is the name of the program to run; the path will be searched for this program. ARG_LIST is a NULL-terminated list of character strings to be passed as the program’s argument list. Returns the process ID of the spawned process. */
int spawn (char* program, char** arg_list)
{
pid_t child_pid;
/* Duplicate this process. */
child_pid = fork ();
if (child_pid != 0)
/* This is the parent process. */
return child_pid;
else {
/* Now execute PROGRAM, searching for it in the path. */
execvp (program, arg_list);
/* The execvp function returns only if an error occurs. */
fprintf (stderr, “an error occurred in execvp\n”);
abort ();
}
}
int main ()
{
/* The argument list to pass to the “ls” command. */
char* arg_list[] = {
“ls”, /* argv[0], the name of the program. */
“-l”,
“/”,
NULL /* The argument list must end with a NULL. */
};
/* Spawn a child process running the “ls” command. Ignore the returned child process ID. */
spawn (“ls”, arg_list);
printf (“done with main program\n”);
return 0;
}
3.2 Signals
- A signal is a special message sent to a process.
- When a process receives a signal, it processes the signal immediately, without finishing the current function or even the current line of code
- The Linux system sends signals to processes in response to specific conditions.
- SIGBUS (bus error),
- SIGSEGV (segmentation violation),
- SIGFPE (floating point exception)
- A process may also send a signal to another process.
- End another process by sending it a SIGTERM or SIGKILL signal
- Send a command to a running program.Two “userdefined” signals are reserved for this purpose: SIGUSR1 and SIGUSR2.
- The sigaction function can be used to set a signal disposition.
- SIG_DFL, which specifies the default disposition for the signal.
- SIG_IGN, which specifies that the signal should be ignored.
- A pointer to a signal-handler function.
- Because signals are asynchronous, you should avoid performing any I/O operations or calling most library and system functions from signal handlers.
- A signal handler should perform the minimum work necessary to respond to the signal.
- It is possible for a signal handler to be interrupted by the delivery of another signal.
- If you use a global variable to flag a signal from a signal-handler function, it should be of the special type sig_atomic_t.
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
sig_atomic_t sigusr1_count = 0;
void handler(int signal_number)
{
++sigusr1_count;
}
int main(int argc, char* argv[])
{
printf("the process ID is %d\n", (int)getpid());
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = &handler;
sigaction(SIGUSR1, &sa, NULL);
int i = 0;
while(i < 100)
{
sleep(1);
i++;
}
printf("SIGUSR was raised %d times\n", sigusr1_count);
return 0;
}
- Compile the above code to program sigusr1
gcc -o sigusr1 sigusr1.c
- Run the program
[liuchao@localhost Signal]$ ./sigusr1
the process ID is 3401
- From another terminal, use ps to see the pid of sigusr1
[liuchao@localhost ~]$ ps -a
PID TTY TIME CMD
3401 pts/1 00:00:00 sigusr1
3403 pts/3 00:00:00 ps
- Send many sigusr1 signals to the process id.
[liuchao@localhost ~]$ kill -s SIGUSR1 3401
[liuchao@localhost ~]$ kill -s SIGUSR1 3401
[liuchao@localhost ~]$ kill -s SIGUSR1 3401
[liuchao@localhost ~]$ kill -s SIGUSR1 3401
[liuchao@localhost ~]$ kill -s SIGUSR1 3401
- After the process finish.
[liuchao@localhost Signal]$ ./sigusr1
the process ID is 3401
SIGUSR was raised 5 times
3.3 Process Termination
- A process terminates in one of two ways
- The executing program calls the exit function, or the program’s main function returns.
- A process may also terminate abnormally, in response to a signal.
- SIGINT for ctrl + C
- SIGTERM for kill command
- SIGABRT for abort function
- SIGKILL ends a process immediately and cannot be blocked or handled by a program.
- Any of these signals can be sent using the kill command
% kill -KILL pid
- To send a signal from a program, use the kill function.
kill (child_pid, SIGTERM);
- wait blocks the calling process until one of its child processes exits (or an error occurs).
- The waitpid function can be used to wait for a specific child process to exit instead of any child process.
- The wait3 function returns CPU usage statistics about the exiting child process
- wait4 function allows you to specify additional options about which processes to wait for.
int main ()
{
int child_status;
/* The argument list to pass to the “ls” command. */
char* arg_list[] = {
“ls”, /* argv[0], the name of the program. */
“-l”,
“/”,
NULL /* The argument list must end with a NULL. */
};
/* Spawn a child process running the “ls” command. Ignore the returned child process ID. */
spawn (“ls”, arg_list);
/* Wait for the child process to complete. */
wait (&child_status);
if (WIFEXITED (child_status))
printf (“the child process exited normally, with exit code %d\n”, WEXITSTATUS (child_status));
else
printf (“the child process exited abnormally\n”);
return 0;
}
- A zombie process is a process that has terminated but has not been cleaned up yet.
- It is the responsibility of the parent process to clean up its zombie children with wait function.
- If the parent does not clean up its children, they stay around in the system as zombie processes.
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main ()
{
pid_t child_pid;
/* Create a child process. */
child_pid = fork ();
if (child_pid > 0) {
/* This is the parent process. Sleep for a minute. */
sleep (60);
}
else {
/* This is the child process. Exit immediately. */
exit (0);
}
return 0;
}
- % ps -e -o pid,ppid,stat,cmd
3824 2888 S+ ./zombie
3825 3824 Z+ [zombie] <defunct>
- When a program exits, its children are inherited by a special process, the init program which automatically cleans up any zombie child processes that it inherits.
- When a child process terminates, Linux sends the parent process the SIGCHLD signal.
- An easy way to clean up child processes is by handling SIGCHLD.
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
sig_atomic_t child_exit_status;
void clean_up_child_process (int signal_number)
{
/* Clean up the child process. */
int status;
wait (&status);
/* Store its exit status in a global variable. */
child_exit_status = status;
}
int main ()
{
/* Handle SIGCHLD by calling clean_up_child_process. */
struct sigaction sigchld_action;
memset (&sigchld_action, 0, sizeof (sigchld_action));
sigchld_action.sa_handler = &clean_up_child_process;
sigaction (SIGCHLD, &sigchld_action, NULL);
/* Now do things, including forking a child process. */
/* ... */
return 0;
}