Whirlwind Tour of Process APIs


As we defined previously, we can create a new process with the fork or clone API function. But in fact, we create a new process every time we execute a command or start a program. Consider the simple program shown in Listing 13.1.

Listing 13.1: First Process Example (on the CD-ROM at ./source/ch13/process.c )
start example
  1  :       #include <stdio.h>  2:  #include <unistd.h>  3:  #include <sys/types.h>  4:   5:  int main()  6:  {  7:  pid_t myPid;  8:  pid_t myParentPid;  9:  gid_t myGid;  10:  uid_t myUid;  11:   12:  myPid =  getpid();   13:  myParentPid = getppid();  14  :         myGid = getgid();  15  :         myUid = getuid();  16  :  17  :         printf("my process id is %d\n", myPid);  18  :  19  :         printf("my parents process id is %d\n", myParentPid);  20  :  21  :         printf("my group id is %d\n", myGid);  22  :  23  :         printf("my user id is %d\n", myUid);  24  :  25  :         return 0;  26  :       } 
end example
 

Every process in GNU/Linux has a unique identifier called a process ID (or pid). Every process also has a parent (except for the init process). In Listing 13.1, we use the getpid() function to get the current process ID and the getppid() function to retrieve the process s parent ID. Then we grab the group ID and the user ID using getuid() and getgid() .

If we were to compile and then execute this application, we d see the following:

 $  ./process  my process id is 10932     my parents process id is 10795     my group id is 500     my user id is 500     $ 

We see our process ID is 10932, and our parent is 10795 (our bash shell). If we execute the application again, we see:

 $  ./process  my process id is 10933     my parents process id is 10795     my group id is 500     my user id is 500     $ 

Note that our process ID has changed, but all other values have remained the same. This is expected, as the only thing we ve done is created a new process that performs its I/O and then exits. Each time a new process is created, a new pid is allocated to it.

Creating a Subprocess with fork

Let s now move on to the real topic of this chapter, creating new processes within a given process. The fork API function is the most common method to achieve this.

The fork call is an oddity when you consider what is actually occurring. When the fork API function returns, the split occurs, and the return value from fork identifies in which context the process is running. Consider the following code snippet:

 pid_t pid;     ...     pid =  fork  ();     if (pid > 0) {       /* Parent context, child is pid */     } else if (pid == 0) {       /* Child context */     } else {       /* Parent context, error occurred, no child created */     } 

We see here three possibilities from the return of the fork call. When the return value of fork is greater than zero, then we re in the parent context and the value represents the pid of the child. When the return value is zero, then we re in the child process s context. Finally, any other value (less than zero) represents an error and is performed within the context of the parent.

Let s now look at a sample application of fork (shown in Listing 13.2). This working example illustrates the fork call, identifying the contexts. At line 11, we call fork to split our process into parent and child. Both the parent and child emit some text to standard-out in order to see each execution. Note that a shared variable ( role ) is updated by both parent and child and emitted at line 45.

Listing 13.2: Working Example of the fork Call (on the CD-ROM at ./source/ch13/smplfork.c )
start example
  1:  #include <sys/types.h>  2:  #include <unistd.h>  3:  #include <errno.h>  4:   5:  int main()  6:  {  7:  pid_t ret;  8:  int   status, i;  9:  int   role = -1;  10:   11:  ret =  fork  ();  12:   13:  if (ret > 0) {  14:   15:  printf("Parent: This is the parent process (pid %d)\n",  16:   getpid  ());  17:   18:  for (i = 0 ; i < 10 ; i++) {  19:  printf("Parent: At count %d\n", i);  20:  sleep(1);  21:  }  22:   23:  ret =  wait  (&status);  24:   25:  role = 0;  26:   27:  } else if (ret == 0) {  28:   29:  printf("Child: This is the child process (pid %d)\n",  30:   getpid  ());  31:   32:  for (i = 0 ; i < 10 ; i++) {  33:  printf("Child: At count %d\n", i);  34:  sleep(1);  35:  }  36:   37:  role = 1;  38:   39:  } else {  40:   41:  printf("Parent: Error trying to fork() (%d)\n", errno);  42:   43:  }  44:   45:  printf("%s: Exiting...\n",  46:  ((role == 0) ? "Parent" : "Child"));  47:   48:  return 0;  49:  } 
end example
 

The output of the application shown in Listing 13.2 is shown in the following. We see that the child is started and in this case immediately emits some output (its pid and the first count line). The parent and the child then switch off from the GNU/Linux scheduler, each sleeping for one second and emitting a new count .

 # ./smplfork     Child: This is the child process (pid 11024)     Child: At count 0     Parent: This is the parent process (pid 11023)     Parent: At count 0     Parent: At count 1     Child: At count 1     Parent: At count 2     Child: At count 2     Parent: At count 3     Child: At count 3     Parent: At count 4     Child: At count 4     Parent: At count 5     Child: At count 5     Child: Exiting...     Parent: Exiting...     # 

At the end, we see the role variable used to emit the role of the process (parent or child). In this case, while the role variable was shared between the two processes, once the write occurs, the memory is split, and each process has its own variable, independent of the other. How this occurs is really unimportant. What s important to note is that each process has a copy of its own set of variables .

Synchronizing with the Creator Process

One element of Listing 13.2 was ignored, but we ll dig into it now. At line 23, the wait function was called within the context of the parent. The wait function suspends the parent until the child exits. If the wait function is not called by the parent and the child exits, the child becomes what is known as a zombie process ( neither alive nor dead). It can be problematic to have these processes lying around (due to the resources that they waste), so handling child exit is necessary. Note that if the parent exits first, the children that have been spawned are inherited by the init process.

Note  

Another way to avoid zombie processes is to tell the parent to ignore child exit signals when they occur. This can be achieved using the signal API function, which we explore in the next section, Catching a Signal. In any case, once the child has stopped , any system resources that were used by the process are immediately released.

The first two methods that we ll discuss for synchronizing the exit of a child process are the wait and waitpid API functions. The waitpid API function provides greater control over the wait process; here we ll look exclusively at the wait API function.

The wait function suspends the caller (in this case, the parent) awaiting the exit of the child. Once the child exits, the integer value reference (passed to wait ) is filled in with the particular exit status. Sample use of the wait function, including parsing of the successful status code, is shown in the following code snippet:

 int status;     pid_t pid;     ...     pid =  wait  (&status);     if (  WIFEXITED  (status)) {       printf("Process %d exited normally\n", pid);     } 

The wait function can set other potential status values, which we ll investigate in the wait section, later in this chapter.

Catching a Signal

A signal is fundamentally an asynchronous callback for processes in GNU/Linux. We can register to receive a signal when an event occurs for a process or register to ignore signals when a default action exists. GNU/Linux supports a variety of signals, which we ll cover later. Signals are an important topic here in process management because they allow processes to communicate with one another.

To catch a signal, we provide a signal handler for the process (a kind of callback function) and the signal that we re interested in for this particular callback. Let s now look at an example of registering for a signal. In this example, we ll register for the SIGINT signal. This particular signal is used to identify that a Ctrl+C was received.

Our main program in Listing 13.3 (lines 14 “24) begins with registering our callback function (also known as the signal handler). We use the signal API function to register our handler (at line 17). We specify first the signal of interest and then the handler function that will react to the signal. At line 21, we pause, which suspends the process until a signal is received.

Our signal handler is shown at Listing 13.3 at lines 6 “12. We simply emit a message to stdout and then flush it to ensure that it s been emitted. We return from our signal handler, which allows our main function to continue from the pause call and exit .

Listing 13.3: Registering for Catching a Signal (on the CD-ROM at ./source/ch13/_sigcatch.c )
start example
  1:  #include <stdio.h>  2:  #include <sys/types.h>  3:  #include <signal.h>  4:  #include <unistd.h>  5:   6:  void catch_ctlc(int sig_num)  7:  {  8:  printf("Caught Control-C\n");  9:  fflush(stdout);  10:   11:  return;  12:  }  13:   14:  int main()  15:  {  16:   17:   signal  (SIGINT, catch_ctlc);  18:   19:  printf("Go ahead, make my day.\n");  20:   21:   pause  ();  22:   23:  return 0;  24:  } 
end example
 

Raising a Signal

In the previous example, we illustrated a process receiving a signal. We can also have a process send a signal to another process using the kill API function. The kill API function takes a process ID (to whom the signal is to be sent) and the signal to send.

Let s look at a simple example of two processes communicating via a signal. This will use the classic parent/child process creation via fork (see Listing 13.4).

At lines 8 “13, we declare our signal handler. This handler is very simple, as shown, and simply emits some text to stdout indicating that the signal was received, in addition to the process context (identified by the pid).

Our main (lines 15 “61) is a simple parent/child fork example. Our parent context (starting at line 25) installs our signal handler and then pauses (awaiting the receipt of a signal). It then continues by awaiting the exit of the child process.

The child context (starting at line 39) sleeps for one second (allowing the parent context to execute and install its signal handler) and then raises a signal. Note that we use the kill API function ( line 47 ) to direct the signal to the parent process ID (via getppid ). The signal we use is SIGUSR1 , which is a user-definable signal. Once the signal has been raised, the child sleeps another two seconds and then exits.

Listing 13.4: Raising a Signal from a Child to a Parent Process (on the CD-ROM at ./source/ch13/raise.c )
start example
  1:  #include <stdio.h>  2:  #include <sys/types.h>  3:  #include <sys/wait.h>  4:  #include <unistd.h>  5:  #include <signal.h>  6:  #include <errno.h>  7:   8:  void usr1_handler(int sig_num)  9:  {  10:   11:  printf("Parent (%d) got the SIGUSR1\n", getpid());  12:   13:  }  14:   15:  int main()  16:  {  17:  pid_t ret;  18:  int   status;  19:  int   role = -1;  20:   21:  ret =  fork  ();  22:   23:  if (ret > 0) {                /* Parent Context */  24:   25:  printf("Parent: This is the parent process (pid %d)\n",  26:   getpid  ());  27:   28:   signal  (SIGUSR1, usr1_handler);  29:   30:  role = 0;  31:   32:   pause  ();  33:   34:  printf("Parent: Awaiting child exit\n");  35:  ret =  wait  (&status);  36:   37:  } else if (ret == 0) {        /* Child Context */  38:   39:  printf("Child: This is the child process (pid %d)\n",  40:   getpid  ());  41:   42:  role = 1;  43:   44:  sleep(1);  45:   46:  printf("Child: Sending SIGUSR1 to pid %d\n",  getppid  ());  47:   kill  (  getppid  (), SIGUSR1);  48:   49:  sleep(2);  50:   51:  } else {                      /* Parent Context  Error */  52:   53:  printf("Parent: Error trying to fork() (%d)\n", errno);  54:   55:  }  56:   57:  printf("%s: Exiting...\n",  58:  ((role == 0) ? "Parent" : "Child"));  59:   60:  return 0;  61:  } 
end example
 

While this was probably self-explanatory, looking at its output can be beneficial to understanding exactly what s going on. The output for the application shown in Listing 13.4 is as follows :

 $ ./raise     Child: This is the child process (pid 14960)     Parent: This is the parent process (pid 14959)     Child: Sending SIGUSR1 to pid 14959     Parent (14959) got the SIGUSR1     Parent: Awaiting child exit     Child: Exiting...     Parent: Exiting...     $ 

We see that the child performs its first printf first (the fork gave control of the CPU to the child first). The child then sleeps, allowing the parent to perform its first printf , install the signal handler, and then pause awaiting a signal. Now that the parent has suspended , the child can then execute again (once the 1-second sleep has finished). It emits its message, indicating that the signal is being raised, and then raises the signal using the kill API function. The parent then performs the printf within the signal handler (in the context of the parent process as shown by the process ID) and then suspends again awaiting child exit via the wait API function. The child process can then execute again, and once the 2-second sleep has finished, it exits, releasing the parent from the wait call so that it too can exit.

It s fairly simple to understand, but it s a powerful mechanism for coordination and synchronization between processes. The entire thread is shown graphically in Figure 13.1. This illustrates the coordination points that exist within our application (shown as dashed horizontal lines from the child to the parent).

click to expand
Figure 13.1: Graphical illustration of Listing 13.4.
Note  

If we re raising a signal to ourselves (same process), we could also use the raise API function. This takes the signal to be raised but no process ID argument (because it s automatically getpid ).




GNU/Linux Application Programming
GNU/Linux Application Programming (Programming Series)
ISBN: 1584505680
EAN: 2147483647
Year: 2006
Pages: 203
Authors: M. Tim Jones

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net