Чтобы проиллюстрировать функцию копировать при записи, я только что разработал следующий фрагмент кода:
#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