fork

linux可以通过fork()创建一个新的进程。
例如通过shell命令运行ELF的时候,shell程序就会调用fork()创建一个子进程。

pid_t fork(void);
  • fork函数进入到内核中会先创建新进程对应的内核数据结构(PCB进程控制块什么的)
  • 给新进程分配对应的地址空间
  • 将父进程的所有段的数据和栈堆等数据都map到进程的地址空间中。
  • fork()返回:fork会返回两次,在父进程中返回时返回值为子进程的PID, 在子进程中返回0
//打印三个A和三个B
int main(){
    int i;
    for(i=0;i<2;i++){
        if(fork()==0){
            printf("A\n");
  	    }
        else{
            printf("B\n");
        }
    }
}

fork的写时复制(copy-on-write)

因为fork是把当前进程重新map一份作为子进程,而子进程内存空间与父进程内存空间映射的物理内存是同一块。
只有当子进程向内存空间中写入数据时,此内存块对应的物理内存会copy一份然后重新映射到对应的内存地址空间中。这样做的好处是可以节省系统开销,子进程如果只是访问数据时不会分配新的物理内存的,只有当需要写入数据时才会。

fork后调用exec

一般创建子进程,fork返回到子进程中后程序往往会接着调用exec。exec会将子进程的所有内存空间覆盖掉,包括.text,.data,.bss段和所有的堆栈等内存。然后就可以映射新的ELF到内存空间中作为新进程。(fork与exec并没有什么直接关联,并不是调用了fork就一定要调用exec)

vfork

vfork与fork的区别在于vfork创建的子进程与父进程共享所有内存空间,而fork创建的子进程当触发写时复制的时候就会将原内存空间数据进行copy。

clone

无论是fork创建进程还是pthread_create创建线程,其底层实际都是调用的clone,只不过传入的参数flags不同。

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);

clone允许新进程继承父进程的哪些资源(共享),如果全部共享就意味着相当于一个线程。

  • 如果flags指定CLONE_NEWNS,则对应创建的是一个新进程,新进程拥有自己的内存空间
  • 如果flags指定CLONE_VM | CLONE_PID就表示创建的是一个线程,其与进程共享所有的内存空间,没有自己的内存空间。