- •Часть 4. Локальное взаимодействие процессов
- •Глава 16. Блокирование записей 89
- •12.2. Процессы, потоки и общий доступ к информации
- •12.3. Живучесть объектов ipc
- •12.4. Пространства имен
- •12.5. Действие команд fork, exec и exit на объекты ipc
- •12.6. Комментарии к примерам ipc
- •12.7. Выводы по главе 12
- •12.8. Упражнения по главе 12
- •Глава 13. Именованные и неименованные каналы
- •13.1. Введение
- •13.2. Приложение типа клиент-сервер
- •13.3. Программные каналы
- •13.4. Функции popen и pclose
- •13.5. Именованные каналы (fifo)
- •13.6. Некоторые свойства именованных и неименованных каналов
- •13.7. Один сервер, несколько клиентов
- •13.8. Последовательные и параллельные серверы
- •13.9. Ограничения программных каналов и fifo
- •13.10. Выводы по главе 13
- •13.11. Упражнения по главе 13
- •Глава 14. Программные потоки
- •14.1. Введение
- •14.2. Концепция потоков
- •14.3. Идентификация потоков
- •14.4. Создание потока
- •14.5. Завершение потока
- •Функции управления процессами и потоками
- •14.6. Установка атрибутов потока
- •14.7. Реентерабельность
- •Альтернативные версии функций, безопасные в многопоточной среде
- •14.8. Локальные данные потоков
- •14.9. Принудительное завершение потоков
- •Некоторые точки выхода, определенные стандартом Posix.1
- •14.10. Потоки и сигналы
- •14.11. Выводы по главе 14
- •14.12. Упражнения по главе 14 Глава 15. Средства синхронизации потоков
- •15.1. Введение
- •15.2. Взаимные исключения: установка и снятие блокировки
- •15.2.1. Схема производитель-потребитель
- •15.2.2. Блокирование и опрос
- •15.2.3. Предотвращение тупиковых ситуаций
- •15.3. Условные переменные
- •15.3.1. Ожидание и сигнализация
- •15.3.2. Исключение состояния гонок
- •15.4. Блокировки чтения-записи
- •15.5. Атрибуты средств синхронизации потоков
- •15.5.1. Атрибуты взаимных исключений
- •Поведение взаимных исключений различных типов
- •15.5.2. Атрибуты условных переменных
- •15.5.3. Атрибуты блокировок чтения-записи
- •15.6. Выводы по главе 15
- •15.7. Упражнения по главе 15
- •Глава 16. Блокирование записей
- •16.1. Введение
- •16.2. Блокирование записей и файлов
- •16.3. Блокирование записей с помощью fcntl по стандарту Posix
- •16.4. Рекомендательная блокировка
- •16.5. Обязательная блокировка
- •16.6. Приоритет чтения и записи Выводы по главе 16
- •Упражнения по главе 16 Глава 17. System V ipc
- •17.1. Введение
- •17.2. Ключи типа key_t и функция ftok
- •17.3. Структура ipc_perm
- •17.4. Создание и открытие каналов ipc
- •17.5. Разрешения ipc
- •17.6. Программы ipcs и ipcrm
- •17.7. Ограничения ядра
- •17.8. Выводы по главе 17
- •17.9. Упражнения по главе 17
- •Глава 18. Очереди сообщений System V
- •18.1. Введение
- •18.2. Функция msgget
- •18.3. Функция msgsnd
- •18.4. Функция msgrcv
- •18.5. Функция msgctl
- •18.6. Пример программы клиент-сервер
- •18.7. Мультиплексирование сообщений
- •18.7.1. Пример: одна очередь на приложение
- •18.7.2. Пример: одна очередь для каждого клиента
- •18.8. Ограничения, накладываемые на очереди сообщений
- •18.9. Выводы по главе 18
- •18.10. Упражнения по главе 18
- •Глава 19. Семафоры System V
- •19.1. Введение
- •19.2. Функция semget
- •19.3. Функция semop
- •19.4. Функция semctl
- •19. . Ограничения семафоров System V
- •19. . Выводы по главе 19
- •19. . Упражнения по главе 19 Глава 20. Введение в разделяемую память
- •20.1. Введение
- •20.2. Функции mmap, munmap и msync
- •20.3. Увеличение счетчика в отображаемом в память файле
- •20.4. Неименованное отображение в память
- •20.5. Обращение к объектам, отображенным в память
- •20.6. Выводы по главе 20
- •20.7. Упражнения по главе 20
- •Глава 21. Разделяемая память System V
- •21.1. Введение
- •21.2. Функция shmget
- •21.3. Функция shmat
- •21.4. Функция shmdt
- •21.5. Функция shmctl
- •21.6. Ограничения, накладываемые на разделяемую память
- •21.7. Выводы по главе 21
- •21.8. Упражнения по главе 21
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, но обе копии указывают на одну и ту же область памяти – счетчик, увеличиваемый обоими процессами.
