munotes®

Running a Different Program: exec

Get access to whole semester resourcesSemester Pass

Chapter Nineteen

Syllabus topic Module 1, "Processes - Operations on Processes"

Pages 73 to 76 of 452

In one line

exec throws away the program a process is running and loads a different one in its place, keeping the same process.

Why it is not "start a program"

The name misleads every beginner, so put it plainly: exec does not create anything. There is one process before and one process after, with the same process id, the same parent, and the same open files. What changed is the program inside it.

A successful exec never returns. There is nothing to return to: the instructions that would have received the return value have been replaced. So the line after an exec runs only if the exec failed, and that is why every correct program treats reaching it as an error.

What survives an exec and what does not

SurvivesReplaced
Process idyes
Parent, and the parent's knowledge of ityes
Open file descriptorsyes, unless marked close on exec
Current directory, user, groupyes
Text, data, heap, stackall replaced by the new program's
Registers and program counterset to the new program's start
Signal handlersreset: the new program has none of the old one's

The surviving descriptors are the point. They are how redirection works: the child opens a file as descriptor 1, then execs, and the new program, which knows nothing about any of this, writes to descriptor 1 and the bytes land in the file.

The six forms, and how to remember them

There is one system call, execve, and the C library offers six front doors to it. The names look arbitrary until you learn that the letters after exec are the answer to three questions.

LetterMeans
lthe arguments are a list, written out one by one, ending with a null pointer
vthe arguments are a vector, an array of strings you built
pthe program is looked for on the path, so a bare name such as ls works
eyou supply the environment yourself
FormArgumentsSearches the path?Environment
execllistnoinherited
execlplistyesinherited
execlelistnoyou supply it
execvvectornoinherited
execvpvectoryesinherited
execvevectornoyou supply it

So execlp("ls", "ls", "-l", NULL) is a list, searched on the path. execv("/bin/ls", args) is a vector, with a full path. execve is the real system call, which is why the trace in Chapter ten showed that name whatever the program wrote.

The first argument is the program's own name and must be given twice. execlp("ls", "ls", NULL) looks like a mistake and is not: the first is what to run, the second becomes the new program's argument zero. Leaving the second out gives a program that does not know its own name, and some programs behave differently depending on it.

munotes.in73

Running a Different Program: exec

fork then exec, in one program

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    printf("shell : I am %d, about to run wc\n", getpid());
    fflush(stdout);                       /* see the note below */

    pid_t child = fork();
    if (child < 0) {
        perror("fork");
        return 1;
    }
    if (child == 0) {
        printf("child : I am %d, and I am about to become wc\n", getpid());
        fflush(stdout);
        execlp("wc", "wc", "-l", "lines.txt", NULL);
        perror("execlp");                 /* reached only if exec failed */
        _exit(127);
    }
    int status = 0;
    wait(&status);
    printf("shell : child %d finished with status %d\n", child,
        WEXITSTATUS(status));
    return 0;
}
one
two
three
$ gcc -std=c17 -Wall -Wextra -o forkexec forkexec.c
$ ./forkexec
shell : I am 24, about to run wc
child : I am 25, and I am about to become wc
3 lines.txt
shell : child 25 finished with status 0

Three observations, and each is examinable.

  1. The child printed a line and then stopped being itself. The printf after execlp never

ran, and neither did perror, because the exec succeeded and the program was gone.

  1. wc printed as process 21, the same process the child was. The parent waited for 21 and

got it.

  1. fflush is not decoration. Chapter nine showed that printf buffers. A buffer is part of

the program's data, so exec throws it away unflushed, and the line would have been lost. The same happens across fork, where the buffer is copied and the line is printed twice.

That third point is the bug this chapter exists to prevent. A printf before a fork or an exec, with output going to a file, is printed twice or not at all. Flush, or use write.

Redirection, which is the whole reason for the gap

Now the thing Chapter eight promised. The child changes descriptor 1 before exec, and the new program never knows.

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    pid_t child = fork();

    if (child == 0) {
        int fd = open("count.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
        dup2(fd, STDOUT_FILENO);          /* make the file descriptor 1 */
        close(fd);
        execlp("wc", "wc", "-l", "lines.txt", NULL);
        _exit(127);
    }
    wait(NULL);
    printf("wc wrote nothing to the screen. the file holds:\n");
    fflush(stdout);
    execlp("cat", "cat", "count.txt", NULL);
    return 0;
}
$ gcc -std=c17 -Wall -Wextra -o redirect redirect.c
$ ./redirect
wc wrote nothing to the screen. the file holds:
3 lines.txt

wc was given no file to write to and no flag. It wrote to descriptor 1, as it always does. The child had quietly made descriptor 1 be the file, with dup2, before becoming wc. That is exactly what the shell does when you type wc -l lines.txt > count.txt, and it is why fork and exec are two calls.

munotes.in74

Running a Different Program: exec

Notice the last line of the parent: it execs cat, so the parent becomes cat and never returns. There is no return 0 reached. The process ends as cat.

Distinctions that carry marks

forkexec
Creates a processyesno
Processes afterwardstwoone
Returnstwicenever, if it succeeds
The programunchanged in bothreplaced
Process idthe child gets a new oneunchanged
execl familyexecv family
Arguments written asa list in the call, ending in NULLan array you built
Use whenyou know the arguments while writingthe arguments are computed

What it does not mean

exec is not slow because it loads a program. On a paged system it maps the file rather than reading it, and pages arrive as they are touched (Chapter eighty one).

A failed exec is not a crash. It returns -1 like any call, and the program is still the old one, which is why the error must be handled: usually by exiting, because the child has nothing useful left to do.

exec does not close your files. They survive, on purpose. A descriptor that should not survive is marked close on exec when it is opened.

Quick revision

  • exec replaces the program running in a process. It creates nothing, and

a successful exec never returns.

  • Survives: process id, parent, open descriptors, current directory, user. Replaced: text, data,

heap, stack, registers, signal handlers.

  • Six forms, from three letters: l list, v vector, p search the path, e supply the

environment. execve is the real system call.

  • The first argument is given twice: what to run, and what the program's own argument zero should

be.

  • Flush before fork or exec, or a buffered printf is lost or printed twice.
  • Redirection is done in the gap between fork and exec, with dup2, and the new program

never knows.

Test yourself

  1. What does exec do, and how many processes exist afterwards? It replaces the program

running in the calling process. One process exists, with the same id.

  1. Why does a successful exec never return? Because the code that would receive the return

value has been replaced by the new program.

  1. What does the line after exec mean? That the exec failed. It is an error path, and

should report and exit.

  1. Explain the letters in execlp. l for a list of arguments written out in the call, p

for searching the path so that a bare program name works.

  1. How does the shell implement command > file? It forks; in the child it opens the file
munotes.in75

Running a Different Program: exec

and makes it descriptor 1 with dup2; then it execs the command, which writes to descriptor 1 as usual and knows nothing about the redirection.

  1. Why must you flush before forking? Because the C library's output buffer is part of the

process's data. After a fork both copies hold it and both print it; after an exec it is thrown away unprinted.

munotes.in76

The rest of this subject

These notes are cut from the University's printed syllabus. Open the syllabus itself, or the past papers, for the same subject.

Issue
Done!