как я могу продемонстрировать КОПИРОВАНИЕ ПРИ ЗАПИСИ в fork(), linux

В соответствии с механизмом COW страницы из родительского региона не копируются в дочерний регион до записи. поэтому я сделал этот код, пожалуйста, посмотрите этот код

#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 = "hello"), но адрес 'b' одинаков как родительский, так и дочерний. почему они одинаковые?


person Setian    schedule 02.09.2015    source источник
comment
Процессы do имеют разные виртуальные адресные пространства. Посмотрите здесь: stackoverflow.com/questions/5365580/fork-same-memory -адреса   -  person cadaniluk    schedule 02.09.2015
comment
Спасибо! учитель. Теперь, когда я знаю, что это виртуальный адрес, нет ли способа продемонстрировать КОПИРОВАНИЕ ПРИ ЗАПИСИ? По крайней мере, я хочу видеть разные физические адреса между родителями и ребенком.   -  person Setian    schedule 02.09.2015
comment
Возможно, вы найдете что-то полезное для определения физического адреса здесь: user-space-in-linux" title="как найти физический адрес переменной из пользовательского пространства в Linux"> stackoverflow.com/questions/2440385/   -  person cadaniluk    schedule 02.09.2015
comment
Кстати, это все ресурсы из интернета, они общедоступны. Сначала ищи, потом спрашивай.   -  person cadaniluk    schedule 02.09.2015
comment
Извините, отредактируйте свой вопрос и укажите наблюдаемое поведение (вывод программы) и ожидаемое. Я попробовал вашу программу, и она отлично работает, показывая разные виртуальные адресные пространства и разные указатели стека в родительском и дочернем элементах. Возможно, вы неверно истолковали вывод программы. Кроме того, копирование сегментов стека отличается от функции COPY ON WRITE. Что именно вы хотите узнать?   -  person Luis Colorado    schedule 03.09.2015


Ответы (2)


Чтобы проиллюстрировать функцию копировать при записи, я только что разработал следующий фрагмент кода:

#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()ing, так как мы хотим убедиться, что все буферные страницы существуют и выделены перед разветвлением. Мы также увидим, что начальное время для получения всех страниц в основной памяти находится в том же порядке, что и дочерний процесс, необходимый для копирования при записи (ну, на 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% времени — это системное время, время, которое система должна была загрузить.

Все тесты проводились в 64-битной памяти i5/8Gb/Debian, поэтому, поскольку требования к памяти для родительского и дочернего процессов были равны всей памяти (1000000 страниц 4K составляют 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); или fflush(NULL); перед fork() решает эту проблему, заставляя ее появляться только один раз.

person Luis Colorado    schedule 03.09.2015

Указатель и переменные дублируются для разветвленного процесса. В ядре fork реализован системным вызовом clone. Эти клонированные интерфейсы эффективно обеспечивают уровень абстракции того, как ядро ​​Linux может создавать процессы.

clone позволяет вам явно указать, какие части нового процесса копируются в новый процесс, а какие части являются общими для двух процессов. Это означает, что родительский и дочерний элементы совместно используют одну и ту же память, которая включает программный код и данные. память фактически распределяется, а не копируется между двумя процессами при вызове fork.

person gaurav    schedule 02.09.2015
comment
Я бы сказал, по крайней мере, в последней 3-й части вашего ответа вы находитесь на неправильном пути. - person alk; 05.09.2015