Скачиваний:
90
Добавлен:
12.05.2015
Размер:
913.92 Кб
Скачать

20.3. Увеличение счетчика в отображаемом в память файле

Изменим программу из листинга 19.1, которая работала некорректно, таким образом, чтобы родительский и дочерний процессы совместно использовали область памяти, в которой хранится счетчик. Для этого отобразим файл в память, используя функцииopenиmmap. Влистинге 19.2приведен текст новой программы.

Листинг 19.2. Родительский и дочерний процессы увеличивают значение счетчика, расположенного в разделяемой памяти

#include <errno.h>

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/stat.h>

#include <sys/sem.h>

#include <sys/mman.h>

#define SEM_R S_IRUSR

#define SEM_A S_IWUSR

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

#define SVSEM_MODE (SEM_R | SEM_A | SEM_R>>3 | SEM_R>>6)

union semun

{

int val;

struct semid_ds *buf;

unsigned short *array;

} arg;

int semid;

void sem_del(void)

{

/* удаление семафора осуществляет родительский процесс, */

/* дождавшись завершения дочернего процесса */

if (semctl(semid, 0, IPC_RMID) == -1)

fprintf(stderr, "Ошибка вызова функции semctl(IPC_RMID): %s\n",

strerror(errno));

}

int main(int argc, char **argv)

{

int fd, i, nloop, oflag, *ptr, zero = 0;

struct sembuf ops;

pid_t pid;

if (argc != 3)

{

fprintf(stderr, "Использование: incr2 <имя_файла> <количество_циклов>\n");

exit(1);

}

nloop = atoi(argv[2]);

/* открываем файл, инициализируем нулем и отображаем в память */

if ((fd = open(argv[1], O_RDWR | O_CREAT, FILE_MODE)) == -1)

{

fprintf(stderr, "Ошибка открытия файла %s: %s\n", argv[1], strerror(errno));

exit(1);

}

if (write(fd, &zero, sizeof(int)) == -1)

{

fprintf(stderr, "Ошибка записи в файл %s: %s\n", argv[1], strerror(errno));

exit(1);

}

if ((ptr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED)

{

fprintf(stderr, "Ошибка отображения в память файла %s: %s\n", argv[1],

strerror(errno));

exit(1);

}

close(fd);

oflag = SVSEM_MODE | IPC_CREAT;

if ((semid = semget(IPC_PRIVATE, 1, oflag)) == -1)

{ /* создание семафора */

fprintf(stderr, "Ошибка вызова функции semget: %s\n", strerror(errno));

exit(1);

}

if (atexit(sem_del))

{

fprintf(stderr, "Невозможно зарегистрировать sem_del: %s\n",

strerror(errno));

exit(1);

}

arg.val = 1;

if (semctl(semid, 0, SETVAL, arg) == -1)

{ /* инициализация семафора */

fprintf(stderr, "Ошибка вызова функции semctl(SETVAL): %s\n",

strerror(errno));

exit(1);

}

setbuf(stdout, NULL); /* stdout не буферизуется */

ops.sem_num = 0; /* номер семафора в наборе */

ops.sem_flg = 0; /* флаги операции */

if ((pid = fork()) == -1)

{ /* создание дочернего процесса */

fprintf(stderr, "Ошибка вызова функции fork: %s\n", strerror(errno));

exit(1);

}

if (pid == 0)

{ /* дочерний процесс */

for (i = 0; i < nloop; i++)

{

ops.sem_op = -1;

if (semop(semid, &ops, 1) == -1)

{

fprintf(stderr, "Потомок: ошибка вызова функции semop(wait): %s\n",

strerror(errno));

_exit(1);

}

printf("Потомок: %d\n", (*ptr)++);

ops.sem_op = 1;

if (semop(semid, &ops, 1) == -1)

{

fprintf(stderr, "Потомок: ошибка вызова функции semop(post): %s\n",

strerror(errno));

_exit(1);

}

}

_exit(0); /* для предотвращения вызова обработчиков выхода */

}

/* родительский процесс */

for (i = 0; i < nloop; i++)

{

ops.sem_op = -1;

if (semop(semid, &ops, 1) == -1)

{

fprintf(stderr, "Родитель: ошибка вызова функции semop(wait): %s\n",

strerror(errno));

exit(1);

}

printf("Родитель: %d\n", (*ptr)++);

ops.sem_op = 1;

if (semop(semid, &ops, 1) == -1)

{

fprintf(stderr, "Родитель: ошибка вызова функции semop(post): %s\n",

strerror(errno));

exit(1);

}

}

if (wait(NULL) == -1)

{ /* ожидание завершения дочернего процесса */

fprintf(stderr, "Родитель: ошибка вызова функции wait: %s\n",

strerror(errno));

exit(1);

}

exit(0);

}

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

Вызов mmapпозволяет отобразить открытый файл в адресное пространство процесса. Первый аргумент является нулевым указателем, при этом система сама выбирает адрес начала отображаемого сегмента. Длина файла совпадает с размером целого числа. Установлен доступ для чтения и записи. Четвертый аргумент имеет значениеMAP_SHARED, что позволяет процессам “видеть” изменения, вносимые друг другом. Функцияmmapвозвращает адрес начала участка разделяемой памяти, мы сохраняем его в переменнойptr.

Мы отключаем буферизацию стандартного потока вывода и вызываем функцию fork. Родительский и дочерний процессы по очереди увеличивают целое значение, адрес которого хранится в переменнойptr. Отображенные в память файлы при вызовеforkобрабатываются специфическим образом в том смысле, что созданные родительским процессом отображения наследуются дочерним процессом. Следовательно, открыв файл и вызвав функциюmmapс флагомMAP_SHARED, мы получили область памяти, совместно используемую родительским и дочерним процессами. Более того, поскольку эта общая область на самом деле представляет собой отображенный файл, все изменения, вносимые в нее, также действуют и на содержимое реального файла, имя которого было передано программе в командной строке.

Запустив эту программу на выполнение, мы увидим, что участок памяти, на которую указывает ptr, действительно используется совместно родительским и дочерним процессами. Пусть программа запущена со следующими параметрами:

$ incr2 /tmp/temp.1 10000

Потомок: 0 первая строка вывода

...

Родитель: 19999 последняя строка вывода

Поскольку использовалось отображение файла в память, мы можем взглянуть на его содержимое с помощью утилиты odи увидеть, что окончательное значение счетчика (20000) действительно было сохранено в файле.

На рис. 19.4изображена схема работы программы излистинга 19.2. Здесь используется разделяемая память и показано, что семафор также используется совместно.

Рис. 19.4

Мы показали, что у родительского и дочернего процессов имеются собственные копии указателя ptr, но обе копии указывают на одну и ту же область памяти – счетчик, увеличиваемый обоими процессами.

Соседние файлы в папке Chapter.4