复制进程 fork ()&写实拷贝&僵死进程

fork 函数

返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程PID;否则,出错返回-1

fork()函数将运行着的程序分成2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。这两个进程中的线程继续执行,就像是两个用户同时启动了该应用程序的两个副本

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

 

一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID

为什么fork会返回两次?

由于在复制时复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。因此fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。

在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

下面是代码实现

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

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

    pid_t pid = fork();
    assert(pid != -1);

    if(pid == 0)
    {
        n = 5;
        s = "child";
    }
    else
    {
        n = 7;
        s = "parent";
    }

    int i = 0;
    for( ; i < n ; i++)
    {
        printf("curr_pid = %d, curr_ppid = %d, s = %s, &n = %x, n = %d\n", getpid(), getppid(), s, &n , n);
        sleep(1);   
    }
    exit(0);
}

由于父进程返回的是子进程的进程id,所以执行的是else里的内容,而子进程中执行的是if中断内容。

我们再来看下面这一道题

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

int main()
{
	fork() || fork();
    printf("A");
    exit(0);
}

这三个为什么是三个A呢?

ok,fork到底有什么用呢?下面我们来举一个fork()函数应用的例子

写实拷贝

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <fcntl.h>

int main()
{
    char * s = NULL;

    pid_t pid = fork();
    assert(pid != -1);
    char buff[128] = {0};
    int fd = open("a.txt",O_RDONLY);
    assert(fd != -1);

    if(pid == 0)
    {
        read(fd,buff,1);
        printf("child buff=%s\n",buff);
        sleep(1);
        read(fd,buff,1);
        printf("child buff=%s\n",buff);
    }
    else
    {
        read(fd,buff,1);
        printf("parent buff=%s\n",buff);
        sleep(1);
        read(fd,buff,1);
        printf("parent buff=%s\n",buff);
    }

    close(fd);
    exit(0);
}

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

僵死进程概念及处理方式

子进程先于父进程结束,父进程没有调用 wait 获取子进程的退出码,此时,子进程变成僵死进程。
解决办法:父进程调用 wait 获取退出码,或 父进程先结束,让 init 进程接管子进程。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值