Executing New Programs

While the fork(2) system call starts a clone of the current process, the programmer is still left needing a way to start a new executable program. The exec(2) family of functions fills this need.

When a new program is executed, the following general steps are performed:

  1. The kernel opens the new executable file for reading (and checks that the executable bit is set). An error is immediately returned to the caller if this fails.

  2. The same process ID and addressable memory is retained, while the current execution becomes suspended.

  3. The new program instructions are loaded from the executable file that has been opened by the kernel.

  4. Certain process flags and registers are reset (for example the stack pointer is reset).

  5. The execution of the new process begins.

The overall effect of an exec(2) call is to replace the currently executing process with a new program, within the same process memory. When the exec(2) system call is successful, it never returns control to the calling process.

There are a number of functions that provide the ability to start a new program within the exec(2) family, but the execve(2) function will be described first:

#include <unistd.h>

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

This function takes three arguments:

  • The pathname of the executable program or interpreter script.

  • The argv[] array to be passed to the main() program.

  • The envp[] array of environment variables to export.

When the function execve(2) is successful, it does not return (your current program is replaced). If the call fails, the returned value is -1 and the value of errno will contain the error code.

The pathname argument must represent an ordinary file that has execute permission bits for the current effective user or group. The pathname argument may be an executable file image or it may be a file that is read by an interpreter (that is, a script file). Interpreted files may start with the following initial content:

#! interpreter [arg]

The space between the ! character and the interpreter pathname is optional. The pathname interpreter, however, must be the pathname of an existing regular file that can be loaded as the interpreter for the script file. The script file must have read permissions for the current effective user or group ID.

The [arg] represents an optional argument. This argument becomes the command name (argv[0] value) of the interpreter when it runs. When this argument is absent, the argv[0] value is derived from the interpreter pathname instead.

On some UNIX systems, this initial script line is limited in length. Under Linux, for example, the initial line is only inspected for a maximum of 32 characters. Anything beyond this limit is ignored.

The arguments argv[] and envp[] are arrays containing character string pointers. The end of each array is marked by an array element containing a null pointer. The argv[] array specifies the command name in argv[0] and any command-line arguments starting with argv[1]. Note that for scripts (for interpreted files), argv[0] will be ignored since this information comes from the initial text line of the executable file.

The array envp[] lists all of the environment variables that you want to export to the new program. The strings must all be in the form

VARIABLE=VALUE

For example, you might use the following:

PATH=/bin:/usr/bin

Programmers often simply pass the current environment to the new process. This can easily be done by using the pointer environ:

extern char **environ;

When the external variable environ is declared as shown, you can simply pass environ in place of the envp[] array.

To illustrate the use of the execve(2) call, Listing 19.4 shows a program that starts the ps(1) command without any assistance from the shell. In a limited sense, this program performs the same steps that a shell would use.

Code Listing 19.4. exec.cā€”Example Using exec(2) to Start the ps(1) Command
1:   /* exec.c */
2:
3:   #include <stdio.h>
4:   #include <unistd.h>
5:   #include <string.h>
6:   #include <errno.h>
7:   #include <sys/types.h>
8:   #include <sys/wait.h>
9:
10:  /*
11:   * If the ps(1) command is not located at /bin/ps on your system,
12:   * then change the pathname defined for PS_PATH below.
13:   */
14:  #define PS_PATH "/bin/ps"       /* PS(1) */
15:
16:  extern char **environ;          /* Our environment */
17:
18:  /*
19:   * EXEC(2) the PS(1) Command :
20:   */
21:  static void
22:  exec_ps_cmd(void) {
23:      static char *argv[] = {  "ps", "-l", NULL } ;
24:
25:      /*
26:       * Exec the ps command: ps f
27:       */
28:      execve(PS_PATH,argv,environ);
29:
30:      /*
31:       * If control reaches here, then the execve(2)
32:       * call has failed!
33:       */
34:      fprintf(stderr,"%s: execve(2)
",strerror(errno));
35:  }
36:
37:  /*
38:   * Main program :
39:   */
40:  int
41:  main(int argc,char **argv) {
42:      pid_t pid;          /* Process ID of the child process */
43:      pid_t wpid;         /* Process ID from wait() */
44:      int status;         /* Exit status from wait() */
45:
46:      /*
47:       * First create a new child process :
48:       */
49:      pid = fork();
50:
51:      if ( pid == -1 ) {
52:          /*
53:           * Fork failed to create a process :
54:           */
55:          fprintf(stderr,"%s: Failed to fork()
",strerror(errno));
56:          exit(13);
57:
58:      }  else if ( pid == 0 ) {
59:          /*
60:           * This is the child process running :
61:           */
62:          exec_ps_cmd();          /* Start the ps command */
63:
64:      }
65:
66:      /*
67:       * This is the parent process running :
68:       */
69:      printf("PID %ld: Started child PID %ld.
",
70:          (long)getpid(),     /* Our PID */
71:          (long)pid);         /* Child's PID */
72:
73:      /*
74:       * Wait for the child process to terminate :
75:       */
76:      wpid = wait(&status);   /* Child's exit status */
77:      if ( wpid == -1 ) {
78:          /*
79:           * The wait() call failed :
80:           */
81:          fprintf(stderr,"%s: wait(2)
",strerror(errno));
82:          return 1;
83:
84:      }  else if ( wpid != pid ) {
85:          /* Should never happen in this program: */
86:          abort();
87:      }
88:
89:      /*
90:       * The child process has terminated:
91:       */
92:      if ( WIFEXITED(status) ) {
93:          /*
94:           * Normal exit -- print status
95:           */
96:          printf("Exited: $? = %d
",WEXITSTATUS(status));
97:
98:      }  else if ( WIFSIGNALED(status) ) {
99:          /*
100:          * Process abort, kill or signal:
101:          */
102:         printf("Signal: %d%s
",
103:             WTERMSIG(status),
104:             WCOREDUMP(status) ? " with core file." : "");
105:
106:     }  else {
107:         /*
108:          * Stopped child process :
109:          */
110:         puts("Stopped child process.");
111:     }
112:
113:     return 0;
114: }

Listing 19.4 starts by calling upon fork(2) to create a child process. The parent process reports the process ID values in lines 69ā€“71 and then calls wait(2) to suspend its execution until the child process terminates in line 76.

While the parent process is waiting for the child process to terminate, the child process executes line 62, which causes the function exec_ps_cmd() to be called. This is declared in lines 21ā€“35. The argv[] array is declared in line 23, where it supplies one command-line argument, -l. This will cause the command ps -l to be executed. The function execve(2) is called in line 28. If the function call is successful, line 34 is never executed.

The following shows a compile and run session under FreeBSD for this program:

$ make exec
cc -c  -Wall exec.c
cc -o exec exec.o
$ ./exec
PID 1744: Started child PID 1745.
  UID   PID  PPID CPU PRI NI   VSZ  RSS WCHAN  STAT  TT  TIME    COMMAND
 1001  1231     1   0  10  0   596  344 wait   Is    p0  0:00.07 -sh (sh)
 1001  1234  1233   0  10  0   600  348 wait   Ss    p1  0:00.43 -sh (sh)
 1001  1744  1234   1  10  0   824  396 wait   S+    p1  0:00.01 ./exec
 1001  1745  1744   2  28  0   376  244 -      R+    p1  0:00.00 ps -l
Exited: $? = 0
$

The parent process starts by reporting its process ID as 1744 and its child process as 1745. Then the remaining lines displayed are the result of the ps(1) command being executed successfully. This display shows the parent process as ./exec and the ps(1) command as ps -l. The last line reported shows that the ps(1) command exited normally with a return code of 0.

Other exec(2) Family Members

There are a number of other exec(2) family member functions that act as front-end functions to the execve(2) function. Their synopsis follows:

#include <unistd.h>

int execl(const char *path, const char *arg, ...);

int execlp(const char *file, const char *arg, ...);

int execle(const char *path, const char *arg, ...);

int exect(const char *path, char *const argv[], char *const envp[]);

int execv(const char *path, char *const argv[]);

int execvp(const char *file, char *const argv[]);

The functions that accept an argument named path indicate the specific regular file to be executed by this argument as a pathname. The functions that define an argument named file (execlp(3) and execvp(3)) will search the PATH variable for the file in the same way that the shell searches for a command. If there is no environment variable PATH defined, the value /bin:/usr/bin is used by default.

The functions execl(2), execlp(2), and execle(2) allow the programmer to specify argv[] values as individual arguments instead of using an array. The last argument in the argument list must be a null pointer, however. For example, Listing 19.4 could have used

execlp("ps","ps","-l",NULL);

in place of calling execve(2). Note, however, the argv[0] is specified first, and in this example repeats the filename "ps".

The execle(3) function needs special attention because it requires an array of environment variables as the last argument. The following is an example execle(3) function call:

extern char **environ;

execle("/bin/ps","ps","-l",NULL,environ);

Notice that the array of environment variables follows the NULL argument, which marks the end of the command line.

Warning

Forgetting to specify the null pointer after the last command argument to execl(3), execlp(3), or execle(3) is a common cause of program aborts during program development.

Note also that execle(3) requires an array of environment variables as the very last argument.


The exect(2) system call executes a program with program tracing facilities enabled. See ptrace(2) for more information about this facility. The functions execv(2) and execvp(2) use the current environment settings when starting the new program.

Figure 19.2 provides a feature grid of the various function calls.

Figure 19.2. A feature grid for the various exec() function calls.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.71.106