Traditional Process API


We ve looked at a number of different API functions that relate to the GNU/Linux process model. Let s now dig further into these functions (and others) and explore them in greater detail. Table 13.1 provides a list of the functions that we ll explore in the remainder of this section, including their uses.

Table 13.1: Traditional Process and Related APIs

API Function

Use

fork

Create a new child process

wait

Suspend execution until a child processes exits

waitpid

Suspend execution until a specific child process exits

signal

Install a new signal handler

pause

Suspend execution until a signal is caught

kill

Raise a signal to a specified process

raise

Raise a signal to the current process

exec

Replace the current process image with a new process image

exit

Cause normal program termination of the current process

We ll address each of these functions in detail in the remainder of this chapter, illustrated in sample applications.

fork

The fork API function provides the means to create a new child subprocess from an existing parent process. The new child process is identical to the parent process in almost every way. Some differences include the process ID (a new ID for the child) and that the parent process ID is set to the parent. File locks and signals that are pending to the parent are not inherited by the child process. The prototype for the fork function is defined as follows :

 pid_t  fork  (void); 

The fork API function takes no arguments and returns a pid (process identifier). The fork call has a very unique structure in that the return value identifies the context in which the process is running. If the return value is zero, then the current process is the newly created child process. If the return value is greater than zero, then the current process is the parent, and the return value represents the process ID of the child.

This is illustrated in the following snippet:

 #include <sys/types.h>     #include <unistd.h>     #include <errno.h>     ...     pid_t ret;     ret =  fork  ();     if        (ret > 0) {       /* Parent Process */       printf("My pid is %d and my childs is %d\n",  getpid  (), ret);     } else if (ret == 0) {       /* Child Process */       printf("My pid is %d and my parents is %d\n",  getpid  (),  getppid  ());     } else {       /* Parent Process  error */       printf("An error occurred in the fork (%d)\n", errno);     } 

Within the fork() call, the process is duplicated , and then control is returned to the unique process (parent and child). If the return value of fork is less than zero, then an error has occurred. The errno value will represent either EAGAIN or ENOMEM . Both errors arise from a lack of available memory.

The fork API function is very efficient in GNU/Linux because of its unique implementation. Rather than copy the page tables for the memory when the fork takes place, the parent and child share the same page tables but are not permitted to write to them. When a write takes place to one of the shared page tables, the page table is copied for the writing process so that it has its own copy. This is called copy-on-write in GNU/Linux and permits the fork to take place very quickly. Only as writes occur to the share data memory does the segregation of the page tables take place.

wait

The purpose of the wait API function is to suspend the calling process until a child process (created by this process) exits or until a signal is delivered. If the parent isn t currently waiting on the child to exit, the child exits, and the child process becomes a zombie process.

The wait function provides an asynchronous mechanism as well. If the child process exits before the parent has had a chance to call wait , then the child becomes a zombie but then is freed once wait is called. The wait function, in this case, returns immediately.

The prototype for the wait function is defined as:

 pid_t  wait  (int *status); 

The wait function returns the pid of the child that exited, or “1 if an error occurred. The status variable (whose reference is passed into wait as its only argument) returns status information about the child exit . This variable can be evaluated using a number of macros. These macros are listed in Table 13.2.

Table 13.2: Macro Functions to Evaluate wait Status

Macro

Description

WIFEXITED

Nonzero if the child exited normally

WEXITSTATUS

Returns the exit status of the child

WIFSIGNALED

Returns true if child exited due to a signal that wasn t caught by the child

WTERMSIG

Returns the signal number that caused the child to exit (relevant only if WIFSIGNALED was true )

The general form of the status evaluation macro is demonstrated in the following code snippet:

 pid =  wait  (&status);     if        (  WIFEXITED  (status)) {       printf("Child exited normally with status %d\n",  WEXITSTATUS  (status));     } else if (  WIFSIGNALED  (status)) {       printf("Child exited by signal with status %d\n",  WTERMSIG  (status));     } 

In some cases, we re not interested in the exit status of our child processes. In the signal API function discussion, we ll look at a way to ignore this status so that wait is not necessary to be called by the parent to avoid child zombie processes.

waitpid

While the wait API function suspends the parent until a child exits (any child), the waitpid API function suspends until a specific child exits. The waitpid function provides some other capabilities, which we ll explore here. The waitpid function prototype is defined as:

 pid_t  waitpid  (pid_t pid, int *status, int options); 

The return value for waitpid is the process identifier for the child that exited. The return value can also be zero if the options argument was set to WNOHANG and no child process has exited (returns immediately).

The arguments to waitpid are a pid value, a reference to a return status, and a set of options. The pid value can be a child process ID or other values that provide different behaviors. Table 13.3 lists the possible pid values for waitpid .

Table 13.3: Pid Arguments for waitpid

Value

Description

> 0

Suspend until the child identified by the pid value has exited

Suspend until any child exits whose group ID matches that of the calling process

“1

Suspend until any child exits (identical to the wait function)

< “1

Suspend until any child exits whose group ID is equal to the absolute value of the pid argument

The status argument for waitpid is identical to the wait function, except that two new status macros are possible (see Table 13.4). These macros are seen only if the WUNTRACED option is specified.

Table 13.4: Extended Macro Functions for waitpid

Macro

Description

WIFSTOPPED

Returns true if the child process is currently stopped .

WSTOPSIG

Returns the signal that caused the child to stop (relevant only if WIFSTOPPED was nonzero).

The final argument to waitpid is the options argument. Two options are available: WNOHANG and WUNTRACED . WNOHANG , as we discussed, avoids suspension of the parent process and returns only if a child has exited. The WUNTRACED option returns for children that have been stopped and not yet reported .

Let s now look at some examples of the waitpid function. In the first code snippet, we ll fork off a new child process and then await it explicitly (rather than the wait method that waits for any child).

 pid_t child_pid, ret;     int status;     ...     child_pid =  fork  ();     if (child_pid == 0) {       // Child process...     } else if (child_pid > 0) {       ret =  waitpid  (child_pid, &status, 0);       /* Note ret should equal child_pid on success */       if (  WIFEXITED  (status)) {         printf("Child exited normally with status %d\n",  WEXITSTATUS  (status));       }     } 

In this example, we fork off our child and then use waitpid with the child s process ID. Note here that we can use the status macro functions that were defined with wait (as demonstrated with WIFEXITED ). If we didn t want to wait for the child, we could specify WNOHANG as an option. This requires us to call waitpid periodically to handle the child exit :

 ret =  waitpid  (child_pid, &status, WNOHANG); 

The following line awaits a child process exiting the defined group. Note that we negate the group ID in the call to waitpid . Also notable is passing NULL as the status reference. In this case, we re not interested in getting the child s exit status. In any case, the return value is the process ID for the child process that exited.

 pid_t group_id;     ...     ret =  waitpid  (-group_id, NULL, 0); signal 

The signal API function allows us to install a signal handler for a process. The signal handler passed to the signal API function has the form:

 void signal_handler(int signal_number); 

Once installed, the function is called for the process when the particular signal is raised to the process. The prototype for the signal API function is defined as:

 sighandler_t  signal  (int signum, sighandler_t handler); 

where the sighandler_t typedef is:

 typedef void (*sighandler_t)(int); 

The signal function returns the previous signal handler that was installed, which allows the new handler to chain the older handlers together (if necessary).

A process can install handlers to catch signals, and it can also define that signals should be ignored ( SIG_IGN ). To ignore a signal for a process, the following code snippet can be used:

  signal  (SIGCHLD, SIG_IGN); 

Once this particular code is executed, it is not necessary for a parent process to wait for the child to exit using wait or waitpid .

Signal handlers for a process can be of three different types. They can be ignored (via SIG_IGN ), the default handler for the particular signal type ( SIG_DFL ), or a user -defined handler (installed via signal ).

A large number of signals exist for GNU/Linux. They are provided in Table 13.4 “13.18 with their meanings. The signals are split into four groups, based upon default action for the signal.

The first group (terminate) lists the signals whose default action is to terminate the process. The second group (ignore) lists the signals for which the default action is to ignore the signal. The third group (core) lists those signals whose action is to both terminate the process and perform a core dump (generate a core dump file). And finally, the fourth group (stop) stops the process (suspend, rather than terminate).

Table 13.5: GNU/Linux Signals That Default to Terminate

Signal

Description

SIGHUP

Hang up ”commonly used to restart a task

SIGINT

Interrupt from the keyboard

SIGKILL

Kill signal

SIGUSR1

User-defined signal

SIGUSR2

User-defined signal

SIGPIPE

Broken pipe (no reader for write)

SIGALRM

Timer signal (from API function alarm )

SIGTERM

Termination signal

SIGPROF

Profiling timer expired

Table 13.6: GNU/Linux Signals That Default to Ignore

Signal

Description

SIGCHLD

Child stopped or terminated

SIGCLD

Same as SIGCHLD

SIGURG

Urgent data on a socket

Table 13.7: GNU/Linux Signals That Default to Stop

Signal

Description

SIGSTOP

Stop process

SIGTSTP

Stop initiated from TTY

SIGTTIN

Background process has TTY input

SIGTTOU

Background process has TTY output

Table 13.8: GNU/Linux Signals That Default to Core Dump

Signal

Description

SIGQUIT

quit signal from keyboard

SIGILL

Illegal instruction encountered

SIGTRAP

Trace or breakpoint trap

SIGABRT

Abort signal (from API function abort )

SIGIOT

IOT trap, same as SIGABRT

SIGBUS

Bus error (invalid memory access)

SIGFPE

Floating-point exception

SIGSEGV

Segment violation (invalid memory access)

It s important to note that the SIGSTOP and SIGKILL signals cannot be ignored or caught by the application. One other signal not categorized above is the SIGCONT signal, which is used to continue a process if it was previously stopped.

GNU/Linux also supports 32 real-time signals (of POSIX 1003.1-2001). The signals are numbered from 32 ( SIGRTMIN ) up to 63 ( SIGRTMAX ) and can be sent using the sigqueue API function. The receiving process must use sigaction to install the signal handler (discussed later in this chapter) in order to collect other data provided in this signaling mechanism.

Let s now look at a simple application that installs a signal handler at the parent, which is inherited by the child (see Listing 13.4). In this listing, we first declare a signal handler (lines 8 “13) that will be installed by the parent prior to the fork (at line 21). Installing the handler prior to the fork means that child will inherit this signal handler as well.

After the fork (at line 23), the parent and child context emit an identification string to stdout and then call the pause API function (which suspends each process until a signal is received). When a signal is received, the signal handler will print out the context in which it caught the signal (via getpid ) and then either exit (child process) or await the exit of the child (parent process).

Listing 13.5: Signal Demonstration with a Parent and Child Process (on the CD-ROM at ./source/ch13/sigtest.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("Process (%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:   signal  (SIGUSR1, usr1_handler);  22:   23:  ret =  fork  ();  24:   25:  if (ret > 0) {                /* Parent Context */  26:   27:  printf("Parent: This is the parent process (pid %d)\n",  28:   getpid  ());  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:   pause  ();  45:   46:  } else {                      /* Parent Context  Error */  47:   48:  printf("Parent: Error trying to fork() (%d)\n", errno);  49:   50:  }  51:   52:  printf("%s: Exiting...\n",  53:  ((role == 0) ? "Parent" : "Child"));  54:   55:  return 0;  56:  } 
end example
 

Let s now look at the sample output for this application to better understand what happens. Note that neither the parent nor the child raises any signals to each other. We ll take care of sending the signal at the command line, using the kill command.

 #  ./sigtest  &     [1] 20152     # Child: This is the child process (pid 20153)     Parent: This is the parent process (pid 20152)          #  kill -10 20152  Process (20152) got the SIGUSR1     Parent: Awaiting child exit     #  kill -10 20153  Process (20153) got the SIGUSR1     Child: Exiting...     Parent: Exiting...     # 

We begin by running the application (called sigtest ) and placing it in the background (via the & symbol). We see the expected outputs from the child and parent processes identifying that the fork has occurred and that both processes are now active and awaiting signals at the respective pause calls. We use the kill command with the signal of interest (“10, or SIGUSR1 ) and the process identifier to which to send the signal. In this case, we send the first SIGUSR1 to the parent process (20152). The parent immediately identifies receipt of the signal via the signal handler, but note that it executes within the context of the parent process (as identified by the process ID of 20152). The parent then returns from the pause function and awaits the exit of the child via the wait function. We then send another SIGUSR1 signal to the child using the kill command. In this case, we direct the kill command to the child by its process ID (20153). The child also indicates receipt of the signal by the signal handler and in its own context. The child then exits and permits the parent to return from the wait function and exit also.

Despite the simplicity of the signals mechanism, it can be quite a powerful method to communicate with processes in an asynchronous fashion.

pause

The pause function is used to suspend the calling process until a signal is received. Once the signal is received, the calling process returns from the pause function, permitting it to continue. The prototype for the pause API function is:

 int  pause  (void); 

If the process has installed a signal handler for the signal that was caught, then the pause function returns after the signal handler has been called and returns.

kill

The kill API function is used to raise a signal to a process or set of processes. A return of zero indicates that the signal was successfully sent, otherwise “1 is returned. The kill function prototype is:

 int  kill  (pid_t pid, int sig_num); 

The sig_num argument represents the signal to send. The pid argument can be a variety of different values (as shown in Table 13.9).

Table 13.9: Values of pid Argument for kill Function

pid

Description

Signal sent to the process defined by pid

Signal sent to all processes within the process group

1

Signal sent to all processes (except for the init process)

“1

Signal sent to all processes within the process group defined by the absolute value of pid

Some simple examples of the kill function are now explored. We can send a signal to ourselves using the following code snippet:

  kill  (getpid(), SIGHUP); 

The process group allows us to collect a set of processes together that can be signaled together as a group. API functions such as getpgrp (get process group) and setpgrp (set process group) can be used to read and set the process group identifier. We can send a signal to all processes within a defined process group as

  kill  (0, SIGUSR1); 

or to another process group as

 pid_t group;     ...  kill  (-group, SIGUSR1); 

We can also mimic the behavior of sending to the current process group by identifying the group and then passing the negative of this value to signal:

 pid_t group =  getpgrp  ();     ...  kill  (-group, SIGUSR1); 

Finally, we can send a signal to all processes (except for init ) using the “1 pid identifier. This of course requires that we have permission to do this.

  kill  (-1, SIGUSR1); 

raise

The raise API function can be used to send a specific signal to the current process (the process context in which the raise function is called). The prototype for the raise function is:

 int  raise  (int sig_num); 

The raise function is a constrained version of the kill API function that targets only the current process ( getpid() ).

exec Variants

The fork API function provided a mechanism to split an application into separate parent and child processes, sharing the same code but potentially serving different roles. The exec family of functions replaces the current process image altogether.

Note  

Once the exec function replaces the current process, its pid is the same as the creating process.

The prototypes for the variants of exec are provided here:

 int  execl  (const char *path, const char *arg, ...);     int  execlp  (const char *path, const char *arg, ...);     int  execle  (const char *path, const char *arg, ...,                    char * const envp[]);     int  execv  (const char *path, char *const argv[]);     int  execvp  (const char *file, char *const argv[]);     int  execve  (const char *filename, char *const argv[],               char *const envp[]); 

One of the notable differences between these functions is that one set takes a list of parameters ( arg0 , arg1 , and so on) and the other takes an argv array. The path argument specifies the program to run, and the remaining parameters specify the arguments to pass to the program.

The exec commands permit the current process context to be replaced with the program (or command) specified as the first argument. Let s look at a quick example of execcl to achieve this:

  execl  ("/bin/ls", "ls", "-la", NULL); 

This command replaces the current process with the ls image (list directory). We specify the command to execute as the first argument (including its path). The second argument is the command again (recall that arg0 of the main program call is the name of the program). The third argument is an option that we pass to ls , and finally, we identify the end of our list with a NULL . Invoking an application that performs this command results in an ls -la .

The important item to note here is that the current process context is replaced by the command requested via execl . Therefore, when the preceding command is successfully executed, it will never return.

One additional item to note is that execl includes the absolute path to the command. If we had executed execlp instead, the full path would not have been required because the parent s PATH definition is used to find the command.

One interesting example of execlp is its use in creating a simple shell (on top of an existing shell). We ll support only simple commands within this shell (those that take no arguments). See Listing 13.6 for an example.

Listing 13.6: Simple Shell Interpreter Using execlp (on the CD-ROM at ./source/ch13/_simpshell.c )
start example
  1:  #include <sys/types.h>  2:  #include <sys/wait.h>  3:  #include <unistd.h>  4:  #include <stdio.h>  5:  #include <stdlib.h>  6:  #include <string.h>  7:   8:  #define MAX_LINE        80  9:   10:  int main()  11:  {  12:  int status;  13:  pid_t childpid;  14:  char cmd[MAX_LINE+1];  15:  char *sret;  16:   17:  while (1) {  18:   19:  printf("mysh>");  20:   21:  sret = fgets(cmd, sizeof(cmd), stdin);  22:   23:  if (sret == NULL) exit(-1);  24:   25:  cmd[ strlen(cmd)-1] = 0;  26:   27:  if (!strncmp(cmd, "bye", 3)) exit(0);  28:   29:  childpid =  fork  ();  30:   31:  if (childpid == 0) {  32:   33:   execlp  (cmd, cmd, NULL);  34:   35:  } else if (childpid > 0) {  36:   37:   waitpid  (childpid, &status, 0);  38:   39:  }  40:   41:  printf("\n");  42:   43:  }  44:   45:  return 0;  46:  } 
end example
 

Our simple shell interpreter is built around the simple parent/child fork application. The parent forks off the child (at line 29) and then awaits completion. The child takes the command read from the user (at line 21) and executes this using execlp (line 33). We simply specify the command as the command to execute and also include it for arg0 (second argument). The NULL terminates the argument list, in this case no arguments are passed for the command. The child process never returns, but its exit status is recognized by the parent at the waitpid function (line 37).

As the user types in commands, they are executed via execlp . Typing in the command bye causes the application to exit.

Since no arguments are passed to the command (via execlp ), the user may type in only commands and no arguments. Any arguments that are provided are simply ignored by the interpreter.

A sample execution of this application is shown here:

 $  ./simpshell  mysh>  date  Sat Apr 24 13:47:48 MDT 2004     mysh>ls     simpshell    simpshell.c     mysh>bye     $ 

We see that after executing our shell, the prompt is displayed, indicating that commands can be entered. The date command is entered first, which provides the current date and time. Next, we do an ls , which gives us the contents of the current directory. Finally, we exit the shell using the bye internal command.

Let s look at one final exec variant as a way to explore the argument and environment aspects of a process. The execve variant allows an application to provide a command with a list of command-line arguments (as a vector) as well as an environment for the new process (as a vector of environment variables ). Let s look back at the execve prototype:

 int  execve  (const char *filename, char *const argv[],                 char *const envp[]); 

The filename argument is the program to execute (which must be a binary executable or a script (that includes the #! interpreter spec at the top of the file). The argv argument is an array of arguments for the command (with the first argument being the command itself (same as the filename argument). Finally, the envp argument is an array of key/value strings containing environment variables. Consider the following simple example that retrieves the environment variables through the main function (on the CD-ROM at ./source/ch13/sigenv.c ).

 #include <stdio.h>     #include <unistd.h>     int main(int argc, char *argv[], char *envp[])     {       int ret;       char *args[]={ "ls", "-la", NULL };       ret =  execve  ("/bin/ls", args, envp);       fprintf(stderr, "execve failed\n");       return 0;     } 

The first item to note in this example is the main function definition. We use a variant that passes in a third parameter that lists the environment for the process. This can also be gathered by the program using the special environ variable, which has the definition:

 extern char *environ[]; 
Note  

POSIX systems do not support the envp argument to main , so it s best to use the environ variable.

We specify our argument vector ( args ), which contains our command name and arguments, terminated by a NULL . This is provided as the argument vector to execve , along with the environment (passed in through the main function). This particular example simply performs an ls operation (by replacing the process with the ls command). Note also that we provide the -la option.

We could also specify our own environment similar to the args vector. For example, the following specifies a new environment for the process:

 char *envp[] = { "PATH=/bin", "FOO=99", NULL };     ...     ret =  execve  (command, args, envp); 

The envp variable provides the set of variables that define the environment for the newly created process.

alarm

The alarm API function can be very useful to time out other functions. The alarm function works by raising a SIGALRM signal once the number of seconds passed to alarm has expired. The function prototype for alarm is:

 unsigned int  alarm  (unsigned int secs); 

The user passes in the number of seconds to wait before sending the SIGALRM signal. The alarm function returns zero if no alarm was previously scheduled; otherwise, it returns the number of seconds pending on the previous alarm.

Here s an example of alarm to kill the current process if the user isn t able to enter a password in a reasonable amount of time (see Listing 13.7). At line 18, we install our signal handler for the SIGALRM signal. The signal handler is for the wakeup function (lines 6 “9), which simply raises the SIGKILL signal. This will terminate the application. We then emit the message to enter the password within three seconds and try to read the password from the keyboard ( stdin ). If the read call succeeds, we disable the alarm (by calling alarm with an argument of zero). The else portion of the test (line 30) would check the user password and continue. If the alarm timed out, a SIGALRM would be generated, resulting in a SIGKILL signal, which would terminate the program.

Listing 13.7: Example Use of alarm and Signal Capture (on the CD-ROM at ./source/_ch13/alarm.c )
start example
  1:  #include <stdio.h>  2:  #include <unistd.h>  3:  #include <signal.h>  4:  #include <string.h>  5:   6:  void wakeup(int sig_num)  7:  {  8:  raise(SIGKILL);  9:  }  10:   11:  #define MAX_BUFFER      80  12:   13:  int main()  14:  {  15:  char buffer[MAX_BUFFER+1];  16:  int ret;  17:   18:   signal  (SIGALRM, wakeup);  19:   20:  printf("You have 3 seconds to enter the password\n");  21:   22:   alarm  (3);  23:   24:  ret = read(0, buffer, MAX_BUFFER);  25:   26:   alarm  (0);  27:   28:  if (ret == -1) {  29:   30:  } else {  31:   32:  buffer[strlen(buffer)-1] = 0;  33:  printf("User entered %s\n", buffer);  34:   35:  }  36:   37:  } 
end example
 

exit

The exit API function terminates the calling process. The argument passed to exit is returned to the parent process as the status of the parent s wait or waitpid call. The function prototype for exit is:

 void  exit  (int status); 

The process calling exit also raises a SIGCHLD to the parent process and frees the resources allocated by the process (such as open file descriptors). If the process had registered a function with atexit or on_exit , these would be called (in the reverse order to their registration).

This call is very important because it indicates success or failure to the shell environment. Scripts that rely on a program s exit status can behave improperly if the application does not provide an adequate status. This call provides that linkage to the scripting environment. Returning 0 to the script indicates a TRUE or SUCCESS indication.

POSIX Signals

Before we end our discussion of process-related functions, let s take a quick look at the POSIX signal APIs. The POSIX-compliant signals were introduced first in BSD and provide a portable API over the use of the signal API function. Let s now have a look at a multiprocess application that uses the sigaction function to install a signal handler. The sigaction API function has the following prototype:

 #include <signal.h>     int  sigaction  (int signum,                 const struct sigaction *act,                 struct sigaction *oldact); 

signum is the signal for which we re installing the handler, act specifies the action to take for signum , and oldact is used to store the previous action. The sigaction structure contains a number of elements that can be configured:

 struct sigaction {         void (*sa_handler)(int);         void (*sa_sigaction)(int, siginfo_t *, void *);         sigset_t sa_mask;         int sa_flags;     }; 

The sa_handler is a traditional signal handler that accepts a single argument (and int representing the signal). The sa_sigaction is a more refined version of a signal handler. The first int argument is the signal, and the third void* argument is a context variable (provided by the user). The second argument ( siginfo_t ) is a special structure that provides more detailed information about the signal that was generated:

 siginfo_t {       int        si_signo;    /* Signal number */       int        si_errno;    /* Errno value */       int        si_code;     /* Signal code */       pid_t      si_pid;      /* Pid of signal sending process */       uid_t      si_uid;      /* User id of signal sending process */       int         si_status;    /* Exit value or signal */       clock_t     si_utime;     /* User time consumed */       clock_t     si_stime;     /* System time consumed */       sigval_t    si_value      /* Signal value */       int         si_int;       /* POSIX.1b signal */       void *      si_ptr        /* POSIX.1b signal */       void *      si_addr       /* Memory location which caused fault */       int         si_band;      /* Band Event */       int         si_fd;        /* File Descriptor */     } 

One of the interesting items to note from siginfo_t is that with this API, we can identify the source of the signal ( si_pid ). The si_code field can be used to identify how the signal was raised. For example, if its value was SI_USER , then it was raised by a kill , raise , or sigsend API function. If SI_KERNEL , then it was raised by the kernel. SI_TIMER indicates that a timer expired and resulted in the signal generation.

The si_signo , si_errno , and si_code are set for all signals. The si_addr field (indicating the memory location where the fault occurred) is set for SIGILL , SIGFPE , SIGSEGV , and SIGBUS . The sigaction main page identifies which fields are relevant for which signals.

The sa_flags argument of sigaction allows a modification of the behavior of sigaction function. For example, if we provide SA_SIGINFO , then the sigaction will use the sa_sigaction field to identify the signal handler instead of sa_handler . Flag SA_ONESHOT can be used to restore the signal handler to the prior state after the signal handler has been called once. The SA_NOMASK (or SA_NODEFER ) flag can be used to not inhibit the reception of the signal while in the signal handler (use with care).

Our example function is provided in Listing 13.8. The only real difference we see here from other examples is that sigaction is used at line 49 to install our signal handler. We create a sigaction structure at line 42 and then initialize it with our function at line 48 and also identify that we re using the new sigaction handler via the SA_SIGINFO flag at line 47. When our signal finally fires (at line 34 in the parent process), our signal handler emits the originating pid at line 12 (using the si_pid field of the siginfo reference).

Listing 13.8: Simple Application Illustrating sigaction for Signal Installation (on the CD-ROM at ./source/ch13/posixsig.c )
start example
  1:  #include <sys/types.h>  2:  #include <sys/wait.h>  3:  #include <signal.h>  4:  #include <stdio.h>  5:  #include <unistd.h>  6:  #include <errno.h>  7:   8:  static int stopChild = 0;  9:   10:  void sigHandler(int sig, siginfo_t *siginfo, void *ignore)  11:  {  12:  printf("Got SIGUSR1 from %d\n", siginfo->si_pid);  13:  stopChild=1;  14:   15:  return;  16:  }  17:   18  :       int main()  19  :       {  20  :         pid_t ret;  21  :         int   status;  22  :         int   role = -1;  23  :  24  :         ret =  fork  ();  25  :  26  :         if (ret > 0) {  27  :  28  :           printf("Parent: This is the parent process (pid %d)\n",  29  :  getpid  ());  30  :  31  :           /* Let the child init */  32  :           sleep(1);  33  :  34  :  kill  (ret, SIGUSR1);  35  :  36  :           ret =  wait  (&status);  37  :  38  :           role = 0;  39  :  40  :         } else if (ret == 0) {  41  :  42  :           struct sigaction act;  43  :  44  :           printf("Child: This is the child process (pid %d)\n",  45  :  getpid  ());  46  :  47  :           act.sa_flags = SA_SIGINFO;  48  :           act.sa_sigaction = sigHandler;  49  :  sigaction  (SIGUSR1, &act, 0);  50  :  51  :           printf("Child Waiting...\n");  52  :           while (!stopChild);  53  :  54  :           role = 1;  55  :  56  :         } else {  57  :  58  :           printf("Parent: Error trying to fork() (%d)\n", errno);  59  :  60  :         }  61  :  62  :         printf("%s: Exiting...\n",  63  :                 ((role == 0) ? "Parent" : "Child"));  64  :  65  :         return 0;  66  :       } 
end example
 

The sigaction function provides a more advanced mechanism for signal handling, in addition to greater portability. For this reason, sigaction should be used over signal .




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