Linux 创建进程 fork、vfork、进程管理
- 一、Linux的0号、1号、2号进程
- 二、Linux的进程标识
- 三、fork() 函数
- 1、基本概念
- 2、函数特点
- 3、用法以及应用场景
- (1)父子进程执行不同的代码
- (2)进程执行另一个程序
- 4、工作原理
- 四、vfork() 函数
- 1、基本概念
- 2、函数特点
- 3、用法以及应用场景
- 4、工作原理
- 五、fork 与 vfork 的比较
- 六、父子进程共享文件的场景
- 示例:父子进程共享文件描述符
在Linux操作系统中,进程是资源分配的基本单位。进程的创建是系统正常运行的重要组成部分。通过理解进程创建的机制,开发者能够更高效地控制系统的资源和多任务处理
一、Linux的0号、1号、2号进程
在Linux操作系统中,进程以树状结构组织。最重要的进程包括0号、1号和2号进程,它们具有特殊的意义:
- 0号进程(系统进程):也被称为swapper或idle进程,是所有进程的祖先,负责初始化系统的基本工作。
- 1号进程(systemd):是系统的初始化进程,负责系统的启动、硬件初始化以及进程管理。它是所有其他进程的父进程。
- 2号进程(kthreadd):是内核线程的管理者,负责内核级线程的创建、管理和调度。
这些进程在系统启动时就已经存在,确保了系统的正常运行。
二、Linux的进程标识
每个进程在Linux系统中都有一个唯一的进程标识符(PID)。进程标识符用于标识和管理进程。除了PID外,还有一些相关的进程标识符:
- PPID(父进程ID):指向父进程的PID。
- PGID(进程组ID):用于管理进程组,多个进程可以组成一个进程组。
- SID(会话ID):代表一组进程的会话标识,通常与终端的会话关联。
在Linux中,进程的创建通常使用fork()
系统调用来实现。
三、fork() 函数
1、基本概念
fork()
是Unix/Linux系统中用于创建新进程的一个系统调用。调用fork()
时,当前进程(父进程)会复制一份新的进程(子进程)。子进程将继承父进程的大部分资源(如文件描述符、环境变量等)。
2、函数特点
- 进程复制:
fork()
会复制父进程的虚拟地址空间,但父子进程的实际物理地址不同。 - 内存副本:父进程的堆栈空间和数据空间会被复制到子进程中,父子进程的数据并不共享。
- 顺序不确定:
fork()
调用后,父子进程的执行顺序是不可预知的。父进程可能先执行,也可能子进程先执行。
3、用法以及应用场景
(1)父子进程执行不同的代码
在调用fork()
后,父子进程各自从fork()
返回的位置继续执行代码。父进程和子进程通常执行不同的操作。
#include <stdio.h>
#include <unistd.h>int main() {pid_t pid = fork();if (pid < 0) {// 错误处理perror("fork failed");return 1;}if (pid == 0) {// 子进程执行printf("This is the child process. PID: %d\n", getpid());} else {// 父进程执行printf("This is the parent process. PID: %d\n", getpid());}return 0;
}
运行该代码时,父子进程会并行执行,打印各自的进程ID。
(2)进程执行另一个程序
父进程可以通过fork()
创建子进程,然后使用exec()
系列函数来让子进程执行不同的程序。这在处理多任务时非常常见。
#include <stdio.h>
#include <unistd.h>int main() {pid_t pid = fork();if (pid < 0) {perror("fork failed");return 1;}if (pid == 0) {// 子进程执行新的程序execlp("/bin/ls", "ls", "-l", NULL);// 如果 execlp 调用成功,下面的代码不会执行perror("execlp failed");} else {printf("Parent process is running. PID: %d\n", getpid());}return 0;
}
在此示例中,子进程通过execlp()
调用执行ls
命令。
4、工作原理
fork()
系统调用的工作原理基于复制父进程的虚拟内存空间并为子进程创建一个新的进程。虽然父子进程的虚拟地址空间相同,但它们是不同的物理地址。
四、vfork() 函数
1、基本概念
vfork()
函数和fork()
类似,也用于创建子进程。但是,vfork()
不会复制父进程的虚拟内存,而是让父进程暂时停止执行,直到子进程调用exec()
或_exit()
。这种行为使得vfork()
比fork()
更高效,尤其在子进程即将调用exec()
时。
2、函数特点
- 共享内存空间:
vfork()
创建的子进程共享父进程的地址空间,直到子进程调用exec()
或_exit()
。 - 父进程暂停:父进程会暂停执行,直到子进程完成某些操作(通常是执行新程序或退出)。
3、用法以及应用场景
vfork()
通常用于需要子进程快速调用exec()
的场景,能够提高效率。
#include <stdio.h>
#include <unistd.h>int main() {pid_t pid = vfork();if (pid < 0) {perror("vfork failed");return 1;}if (pid == 0) {// 子进程执行execlp("/bin/ls", "ls", "-l", NULL);perror("execlp failed");} else {printf("Parent process is waiting. PID: %d\n", getpid());}return 0;
}
4、工作原理
vfork()
会让子进程与父进程共享内存空间,父进程在子进程调用exec()
或_exit()
之前会被挂起,从而避免了不必要的内存复制操作。
五、fork 与 vfork 的比较
特性 | fork() | vfork() |
---|---|---|
内存复制 | 复制父进程的内存 | 子进程和父进程共享内存空间 |
父进程执行 | 父子进程并行执行 | 父进程暂停,直到子进程执行完毕 |
性能 | 较低(内存复制开销) | 较高(减少内存复制,提高效率) |
适用场景 | 适用于父子进程都需要独立运行的情况 | 适用于子进程会调用exec() 的情况 |
六、父子进程共享文件的场景
在Linux中,父进程和子进程共享文件描述符,这意味着它们对同一个文件的操作可能相互影响。例如,如果父进程和子进程都操作同一个文件描述符,文件的偏移量可能会改变,从而导致输出混合。为了避免这种情况,必须显式地同步文件操作。
示例:父子进程共享文件描述符
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>int main() {int fd = open("test.txt", O_RDWR | O_CREAT, 0644);if (fd == -1) {perror("File open failed");return 1;}pid_t pid = fork();if (pid < 0) {perror("fork failed");return 1;}if (pid == 0) {// 子进程写入文件write(fd, "Hello from child\n", 17);} else {// 父进程写入文件write(fd, "Hello from parent\n", 18);}close(fd);return 0;
}
在这个示例中,父进程和子进程共享fd
文件描述符。如果没有适当的同步机制,写入可能会交替进行,从而产生不一致的结果。