About Fork and Execve System Call

about fork and execve system call

Each step is relatively simple.

In Unix, your process has two parts -- a read-only memory area with the application code ("text") and the read-write memory area ("data").

A fork clones the read-write area, leaving the text page alone. You now have two processes running the same code. They differ by a register value -- the return value from fork -- which separates parent from child.

An exec replaces the text page, leaving the data page alone. There are many forms of exec, depending on how much environment information you're passing to it. See http://linux.die.net/man/3/exec for an additional list of variants.

Differences between fork and exec

The use of fork and exec exemplifies the spirit of UNIX in that it provides a very simple way to start new processes.

The fork call basically makes a duplicate of the current process, identical in almost every way. Not everything is copied over (for example, resource limits in some implementations) but the idea is to create as close a copy as possible.

The new process (child) gets a different process ID (PID) and has the PID of the old process (parent) as its parent PID (PPID). Because the two processes are now running exactly the same code, they can tell which is which by the return code of fork - the child gets 0, the parent gets the PID of the child. This is all, of course, assuming the fork call works - if not, no child is created and the parent gets an error code.

The exec call is a way to basically replace the entire current process with a new program. It loads the program into the current process space and runs it from the entry point.

So, fork and exec are often used in sequence to get a new program running as a child of a current process. Shells typically do this whenever you try to run a program like find - the shell forks, then the child loads the find program into memory, setting up all command line arguments, standard I/O and so forth.

But they're not required to be used together. It's perfectly acceptable for a program to fork itself without execing if, for example, the program contains both parent and child code (you need to be careful what you do, each implementation may have restrictions). This was used quite a lot (and still is) for daemons which simply listen on a TCP port and fork a copy of themselves to process a specific request while the parent goes back to listening.

Similarly, programs that know they're finished and just want to run another program don't need to fork, exec and then wait for the child. They can just load the child directly into their process space.

Some UNIX implementations have an optimized fork which uses what they call copy-on-write. This is a trick to delay the copying of the process space in fork until the program attempts to change something in that space. This is useful for those programs using only fork and not exec in that they don't have to copy an entire process space.

If the exec is called following fork (and this is what happens mostly), that causes a write to the process space and it is then copied for the child process.

Note that there is a whole family of exec calls (execl, execle, execve and so on) but exec in context here means any of them.

The following diagram illustrates the typical fork/exec operation where the bash shell is used to list a directory with the ls command:

+--------+
| pid=7 |
| ppid=4 |
| bash |
+--------+
|
| calls fork
V
+--------+ +--------+
| pid=7 | forks | pid=22 |
| ppid=4 | ----------> | ppid=7 |
| bash | | bash |
+--------+ +--------+
| |
| waits for pid 22 | calls exec to run ls
| V
| +--------+
| | pid=22 |
| | ppid=7 |
| | ls |
V +--------+
+--------+ |
| pid=7 | | exits
| ppid=4 | <---------------+
| bash |
+--------+
|
| continues
V

System call fork() and execv function

You have a couple of problems. First, if you only want to run two programs, you only need to call fork() once. Then run one program in the parent process and one in the child. Second, you're constructing the argv array to be passed to execv incorrectly. The first entry should be the executable name. Do something like:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(int argc, char **argv)
{
pid_t i = fork();
if (i == 0)
{
execv("./prcs1", (char *[]){ "./prcs1", argv[1], NULL });
_exit(1);
}
else if (i > 0)
{
execv("./prcs2", (char *[]){ "./prcs2", argv[0], NULL });
_exit(2);
}
else
{
perror("fork failed");
_exit(3);
}
}

Note that this example does no error checking.

In terms of call-return behaviour, how are the fork() and exec() system calls different from other system calls?

Actually, there are a few which don't obey the "returns once" paradigm.

A call to fork() returns once or twice - the latter on success where it returns once in the parent and once in the child, the former on failure where it simply returns once in the parent.

A call to exec() will return on failure but, if successful, the current process is simply overwritten with a new program.

There are others, such as exit() or abort(), which are not expected to return at all.

Time waste of execv() and fork()

What is the advantage that is achieved by using this combo (instead of some other solution) that makes people still use this even though we have waste?

You have to create a new process somehow. There are very few ways for a userspace program to accomplish that. POSIX used to have vfork() alognside fork(), and some systems may have their own mechanisms, such as Linux-specific clone(), but since 2008, POSIX specifies only fork() and the posix_spawn() family. The fork + exec route is more traditional, is well understood, and has few drawbacks (see below). The posix_spawn family is designed as a special purpose substitute for use in contexts that present difficulties for fork(); you can find details in the "Rationale" section of its specification.

This excerpt from the Linux man page for vfork() may be illuminating:

Under Linux, fork(2) is implemented using copy-on-write pages, so the only penalty incurred by fork(2) is the time and memory required to duplicate the parent’s page tables, and to create a unique task structure for the child. However, in the bad old days a fork(2) would require making a complete copy of the caller’s data space, often needlessly, since usually immediately afterwards an exec(3) is done. Thus, for greater efficiency, BSD introduced the vfork() system call, which did not fully copy the address space of the parent process, but borrowed the parent’s memory and thread of control until a call to execve(2) or an exit occurred. The parent process was suspended while the child was using its resources. The use of vfork() was tricky: for example, not modifying data in the parent process depended on knowing which variables are held in a register.

(Emphasis added)

Thus, your concern about waste is not well-founded for modern systems (not limited to Linux), but it was indeed an issue historically, and there were indeed mechanisms designed to avoid it. These days, most of those mechanisms are obsolete.

to system() or fork()/exec()?

system executes a command-interpreter, i.e. a shell, which (a) is slower than a direct fork/exec, (b) may behave differently on different systems and (c) is a potential security hazard if you pass it a string from an untrusted source. Also, system waits for the child process to exit, while you might want it to run concurrently with the parent process.

More in general, the low-level fork/exec gives you additional control: before or in between the two operations, you might want to chdir, open pipes, close file descriptors, set up shared memory, etc.

(By different systems, I don't mean Windows vs. Unix (as Windows doesn't even have fork): I'm talking Red Hat Linux vs. Ubuntu. The former uses Bash to execute what is passed to system, the latter a lightweight POSIX-compatible shell.)

Understanding fork, exec, and wait in C++ (Linux)

A general breakdown of what each of those system calls do:

fork: Forks the current process. Literally when fork is called, execution is paused at the call to fork, and the entire program is copied into a new process space that is a child of the original. Then both processes continue execution from right after the fork call in parallel. You'll need to get the PID in order to tell if the program that is currently being executed is the child or parent.

exec: Pauses execution of current process, wipes over the current process in memory with the designated new program to run. It then runs the new program instead.

wait: Suspends the current process until at least one child process terminates. It is a wrapper around waitpid() which allows you to pause the current process's execution and wait for a change in the state of a child process of the current process (which may be a clone of itself or a new program swapped in by exec)

Here is some code demoing waiting and forking (but no exec) from a class I took at University:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <semaphore.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
const int BUFFER_SIZE = 1000;

void copySegment(int i, int segmentSize, FILE * fin, FILE * fout) {
// Does not need to be shown to prove point
}

int main(int argc, char * argv[]) {
int i;
sem_t * sem;
pid_t pid;
FILE * fin, * fout;
struct stat sbuf;
int fileSize, fileDescriptor, segmentSize;
fin = fopen(argv[1], "r");
fout = fopen("forkcopy.txt", "w");
fileDescriptor = fileno(fin);
fstat(fileDescriptor, &sbuf);
fileSize = sbuf.st_size;
segmentSize = fileSize / 4;
sem = sem_open("sem", O_CREAT | O_EXCL, 0644, 4);
sem_unlink("sem");
for (i = 0; i < 4; i++) {
pid = fork();
if (pid < 0)
printf("Fork error.\n");
else if (pid == 0)
break;
}
if (pid != 0) {
while (pid = waitpid(-1, NULL, 0)) {
if (errno == ECHILD)
break;
}
sem_destroy(sem);
fclose(fin);
fclose(fout);
exit(0);
} else {
sem_wait(sem);
copySegment(i, segmentSize, fin, fout);
sem_post(sem);
exit(0);
}
}

What is the use of fork() - ing before exec()?

The intermediate step enables you to set up shared resources in the child process without the external program being aware of it. The canonical example is constructing a pipe:

// read output of "ls"
// (error checking omitted for brevity)
int pipe_fd[2];
pipe(&pipe_fd);
if (fork() == 0) { // child:
close(pipe_fd[0]); // we don't want to read from the pipe
dup2(pipe_fd[1], 1); // redirect stdout to the write end of the pipe
execlp("ls", "ls", (char *) NULL);
_exit(127); // in case exec fails
}
// parent:
close(pipe_fd[1]);
fp = fdopen(pipe_fd[0], "r");
while (!feof(fp)) {
char line[256];
fgets(line, sizeof line, fp);
...
}

Note how the redirection of standard output to the pipe is done in the child, between fork and exec. Of course, for this simple case, there could be a spawning API that would simply do this automatically, given the proper parameters. But the fork() design enables arbitrary manipulation of per-process resources in the child — one can close unwanted file descriptors, modify per-process limits, drop privileges, manipulate signal masks, and so on. Without fork(), the API for spawning processes would end up either extremely fat or not very useful. And indeed, the process spawning calls of competing operating systems typically fall somewhere in between.

As for the waste of memory, it is avoided with the copy on write technique. fork() doesn't allocate new memory for the child process, but points the child to the parent's memory, with the instructions to make a copy of a page only if the page is ever written to. This makes fork() not only memory-efficient, but also fast, because it only needs to copy a "table of contents".



Related Topics



Leave a reply



Submit