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包括三种:fork、clone、vfork。
在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提供了vfork。vfork时,父进程会被阻塞,直到子进程调用了exec或exit,因为此时不复制页表结构。
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来决定子进程在执行时父进程是阻塞还是运行,若没有设置该标志,则父子进程同时运行,
设置了该标志,则父进程挂起,直到子进程结束为止。
---------------------
风云来
linux 进程创建clone、fork与vfork的更多相关文章
- 进程创建函数fork()、vfork() ,以及excel()函数
一.进程的创建步骤以及创建函数的介绍 1.使用fork()或者vfork()函数创建新的进程 2.条用exec函数族修改创建的进程.使用fork()创建出来的进程是当前进程的完全复制,然而我们创建进程 ...
- [Chapter 3 Process]Practice 3.1 相关知识:进程创建、fork函数
3.1 Using the program shown in the Figure3.30, explain what the output will be at LINE A 答案:LINE A 处 ...
- Linux 进程创建一(system和fork)
一:system系统调用 #include <stdlib.h> int system(const char *string); system函数传递给/bin/sh -c 来执行stri ...
- Linux进程创建和结束
在Linux中,进程的创建由系统调用fork和vfork完成.它们生成一个子进程并且子进程是父进程的一个复制品. Fork系统调用对应的kernel函数是sys_fork,此函数简单的调用kernel ...
- linux进程原语之fork()
一.用法解析: fork()这个函数,可以说是名如其人了,众所周知fork这个单词本意为叉子,老外取学术名字的时候总会有一些象形的想法,于是就有了下图~ fork()函数是计算机程序设计中的分叉函数. ...
- Linux 进程创建二(execve和wait)
三:execve系统调用 int execve(const char *filename, char *const argv[],char *const envp[]); fork创建了一个新的进程, ...
- linux进程创建
1. 进程是程序的执行,会被加载到内存中,每个进程包括程序的代码和数据,其中数据包括程序的变量的数据,外部数据,程序堆栈. 2. Linux中,输入命令,如vi main.c 通过shell来执行, ...
- Linux -- 进程管理之 fork() 函数
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间.然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同.相当于克隆了一个自己. Test1 f ...
- 作业六:分析Linux内核创建一个新进程的过程
分析Linux内核创建一个新进程的过程 进程描述符PCB----task_struct数据结构 操作系统:1.进程管理 2.内存管理 3 文件系统 一.新进程如何创建和修改task_struct数据结 ...
随机推荐
- aop通配符语法
*.表示通配包名 *. == com. com.rl.ecps.service == *.*.*.*. ..表示 通配任何包及其子包 例如 com.. ==com. *.*.*. com.rl. ...
- Java虚拟机构建对象过程小记
Java对象的内存分布 Java对象的构建 Java程序中,新建对象,除了常见的new语句之外,还可以通过反射机制.Object.clone方法.反序列化以及Unsafe.allocateInstan ...
- SpringBoot之get请求404
后台:SpringBoot 前台:VUE 异常:调get接口,返回404 场景:get请求传参,后台返回n条数据,不传参则返回所有 原因:原请求url为"~/one/{param}" ...
- mfs 使用心得
CentOS的安装方法: To install MooseFS from officially supported repository on EL7, follow the steps below: ...
- VM磁盘映射共享方法,要求文件系统必须一致
如果主机是window系统,那么虚拟机也应该是Windows系统,不然不起作用
- P1008 三连击
题目背景 本题为提交答案题,您可以写程序或手算在本机上算出答案后,直接提交答案文本,也可提交答案生成程序. 题目描述 将1,2,⋯,9共9个数分成3组,分别组成3个三位数,且使这33个三位数构成1:2 ...
- Vue 快速入门
Vue框架介绍 中文文档: https://cn.vuejs.org/v2/guide/ Vue是一个构建数据驱动的web界面的渐进式框架. 目标是通过尽可能简单的API实现响应式的数据绑定和组合的视 ...
- 醉汉随机行走/随机漫步问题(Random Walk Randomized Algorithm Python)
世界上有些问题看似是随机的(stochastic),没有规律可循,但很可能是人类还未发现和掌握这类事件的规律,所以说它们是随机发生的. 随机漫步(Random Walk)是一种解决随机问题的方法,它 ...
- python通过配置文件连接数据库
今天主要是通过读取配置文件(ini文件)获取数据库表的ip,端口,用户,密码,表名等,使用pysql来操作数据库,具体的ini配置文件的操作参见我另一篇博客:https://www.cnblogs.c ...
- BZOJ 2728: [HNOI2012]与非(位运算)
题意 定义 NAND(与非)运算,其运算结果为真当且仅当两个输入的布尔值不全为真,也就是 A NAND B = NOT(A AND B) ,运算位数不会超过 \(k\) 位, 给你 \(n\) 个整数 ...