【Linux进阶】进程之fork——进程复制

目录

前言

一、fork方法

代码举例 

二、fork资源复制理解

三、写时拷贝技术

四、vfork()


前言

一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。

一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

一、fork方法

//需要包含的头文件
#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);

函数返回类型实际为int类型,Linux 内核 2.4.0 版本的定义是:

typedef  int             __kernel_pid_t;
typedef  __kernel_pid_t  pid_t;                                                                      

fork函数会新生成一个进程,调用fork函数的进程为父进程,新生成的进程为子进程。

在父进程中返回子进程的 pid,在子进程中返回 0,失败返回-1。

代码举例 

在以下代码中,fork产生新进程后,子进程输出三次”child“,父进程输出七次"parent";

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    char* s = NULL;
    int n = 0;

    pid_t id = fork();

    if (id == -1)
    {
        exit(1);
    }

    if (id == 0)
    {
        s = "child";
        n = 3;
    } 
    else
    {
        s = "parent";
        n = 7;
    }

    for (int i = 0; i < n; i++)
    {
        printf("s = %s\n", s);
        sleep(1);
    }

    exit(0);
}

二、fork资源复制理解

fork拷贝父进程资源,例如缓冲区,堆区资源等。

首先,我们了解printf首先将需要打印的数据存放在输出缓冲区,在发出清空缓冲区命令或该进程结束时,进行输出。

那么我们编写如下代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
	for (int i = 0; i < 2; i++)
	{
		printf("A");
		fork();
	}
}

输出结果如下

按照我们原本的理解,在fork之前,只有一个进程输出了一次A,在fork之后,两个进程又分别输出了一次A,那么应该输出三次A,但为什么会出现上图结果,画以下图来详解。

由图解得知,在最后时我们实际存在四个进程,他们都拥有相同的缓冲区内容AA,

所以会输出八个A

但如果我们对代码作以修改,如下所示,即直接清空缓冲区,做输出操作

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
	for (int i = 0; i < 2; i++)
	{
		printf("A\n");
		fork();
	}
}

发现所得结果为原本所想的样子。

三、写时拷贝技术

        传统的fork()系统调用直接把所有的资源复制给新创建的进程。这种实现过于简单并且效率低下,因为它拷贝的数据也许并不共享,更糟的情况是,如果新进程打算立即执行一个新的映像,那么所有的拷贝都将前功尽弃。Linux的fork()使用写时拷贝(copy-on-write)页实现。写时拷贝是一种可以推迟甚至免除拷贝数据的技术。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。也就是说,资源的复制只有在需要写入的时候才进行,在此之前,只是以只读方式共享。这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候。在页根本不会被写入的情况下-—举例来说,fork()后立即调用exec()——它们就无需复制了。

fork()的实际开销就是复制父进程的页表以及给子进程创建惟一的进程描述符。在一般情况下,进程创建后都会马上运行一个可执行的文件,这种优化可以避免拷贝大量根本就不会被使用的数据(地址空间里常常包含数十兆的数据)。由于Unix强调进程快速执行的能力,所以这个优化是很重要的。

四、vfork()

vfork()系统调用和fork()的功能相同,除了不拷贝父进程的页表项。子进程作为父进程的一个单独的线程在它的地址空间里运行,父进程被阻塞,直到子进程退出或执行exec()。子进程不能向地址空间写入。在过去3BSD时期,这个优化是很有意义的,那时并未使用写时拷贝页来实现fork()。现在由于在执行fork()时引入了写时拷贝页并且明确了子进程先执行,vfork的好处就仅限于不拷贝父进程的页表项了。
 


 总结

fork 复制进程, 父进程返回值大于0, 子进程返回值等于0,失败返回-1;

程序中使用的地址为,逻辑地址,又因为fork同样复制页表,所以父进程与子进程看起来是一模一样的。

进程资源复制

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值