ls | lpr
, чтобы вывести на печать список файлов в каталоге. Интерпретатор команд создает два отдельных процесса — ls
и lpr
— и соединяет их '|'
. Канал — это однонаправленный способ передачи данных от одного процесса к другому. Процесс ls
записывает данные в канал, а процесс lpr
читает данные из него.
В этой главе рассматриваются пять способов взаимодействия процессов.
■ Совместно используемая память — процессы могут просто читать и записывать данные в рамках заданной области памяти.
■ Отображаемая память — напоминает совместно используемую память, но организуется связь с файлами.
■ Каналы — позволяют последовательно передавать данные от одного процесса к другому.
■ FIFO-файлы — в отличие от каналов, с ними работают несвязанные процессы, поскольку у такого файла есть имя в файловой системе и к нему может обратиться любой процесс.
■ Сокеты — соединяют несвязанные процессы, работающие на разных компьютерах.
Различия между способами взаимодействия определяются следующими критериями:
■ ограничено ли взаимодействие рамками связанных процессов (имеющих общего предка) или же соединяются процессы, выполняющиеся в одной файловой системе либо на разных компьютерах:
■ ограничен ли процесс только чтением либо только записью данных;
■ число взаимодействующих процессов;
■ синхронизируются ли взаимодействующие процессы (например, должен ли читающий процесс перейти в режим ожидания при отсутствии данных на входе).
5.1. Совместно используемая память
Простейшим способом взаимодействия процессов является совместный доступ к общей области памяти. Это выглядит так, как если бы два или более процесса вызвали функцию malloc()
и получили указатели на один и тот же блок памяти. Когда один из процессов меняет содержимое памяти, другие процессы замечают это изменение.
5.1.1. Быстрое локальное взаимодействие
Совместное использование памяти — самый быстрый способ взаимодействия. Процесс обращается к общей памяти с той же скоростью, что и к своей собственной памяти, и никаких системных вызовов или обращений к ядру не требуется. Устраняется также ненужное копирование данных.
Ядро не синхронизирует доступ процессов к общей памяти — об этом следует позаботиться программисту. Например, процесс не должен читать данные из совместно используемой памяти, пока в нее осуществляется запись, и два процесса не должны одновременно записывать данные в одну и ту же область памяти. Стандартная стратегия предотвращения подобной конкуренции заключается в использовании семафоров, о которых пойдет речь в раздаче 5.2, "Семафоры для процессов". Тем не менее в приводимом далее примере программы доступ к памяти осуществляет только один процесс: просто мы хотим сконцентрировать внимание читателей на механизме совместного использования памяти и не перегружать программу кодом синхронизации.
5.1.2. Модель памяти
При совместном использовании сегмента памяти один процесс должен сначала выделить память. Затем все остальные процессы, которые хотят получить доступ к ней, должны подключить сегмент. По окончании работы с сегментом каждый процесс отключает его. Последний процесс освобождает память.
Для того чтобы понять принципы выделения и подключения сегментов памяти, необходимо разобраться в модели памяти Linux. В Linux виртуальная память (ВП) каждого процесса разбита на страницы. Все процессы хранят таблицу соответствий между своими адресами памяти и страницами ВП, содержащими реальные данные. Несмотря на то что за каждым процессом закреплены свои адреса, разным процессам разрешается ссылаться на одни и те же страницы. Это и есть совместное использование памяти.
При выделении совместно используемого сегмента памяти создаются страницы ВП. Это действие должно выполняться только одни раз, так как все остальные процессы будут обращаться к этому же сегменту. Если запрашивается выделение существующего сегмента, новые страницы не создаются; вместо этого возвращается идентификатор существующих страниц. Чтобы сделать сегмент общедоступным, процесс подключает его, при этом создаются адресные ссылки на страницы сегмента. По окончании работы с сегментом адресные ссылки удаляются. Когда все процессы завершили работу с сегментом, один (и только один) из них должен освободить страницы виртуальной памяти.
Размер совместно используемого сегмента кратен getpagesize()
.
5.1.3. Выделение сегментов памяти