C语言 请解释exec()函数及其家族

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4204915/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 07:05:56  来源:igfitidea点击:

Please explain the exec() function and its family

cunixexecl

提问by user507401

What is the exec()function and its family? Why is this function used and how does its work?

什么是exec()功能和它的家人吗?为什么要使用这个函数,它是如何工作的?

Please anyone explain these functions.

请任何人解释这些功能。

回答by paxdiablo

Simplistically, in UNIX, you have the concept of processes and programs. A process is something in which a program executes.

简单地说,在 UNIX 中,您有进程和程序的概念。进程是程序在其中执行的东西。

The simple idea behind the UNIX "execution model" is that there are two operations you can do.

UNIX“执行模型”背后的简单想法是您可以执行两种操作。

The first is to fork(), which creates a brand new process containing a duplicate of the current program, including its state. There are a few differences between the processes which allow them to figure out which is the parent and which is the child.

第一个是 to fork(),它创建一个全新的进程,其中包含当前程序的副本,包括其状态。进程之间存在一些差异,这使他们能够确定哪个是父进程,哪个是子进程。

The second is to exec(), which replaces the program in the current process with a brand new program.

第二个是 to exec(),用一个全新的程序替换当前进程中的程序。

From those two simple operations, the entire UNIX execution model can be constructed.

通过这两个简单的操作,可以构建整个 UNIX 执行模型。



To add some more detail to the above:

为上述内容添加更多细节:

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

使用UNIXfork()exec()举例说明 UNIX 的精神,因为它提供了一种非常简单的方法来启动新进程。

The fork()call makes a near duplicate of the current process, identical in almost every way (not everythingis copied over, for example, resource limits in some implementations, but the idea is to create as close a copy as possible). One process calls fork()while two processes return from it - sounds bizarre but it's really quite elegant

fork()调用几乎复制了当前进程,几乎在所有方面相同(并非所有内容都被复制,例如,某些实现中的资源限制,但其想法是创建尽可能接近的副本)。一个进程调用fork()而两个进程从中返回 - 听起来很奇怪,但它真的很优雅

The new process (called the child) gets a different process ID (PID) and has the the PID of the old process (the parent) as its parent PID (PPID).

新进程(称为子进程)获得不同的进程 ID (PID),并将旧进程(父进程)的 PI​​D 作为其父进程 PID (PPID)。

Because the two processes are now running exactly the same code, they need to be able to tell which is which - the return code of fork()provides this information - the child gets 0, the parent gets the PID of the child (if the fork()fails, no child is created and the parent gets an error code). That way, the parent knows the PID of the child and can communicate with it, kill it, wait for it and so on (the child can always find its parent process with a call to getppid()).

因为这两个进程现在正在运行完全相同的代码,所以他们需要能够分辨哪个是哪个 - 的返回码fork()提供了此信息 - 子进程为0,父进程获取子进程的 PID(如果fork()失败,则不子被创建,父得到一个错误代码)。这样,父进程就知道子进程的 PID 并且可以与它通信、杀死它、等待它等等(子进程总是可以通过调用 找到它的父进程getppid())。

The exec()call replaces the entire current contents of the process with a new program. It loads the program into the current process space and runs it from the entry point.

exec()调用用一个新程序替换了进程的全部当前内容。它将程序加载到当前进程空间并从入口点运行它。

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 findprogram into memory, setting up all command line arguments, standard I/O and so forth.

因此,fork()andexec()通常按顺序使用,以使新程序作为当前进程的子进程运行。每当您尝试运行诸如findshell 之类的程序时,Shell通常都会执行此操作,然后子程序将find程序加载到内存中,设置所有命令行参数、标准 I/O 等。

But they're not required to be used together. It's perfectly acceptable for a program to call fork()without a following exec()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. For this situation, the program contains both the parent andthe child code.

但它们不需要一起使用。例如,如果程序同时包含父代码和子代码,则程序在fork()没有跟随的情况下调用是完全可以接受的exec()(您需要小心您所做的事情,每个实现可能都有限制)。这对于守护进程使用了​​很多(并且仍然是),它们只是在 TCP 端口上侦听并派生自己的副本来处理特定请求,而父进程则返回侦听。对于这种情况,程序包含父代码子代码。

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

类似地,知道它们已经完成并且只想运行另一个程序的程序不需要fork()exec()然后wait()/waitpid()为孩子。他们可以直接将子进程加载到他们当前的进程空间中exec()

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. Under Linux, fork()only makes a copy of the page tables and a new task structure, exec()will do the grunt work of "separating" the memory of the two processes.

一些 UNIX 实现有一个优化fork(),它使用他们所谓的写时复制。这是一个技巧,可以延迟进程空间的复制,fork()直到程序尝试更改该空间中的某些内容。这对于那些只使用fork()而不是exec()因为它们不必复制整个进程空间的程序很有用。在 Linux 下,fork()只需要复制页表和新的任务结构,exec()就会完成“分离”两个进程的内存的繁重工作。

If the execiscalled 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.

如果exec称为以下fork(这是什么会发生大多),导致进程空间写,然后将其复制子进程。

Linux also has a vfork(), even more optimised, which shares just about everythingbetween the two processes. Because of that, there are certain restrictions in what the child can do, and the parent halts until the child calls exec()or _exit().

Linux也有一个vfork(),更加优化,这股几乎一切两个进程之间。正因为如此,子级可以做什么有一定的限制,父级停止直到子级调用exec()_exit()

The parent has to be stopped (and the child is not permitted to return from the current function) since the two processes even share the same stack. This is slightly more efficient for the classic use case of fork()followed immediately by exec().

必须停止父进程(并且不允许子进程从当前函数返回),因为这两个进程甚至共享相同的堆栈。这对于fork()紧接其后的经典用例稍微更有效exec()

Note that there is a whole family of execcalls (execl, execle, execveand so on) but execin context here means any of them.

请注意,有一个全家exec电话(execlexecleexecve等),但exec在上下文中这里是指任何人。

The following diagram illustrates the typical fork/execoperation where the bashshell is used to list a directory with the lscommand:

下图说明了使用 shell 使用命令列出目录的典型fork/exec操作:bashls

+--------+
| 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

回答by T04435

Functions in the exec() family have different behaviours:

exec() 系列中的函数具有不同的行为:

  • l : arguments are passed as a list of strings to the main()
  • v : arguments are passed as an array of strings to the main()
  • p : path/s to search for the new running program
  • e : the environment can be specified by the caller
  • l : 参数作为字符串列表传递给 main()
  • v : 参数作为字符串数组传递给 main()
  • p : path/s 搜索新运行的程序
  • e : 调用者可以指定环境

You can mix them, therefore you have:

你可以混合它们,因此你有:

  • int execl(const char *path, const char *arg, ...);
  • int execlp(const char *file, const char *arg, ...);
  • int execle(const char *path, const char *arg, ..., char * const envp[]);
  • int execv(const char *path, char *const argv[]);
  • int execvp(const char *file, char *const argv[]);
  • int execvpe(const char *file, char *const argv[], char *const envp[]);
  • int execl(const char *path, const char *arg, ...);
  • int execlp(const char *file, const char *arg, ...);
  • int execle(const char *path, const char *arg, ..., char * const envp[]);
  • int execv(const char *path, char *const argv[]);
  • int execvp(const char *file, char *const argv[]);
  • int execvpe(const char *file, char *const argv[], char *const envp[]);

For all of them the initial argument is the name of a file that is to be executed.

对于所有这些,初始参数是要执行的文件的名称。

For more information read exec(3) man page:

有关更多信息,请阅读exec(3) 手册页

man 3 exec  # if you are running a UNIX system

回答by Fred Foo

The execfamily of functions make your process execute a different program, replacing the old program it was running. I.e., if you call

exec系列函数使您的进程执行不同的程序,替换它正在运行的旧程序。也就是说,如果你打电话

execl("/bin/ls", "ls", NULL);

then the lsprogram is executed with the process id, current working dir and user/group (access rights) of the process that called execl. Afterwards, the original program is not running anymore.

然后ls使用进程 id、当前工作目录和调用execl. 之后,原来的程序不再运行。

To start a new process, the forksystem call is used. To execute a program without replacing the original, you need to fork, then exec.

要启动一个新进程,需要使用fork系统调用。要在不替换原始程序的情况下执行程序,您需要fork,然后exec

回答by Reese Moore

what is the exec function and its family.

什么是 exec 函数及其家族。

The execfunction family is all functions used to execute a file, such as execl, execlp, execle, execv, and execvp.They are all frontends for execveand provide different methods of calling it.

exec函数族是用于执行文件中的所有功能,如execlexeclpexecleexecv,和execvp。他们是所有的前端execve,并提供调用它的不同方法。

why is this function used

为什么使用这个函数

Exec functions are used when you want to execute (launch) a file (program).

Exec 函数用于执行(启动)文件(程序)。

and how does it work.

以及它是如何工作的。

They work by overwriting the current process image with the one that you launched. They replace (by ending) the currently running process (the one that called the exec command) with the new process that has launched.

它们的工作方式是用您启动的进程映像覆盖当前进程映像。它们用已启动的新进程替换(通过结束)当前运行的进程(调用 exec 命令的进程)。

For more details: see this link.

有关更多详细信息:请参阅此链接

回答by nategoose

execis often used in conjunction with fork, which I saw that you also asked about, so I will discuss this with that in mind.

exec经常与 结合使用fork,我看到你也问过这个问题,所以我会考虑到这一点来讨论这个。

execturns the current process into another program. If you ever watched Doctor Who, then this is like when he regenerates -- his old body is replaced with a new body.

exec将当前进程转换为另一个程序。如果你看过神秘博士,那就像他重生时一样——他的旧身体被新身体取代。

The way that this happens with your program and execis that a lot of the resources that the OS kernel checks to see if the file you are passing to execas the program argument (first argument) is executable by the current user (user id of the process making the execcall) and if so it replaces the virtual memory mapping of the current process with a virtual memory the new process and copies the argvand envpdata that were passed in the execcall into an area of this new virtual memory map. Several other things may also happen here, but the files that were open for the program that called execwill still be open for the new program and they will share the same process ID, but the program that called execwill cease (unless exec failed).

这种情况在您的程序中发生的方式exec是操作系统内核检查的许多资源,以查看您exec作为程序参数(第一个参数)传递给的文件是否可由当前用户(进程的用户 ID)执行进行exec调用),如果是这样,它将用新进程的虚拟内存替换当前进程的虚拟内存映射,并将调用中传递的数据argvenvp数据复制exec到此新虚拟内存映射的区域中。其他一些事情也可能在这里发生,但为被调用的程序打开的文件exec仍将为新程序打开,它们将共享相同的进程 ID,但被调用的程序exec将停止(除非 exec 失败)。

The reason that this is done this way is that by separating runninganewprograminto two steps like this you can do some things between the two steps. The most common thing to do is to make sure that the new program has certain files opened as certain file descriptors. (remember here that file descriptors are not the same as FILE *, but are intvalues that the kernel knows about). Doing this you can:

这样做的原因是通过将运行一个程序分成两个步骤,你可以在两个步骤之间做一些事情。最常见的做法是确保新程序将某些文件作为某些文件描述符打开。(请记住,文件描述符与 不同FILE *,而是int内核知道的值)。这样做你可以:

int X = open("./output_file.txt", O_WRONLY);

pid_t fk = fork();
if (!fk) { /* in child */
    dup2(X, 1); /* fd 1 is standard output,
                   so this makes standard out refer to the same file as X  */
    close(X);

    /* I'm using execl here rather than exec because
       it's easier to type the arguments. */
    execl("/bin/echo", "/bin/echo", "hello world");
    _exit(127); /* should not get here */
} else if (fk == -1) {
    /* An error happened and you should do something about it. */
    perror("fork"); /* print an error message */
}
close(X); /* The parent doesn't need this anymore */

This accomplishes running:

这完成了运行:

/bin/echo "hello world" > ./output_file.txt

from the command shell.

从命令外壳。

回答by Shivam Mitra

When a process uses fork(), it creates a duplicate copy of itself and this duplicates becomes the child of the process. The fork() is implemented using clone() system call in linux which returns twice from kernel.

当一个进程使用 fork() 时,它会创建自己的副本,这个副本成为进程的子进程。fork() 是在 linux 中使用 clone() 系统调用实现的,它从内核返回两次。

  • A non-zero value(Process ID of child) is returned to the parent.
  • A value of zero is returned to the child.
  • In case the child is not created successfully due to any issues like low memory, -1 is returned to the fork().
  • 将非零值(子进程 ID)返回给父进程。
  • 零值返回给孩子。
  • 如果由于内存不足等任何问题而未能成功创建子进程,则将 -1 返回给 fork()。

Let's understand this with an example:

让我们通过一个例子来理解这一点:

pid = fork(); 
// Both child and parent will now start execution from here.
if(pid < 0) {
    //child was not created successfully
    return 1;
}
else if(pid == 0) {
    // This is the child process
    // Child process code goes here
}
else {
    // Parent process code goes here
}
printf("This is code common to parent and child");

In the example, we have assumed that exec() is not used inside the child process.

在示例中,我们假设 exec() 未在子进程内使用。

But a parent and child differs in some of the PCB(process control block) attributes. These are:

但是父子进程在某些 PCB(进程控制块)属性上有所不同。这些是:

  1. PID - Both child and parent have a different Process ID.
  2. Pending Signals - The child doesn't inherit Parent's pending signals. It will be empty for the child process when created.
  3. Memory Locks - The child doesn't inherit its parent's memory locks. Memory locks are locks which can be used to lock a memory area and then this memory area cannot be swapped to disk.
  4. Record Locks - The child doesn't inherit its parent's record locks. Record locks are associated with a file block or an entire file.
  5. Process resource utilisation and CPU time consumed is set to zero for the child.
  6. The child also doesn't inherit timers from the parent.
  1. PID - 子进程和父进程都有不同的进程 ID。
  2. 待处理信号 - 子进程不会继承父进程的待处理信号。子进程在创建时将为空。
  3. 内存锁 - 子进程不会继承其父进程的内存锁。内存锁是可用于锁定内存区域的锁,然后该内存区域无法交换到磁盘。
  4. 记录锁 - 子进程不会继承其父进程的记录锁。记录锁与文件块或整个文件相关联。
  5. 对于子进程,进程资源利用率和消耗的 CPU 时间设置为零。
  6. 孩子也不会从父母那里继承计时器。

But what about the child memory? Is a new address space created for a child?

但是孩子的记忆呢?是否为孩子创建了新的地址空间?

The answers in no. After the fork(), both parent and child share the memory address space of parent. In linux, these address space are divided into multiple pages. Only when the child writes to one of the parent memory pages, a duplicate of that page is created for the child. This is also known as copy on write(Copy parent pages only when the child writes to it).

没有答案。在 fork() 之后,父子进程共享父进程的内存地址空间。在linux中,这些地址空间被划分为多个页面。只有当孩子写入父内存页面之一时,才会为孩子创建该页面的副本。这也称为写入时复制(仅在子级写入时复制父页面)。

Let's understand copy on write with an example.

让我们通过一个例子来理解写时复制。

int x = 2;
pid = fork();
if(pid == 0) {
    x = 10;
    // child is changing the value of x or writing to a page
    // One of the parent stack page will contain this local               variable. That page will be duplicated for child and it will store the value 10 in x in duplicated page.  
}
else {
    x = 4;
}

But why is copy on write necessary?

但为什么写时复制是必要的?

A typical process creation takes place through fork()-exec() combination. Let's first understand what exec() does.

典型的进程创建是通过 fork()-exec() 组合进行的。让我们首先了解 exec() 是做什么的。

Exec() group of functions replaces the child's address space with a new program. Once exec() is called within a child, a separate address space will be created for the child which is totally different from the parent's one.

Exec() 函数组用新程序替换子进程的地址空间。一旦在子进程中调用 exec(),就会为子进程创建一个与父进程完全不同的单独地址空间。

If there was no copy on write mechanism associated with fork(), duplicate pages would have created for the child and all the data would have been copied to child's pages. Allocating new memory and copying data is a very expensive process(takes processor's time and other system resources). We also know that in most cases, the child is going to call exec() and that would replace the child's memory with a new program. So the first copy which we did would have been a waste if copy on write was not there.

如果没有与 fork() 相关联的写时复制机制,将为孩子创建重复页面,并且所有数据都将被复制到孩子的页面。分配新内存和复制数据是一个非常昂贵的过程(占用处理器的时间和其他系统资源)。我们也知道,在大多数情况下,孩子会调用 exec() ,这将用新程序替换孩子的内存。因此,如果没有写入时的副本,我们所做的第一个副本将是一种浪费。

pid = fork();
if(pid == 0) {
    execlp("/bin/ls","ls",NULL);
    printf("will this line be printed"); // Think about it
    // A new memory space will be created for the child and that   memory will contain the "/bin/ls" program(text section), it's stack, data section and heap section
else {
    wait(NULL);
    // parent is waiting for the child. Once child terminates, parent will get its exit status and can then continue
}
return 1; // Both child and parent will exit with status code 1.

Why does parent waits for a child process?

为什么父进程等待子进程?

  1. The parent can assign a task to it's child and wait till it completes it's task. Then it can carry some other work.
  2. Once the child terminates, all the resources associated with child are freed except for the process control block. Now, the child is in zombie state. Using wait(), parent can inquire about the status of child and then ask the kernel to free the PCB. In case parent doesn't uses wait, the child will remain in the zombie state.
  1. 父级可以将任务分配给它的子级并等待它完成任务。然后它可以进行一些其他的工作。
  2. 一旦子进程终止,除了进程控制块之外,所有与子进程相关的资源都将被释放。现在,孩子处于僵尸状态。使用wait(),父进程可以查询子进程的状态,然后请求内核释放PCB。如果父级不使用等待,子级将保持僵尸状态。

Why is exec() system call necessary?

为什么需要 exec() 系统调用?

It's not necessary to use exec() with fork(). If the code that the child will execute is within the program associated with parent, exec() is not needed.

没有必要将 exec() 与 fork() 一起使用。如果子进程将执行的代码在与父进程关联的程序中,则不需要 exec()。

But think of cases when the child has to run multiple programs. Let's take the example of shell program. It supports multiple commands like find, mv, cp, date etc. Will be it right to include program code associated with these commands in one program or have child load these programs into the memory when required?

但是想想孩子必须运行多个程序的情况。让我们以shell程序为例。它支持多种命令,如 find、mv、cp、date 等。将与这些命令关联的程序代码包含在一个程序中或在需要时让孩子将这些程序加载到内存中是否正确?

It all depends on your use case. You have a web server which given an input x that returns the 2^x to the clients. For each request, the web server creates a new child and asks it to compute. Will you write a separate program to calculate this and use exec()? Or you will just write computation code inside the parent program?

这一切都取决于您的用例。您有一个 Web 服务器,它给出了一个输入 x,它将 2^x 返回给客户端。对于每个请求,Web 服务器都会创建一个新子项并要求它进行计算。你会写一个单独的程序来计算这个并使用 exec() 吗?或者你只是在父程序中编写计算代码?

Usually, a process creation involves a combination of fork(), exec(), wait() and exit() calls.

通常,进程创建涉及 fork()、exec()、wait() 和 exit() 调用的组合。

回答by Ignacio Vazquez-Abrams

The exec(3,3p)functions replacethe current process with another. That is, the current process stops, and another runs instead, taking over some of the resources the original program had.

这些exec(3,3p)函数用另一个替换当前进程。也就是说,当前进程停止,而另一个运行,接管原始程序拥有的一些资源。