- •Часть 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.4. Неименованное отображение в память
Наш пример из листинга 19.2работает отлично, но нам приходится создавать файл в файловой системе (аргумент командной строки), вызыватьopen, записывать нули в файл вызовомwrite(чтобы проинициализировать его). Если после вызоваmmapследует вызовforkдля совместного использования области разделяемой памяти родственными процессами, то можно упростить эту схему, используя возможности, предоставляемые операционной системой.
В ОС Linux существует возможность неименованного отображения в память. При этом полностью исчезает необходимость создавать или открывать файлы. Вместо этого указываются флаги MAP_SHARED | MAP_ANON, а в качестве номера дескриптора файла передается –1. Смещение, задаваемое аргументомoffset, игнорируется. Участок памяти автоматически заполняется нулями. Соответствующий фрагмент программного кода приведен ниже.
ptr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANON, -1, 0);
Автоматические переменные fdиzeroбольше не используются, как и аргумент командной строки, задающий имя файла. Файл больше не нужно открывать. Вместо этого при вызове функцииmmapуказывается флагMAP_ANON, а пятый аргумент этой функции (дескриптор файла) принимает значение –1.
В ОС Linux имеется файл /dev/zero, который необходимо открыть и полученный дескриптор передать функции mmap. Этот файл, являющийся символьным устройством, возвращает нули при попытке чтения из него, а весь направляемый в него вывод сбрасывается. Соответствующий фрагмент программного кода приведен ниже.
fd = open(“/dev/zero”, O_RDWR);
ptr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
Автоматическая переменная zeroбольше не используется, как и аргумент командной строки, задающий имя файла. Мы открываем файл /dev/zero и передаем полученный дескриптор функцииmmap. Выделенная область разделяемой памяти будет гарантированно заполнена нулями.
20.5. Обращение к объектам, отображенным в память
Когда в память отображается обычный файл, размер полученной области (второй аргумент функции mmap), как правило, совпадает с размером файла. Например, влистинге 19.2размер файла устанавливается равным размеру целого числа (переменнаяzero) при вызове функцииwriteи это же значение размера области используется при отображении файла в память. Однако эти два параметра – размер файла и размер области памяти, в которую он отображен, – могут и отличаться.
Для детального изучения особенностей функции mmapвоспользуемся программой, представленнойлистингом 19.3.
Листинг 19.3. Отображение файла: размер файла совпадает с размером области памяти
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
#define max(a,b) ((a) > (b) ? (a) : (b))
int main(int argc, char **argv)
{
int fd, i;
char *ptr;
size_t filesize, mmapsize, pagesize;
if (argc != 4)
{
fprintf(stderr, "Использование: test1 <имя_файла> <размер_файла>
<размер_области_памяти>\n");
exit(1);
}
filesize = atoi(argv[2]);
mmapsize = atoi(argv[3]);
/* открытие или создание файла; установка его размера */
if ((fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) == -1)
{
fprintf(stderr, "Ошибка открытия файла %s: %s\n", argv[1], strerror(errno));
exit(1);
}
if (lseek(fd, filesize-1, SEEK_SET) == -1)
{
fprintf(stderr, "Ошибка позиционирования файла %s: %s\n", argv[1],
strerror(errno));
exit(1);
}
if (write(fd, "", 1) == -1)
{
fprintf(stderr, "Ошибка записи в файл %s: %s\n", argv[1], strerror(errno));
exit(1);
}
if ((ptr = mmap(NULL, mmapsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) ==
MAP_FAILED)
{
fprintf(stderr, "Ошибка отображения в память файла %s: %s\n", argv[1],
strerror(errno));
exit(1);
}
close(fd);
if ((pagesize = sysconf(_SC_PAGESIZE)) == -1)
{
fprintf(stderr, "Ошибка вызова функции sysconf: %s\n", strerror(errno));
exit(1);
}
printf("PAGESIZE = %ld\n", (long) pagesize);
for (i = 0; i < max(filesize, mmapsize); i += pagesize)
{
printf("ptr[%d] = %d\n", i, ptr[i]);
ptr[i] = 1;
printf("ptr[%d] = %d\n", i + pagesize - 1, ptr[i + pagesize - 1]);
ptr[i + pagesize - 1] = 1;
}
printf("ptr[%d] = %d\n", i, ptr[i]);
exit(0);
}
Аргументы командной строки задают полное имя создаваемого и отображаемого в память файла, его размер и размер области памяти. Если файл не существует, он будет создан. Если он существует, его длина будет установлена равной нулю. Затем размер файла устанавливается равным указанному размеру путем вызова функции lseekдля установки текущей позиции, равной требуемому размеру файла минус 1, и записи 1 байта.
Файл отображается в память, причем размер области памяти задается последним аргументом командной строки. После этого мы закрываем файл. Размер страницы памяти мы получаем с помощью вызова sysconfи выводим его на экран.
Данные считываются и выводятся из той области памяти, в которую отображен файл. Считываются первый и последний байты каждой страницы этой области памяти. Все значения должны быть нулевыми. Затем первый и последний байты каждой страницы устанавливаются в 1. Одно из обращений к памяти может привести к ошибке и отправке соответствующего сигнала процессу, что приведет к его завершению. После завершения цикла forмы добавляем еще одно обращение к следующей странице памяти, что должно заведомо привести к ошибке и завершению работы программы (если ошибка не возникла раньше).
Рассмотрим первую ситуацию, когда размер файла совпадает с размером области памяти, но эта величина не кратна размеру страницы памяти ОС Linux:
$test1 foo 5000 5000
PAGESIZE = 4096
ptr[0] = 0
ptr[4095] = 0
ptr[4096] = 0
ptr[8191] = 0
Segmentation fault
Размер страницы памяти составляет 4096 байт, и мы смогли обратиться ко всему содержимому второй страницы (индексы 4096–8191), но обращение к третьей странице (начиная с индекса 8192) приводит к отправке сигнала SIGSEGV, о чем командный интерпретатор уведомляет нас, выводя сообщение “Segmentation fault”. Хотя мы и установили значениеptr[8191]равным 1, оно не было записано в файл, и поэтому его размер остался равным 5000 байт. Ядро позволяет считывать и записывать данные в ту часть последней страницы, которая не относится к отображенному файлу, поскольку защита памяти осуществляется ядром постранично, но изменения в этой части области памяти не будут скопированы в файл. А вот относящиеся к файлу изменения (индексы 0, 4095 и 4096) были скопированы в него, в чем легко убедиться с помощью утилитыod. Нарис. 19.5изображена схема памяти для данного примера.

Рис. 19.5
Рассмотрим теперь вторую ситуацию, когда размер области памяти (15000 байт) превышает размер файла (5000 байт):
$test1 foo 5000 15000
PAGESIZE = 4096
ptr[0] = 0
ptr[4095] = 0
ptr[4096] = 0
ptr[8191] = 0
Bus error
Полученный результат аналогичен результату предыдущего примера, в котором размер файла равнялся размеру области отображения (5000 байт). Однако в данном случае генерируется сигнал SIGBUS(о чем командный интерпретатор уведомляет нас, выводя сообщение “Bus error”), тогда как в предыдущем примере отправлялся сигналSIGSEGV. Отличие здесь в том, чтоSIGBUSозначает выход за границы отображенного файла внутри области памяти, аSIGSEGV– выход за границы области. Этот пример демонстрирует, что ядро хранит информацию о размере отображенного объекта, даже несмотря на то, что его дескриптор закрыт. Ядро позволяет указать при вызове функцииmmapразмер области памяти, превосходящий размер отображаемого файла, но не позволяет обращаться по адресам в этой области, кроме остатка последней страницы памяти, в которой еще имеется содержимое собственно файла – индексы с 5000 по 8191 в данном случае. Нарис. 19.6приведена иллюстрация к этому примеру.

Рис. 19.6
Следующая программа приведена в листинге 19.4. С ее помощью мы иллюстрируем типичные методы работы с файлами, размер которых увеличивается: при отображении в память заказывается большой размер области, текущий размер файла учитывается при всех операциях (чтобы не выйти за его пределы в памяти), а затем он просто увеличивается, по мере того, как в файл записываются данные.
Листинг 19.4. Отображение увеличивающегося файла в память
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
#define FILENAME "test.data"
#define SIZE 32768
int main(int argc, char **argv)
{
int fd, i;
char *ptr;
/* открытие или создание файла; установка его размера */
if ((fd = open(FILENAME, O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) == -1)
{
fprintf(stderr, "Ошибка открытия файла %s: %s\n", FILENAME,
strerror(errno));
exit(1);
}
/* отображение файла в память */
if ((ptr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) ==
MAP_FAILED)
{
fprintf(stderr, "Ошибка отображения в память файла %s: %s\n", FILENAME,
strerror(errno));
exit(1);
}
for (i = 4096; i <= SIZE; i += 4096)
{
printf("Размер файла устанавливается в %d байт\n", i);
if (ftruncate(fd, i) == -1)
{
fprintf(stderr, "Ошибка установки размера файла %s: %s\n", FILENAME,
strerror(errno));
exit(1);
}
printf("ptr[%d] = %d\n", i-1, ptr[i-1]);
}
close(fd);
exit(0);
}
Мы создаем файл, если он еще не существует, или укорачиваем его до нулевой длины, если он существует. Затем файл отображается в область памяти размером 32768 байт, хотя его текущий размер равен нулю. Мы увеличиваем размер файла на 4096 байт за один вызов ftruncateи считываем из него последний байт в каждом проходе цикла.
Запустив эту программу, мы убедимся в возможности обращаться к новым данным по мере роста размера файла:
$ test2
Размер файла устанавливается в 4096 байт
ptr[4095] = 0
Размер файла устанавливается в 8192 байт
ptr[8191] = 0
Размер файла устанавливается в 12288 байт
ptr[12287] = 0
Размер файла устанавливается в 16384 байт
ptr[16383] = 0
Размер файла устанавливается в 20480 байт
ptr[20479] = 0
Размер файла устанавливается в 24576 байт
ptr[24575] = 0
Размер файла устанавливается в 28672 байт
ptr[28671] = 0
Размер файла устанавливается в 32768 байт
ptr[32767] = 0
Этот пример показывает, что ядро всегда следит за размером отображаемого в память объекта (здесь это файл test.data), и мы всегда имеем возможность обратиться к байтам, лежащим внутри области, ограниченной размером файла и размером отображения.
