嵌入式Linux:创建进程

2026-10-051

在 Linux 系统中,fork() 和 vfork() 是两个常用的系统调用,用于创建新的进程。

1、fork() 系统调用

函数原型如下:

#include <unistd.h>
pid_t fork(void);

返回值:调用 fork() 的进程(父进程)会得到一个返回值。在父进程中,fork() 返回子进程的进程 ID (PID);在子进程中,fork() 返回 0;如果发生错误,则返回 -1,并设置全局变量 errno。

1.1、工作机制

fork() 会创建一个新的进程,这个进程被称为子进程。子进程几乎是父进程的完整副本,包括父进程的代码段、数据段、堆栈段和打开的文件描述符。子进程和父进程之间的唯一区别在于它们拥有不同的进程 ID,并且 fork() 的返回值不同。

内存空间:尽管子进程复制了父进程的内存空间,但实际上,现代 Linux 系统使用了“写时复制”(Copy-on-Write, CoW)技术。只有在父进程或子进程试图修改内存中的数据时,内核才会真正为子进程分配新的内存。这种机制极大地提高了 fork() 的效率,避免了不必要的内存复制。

1.2、使用场景

fork() 适用于需要创建子进程来执行与父进程不同任务的场景。它在网络服务器、守护进程和并行处理任务中被广泛应用。例如,网络服务器可以使用 fork() 为每个客户端请求创建一个新的子进程,从而提高系统的并发性。

示例如下:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
 
int main() {
    pid_t pid = fork(); // 创建子进程
 
    if (pid < 0) {
        // fork() 失败
        fprintf(stderr, "fork() 失败\n");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("这是子进程, PID: %d\n", getpid());
    } else {
        // 父进程
        printf("这是父进程, 子进程的 PID: %d\n", pid);
    }
 
    return 0;
}

在这个例子中,fork() 创建了一个新的子进程。父进程和子进程会从 fork() 调用之后的代码开始执行,但它们各自运行在独立的内存空间中。

2、vfork() 系统调用

函数原型如下:

#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);

返回值:vfork() 的返回值与 fork() 相同。在父进程中,返回子进程的 PID;在子进程中,返回 0;如果出错,则返回 -1 并设置 errno。

2.1、工作机制

vfork() 与 fork() 类似,用于创建一个子进程,但它有不同的内存管理机制。vfork() 创建的子进程与父进程共享相同的地址空间,这意味着子进程在调用 exec() 或 _exit() 之前,不会复制父进程的内存空间。这使得 vfork() 比 fork() 更加高效,特别是在子进程很快就要执行新的程序时。

共享地址空间:由于 vfork() 是为了在子进程立即调用 exec() 或 _exit() 的场景下优化的,因此子进程和父进程在 exec() 或 _exit() 之前共享同一内存空间。这意味着如果子进程在此期间修改了共享的内存数据,可能会导致不可预知的后果。

执行顺序:vfork() 保证子进程会先运行,直到调用 exec() 或 _exit() 后,父进程才会继续执行。这种行为确保了父进程不会在子进程完成执行之前访问可能被修改的共享内存。

2.2、使用场景

vfork() 适用于子进程需要立即调用 exec() 来执行新程序的场景。例如,在 shell 脚本中执行外部命令时,vfork() 可以提高效率。但由于 vfork() 在某些情况下会带来潜在的安全风险(例如子进程误修改父进程的内存),现代系统通常更推荐使用优化后的 fork()。

示例如下:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
 
int main() {
    pid_t pid = vfork(); // 创建子进程
 
    if (pid < 0) {
        // vfork() 失败
        fprintf(stderr, "vfork() 失败\n");
        return 1;
    } else if (pid == 0) {
        // 子进程:立即执行新的程序
        execlp("/bin/ls", "ls", NULL); // 执行 ls 命令
        _exit(0); // 使用 _exit 确保快速退出子进程
    } else {
        // 父进程
        printf("这是父进程\n");
    }
 
    return 0;
}

在这个例子中,vfork() 创建了一个子进程,并立即使用 execlp() 执行 ls 命令。由于 vfork() 子进程在执行 exec() 之前与父进程共享内存,因此在调用 exec() 之前不能修改父进程的数据,否则可能导致程序行为不确定。

fork() 和 vfork() 的区别总结:

  • 内存管理:fork() 通过写时复制为子进程创建独立的内存空间,而 vfork() 让子进程与父进程共享内存空间,直到子进程执行 exec() 或 _exit()。
  • 效率:vfork() 比 fork() 更高效,特别是在子进程需要立即执行新程序时。但由于现代 Linux 内核的写时复制优化,fork() 的效率也得到了显著提升。
  • 安全性:fork() 更加安全可靠,因为它为子进程分配了独立的内存空间。而 vfork() 可能会带来潜在的风险,建议在绝对需要优化性能的情况下使用。

使用建议:

  • 优先使用 fork():由于现代 Linux 系统已经优化了 fork(),在大多数情况下使用 fork() 是更为安全和通用的选择。尤其是在编写复杂的应用程序时,fork() 可以减少不确定性和潜在的错误。
  • 在特定场景下使用 vfork():如果需要创建子进程并立即调用 exec(),可以考虑使用 vfork() 以提高效率。但应确保在调用 exec() 或 _exit() 之前,不会对父进程的内存空间进行任何修改。

理解 fork() 和 vfork() 的工作原理和差异,对于设计和实现高效并发的 Linux 程序至关重要。在不同的应用场景中,根据具体需求选择合适的系统调用,将有助于提高程序的效率和稳定性。


作者:不脱发的程序猿。原文:https://handsome-man.blog.csdn.net/article/details/141822762 。原文日期:2025-01-06。转载遵循 CC BY-SA 4.0,保留署名与来源。

创作许可协议

本项目采用 CC BY-SA(署名-相同方式共享) 进行许可。

评论(0)
- 没有更多了 -