linux 进程创建clone、fork与vfork

 目录:

1、clone、fork与vfork介绍

2、fork说明

3、vfork说明

4、clone说明
5、fork,vfork,clone的区别


内容:

1、clone、fork与vfork介绍

Linux下的进程与线程相同点是都有进程控制块(PCB,具体的类是task_struct。区别在于一个有独立的进程资源,一个是共享的进程资源。除了内核线程是完全没有用户空间。进程资源包括进程的PCB、线程的系统堆栈、进程的用户空间、进程打开的设备(文件描述符集)等。

    Linux的用户进程不能直接被创建出来,因为不存在这样的API。它只能从某个进程中复制出来,有的需要通过exec这样的API来切换到实际想要运行的程序文件。

    复制的API包括三种:forkclonevfork

在linux源码中这三个调用的执行过程是执行fork(),vfork(),clone()时,通过一个系统调用表映射到sys_fork(),sys_vfork(),sys_clone(),再在这三个函数中去调用do_fork()去做具体的创建进程工作。这三个API的内部实际都是调用一个内核内部函数do_fork,只是填写的参数不同而已。

   

1.fork, vfork and clone三者最终都会调用do_fork函数,三者的差别就是参数上的不同而已。
fork
的实现:
   do_fork(CLONE_SIGCHLD,...)
clone
的实现:
    do_fork(CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGCHLD,...)
vfork
的实现:
     do_fork(CLONE_VFORK|CLONE_VM|CLONE_SIGCHLD,...)

实际上产生效果的也是这些参数:

CLONE_VM标识:表示共享地址空间(变量等)

CLONE_FILES标志:表示共享文件描述符表

CLONE_VFORK标识:标识父进程会被阻塞,子进程会把父进程的地址空间锁住,直到子进程退出或执行exec时才释放该锁

SIGCHLD标识:共享信号


2. Linux使用copy on wirte的技术,Linux中的fork代价仅仅是创建子进程的页表结构和创建一个task_struct结构。
3. 
为了优化那些:fork然后就是exec的程序,Linux提供了vforkvfork时,父进程会被阻塞,直到子进程调用了execexit,因为此时不复制页表结构。
4. clone()
系统调用是fork()的推广形式,它允许新进程共享父进程的存储空间、文件描述符和信号处理程序


2、fork

共享资源:

fork创建一个进程时,子进程只是完全复制父进程的资源,复制出来的子进程有自己的task_struct结构和pid,但却复制父进程其它资源(用户空间、文件描述符集)。

写时复制:

fork是一个开销十分大的系统调用,这些开销并不是所有的情况下都是必须的,比如某进程fork出一个子进程后,其子进程仅仅是为了调用exec执行另一个可执行文件,那么在fork过程中对于虚存空间的复制将是一个多余的过程。但由于现在Linux中是采取了copy-on-write(COW写时复制)技术,为了降低开销,fork最初并不会真的产生两个不同的拷贝,因为在那个时候,大量的数据其实完全是一样的。写时复制是在推迟真正的数据拷贝。若后来确实发生了写入,那意味着parent和child的数据不一致了,于是产生复制动作,每个进程拿到属于自己的那一份,这样就可以降低系统调用的开销。所以有了写时复制。 

返回值:

fork()调用执行一次返回两个值,对于父进程,fork函数返回子程序的进程号,而对于子程序,fork函数则返回零,这就是一个函数返回两次的本质。

共享代码段:

在fork之后,子进程和父进程都会继续执行fork调用之后的指令。子进程是父进程的副本。它将获得父进程的数据空间,堆和栈的副本,这些都是副本,父子进程并不共享这部分的内存。也就是说,子进程对父进程中的同名变量进行修改并不会影响其在父进程中的值。但是父子进程又共享一些东西,简单说来就是程序的正文段。正文段存放着由cpu执行的机器指令,通常是read-only的。


(1)调用方法
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
正确返回:在父进程中返回子进程的进程号,在子进程中返回0
错误返回:-1

(2) 函数调用的用途
一个进程希望复制自身,从而父子进程能同时执行不同段的代码。


下面是一个验证的例子:
 

例1:fork.c
 
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<errno.h>
 
int main()
{
	int a = 5;
	int b = 2;
	pid_t pid;
	pid = fork();
	if(pid == 0) {
	   a = a-4;
	   printf("I'm a child process with PID [%d],the value of a: %d,the value of b:%d.\n",pid,a,b);
	}else if(pid < 0) {
	   perror("fork");
	}else {
	   printf("I'm a parent process, with PID  [%d], the value of a: %d, the value of b:%d.\n", pid, a, b);
	}
	return 0;
}
 

3、vfork


vfork系统调用不同于fork,用vfork创建的子进程与父进程共享地址空间,也就是说子进程完全运行在父进程的地址空间上,如果这时子进程修改了某个变量,这将影响到父进程。
因此,上面的例子如果改用vfork()的话,那么两次打印a,b的值是相同的,所在地址也是相同的。
但此处有一点要注意的是用vfork()创建的子进程必须显示调用exit()来结束,否则子进程将不能结束,而fork()则不存在这个情况。
Vfork也是在父进程中返回子进程的进程号,在子进程中返回0。

执行时机:
用 vfork创建子进程后,父进程会被阻塞直到子进程调用exec(exec,将一个新的可执行文件载入到地址空间并执行之)或exit。
vfork的好处是在子进程被创建后往往仅仅是为了调用exec执行另一个程序,因为它就不会对父进程的地址空间有任何引用,
所以对地址空间的复制是多余的 ,因此通过vfork共享内存可以减少不必要的开销。


(1) 调用方法
与fork函数完全相同
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
正确返回:在父进程中返回子进程的进程号,在子进程中返回0
错误返回:-1
2. vfork函数调用的用途
用vfork创建的进程主要目的是用exec函数执行另外的程序。


下面这个例子可以验证子进程调用exec时父进程是否真的已经结束阻塞:
 
例2:execl.c
 
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
 
int main()
{
int a = 1;
int b = 2;
pid_t pid;
int status;
pid = vfork();
if(pid == -1) {
  perror("Fork failed to creat a process");
  exit(1);
}else if(pid == 0) {
//     sleep(3);
  if(execl("/bin/example","example",NULL)<0) {
      perror("Exec failed");
      exit(1);
  }
  exit(0);
//  }else 
//     if(pid != wait(&status)) {
//     perror("A Signal occured before the child exited");
  }else 
      printf("parent process,the value of a :%d, b:%d, addr of a: %p,b: %p\n",a,b,&a,&b);
  exit(0);
}
Example.c
#include<stdio.h>
 
int main()
{
int a = 1;
int b = 2;
sleep(3);
printf("Child process,the value of a is %d,b is %d,the address a %p,b %p\n",a,b,&a,&b);
return 0;
}
#gcc –o execl execl.c
#./ execl
运行结果:
Parent process,the value of a:1,b:2,addr of a:0xbfaa710c,b:0xbfaa7108
Child process ,The value of a is 1,b is 2,the address a 0xbfb73d90,b 0xbfb73d8c
如果将注释掉的三行加入程序的话,由于父进程wait()而阻塞,因此即使此时子进程阻塞,父进程也得不到运行,因此运行结果如下:
The value of a is 1,b is 2,the address a 0xbfb73d90,b 0xbfb73d8c
Parent process,the value of a:1,b:2,addr ofa:0xbfaa710c, b:0xbf aa7108
另外还应注意的是在它调用exec后父进程才可能调度运行,因此sleep(3)函数必须放在example程序中才能生效。

#gcc –o fork fork.c
#./fork
运行结果:
I’m a child process with PID[0],the value of a:1,the value of b:2.
I’m a parent process with PID[19824],the value of a:5,the value of b:2.
可见,子进程中将变量a 的值改为1,而父进程中则保持不变。

4、clone

系统调用fork()和vfork()是无参数的,而clone()则带有参数。


fork()是全部复制,vfork()是共享内存,而clone()是则可以将父进程资源有选择地复制给子进程,

而没有复制的数据结构则通过指针的复制让子进程共享,具体要复制哪些资源给子进程,由参数列表中的clone_flags来决定。
另外,clone()返回的是子进程的pid。


1)调用方法
#include <sched.h>
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
正确返回:返回所创建进程的PID,函数中的flags标志用于设置创建子进程时的相关选项。
错误返回:-1

关于参数

CLONE_VM标识:表示共享地址空间(变量等)
CLONE_FILES标志:表示共享文件描述符表

CLONE_VFORK标识:标识父进程会被阻塞,子进程会把父进程的地址空间锁住,直到子进程退出或执行exec时才释放该锁

SIGCHLD标识:共享信号


(2)clone()函数调用的用途
用于有选择地设置父子进程之间需共享的资源


下面来看一个例子:
 
例3:clone.c
 
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
 
int variable,fd;
int do_something() {
       variable = 42;
       printf("in child process\n");
       close(fd);
//     _exit(0);
       return 0;
}
 
int main(int argc, char *argv[]) {
       void *child_stack;
       char tempch;
       variable = 9;
       fd = open("/test.txt",O_RDONLY);
       child_stack = (void *)malloc(16384);
       printf("The variable was %d\n",variable);
       clone(do_something, child_stack+10000, CLONE_VM |CLONE_FILES,NULL);
       sleep(3);        /* 延时以便子进程完成关闭文件操作、修改变量 */
       printf("The variable is now %d\n",variable);
       if(read(fd,&tempch,1) < 1) {
              perror("File Read Error");
              exit(1);
       }
       printf("We could read from the file\n");
       return 0;
}
#gcc –o clone clone.c 
#./clone
运行结果:
the value was 9
in child process
The variable is now 42
File Read Error
 
从程序的输出结果可以看出:
子进程将文件关闭并将变量修改(调用clone时用到的CLONE_VM、CLONE_FILES标志将使得变量和文件描述符表被共享),
父进程随即就感觉到了,这就是clone的特点。由于此处没有设置标志CLONE_VFORK,因此子进程在运行时父进程也不会阻塞,两者同时运行。
  

5、fork,vfork,clone的区别
1)拷贝内容

对于fork,子进程拷贝父进程的数据段和堆栈段,共享方式访问代码段,由于在Linux中采用的写时复制技术,也就是说,fork执行时并不真正复制用户空间的所有页面,而只是复制页面表。这样,无论父进程还是子进程,当发生用户空间的写操作时,都会引发写复制操作,而另行分配一块可用的用户空间,使其完全独立。这是一种提高效率的非常有效的方法。

对于vfork,共享所有的父进程资源,子进程与父进程共享内存空间, 子进程对虚拟地址空间任何数据的修改同样为父进程所见,故而是真正意义上的共享,因此对共享数据的保护必须有上层应用来保证。所以才需要等到exec(只是用另一个新程序替换了当前进程的正文,数据,堆和栈段)或子进程退出后父进程才能被调度。

对于clone,通过参clone_flags 的设置来决定哪些资源共享,哪些资源拷贝,一般只有进程的PCB和线程的系统堆栈被复制了,(也就是共享了进程的用户空间、进程打开的设备(文件描述符集),但需要依赖共享标识的参数 CLONE_VM(共享地址空间)|CLONE_FS|CLONE_FILES(共享文件描述符集)|CLONE_SIGCHLD(共享信号))。

ps:

在四项进程资源的复制中(进程资源包括进程的PCB线程的系统堆栈、进程的用户空间、进程打开的设备(文件描述符集)),用户空间是相对庞大的,如果完全复制则效率会很低。


(2)访问次序控制

fork不对父子进程的执行次序进行任何限制,fork返回后,子进程和父进程都从调用fork函数的下一条语句开始行,
但父子进程运行顺序是不定的,它取决于内核的调度算法;

而在vfork调用中,子进程先运行,父进程挂起,直到子进程调用了exec或exit之后,
父子进程的执行次序才不再有限制;

clone中由标志CLONE_VFORK来决定子进程在执行时父进程是阻塞还是运行,若没有设置该标志,则父子进程同时运行,
设置了该标志,则父进程挂起,直到子进程结束为止。







 

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
本PDF电子书包含上下两册,共1576页,带目录,高清非扫描版本。 作者: 毛德操 胡希明 丛书名: Linux内核源代码情景分析 出版社:浙江大学出版社 目录 第1章 预备知识 1.1 Linux内核简介. 1.2 Intel X86 CPU系列的寻址方式 1.3 i386的页式内存管理机制 1.4 Linux内核源代码的C语言代码 1.5 Linux内核源代码的汇编语言代码 第2章 存储管理 2.1 Linux内存管理的基本框架 2.2 地址映射的全过程 2.3 几个重要的数据结构和函数 2.4 越界访问 2.5 用户堆栈的扩展 2.6 物理页面的使用和周转 2.7 物理页面的分配 2.8 页面的定期换出 2.9 页面的换入 2.10 内核缓冲区的管理 2.11 外部设备存储空间的地址映射 2.12 系统调用brk() 2.13 系统调用mmap() 第3章 断、异常和系统调用 3.1 X86 CPU对断的硬件支持 3.2 断向量表IDT的初始化 3.3 断请求队列的初始化 3.4 断的响应和服务 3.5 软断与Bottom Half 3.6 页面异常的进入和返回 3.7 时钟断 3.8 系统调用 3.9 系统调用号与跳转表 第4章 进程进程调度 4.1 进程四要素 4.2 进程三部曲:创建、执行与消亡 4.3 系统调用fork()、vfork()与clone() 4.4 系统调用execve() 4.5 系统调用exit()与wait4() 4.6 进程的调度与切换 4.7 强制性调度 4.8 系统调用nanosleep()和pause() 4.9 内核的互斥操作 第5章 文件系统 5.1 概述 5.2 从路径名到目标节点 5.3 访问权限与文件安全性 5.4 文件系统的安装和拆卸 5.5 文件的打开与关闭 5.6 文件的写与读 5.7 其他文件操作 5.8 特殊文件系统/proc 第6章 传统的Unix进程间通信 6.1 概述 6.2 管道和系统调用pipe() 6.3 命名管道 6.4 信号 6.5 系统调用ptrace()和进程跟踪 6.6 报文传递 6.7 共享内存 6.8 信号量 第7章基于socket的进程间通信 7.1系统调用socket() 7.2函数sys—socket()——创建插口 7.3函数sys—bind()——指定插口地址 7.4函数sys—listen()——设定server插口 7.5函数sys—accept()——接受连接请求 7.6函数sys—connect()——请求连接 7.7报文的接收与发送 7.8插口的关闭 7.9其他 第8章设备驱动 8.1概述 8.2系统调用mknod() 8.3可安装模块 8.4PCI总线 8.5块设备的驱动 8.6字符设备驱动概述 8.7终端设备与汉字信息处理 8.8控制台的驱动 8.9通用串行外部总线USB 8.10系统调用select()以及异步输入/输出 8.11设备文件系统devfs 第9章多处理器SMP系统结构 9.1概述 9.2SMP结构的互斥问题 9.3高速缓存与内存的一致性 9.4SMP结构断机制 9.5SMP结构进程调度 9.6SMP系统的引导 第10章系统引导和初始化 10.1系统引导过程概述 10.2系统初始化(第一阶段) 10.3系统初始化(第二阶段) 10.4系统初始化(第三阶段) 10.5系统的关闭和重引导
fork和vforkLinux系统用来创建进程的函数,它们的区别在于进程创建和共享数据段的方式。当调用fork函数时,父进程创建一个子进程,并且子进程会拷贝父进程的数据段。子进程fork之后会执行父进程的代码,但是父子进程的执行顺序是不确定的。而当调用vfork函数时,父进程创建一个子进程,但是子进程不会拷贝父进程的数据段,而是与父进程共享数据段。在vfork,子进程会先运行,而父进程会挂起,直到子进程调用了exec或者exit函数,父进程才会被执行。 因此,fork适用于需要在子进程修改数据的情况,而vfork适用于需要在子进程立即执行一个新程序的情况。另外,vfork函数的性能比fork函数要好,因为vfork不需要完全拷贝父进程的数据段。然而,由于vfork与父进程共享数据段,所以需要注意在子进程不要修改共享的数据,以免影响到父进程的执行。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [详解linuxfork、vforkclone函数的区别](https://download.csdn.net/download/weixin_38509656/12844110)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [fork与vfork详解](https://blog.csdn.net/iteye_4389/article/details/82518147)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [fork/vfork详解](https://blog.csdn.net/weixin_36750623/article/details/83041030)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值