📜  fork() 和 exec() 的区别(1)

📅  最后修改于: 2023-12-03 14:41:18.848000             🧑  作者: Mango

fork() 和 exec() 的区别

在 Unix (包括 Linux)操作系统中,fork()exec() 是两个非常常用的系统调用,用于创建进程和运行程序,下面我们来详细介绍它们之间的区别。

fork()

fork() 系统调用用于创建一个新的进程,这个新的进程几乎与原进程完全相同,包括代码、数据以及堆栈等等。包括进程 ID、父进程 ID 等进程信息也会相同,但是在它们的内存地址和数据空间是独立的。

fork() 的用法如下所示:

#include <unistd.h>

pid_t fork(void);

其中 pid_t 是一种定义在 sys/types.h 中的数据类型,用于表示进程 ID。调用 fork() 会返回两个值,父进程中 fork() 返回新创建的子进程的进程 ID,而在子进程中调用 fork() 则会返回 0。如果 fork() 调用出现了错误,则会返回负数。

下面是 fork() 的示例代码:

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

int main()
{
    pid_t pid = fork();
    if (pid < 0) // 出现错误
    {
        fprintf(stderr, "fork error!\n");
        return 1;
    }
    else if (pid == 0) // 子进程
    {
        printf("I'm the child process!\n");
        printf("My PID is %d.\n", getpid());
    }
    else // 父进程
    {
        printf("I'm the parent process!\n");
        printf("My PID is %d.\n", getpid());
        printf("The PID of my child process is %d.\n", pid);
    }
    return 0;
}

这个程序中,通过调用 fork() 创建了一个新的子进程,然后在父进程和子进程中分别输出了不同的信息。需要注意的是,输出的进程 ID 都是不同的,这是因为在 fork() 后,父子进程有着不同的进程 ID。

exec()

exec() 系统调用用于启动一个新的程序,它会覆盖当前进程的地址空间,并从指定的位置开始执行一个新的程序。因为 exec() 的结果是当前的进程被替换掉,所以它本身不会创建新的进程。

exec() 族函数的用法如下所示:

#include <unistd.h>

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

其中各个参数的含义如下:

  • path:要执行的可执行文件的路径名或文件名
  • file:要执行的可执行文件的文件名,相当于在 PATH 环境变量所指定的路径中查找
  • arg:要传递给新程序的命令行参数,可以是一个字符串或多个字符串
  • argv:要传递给新程序的命令行参数列表
  • envp:要传递给新程序的环境变量列表

下面是 exec() 函数族的示例代码:

#include <stdio.h>
#include <unistd.h>

int main()
{
    char *const argv[] = {"ls", "-l", NULL};
    if (fork() == 0) // 子进程
    {
        execl("/bin/ls", "ls", "-l", NULL);
    }
    else // 父进程
    {
        printf("I'm the parent process!\n");
        execv("/bin/ls", argv);
    }
    return 0;
}

这个程序中,首先在子进程中使用了 execl() 函数来执行了 /bin/ls -l 命令,然后在父进程中使用了 execv() 函数来执行了和子进程一样的命令。值得注意的是,如果 exec() 函数执行成功,那么它的调用者进程就不存在了,它会被新执行的程序替换掉。

总结
  • fork() 创建一个新的进程,该进程几乎与原进程相同,但独立于原进程的地址空间;
  • exec() 执行一个新的程序,在当前进程的地址空间中覆盖原先的程序;
  • fork()exec() 往往结合使用,比如先用 fork() 创建子进程,然后在子进程中使用 exec() 运行一个新的程序。