如何在fork(),linux中演示COPY ON WRITE

时间:2015-09-02 05:16:14

标签: c linux fork copy-on-write

根据COW机制,在写入之前,父区域中的页面不会被复制到子区域。所以我制作了这段代码 Plz看到这段代码

#include <stdio.h>
#include <stdlib.h> // for system()
#include <unistd.h> // for execl(), fork()
#include <wait.h>       // for wait()
int main(int argc, char *argv[]) {   
    int pid, i;               /* fork another process */ 
    char *ptr = "ptr";
    char *b = ptr;
    printf("%s , %p, %p\n " , b, b, &b); 
    pid = fork();

    if(pid < 0) {             /* error occurred */    
            fprintf(stderr,"Fork Failed");    
            exit(-1);   
    }   
    else if (pid== 0) {           /* child process */
            printf("it should be same with parent : %s , %p, %p\n " , b, b, &b); 
            b = "hello";
            printf("it might be differ : %s , %p, %p\n " , b, b, &b); 
    }   
    else {    
            wait(NULL);    
            printf("parent : %s , %p, %p\n " , b, b, &b); 
            exit(0);   
    }   
}

我认为子进程的堆栈在我写东西时会使用与父进程不同的地址空间(b =&#34; hello&#34;),但地址为&#39; b&#39 ;父母和孩子都是一样的。 他们为什么一样?

2 个答案:

答案 0 :(得分:1)

为了说明写入时复制功能,我刚开发了以下代码片段:

#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h> // for system()
#include <time.h>
#include <unistd.h> // for execl(), fork()
#include <wait.h>       // for wait()

#define D(x) __FILE__":%d:%s: " x, __LINE__, __func__
#define PAGESIZE    4096U
#define NPAGES      100000U

/* auxiliary routines to do time accounting */
t_start(struct timespec *t)
{
    clock_gettime(CLOCK_REALTIME, t);
} /* t_start */

t_print(struct timespec *t)
{
    struct timespec now;

    clock_gettime(CLOCK_REALTIME, &now); /* stop */
    now.tv_nsec -= t->tv_nsec;
    now.tv_sec -= t->tv_sec;
    if (now.tv_nsec < 0) {
        now.tv_nsec += 1000000000L;
        now.tv_sec--;
    } /* if */
    printf(D("elapsed: %d.%09d\n"),
           now.tv_sec, now.tv_nsec);
} /* t_print */

void testv(
        struct timespec *t,
        char *b,
        size_t bs,
        const char *fmt,
        va_list p)
{
    int i;
    static char c = 0;
    vprintf(fmt, p);
    t_start(t);
    for (i = 0; i < bs; i += PAGESIZE)
        b[i] = c;
    c++;
    t_print(t);
} /* testv */

void test(struct timespec *t,
        char *b,
        size_t bs,
        const char *fmt,
        ...)
{
    va_list p;
    va_start(p, fmt);
    testv(t, b, bs, fmt, p);
    va_end(p);
} /* test */

int main(int argc, char *argv[])
{   
    static char buffer[NPAGES*PAGESIZE];
    struct timespec ts;
    int i, res;

    test(&ts, buffer, sizeof buffer,
            D("The first test (expect high time--page allocating)\n"));
    test(&ts, buffer, sizeof buffer,
            D("The second test (expect low time)\n"));

    switch(res = fork()) { 
    case -1:
        fprintf(stderr,
                D("Cannot fork: %s(errno=%d)\n"),
                strerror(errno), errno);
        exit(EXIT_FAILURE);
    case 0: /* child */
        test(&ts, buffer, sizeof buffer,
                D("child[%d]: third test (expect high time--copy on write)\n"),
                getpid());
        test(&ts, buffer, sizeof buffer,
                D("child[%d]: fourth test (expect low time)\n"),
                getpid());
        exit(EXIT_SUCCESS);
    default: /* parent */
        printf(D("parent[%d]: waiting for child[%d] to finish\n"),
                getpid(), res);
        wait(NULL); /* expect so the calls don't get intermixed */
        test(&ts, buffer, sizeof buffer,
                D("parent[%d]: third test (expect medium time--swapping)\n"),
                getpid());
        test(&ts, buffer, sizeof buffer,
                D("parent[%d]: third test (expect low time)\n"),
                getpid());
        exit(EXIT_SUCCESS);
    } /* if */
    /*NOTREACHED*/
} /* main */

我现在会尝试解释代码:

  • 首先,一些函数准备进行一些时间戳,并允许从代码执行中计算时间。 t_start()只需启动一个计时器,t_print()需要第二个时间戳,并在最后t_start()和现在之间打印差异时间。
  • test()做了一些测试。它测量写入整个缓冲区的时间(在一个页面的跳转中使其尽可能快地运行,以显示发生在其上的页面错误)
  • main()fork()之前进行了一些测试,因为我们希望确保所有缓冲区页面都存在并在分叉之前分配。我们还将看到,获取核心内存中所有页面的初始时间与孩子在写入时需要进行复制的顺序相同(好吧,孩子多50%)
  • main() fork()是一个孩子并等待它执行。我可以反过来完成测试,但父母可以wait()为孩子,但不是相反。
  • 然后,父母和孩子都做了一些测试,表明孩子必须进一步前进50%等待写东西的副本发生。

计划结果如下:

$ time pru
pru.c:70:main: The first test (expect high time--page allocating)
pru.c:30:t_print: elapsed: 0.126230771
pru.c:72:main: The second test (expect low time)
pru.c:30:t_print: elapsed: 0.002087815
pru.c:82:main: child[4392]: third test (expect high time--copy on write)
pru.c:30:t_print: elapsed: 0.152463844
pru.c:85:main: child[4392]: fourth test (expect low time)
pru.c:30:t_print: elapsed: 0.001906929
pru.c:70:main: The first test (expect high time--page allocating)
pru.c:30:t_print: elapsed: 0.126230771
pru.c:72:main: The second test (expect low time)
pru.c:30:t_print: elapsed: 0.002087815
pru.c:89:main: parent[4390]: waiting for child[4392] to finish
pru.c:93:main: parent[4390]: third test (expect medium time--swapping)
pru.c:30:t_print: elapsed: 0.046004906
pru.c:96:main: parent[4390]: third test (expect low time)
pru.c:30:t_print: elapsed: 0.001905371
0m0.35s real     0m0.05s user     0m0.30s system

如您所见,几乎90%的时间是系统时间,系统必须登录的时间。

所有测试都是在i5 / 8Gb mem / Debian 64bit中进行的,因此,由于父母和孩子的内存需求是整个内存(4K000000页,每个进程4Gb),因此交换的时间是合理的

另一方面,我修改了程序以测量fork()显示的时间:

$ pru
pru.c:70:main: pid=4786: The first test (expect high time--page allocating)
pru.c:30:t_print: pid=4786: elapsed: 0.128644899
pru.c:73:main: pid=4786: The second test (expect low time)
pru.c:30:t_print: pid=4786: elapsed: 0.001815846
pru.c:85:main: child[4788]: fork time:
pru.c:30:t_print: pid=4788: elapsed: 0.003451545
pru.c:88:main: child[4788]: third test (expect high time--copy on write)
pru.c:30:t_print: pid=4788: elapsed: 0.151911675
pru.c:91:main: child[4788]: fourth test (expect low time)
pru.c:30:t_print: pid=4788: elapsed: 0.001831539
pru.c:70:main: pid=4786: The first test (expect high time--page allocating)
pru.c:30:t_print: pid=4786: elapsed: 0.128644899
pru.c:73:main: pid=4786: The second test (expect low time)
pru.c:30:t_print: pid=4786: elapsed: 0.001815846
pru.c:95:main: parent[4786]: fork time:
pru.c:30:t_print: pid=4786: elapsed: 0.003405556
pru.c:97:main: parent[4786]: waiting for child[4788] to finish
pru.c:101:main: parent[4786]: third test (expect medium time--swapping)
pru.c:30:t_print: pid=4786: elapsed: 0.046373034
pru.c:104:main: parent[4786]: third test (expect low time)
pru.c:30:t_print: pid=4786: elapsed: 0.001782398

正如您所看到的,与复制时间(写入页面时)相比,叉时间(在父级或子级中)太低了。

以下是fork()时间戳的最终代码:

#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h> // for system()
#include <time.h>
#include <unistd.h> // for execl(), fork()
#include <wait.h>       // for wait()

#define D(x) __FILE__":%d:%s: " x, __LINE__, __func__
#define PAGESIZE    4096U
#define NPAGES      100000U

/* auxiliary routines to do time accounting */
t_start(struct timespec *t)
{
    clock_gettime(CLOCK_REALTIME, t);
} /* t_start */

t_print(struct timespec *t)
{
    struct timespec now;

    clock_gettime(CLOCK_REALTIME, &now); /* stop */
    now.tv_nsec -= t->tv_nsec;
    now.tv_sec -= t->tv_sec;
    if (now.tv_nsec < 0) {
        now.tv_nsec += 1000000000L;
        now.tv_sec--;
    } /* if */
    printf(D("pid=%d: elapsed: %d.%09d\n"),
           getpid(), now.tv_sec, now.tv_nsec);
} /* t_print */

void testv(
        struct timespec *t,
        char *b,
        size_t bs,
        const char *fmt,
        va_list p)
{
    int i;
    static char c = 0;
    vprintf(fmt, p);
    t_start(t);
    for (i = 0; i < bs; i += PAGESIZE)
        b[i] = c;
    c++;
    t_print(t);
} /* testv */

void test(struct timespec *t,
        char *b,
        size_t bs,
        const char *fmt,
        ...)
{
    va_list p;
    va_start(p, fmt);
    testv(t, b, bs, fmt, p);
    va_end(p);
} /* test */

int main(int argc, char *argv[])
{   
    static char buffer[NPAGES*PAGESIZE];
    struct timespec ts;
    int i, res, pid = getpid();

    test(&ts, buffer, sizeof buffer,
            D("pid=%d: The first test (expect high time--page allocating)\n"),
            pid);
    test(&ts, buffer, sizeof buffer,
            D("pid=%d: The second test (expect low time)\n"),
            pid);

    t_start(&ts);
    switch(res = fork()) { 
    case -1:
        fprintf(stderr,
                D("Cannot fork: %s(errno=%d)\n"),
                strerror(errno), errno);
        exit(EXIT_FAILURE);
    case 0: /* child */
        pid = getpid();
        printf(D("child[%d]: fork time:\n"), pid);
        t_print(&ts);
        test(&ts, buffer, sizeof buffer,
                D("child[%d]: third test (expect high time--copy on write)\n"),
                getpid());
        test(&ts, buffer, sizeof buffer,
                D("child[%d]: fourth test (expect low time)\n"),
                getpid());
        exit(EXIT_SUCCESS);
    default: /* parent */
        printf(D("parent[%d]: fork time:\n"), pid);
        t_print(&ts);
        printf(D("parent[%d]: waiting for child[%d] to finish\n"),
                getpid(), res);
        wait(NULL); /* expect so the calls don't get intermixed */
        test(&ts, buffer, sizeof buffer,
                D("parent[%d]: third test (expect medium time--swapping)\n"),
                getpid());
        test(&ts, buffer, sizeof buffer,
                D("parent[%d]: third test (expect low time)\n"),
                getpid());
        exit(EXIT_SUCCESS);
    } /* switch */
    /*NOTREACHED*/
} /* main */

在输出中出现输出的第一部分两次(一个在父进程中,一个在子进程中)由于printf()通过缓冲工作(我已将输出过滤到sed -e 's/^/ /'以包含将输出粘贴到此处作为代码片段需要四个空格,缓冲输出的内容在fork()时没有被刷新,它出现在两个输出中,即父项和子项。我已包括它表示父项和子项的数据段完全相等,包括<stdio.h>内部结构,如非刷新输出。fflush(stdout);或{{1} } fflush(NULL);之前解决了这个问题,只让它出现一次。

答案 1 :(得分:-1)

分叉进程重复指针和变量。 在内核中,fork实际上是通过克隆系统调用实现的。这个克隆接口有效地提供了Linux内核如何创建进程的抽象级别。

clone允许您明确指定将新进程的哪些部分复制到新进程中,以及在两个进程之间共享哪些部分。 这意味着父和子共享相同的内存,其中包括程序代码和数据。 当调用fork时,内存实际上是在两个进程之间共享而不是复制。