Waste time execv () and fork ()

I am currently studying fork() and execv() , and I have a question regarding combination efficiency.

I was shown the following standard code:

 pid = fork(); if(pid < 0){ //handle fork error } else if (pid == 0){ execv("son_prog", argv_son); //do father code 

I know that fork() clones the whole process (copying the whole heap, etc.) and that execv() replaces the current address space with that in the new program. With this in mind, does this make this combination very ineffective? We copy the entire address space of the process and immediately rewrite it.

So my question is:
What advantage is achieved by using this combo (instead of some other solution) that makes people use it, even if we have waste?

+42
linux unix multiprocessing operating-system fork
Oct 05 '16 at 15:00
source share
5 answers

What is the advantage achieved with this combo (instead of some other solution) that makes people use it, even if we have waste?

You need to somehow create a new process. There are very few possibilities for this for a user program. POSIX had vfork() alognside fork() , and some systems may have their own mechanisms, such as Linux clone() , but since 2008, POSIX only specifies fork() and posix_spawn() . The fork + exec track is more traditional, well understood, and has several drawbacks (see below). The posix_spawn family is intended for use in a context that is difficult for fork() ; you can find details in the Justification section of its specification .

This excerpt from the Linux man page for vfork() can be highlighted:

In Linux, fork (2) is implemented using copy-to-write pages, so the only penalty imposed by fork (2) is the time and memory required to duplicate the parent page tables and create a unique task structure for the child . However, in the good old days, fork (2) would require a full copy of the caller's data space, which is often unnecessary, as usually exec (3) is executed immediately after. Thus, for greater efficiency, BSD introduced the vfork () system call, which did not completely copy the address space of the parent process, but borrowed the parents' memory and control flow before execve (2) was called or the exit occurred. The parental process was suspended while the child used his resources. Using vfork () was complicated: for example, not changing data in the parent process depended on which variables were stored in the register.

(emphasis added)

Thus, your concern about waste is not justified for modern systems (not limited to Linux), but it is really a problem historically, and there really are mechanisms to avoid this. Today, most of these mechanisms are outdated.

+42
Oct 05 '16 at 15:18
source share

Another answer says:

However, in the bad old days, a fork (2) would require a full copy of the caller's data space, which is often unnecessary, as usually exec (3) is executed immediately after.

Obviously, one person, a bad old day, is much younger than others, remember.

UNIX source systems did not have memory to run multiple processes, and they did not have MMUs to store several processes in physical memory, ready to run in the same logical address space: they swapped processes to disk so that they do not currently works.

The fork system call was almost exactly the same as replacing the current process with a disk, except for the return value and for replacing the remaining copy in memory by replacing in another process. Since you had to change the parent process anyway to start the child, fork + exec did not incur any overhead.

It is true that there was a period of time when fork + exec was inconvenient: when there were MMUs that provided a mapping between the logical and physical address space, but page errors did not save enough information that copy-to-write and the number of other virtual memory / demand schemes paging were feasible.

This situation was rather painful, and not just for UNIX; this error handling of the hardware page was adapted to quickly “play”.

+23
05 Oct '16 at 21:18
source share

Not more. There is something called COW (Copy On Write), only when one of the two processes (parent / child) tries to write to the shared data, it is copied.

In the past:
The fork() system call copied the address space of the calling process (parent) to create a new process (child). Copying the parent address space to the child was the most expensive part of the fork() operation.

Now:
The fork() call is often followed almost immediately by exec() in the child process, which replaces the child memory with a new program. This is what the shell usually does. In this case, the time taken to copy the parent address space is largely lost, because the child process will use very little of its memory before calling exec() .

For this reason, later versions of Unix used virtual memory hardware to allow parent and child users to share memory mapped to their respective address spaces until one of the processes actually changes it. This method is known as copy-on-write . To do this, in fork() kernel will copy the address space mappings from the parent to the child instead of the contents of the displayed pages, and at the same time, it will now mark read-only shared pages. When one of the two processes tries to write to one of these shared pages, the process executes the page error. At this point, the Unix kernel realizes that this page was indeed “virtual” or “copy-write”, and therefore it creates a new, private, writable copy of the page for the crash process. Thus, the contents of individual pages are not actually copied until they are written. This optimization makes fork() and then exec() much cheaper for the child: the child will probably only need to copy one page (the current page of its stack) before it calls exec() .

+21
05 Oct '16 at 15:03
source share

It turns out that all these COW page errors are not cheap at all when the process has several gigabytes of writable RAM. They will all be to blame, even if the child has long called exec (). Since the fork () child can no longer allocate memory even for a single-threaded case (you can thank the apple for this), arranging the vfork () / exec () call instead is now hardly more difficult.

The real advantage for the vfork () / exec () model is that you can set a child with an arbitrary current directory, arbitrary environment variables and arbitrary fs descriptors (not just stdin / stdout / stderr), an arbitrary mask signal, and some arbitrary shared memory (using system calls with shared memory) without using APIs with two arguments CreateProcess (), which receives several arguments every few years.

It turned out that “oops I missed the handles opened by another thread”, from the first attempts of stream processing it was fixed in user space without blocking throughout the process thanks to / proc. The same thing would not have happened in the giant CreateProcess () model without a new version of the OS, and convincing everyone to name the new API.

So you have it. The design crash turned out to be much better than a directly designed solution.

+2
Oct 06 '16 at 2:39 on
source share

A process created by exec () et al. Inherits its file descriptors from the parent process (including stdin, stdout, stderr). If the parent parameter changes them after calling fork (), but before calling exec (), it can control the child standard threads.

+1
05 Oct '16 at 20:10
source share



All Articles