linux学习笔记(11)fork详解
复制进程: fork(在unistd.h)
fork 系统调用会创建一个新的进程,称为子进程
而调用 fork 的进程被称为父进程
子进程是父进程的一个副本,它会复制父进程的地址空间(包括代码段、数据段、堆、栈等)、打开的文件描述符、进程的状态等大部分资源。

返回值
fork 调用会在父进程和子进程中都返回,但是返回值不同:
在父进程中,fork 返回子进程的进程 ID(是一个大于 0 的整数)。这可以让父进程知道自己创建的子进程的标识,以便后续进行管理,比如使用 wait 系列函数等待子进程结束。
在子进程中,fork 返回 0。通过返回值 0,子进程可以知道自己是新创建的子进程,从而执行与父进程不同的代码逻辑。
如果 fork 调用失败,它会在父进程中返回 -1,并设置适当的错误码(比如内存不足等情况导致无法创建新进程)。
#include<stdio.h>
#include<stdlib.h>
int main(){int n = 0;char* s = NULL;pid_t id = fork();if( id == -1 )exit(1);if( id == 0 ){n = 3;s = "child";}else{n = 7;s = "parent";} for(int i = 0; i < n; i++ ){printf("s=%s ,pid=%d\n",s,getpid());sleep(1);}return 0;
}
注意事项
虽然子进程复制了父进程的大部分资源,但并不是完全共享所有内容。
比如,父子进程有各自独立的地址空间,对同一个变量的修改在不同进程中不会相互影响。
不过,像打开的文件描述符这类资源,在父子进程中是共享引用的(但操作是独立的)。
fork 调用后,父子进程执行的先后顺序是不确定的,这取决于操作系统的调度算法。
fork 的核心工作原理和特性
写时复制(Copy-on-Write, COW)
这是理解 fork 效率的关键。早期的 fork 会立即复制父进程的全部内存空间,如果父进程很大,这会非常耗时耗资源。
现代的 fork 采用了写时复制技术。创建子进程时,内核并不会立即复制物理内存页,而是让父进程和子进程共享所有的内存页,并将这些页标记为“只读”。
当父进程或子进程任何一方尝试修改某一块内存时,内核才会在修改前复制这一块内存页,然后让修改方在自己的副本上进行操作。这样就大大提高了 fork 的效率,因为很多进程在 fork 后会立即执行 exec(见下文),根本不会修改大部分内存。
返回值:区分父子进程的关键
fork() 函数最神奇的地方是它被调用一次,但返回两次。
在父进程中,fork() 返回新创建子进程的进程ID(PID)(一个大于0的数字)。
在子进程中,fork() 返回 0。
如果创建失败(例如系统资源不足),则返回 -1。
通过判断返回值,程序就可以让父进程和子进程执行不同的代码路径。
在 fork() 成功返回时,子进程已经被创建了。 它拥有独立的PID、内核数据结构和一个完整的、逻辑上独立的地址空间视图。但是,这个地址空间所对应的物理内存资源,在初始阶段是与父进程共享的,并通过“写时复制”机制在需要修改时才进行真正的分配和复制。
cow的执行界限:
会触发COW的操作:
- 赋值操作:var = new_value
- 自增/自减:var++, ++var, var--
- 复合赋值:+=, -=, *=, /=
- 通过指针修改:*ptr = value
- 数组成员修改:array[index] = value
- 结构体成员修改:struct.member = value
不会触发COW的操作:
- 读取操作:printf("%d", var)
- 取地址:&var
- 条件判断:if (var > 0)
- 表达式计算:result = var + 10
- 函数参数传递:func(var)(传值)
- 返回值的读取:return var;
修改结构体其中某个变量,会复制一整个结构体
修改较大的数组其中一个元素,复制整个内存页(通常是4kb)
修改哪里就cow哪里!!!剩下没修改的就不cow!!!记住!