next up previous contents Back to Operating Systems Home Page
Next: File descriptor manipulation Up: Execution of programs inside Previous: Program environment

Program execution

Now, back to exec. When a process calls one of the exec functions, that process's image is completely replaced by a new program, which then starts executing at its main() function. Since the caller's stack is replaced as well, no return is possible from the new program back to the exec caller, hence this function can return - with -1 return value - only if the kernel cannot start the execution.

Note carefully that the new program is executeded inside the process that calls exec, hence this call does not cause any process id. change, and many other process resources remain unchanged as well. For example, the exec-ed program inherits the open file descriptors of the exec caller. In particular, the three default ones are passed to the exec-ed program, and if the caller had closed them for some reason, they will be closed in the program as well.

The first of the exec calls is execl(), declared as follows:

int execl(char *path, char *arg0, char *arg1, ..., (char *)0);
here path is a string containing a full qualified UNIX pathname for the executable program: either an absolute path starting from root, i.e. with its name starting with `/', or a relative pathname starting from the process's current directory, which is one the process's attributes and it's the one it was run from, unless it was modified using the chdir() system call (check the man page for this call). The rest of the arguments are exactly those which the program will get in the argv argument of its main(), hence the first of them (arg0) should be the program's name, stripped of any path. The last argument of the list must be 0 cast to char pointer, to let the function know the list is finished. Forgetting this is at the origin of a curious phenomenon known as ``Mo' core, mo' dump'', which disk-aware system administrators seem not to appreciate.

Example: to execute the above copy from a C program one might use the following:

...
if (fork()==0)
{
    /* Child does the copy job */
    execl("/usr/bin/mv", "mv", "Budweiser", "McFarlan", (char*)0);
    perror("execl"); /* We're here only if execl fails */
    exit(1);
}
...
and to execute a program in your directory that you're not so proud of:
if (fork()==0)
{
    printf("Yo, I am the executioner\n");
    execl("hang", "hang", "BadGuy", (char*)0);
    perror("execl"); /* We're here only if execl fails */
    exit(1);
}
...
Note that there is no need to check the return value of an exec call: if it returns in the caller process, it certainly failed.

A simpler one to use is execlp()

int execlp(char *file, char *arg0, char *arg1, ..., (char *)0 );
there are two simple (but useful) differences with respect to execl(). One is that execlp() looks for the program in all the directories specified in the PATH environment variable, hence the trailing `p' of the name. This is is nice since most of the commands you usually need are there anyway, so you don't need to write down the whole story, but just the executable file name. If file contains a slash, `/', it's treated exactly as one of the above paths instead. The second difference is that if the file is found, but it does not contain a binary executable program for your machine, like one created with a compiler & linkergif, it then tentatively assumes it is a shell scriptgif, and so it fork-execs good'ole' Bourne shell /bin/sh and feeds her with it. If it's not a script either, then the game's over and execlp() duly returns its -1.

Incidentally, this is why the Bourne shell must be present in every UNIX system, even if users usually stick to friendlier command interpreters, like the C shell csh, the Cornell shell tcsh, the Korn shell ksh, etc.

Incidentally-2, differently from MS-DOS's COMMAND.COM or Macintosh's Finder, a shell has no special blessings in the eyes of UNIX: it's just a program that happens to read whatever gibberish an user types at the keyboard, or feeds it from a file, and executes (i.e. fork-exec) commands in reply. You'll probably write a shell yourselves before the end of this course (hint hint: am I giving away the theme of an assignment?).

Incidentally-3, if you want a script to be exec-ed using a different command interpreter than the Bourne shell, you must specify it in the first line of the script file using the following syntax:

#! <full path of the command interpreter>
So, a Bourne shell script usually begins with
#! /bin/sh
even if Bourne shell is the default, while a C shell script must start with:
#! /bin/csh
Writing anything else on the that line is fishing for troubles.

Do you wish your child's environment be different? Here is the exec that suits you:

int execle(char *file, char *arg0, char *arg1, ..., (char *)0, 
           char *envp[]
           );
where envp has the same format as above detailed.

Now note that all the exec seen so far have in common the characteristic of passing the program arguments as a list which must be known at compile time. You can look at the previous examples to convince yourself that it's actually so: even if the strings that you pass can be crafted during execution, perhaps using strcpy(), or strcat(), or the slow catch-all sprintf(), their number is hard-wired in the C source, since you have to name them all in the call statement. Yet its' often the case that this information is missing. Suppose that you want to extend the previous beer reenaming program to move an unknown number of beer files into a Beers directory, i.e. to emulate commands like:

% mv Leffe DuDemon Beers/
% mv Paulaner Augustiner Guinness Beers/
Clearly the previous exec flavors can be used only at the cost of a grossly inefficent solution, namely passing the call just one argument and placing it in a loop over the arguments, as in:
...
for (i=1; i<=argc; i++) 
    execl("prog", "prog", argv[i], (char *)0);
...
And even this awkward scheme fails if the program you exec needs be passed pairs, triplets, ... groups of parameters whose size is known only at execution time.

UNIX provides three more exec flavors to save the day:

int execv(char *path, char *argv[]);
int execvp(char *file, char *argv[]);
int execve(char *path, char *argv[], char *envp[]);
Here the meaning of the path, file and envp arguments is the same as above, and the trailing `p' in execvp() means again PATH search and shell for scripts. The arguments are passed through an array of strings instead. There's no argument count, hence the same ``NULL at the end'' convention as for envp is used. The whole point is that you can allocate at runtime, using calloc(), as many strings as you need for your arguments, hence there's no inherent, hard-wired compile-time constraintgif.


next up previous contents Back to Operating Systems Home Page
Next: File descriptor manipulation Up: Execution of programs inside Previous: Program environment

Franco Callari