参考:
1.博客1:https://www.pianshen.com/article/4305691855/
fork:在原进程的基础上“分叉”出一个子进程,即创建一个子进程。
NAME fork - create a child process SYNOPSIS #include <unistd.h> pid_t fork(void); DESCRIPTION fork() creates a new process by duplicating the calling process. The new process is referred to as the child process. The calling process is referred to as the parent process.
返回值:
RETURN VALUE On success, the PID of the child process is returned in the parent, and 0 is returned in the child. On failure, -1 is returned in the parent, no child process is created, and errno is set appropriately.
示例代码1,基本使用:
pid_t pid = fork(); if (pid == 0) { printf("child, pid=%d, ppid=%d ", getpid(), getppid()); sleep(2); printf("child, i will die, pid=%d, ppid=%d ", getpid(), getppid()); } else if (pid > 0) { while (true) { printf("parent, child_pid=%d, self_pid=%d, father_pid=%d ", pid, getpid(), getppid()); sleep(1); } }
示例代码2,创建多个子进程:
pid_t pid { 0 }; int i { 0 }; for (i = 0; i < 5; ++i) { pid = fork(); if (pid == 0) { //son printf("i=%d, child, pid=%d, ppid=%d ", i, getpid(), getppid()); break; } else if (pid > 0) { //father //printf("parent, child_pid=%d, self_pid=%d, father_pid=%d ", pid, getpid(), getppid()); } else { perror("error "); exit(1); } } sleep(i+1);//process sleep if (i < 5) { printf("i=%d, child will exit, pid=%d, ppid=%d ", i, getpid(), getppid()); } else { printf("parent will exit, child_pid=%d, self_pid=%d, father_pid=%d ", pid, getpid(), getppid()); }
进程间共享哪些内容?
通过fork函数建立的子进程时:
1、父子进程之间在刚fork后,刚刚创建子进程后:
(这个时候是完全一样的,但是并不代表后面就可以共享,进程间共享是后续的内容:管道,信号,队列,共享内存等,因为进程间的数据是读时共享,写时复制的)
(1)父子相同处: 全局变量、.data、.bbs、.text、栈、堆、环境变量、用户ID、宿主目录(进程用户家目录)、进程工作目录、信号处理方式等等,即0~3G的用户空间是完全一样的。
(2)父子不同处: 1.进程ID 2.fork返回值 3.父进程ID 4.进程运行时间 5.闹钟(定时器) 6.未决信号集
2、似乎,子进程复制了父进程0-3G用户空间内容,以及父进程的PCB(内核模块在物理内存只有一份),但pid等不同。真的每fork一个子进程都要将父进程的0-3G地址空间完全拷贝一份,然后在映射至物理内存吗?
当然不是,父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。具体见下图1和图2解释。
读时共享写时复制这一机制是由MMU(内存管理单元)来实现的。
注意:只有进程空间的各段的内容要发生变化时(子进程或父进程进行写操作时,都会引起复制),才会将父进程的内容复制一份给子进程。在fork之后两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。即父子进程在逻辑上仍然是严格相互独立的两个进程,各自维护各自的参数,只是在物理上实现了读时共享,写时复制。
(1)fork函数创建子进程后,见图1,内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟空间结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,
(2)如下图2,直到父子进程中有更改相应段(用户空间中)的行为发生时,再为子进程相应的段分配物理空间。
3、父子进程一直共享: 1. 文件描述符(打开文件的结构体) ,注意不是共享文件描述符本身这个整形数,而是共享同一个文件对应的FILE *结构体指针,其实一个文件打开后只能有一个FILE结构体,因此对于多有的进程都是共享这一个结构体,不仅仅只是父子进程。 2. mmap建立的映射区 (进程间通信详解)。 —》共享内存区是进程间通信的一种方式。
特别的,fork之后父进程先执行还是子进程先执行不确定。取决于内核所使用的调度算法。