|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
ЧАСТЬ 1ВВЕДЕНИЕ В IPC UNIX ГЛАВА 1Обзор средств взаимодействия процессов Unix 1.1. ВведениеАббревиатура IPC расшифровывается как interprocess communication, то есть взаимодействие процессов. Обычно под этим понимается передача сообщений различных видов между процессами в какой-либо операционной системе. При этом могут использоваться различные формы синхронизации, требуемой современными видами взаимодействия, осуществляемыми, например, через разделяемую память. В процессе развития операционных систем семейства Unix за последние 30 лет методы передачи сообщений эволюционировали следующим образом: ■ Каналы (pipes — глава 4) были первой широко используемой формой взаимодействия процессов, доступной программам и пользователю (из интерпретатора команд). Основным недостатком каналов является невозможность их использования между процессами, не имеющими общего родителя (ancestor), но этот недостаток был устранен с появлением именованных каналов (named pipes), или каналов FIFO (глава 4). ■ Очереди сообщений стандарта System V (System V message queues — глава 4) были добавлены к ядрам System V в начале 80-х. Они могут использоваться для передачи сообщений между процессами на одном узле вне зависимости от того, являются ли эти процессы родственными. Несмотря на сохранившийся префикс «System V», большинство современных версий Unix, включая и те, которые не произошли от System V, поддерживают эти очереди.
■ Очереди сообщений Posix (Posix message queues — глава 5) были добавлены в стандарт Posix (1003.1b-1993, о котором более подробно рассказано в разделе 1.7). Они могут использоваться для взаимодействия родственных и неродственных процессов на каком-либо узле. ■ Удаленный вызов процедур (remote procedure calls — RPC, часть 5) появился в 80-х в качестве средства для вызова функций на одной системе (сервере) программой, выполняемой на другой системе (клиенте). Это средство было разработано в качестве альтернативы для упрощения сетевого программирования. Поскольку между клиентом и сервером обычно передается информация (передаются аргументы для вызова функции и возвращаемые значения) и поскольку удаленный вызов процедур может использоваться между клиентом и сервером на одном узле, RPC можно также считать одной из форм передачи сообщений. Интересно также взглянуть на эволюцию различных форм синхронизации в процессе развития Unix: ■ Самые первые программы, которым требовалась синхронизация (чаще всего для предотвращения одновременного изменения содержимого файла несколькими процессами), использовали особенности файловой системы, некоторые из которых описаны в разделе 9.8, ■ Возможность блокирования записей (record locking — глава 9) была добавлена к ядрам Unix в начале 80-х и стандартизована в версии Posix.1 в 1988. ■ Семафоры System V (System V semaphores — глава 11) были добавлены вместе с возможностью совместного использования памяти (System V shared memory — глава 14) и одновременно с очередями сообщений System V (начало 80-х). Эти IPC поддерживаются большинством современных версий Unix. ■ Семафоры Posix (Posix semaphores — глава 10) и разделяемая память Posix (Posix shared memory— глава 13) были также добавлены в стандарт Posix (1003.1b-1993, который ранее упоминался в связи с очередями сообщений Posix). ■ Взаимные исключения и условные переменные (mutex, conditional variable — глава 7) представляют собой две формы синхронизации, определенные стандартом программных потоков Posix (Posix threads, Pthreads — 1003.1с-1995). Хотя обычно они используются для синхронизации между потоками, их можно применять и при организации взаимодействия процессов. ■ Блокировки чтения-записи (read-write locks — глава 8) представляют собой дополнительную форму синхронизации. Она еще не включена в стандарт Posix, но, вероятно, скоро будет. 1.2. Процессы, потоки и общий доступ к информацииВ традиционной модели программирования Unix в системе могут одновременно выполняться несколько процессов, каждому из которых выделяется собственное адресное пространство. Это иллюстрирует рис. 1.1. Рис. 1.1. Совместное использование информации процессами 1. Два процесса в левой части совместно используют информацию, хранящуюся в одном из объектов файловой системы. Для доступа к этим данным каждый процесс должен обратиться к ядру (используя функции read, write, lseek, write, lseek и аналогичные). Некоторая форма синхронизации требуется при изменении файла, для исключения помех при одновременной записи в файл несколькими процессами и для защиты процессов, читающих из файла, от тех, которые пишут в него. 2. Два процесса в середине рисунка совместно используют информацию, хранящуюся в ядре. Примерами в данном случае являются канал, очередь сообщений или семафор System V. Для доступа к совместно используемой информации в этом случае будут использоваться системные вызовы. 3. Два процесса в правой части используют общую область памяти, к которой может обращаться каждый из процессов. После того как будет получен доступ к этой области памяти, процессы смогут обращаться к данным вообще без помощи ядра. В этом случае, как и в первом, процессам, использующим общую память, также требуется синхронизация. Обратите внимание, что ни в одном из этих случаев количество взаимодействующих процессов не ограничивается двумя. Любой из описанных методов работает для произвольного числа взаимодействующих процессов. На рисунке мы изображаем только два для простоты. ПотокиХотя концепция процессов в системах Unix используется уже очень давно, возможность использовать несколько потоков внутри одного процесса появилась относительно недавно. Стандарт потоков Posix.1, называемый Pthreads, был принят в 1995 году. С точки зрения взаимодействия процессов все потоки одного процесса имеют общие глобальные переменные (то есть поточной модели свойственно использование общей памяти). Однако потокам требуется синхронизация доступа к глобальным данным. Вообще, синхронизация, не являясь собственно формой IPC, часто используется совместно с различными формами IPC для управления доступом к данным. В этой книге описано взаимодействие между процессами и между потоками. Мы предполагаем наличие среды, в которой поддерживается многопоточное программирование, и будем использовать выражения вида «если канал пуст, вызывающий поток блокируется до тех пор, пока какой-нибудь другой поток не произведет запись в канал». Если система не поддерживает потоки, можно в этом предложении заменить «потоки» на «процессы» и получится классическое определение блокировки в Unix, возникающей при считывании из пустого канала командой read. Однако в системе, поддерживающей потоки, блокируется только поток, запросивший данные из пустого канала, а все остальные потоки процесса будут продолжать выполняться. Записать данные в канал сможет другой поток этого же процесса или какой-либо поток другого процесса. В приложении Б сведены некоторые основные характеристики потоков и дано описание пяти основных функций Pthread, используемых в программах этой книги. 1.3. Живучесть объектов IPCМожно определить живучесть (persistence) любого объекта IPC как продолжительность его существования. На рис. 1.2 изображены три возможные группы, к которым могут быть отнесены объекты по живучести. Рис. 1.2. Живучесть объектов IPC 1. Объект IPC, живучесть которого определяется процессом (process-persistent), существует до тех пор, пока не будет закрыт последним процессом, в котором он еще открыт. Примером являются неименованные и именованные каналы (pipes, FIFO). 2. Объект IPC, живучесть которого определяется ядром (kernel-persistent), существует до перезагрузки ядра или до явного удаления объекта. Примером являются очереди сообщений стандарта System V, семафоры и разделяемая память. Живучесть очередей сообщений Posix, семафоров и разделяемой памяти должна определяться по крайней мере ядром, но может определяться и файловой системой в зависимости от реализации. 3. Объект IPC, живучесть которого определяется файловой системой (filesystem-persistent), существует до тех пор, пока не будет удален явно. Его значение сохраняется даже при перезагрузке ядра. Очереди сообщений Posix, семафоры и память с общим доступом обладают этим свойством, если они реализованы через отображаемые файлы (так бывает не всегда). Следует быть аккуратным при определении живучести объекта IPC, поскольку она не всегда очевидна. Например, данные в канале (pipe) обрабатываются ядром, но живучесть каналов определяется процессами, а не ядром, потому что после того, как последний процесс, которым канал был открыт на чтение, закроет его, ядро сбросит все данные и удалит канал. Аналогично, хотя каналы FIFO и обладают именами в файловой системе, живучесть их также определяется процессами, поскольку все данные в таком канале сбрасываются после того, как последний процесс, в котором он был открыт, закроет его. В табл. 1.1 сведена информация о живучести перечисленных ранее объектов IPC. Таблица 1.1. Живучесть различных типов объектов IPC
Обратите внимание, что ни один тип IPC в этой таблице не обладает живучестью, определяемой файловой системой. Мы уже упомянули о том, что три типа объектов IPC в стандарте Posix могут иметь этот тип живучести в зависимости от реализации. Очевидно, что запись данных в файл обеспечивает живучесть, определяемую файловой системой, но обычно IPC таким образом не реализуются. Большая часть объектов IPC не предназначена для того, чтобы существовать и после перезагрузки, потому что ее не переживают процессы. Требование живучести, определяемой файловой системой, скорее всего, снизит производительность данного типа IPC, а обычно одной из задач разработчика является именно обеспечение высокой производительности. 1.4. Пространства именЕсли два неродственных процесса используют какой-либо вид IPC для обмена информацией, объект IPC должен иметь имя или идентификатор, чтобы один из процессов (называемый обычно сервером — server) мог создать этот объект, а другой процесс (обычно один или несколько клиентов — client) мог обратиться к этому конкретному объекту. Программные каналы (pipes) именами не обладают (и поэтому не могут использоваться для взаимодействия между неродственными процессами), но каналам FIFO сопоставляются имена в файловой системе, являющиеся их идентификаторами (поэтому каналы FIFO могут использоваться для взаимодействия неродственных процессов). Для других типов IPC, рассматриваемых в последующих главах, используются дополнительные соглашения об именовании (naming conventions). Множество возможных имен для определенного типа IPC называется его пространством имен (name space). Пространство имен — важный термин, поскольку для всех видов IPC, за исключением простых каналов, именем определяется способ связи клиента и сервера для обмена сообщениями. В табл. 1.2 сведены соглашения об именовании для различных видов IPC. Таблица 1.2. Пространства имен для различных типов IPC
Здесь также указано, какие формы IPC содержатся в стандарте Posix.1 1996 года и какие были включены в стандарт Unix 98. Об обоих этих стандартах более подробно рассказано в разделе 1.7. Для сравнения мы включили в эту таблицу три типа сокетов, которые подробно описаны в [24]. Обратите внимание, что интерфейс сокетов (Application Program Interface — API) стандартизируется рабочей группой Posix.1g и должен в будущем стать частью стандарта Posix.1. Хотя стандарт Posix. 1 и дает возможность использования семафоров, их поддержка не является обязательной для производителей. В табл. 1.3 сведены функции, описанные в стандартах Posix.1 и Unix 98. Каждая функция может быть обязательной (mandatory), неопределенной (not defined) или необязательной (дополнительной — optional). Для необязательных функций мы указываем имя константы (например, _POSIX_THREADS), которая будет определена (обычно в заголовочном файле <unistd.h>), если эта функция поддерживается. Обратите внимание, что Unix 98 содержит в себе Posix.1 в качестве подмножества. Таблица 1.3. Доступность различных форм IPC
1.5. Действие команд fork, exec и exit на объекты IPCНам нужно достичь понимания действия функций fork, exec и _exit на различные формы IPC, которые мы обсуждаем (последняя из перечисленных функций вызывается функцией exit). Информация по этому вопросу сведена в табл. 1.4. Большинство функций описаны далее в тексте книги, но здесь нужно сделать несколько замечаний. Во-первых, вызов fork из многопоточного процесса (multithreaded process) приводит к беспорядку в безымянных переменных синхронизации (взаимных исключениях, условных переменных, блокировках и семафорах, хранящихся в памяти). Раздел 6.1 книги [3] содержит необходимые детали. Мы просто отметим в добавление к таблице, что если эти переменные хранятся в памяти с общим доступом и создаются с атрибутом общего доступа для процессов, они будут доступны любому процессу, который может обращаться к этой области памяти. Во-вторых, три формы IPC System V не могут быть открыты или закрыты. Из листинга 6.6 и упражнений 11.1 и 14.1 видно, что все, что нужно знать, чтобы получить доступ к этим трем формам IPC, — это идентификатор. Поэтому они доступны всем процессам, которым известен этот идентификатор, хотя для семафоров и памяти с общим доступом требуется некая особая обработка. Таблица 1.4. Действие fork, exec и _exit на IPC
1.6. Обработка ошибок: функции-оберткиВ любой реальной программе при любом вызове требуется проверка возвращаемого значения на наличие ошибки. Поскольку обычно работа программ при возникновении ошибок завершается, мы можем сократить объем текста, определив функции-обертки (wrapper functions), которые осуществляют собственно вызов функции, проверяют возвращаемое значение и завершают работу при возникновении ошибок. В соответствии с соглашениями имена функций-оберток совпадают с именами самих функций, за исключением первой буквы, которая делается заглавной, например Sem_post(ptr); Пример функции-обертки приведен в листинге 1.1[1] Листинг 1.1. Функция-обертка к функции sem_post// lib/wrapunix.c 387 void 388 Sem_post(sem_t *sem) 389 { 390 if (sem_post(sem) == –1) 391 err_sys("sem_post error"); 392 } Если в тексте вы встретите имя функции, начинающееся с заглавной буквы, знайте: это наша собственная функция-обертка. Она вызывает функцию с тем же именем, начинающимся со строчной буквы. Функция-обертка приводит к завершению работы процесса с выводом сообщения об ошибке, если таковая возникает. При описании исходного кода, включенного в книгу, мы всегда говорим о вызываемой функции самого низкого уровня (например, sem_post), а не о функции-обертке (например, Sem_post). Аналогично в алфавитном указателе приведены имена самих функций, а не оберток к ним.
Хотя может показаться, что использовать такие функции-обертки не слишком выгодно, вы избавитесь от этого заблуждения в главе 7, где мы обнаружим, что функции для работы с потоками (thread functions) не присваивают значение стандартной переменной Unix errno при возникновении ошибки; вместо этого код ошибки просто возвращается функцией. Это означает, что при вызове функции pthread мы должны каждый раз выделять память под переменную, сохранять в ней возвращаемое функцией значение, а затем устанавливать значение переменной errno равным этой переменной, прежде чем вызывать функцию err_sys (листинг В.4). Чтобы не загромождать текст фигурными скобками, мы используем оператор языка Си «запятая» (comma) и совмещаем присваивание значения переменной errno и вызов err_sys в одном операторе, как в нижеследующем примере: int n; if ((n = pthread_mutex_lock(&ndone_mutex))!=0) errno=n, err_sys("pthread_mutex_lock error"); Альтернативой является определение новой функции обработки ошибок, принимающей код ошибки в качестве аргумента. Однако мы можем сделать этот фрагмент кода гораздо более читаемым, записав Pthread_mutex_lock(&ndone_mutex); где используется наша собственная функция-обертка, приведенная в листинге 1.2. Листинг 1.2. Реализация обертки к функции pthread_mutex_lock//lib/wrappthread.c 125 void 126 Pthread_mutex_lock(pthread_mutex_t *mptr) 127 { 128 int n; 129 if ((n=pthread_mutex_lock(mptr))==0) 130 return; 131 errno=n; 132 err_sys("pthread_mutex_lock error"); 133 }
Далее в тексте книги мы будем использовать эти функции-обертки, если только не потребуется явно проверить наличие ошибки и обработать ее произвольным образом, отличным от завершения процесса. Мы не приводим в книге исходный код для всех оберток, но он свободно доступен в Интернете (см. предисловие). Значение errnoПри возникновении ошибки в функции Unix глобальной переменной errno присваивается положительное значение, указывающее на тип ошибки; при этом функция обычно возвращает значение –1. Наша функция err_sys выводит соответствующее коду ошибки сообщение (например, Resource temporarily unavailable — ресурс временно недоступен, — если переменная errno имеет значение EAGAIN). Функция присваивает значение переменной errno только при возникновении ошибки. В случае нормального завершения работы значение этой переменной не определено. Все положительные значения соответствуют константам с именами из заглавных букв, начинающимися с Е, определяемым обычно в заголовочном файле <sys/errno.h>. Отсутствию ошибок соответствует значение 0. При работе с несколькими потоками в каждом из них должна быть собственная переменная errno. Выделение переменной каждому потоку происходит автоматически, однако обычно это требует указания компилятору на то, что должна быть возможность повторного входа в программу. Задается это с помощью ключей –D_REENTRANT или –D_POSIX_C_SOURCE=199506L или аналогичных. Часто в заголовке <errno.h> переменная errno определяется как макрос, раскрываемый в вызов функции, если определена константа _REENTRANT. Функция обеспечивает доступ к копии errno, относящейся к данному потоку. Далее в тексте мы используем выражения наподобие «функция mq_send возвращает EMSGSIZE», означающие, что функция возвращает ошибку (обычно возвращаемое значение при этом равно –1) и присваивает переменной errno значение указанной константы. 1.7. Стандарты UnixВ настоящее время стандарты Unix определяются Posix и The Open Group. PosixНазвание Posix образовано от «Portable Operating System Interface», что означает приблизительно «интерфейс переносимых операционных систем». Это не один стандарт, а целое семейство, разработанное Институтом инженеров по электротехнике и радиоэлектронике (Institute for Electrical and Electronics Engineers — IEEE). Стандарты Posix были также приняты в качестве международных стандартов ISO (International Organization for Standardization, Международная организация по стандартизации) и IEC (International Electrotechnical Commission, Международная электротехническая комиссия), или ISO/IEC. Стандарты Posix прошли несколько стадий разработки. ■ Стандарт IEEE 1003.1-1988 (317 страниц) был первым стандартом Posix. Он определял интерфейс взаимодействия языка С с ядром Unix-типа в следующих областях: примитивы для реализации процессов (вызовы fork, exec, сигналы и таймеры), среда процесса (идентификаторы пользователей, группы процессов), файлы и каталоги (все функции ввода-вывода), работа с терминалом, базы данных системы (файлы паролей и групп), форматы архивов tar и cpio.
■ Затем вышел стандарт IEЕЕ 1003.1-1990 (356 страниц). Он одновременно являлся и международным стандартом ISO/IEC 9945-1:1990. По сравнению с версией 1988 года изменения в версии 1990 года были минимальными. К заголовку было добавлено: «Part 1: System Application Program Interface (API) [C Language]» («Часть 1: Системный интерфейс разработки программ (API) [Язык С])», и это означало, что стандарт описывал программный интерфейс (API) языка С. ■ IEEE 1003.2-1992 вышел в двух томах общим объемом около 1300 страниц, и его заголовок содержал строку «Part 2: Shell and Utilities» (Часть 2: «Интерпретатор и утилиты»). Эта часть определяла интерпретатор (основанный на Bourne shell в Unix System V) и около ста утилит (программ, обычно вызываемых из интерпретатора — от awk и basename до vi и уасс). В настоящей книге мы будем ссылаться на этот стандарт под именем Posix. 2. ■ IEEE 1003.1b-1993 (590 страниц) изначально был известен как IEEE P1003.4. Этот стандарт представлял собой дополнение к стандарту 1003.1-1990 и включал расширения реального времени, разработанные рабочей группой Р1003.4: синхронизацию файлов, асинхронный ввод-вывод, семафоры, управление памятью, планирование выполнения (scheduling), часы, таймеры и очереди сообщений. ■ IEEE 1003.1, издание 1996 года [8] (743 страницы), включает 1003.1-1990 (базовый интерфейс API), 1003.1b-1993 (расширения реального времени), 1003.1-1995 (Pthreads — программные потоки Posix) и 1003.1i-1995 (технические поправки к 1003.1b). Этот стандарт также называется ISO/IEC 9945-1: 1996. В него были добавлены три главы о потоках и дополнительные разделы, касающиеся синхронизации потоков (взаимные исключения и условные переменные), планирование выполнения потоков, планирование синхронизации. В настоящей книге мы называем этот стандарт Posix.1.
В будущем планируется выход новой версии IEEE 1003.1, включающей стандарт P1003.1g, сетевые интерфейсы (сокеты и XTI), которые описаны в первом томе этой книги. В предисловии стандарта Posix.1 1996 года утверждается, что стандарт ISO/IEC 9945 состоит из следующих частей: 1. Системный интерфейс разработки программ (API) (язык С). 2. Интерпретатор и утилиты. 3. Администрирование системы (в разработке). Части 1 и 2 представляют собой то, что мы называем Posix.1 и Posix.2. Работа над стандартами Posix постоянно продолжается, и авторам книг, с ними связанных, приходится заниматься стрельбой по движущейся мишени. О текущем состоянии стандартов можно узнать на сайте http://www.pasc.org/standing/sd11.html. The Open GroupThe Open Group (Открытая группа) была сформирована в 1996 году объединением X/Open Company (основана в 1984 году) и Open Software Foundation (OSF, основан в 1988 году). Эта группа представляет собой международный консорциум производителей и потребителей из промышленности, правительства и образовательных учреждений. Их стандарты тоже выходили в нескольких версиях: ■ В 1989 году Х/Open опубликовала 3-й выпуск X/Open Portability Guide (Руководство по разработке переносимых программ) — XPG3. ■ В 1992 году был опубликован четвертый выпуск (Issue 4), а в 1994 году — вторая его версия (Issue 4, Version 2). Последняя известна также под названием Spec 1170, где магическое число 1170 представляет собой сумму количества интерфейсов системы (926), заголовков (70) и команд (174). Есть и еще два названия: X/Open Single Unix Specification (Единая спецификация Unix) и Unix 95. ■ В марте 1997 года было объявлено о выходе второй версии Единой спецификации Unix. Этот стандарт программного обеспечения называется также Unix 98, и именно так мы ссылаемся на эту спецификацию далее в тексте книги. Количество интерфейсов в Unix 98 возросло с 1170 до 1434, хотя для рабочей станции это количество достигает 3030, поскольку в это число включается CDE (Common Desktop Environment — общее окружение рабочего стола), которое, в свою очередь, требует системы X Window System и пользовательского интерфейса Motif. Подробно об этом написано в книге [9]. Полезную информацию можно также найти по адресу http://www.UNIX-systems.org/version2.
Версии Unix и переносимостьПрактически все версии Unix, с которыми можно столкнуться сегодня, соответствуют какому-либо варианту стандарта Posix.1 или Posix.2. Мы говорим «какому-либо», потому что после внесения изменений в Posix (например, Добавление расширений реального времени в 1993 и потоков в 1996) производителям обычно требуется год или два, чтобы подогнать свои программы под эти стандарты. Исторически большинство систем Unix являются потомками либо BSD, либо System V, но различия между ними постепенно стираются, по мере того как производители переходят к использованию стандартов Posix. Основные различия лежат в области системного администрирования, поскольку ни один стандарт Posix на данный момент не описывает эту область. В большинстве примеров этой книги мы использовали операционные системы Solaris 2.6 и Digital Unix 4.0B. Дело в том, что на момент написания книги (конец 1997 — начало 1998 года) только эти две операционные системы поддерживали System V IPC, Posix IPC и программные потоки Posix (Pthreads). 1.8. Комментарий к примерам IPCЧаще всего для иллюстрации различных функций в книге используются три шаблона (модели) взаимодействия: 1. Сервер файлов: приложение клиент-сервер, причем клиент посылает серверу запрос с именем файла, а сервер возвращает клиенту его содержимое. 2. Производитель-потребитель: один или несколько потоков или процессов (производителей) помещают данные в буфер общего пользования, а другие потоки или процессы (потребители) производят с этими данными различные операции. 3. Увеличение последовательного номера: один или несколько потоков или процессов увеличивают общий для всех индекс. Число это может храниться в файле с общим доступом или в совместно используемой области памяти. Первый пример иллюстрирует различные формы передачи сообщений, а других два — разнообразные виды синхронизации и использования разделяемой памяти. Таблицы 1.5, 1.6 и 1.7 представляют собой своего рода путеводитель по разрабатываемым нами программам на различные темы, изложенные в книге. В этих таблицах кратко описаны сами программы и указаны номера соответствующих листингов. 1.9. РезюмеВзаимодействие процессов традиционно является одной из проблемных областей в Unix. По мере развития системы предлагались различные решения, и ни одно из них не было совершенным. Мы подразделяем IPC на четыре главных типа. 1. Передача сообщений (каналы, FIFO, очереди сообщений). 2. Синхронизация (взаимные исключения, условные переменные, блокировки чтения-записи, семафоры). 3. Разделяемая память (неименованная и именованная). 4. Вызов процедур (двери в Solaris, RPC Sun). Мы рассматриваем взаимодействие как отдельных потоков одного процесса, так и нескольких независимых процессов. Живучесть каждого типа IPC определяется либо процессом, либо ядром, либо файловой системой в зависимости от продолжительности его существования. При выборе типа IPC для конкретного применения нужно учитывать его живучесть. Другим свойством каждого типа IPC является пространство имен, определяющее идентификацию объектов IPC процессами и потоками, использующими его. Некоторые объекты не имеют имен (каналы, взаимные исключения, условные переменные, блокировки чтения-записи), другие обладают именами в рамках файловой системы (каналы FIFO), третьи характеризуются тем, что в главе 2 названо «именами IPC стандарта Posix», а четвертые — еще одним типом имен, который описан в главе 3 (ключи или идентификаторы IPC стандарта System V). Обычно сервер создает объект IPC с некоторым именем, а клиенты используют это имя для получения доступа к объекту. В исходных кодах, приведенных в книге, используются функции-обертки, описанные в разделе 1.6, позволяющие уменьшить объем кода, обеспечивая, тем не менее, проверку возврата ошибки для любой вызываемой функции. Имена всех функций-оберток начинаются с заглавной буквы. Стандарты IEEE Posix — Posix.1, определяющий основы интерфейса С в Unix, и Posix.2, определяющий основные команды, — это те стандарты, к которым движутся большинство производителей. Однако стандарты Posix в настоящее время быстро поглощаются (включаются в качестве части) и расширяются коммерческими стандартами, в частности The Open Group (Unix 98). Таблица 1.5. Версии модели клиент-сервер
Таблица 1.6. Версии модели производитель-потребитель
Таблица 1.7. Версии программы с увеличением последовательного номера
Упражнения1. На рис 1.1 изображены два процесса, обращающиеся к одному файлу. Если оба процесса только дописывают данные к концу файла (возможно, длинного), какой нужен будет тип синхронизации? 2. Изучите заголовочный файл <errno.h> в вашей системе и выясните, как определена errno. 3. Дополните табл. 1.3 используемыми вами функциями, поддерживаемыми Unix-системами. ГЛАВА 2Posix IPC 2.1. ВведениеИз имеющихся типов IPC следующие три могут быть отнесены к Posix IPC, то есть к методам взаимодействия процессов, соответствующим стандарту Posix: ■ очереди сообщений Posix (глава 5); ■ семафоры Posix (глава 10); ■ разделяемая память Posix (глава 13). Эти три вида IPC обладают общими свойствами, и для работы с ними используются похожие функции. В этой главе речь пойдет об общих требованиях к полным именам файлов, используемых в качестве идентификаторов, о флагах, указываемых при открытии или создании объектов IPC, и о разрешениях на доступ к ним. Полный список функций, используемых для работы с данными типами IPC, приведен в табл. 2.1. Таблица 2.1. Функции Posix IPC
2.2. Имена IPCВ табл. 1.2 мы отметили, что три типа IPC стандарта Posix имеют идентификаторы (имена), соответствующие этому стандарту. Имя IPC передается в качестве первого аргумента одной из трех функций: mq_open, sem_open и shm_open, причем оно не обязательно должно соответствовать реальному файлу в файловой системе. Стандарт Posix.1 накладывает на имена IPC следующие ограничения: ■ Имя должно соответствовать существующим требованиям к именам файлов (не превышать в длину РАТНМАХ байтов, включая завершающий символ с кодом 0). ■ Если имя начинается со слэша (/), вызов любой из этих функций приведет к обращению к одной и той же очереди. В противном случае результат зависит от реализации. ■ Интерпретация дополнительных слэшей в имени зависит от реализации. Таким образом, для лучшей переносимости имена должны начинаться со слэша (/) и не содержать в себе дополнительных слэшей. К сожалению, эти правила, в свою очередь, приводят к проблемам с переносимостью. В системе Solaris 2.6 требуется наличие начального слэша и запрещается использование дополнительных. Для очереди сообщений, например, при этом создаются три файла в каталоге /tmp, причем имена этих файлов начинаются с .MQ. Например, если аргумент функции mq_open имеет вид /queue.1234, то созданные файлы будут иметь имена /tmp/.MQDqueue.1234, /tmp/.MQLqueue.1234 и /tmp/.MQPqueue.1234. В то же время в системе Digital Unix 4.0B просто создается файл с указанным при вызове функции именем. Проблема с переносимостью возникает при указании имени с единственным слэшем в начале: при этом нам нужно иметь разрешение на запись в корневой каталог. Например, очередь с именем /tmp.1234 допустима стандартом Posix и не вызовет проблем в системе Solaris, но в Digital Unix возникнет ошибка создания файла, если разрешения на запись в корневой каталогу программы нет. Если мы укажем имя /tmp/test.1234, проблемы в Digital Unix и аналогичных системах, создающих файл с указанным именем, пропадут (предполагается существование каталога /tmp и наличие у программы разрешения на запись в него, что обычно для большинства систем Unix), однако в Solaris использование этого имени будет невозможно. Для решения подобных проблем с переносимостью следует определять имя в заголовке с помощью директивы #define, чтобы обеспечить легкость его изменения при переносе программы в другую систему.
Стандарт Posix.1 определяет три макроса: S_TYPEISMQ(buf) S_TYPEISSEM(buf) S_TYPEISSHM(buf) которые принимают единственный аргумент — указатель на структуру типа stat, содержимое которой задается функциями fstat, lstat и stat. Эти три макроса возвращают ненулевое значение, если указанный объект IPC (очередь сообщений, семафор или сегмент разделяемой памяти) реализован как особый вид файла и структура stat ссылается на этот тип. В противном случае макрос возвращает 0.
Функция px_ipc_nameСуществует и другое решение упомянутой проблемы с переносимостью. Можно определить нашу собственную функцию px_ipc_name, которая добавляет требуемый каталог в качестве префикса к имени Posix IPC. #include "unpipc.h" char *px_ipc_name(const char *name); /* Возвращает указатель при успешном завершении, NULL при возникновении ошибки */
Аргумент пате (имя) не должен содержать слэшей. Тогда, например, при вызове px_ipc_name("test1") будет возвращен указатель на строку /test1 в Solaris 2.6 или на строку /tmp/test1 в Digital Unix 4.0B. Память для возвращаемой строки выделяется динамически и освобождается вызовом free. Можно установить произвольное значение переменной окружения PX_IPC_NAME, чтобы задать другой каталог по умолчанию. В листинге 2.1[1] приведен наш вариант реализации этой функции. Листинг 2.1. Функция px_ipc_name в нашей реализации. //lib/px_ipc_name.c 1 #include "unpipc.h" 2 char * 3 px_ipc_name(const char *name) 4 { 5 char *dir, *dst, *slash; 6 if ((dst = malloc(РАТН_МАХ)) == NULL) 7 return(NULL); 8 /* есть возможность задать другое имя каталога с помощью переменной окружения */ 9 if ((dir = getenv("PX IPC_NAME")) == NULL) { 10 #ifdef POSIX_IPC_PREFIX 11 dir = POSIX_IPC_PREFIX; /* из "config.h" */ 12 #else 13 dir = "/tmp/"; /* по умолчанию */ 14 #endif 15 } 16 /* имя каталога должно заканчиваться символом '/' */ 17 slash = (dir[strlen(dir) – 1] == '/') ? "" : "/"; 18 snprintf(dst, PATH_MAX, "%s%s%s", dir, slash, name); 19 return(dst); /* для освобождения этого указателя можно вызвать free() */ 20 } 2.3. Создание и открытие каналов IPCВсе три функции, используемые для создания или открытия объектов IPC: mq_open, sem_open и shm_open, — принимают специальный флаг oflag в качестве второго аргумента. Он определяет параметры открытия запрашиваемого объекта аналогично второму аргументу стандартной функции open. Все константы, из которых можно формировать этот аргумент, приведены в табл. 2.2. Таблица 2.2. Константы, используемые при создании и открытии объектов IPC
Первые три строки описывают тип доступа к создаваемому объекту: только чтение, только запись, чтение и запись. Очередь сообщений может быть открыта в любом из трех режимов доступа, тогда как для семафора указание этих констант не требуется (для любой операции с семафором требуется доступ на чтение и запись). Наконец, объект разделяемой памяти не может быть открыт только на запись. Указание прочих флагов из табл. 2.2 не является обязательным. O_CREAT — создание очереди сообщений, семафора или сегмента разделяемой памяти, если таковой еще не существует (см. также флаг O_EXCL, влияющий на результат). При создании новой очереди сообщений, семафора или сегмента разделяемой памяти требуется указание по крайней мере одного дополнительного аргумента, определяющего режим. Этот аргумент указывает биты разрешения на доступ к файлу и формируется путем побитового логического сложения констант из табл. 2.3. Таблица 2.3. Константы режима доступа при создании нового объекта IPC
Эти константы определены в заголовке <sys/stat.h>. Указанные биты разрешений изменяются наложением маски режима создания файлов для данного процесса (с. 83-85 [21]) или с помощью команды интерпретатора umask. Как и со вновь созданным файлом, при создании очереди сообщений, семафора или сегмента разделяемой памяти им присваивается идентификатор пользователя, соответствующий действующему (effective) идентификатору пользователя процесса. Идентификатор группы семафора или сегмента разделяемой памяти устанавливается равным действующему групповому идентификатору процесса или групповому идентификатору, установленному по умолчанию для системы в целом. Групповой идентификатор очереди сообщений всегда устанавливается равным действующему групповому идентификатору процесса (на с. 77-78 [21] рассказывается о групповых и пользовательских идентификаторах более подробно).
O_EXCL — если этот флаг указан одновременно с O_CREAT, функция создает новую очередь сообщений, семафор или объект разделяемой памяти только в том случае, если таковой не существует. Если объект уже существует и указаны флаги O_CREAT | O_EXCL, возвращается ошибка EEXIST. Проверка существования очереди сообщений, семафора или сегмента разделяемой памяти и его создание (в случае отсутствия) должны производиться только одним процессом. Два аналогичных флага имеются и в System V IPC, они описаны в разделе 3.4. O_NONBLOCK — этот флаг создает очередь сообщений без блокировки. Блокировка обычно устанавливается для считывания из пустой очереди или записи в полную очередь. Об этом более подробно рассказано в подразделах, посвященных функциям mq_send и mq_receive раздела 5.4. O_TRUNC — если уже существующий сегмент общей памяти открыт на чтение и запись, этот флаг указывает на необходимость сократить его размер до 0. На рис. 2.1 показана реальная последовательность логических операций при открытии объекта IPC. Что именно подразумевается под проверкой разрешений доступа, вы узнаете в разделе 2.4. Другой подход к изображенному на рис. 2.1 представлен в табл. 2.4. Рис. 2.1. Логика открытия объекта IPC Обратите внимание, что в средней строке табл. 2.4, где задан только флаг O_CREAT, мы не получаем никакой информации о том, был ли создан новый объект или открыт существующий. Таблица 2.4. Логика открытия объекта IPC
2.4. Разрешения IPCНовая очередь сообщений, именованный семафор или сегмент разделяемой памяти создается функциями mq_open, sem_open и shm_open, при условии, что аргумент oflag содержит константу O_CREAT. Согласно табл. 2.3, любому из данных типов IPC присваиваются определенные права доступа (permissions), аналогичные разрешениям доступа к файлам в Unix. При открытии существующей очереди сообщений, семафора или сегмента разделяемой памяти теми же функциями (в случае, когда не указан флаг O_CREAT или указан O_CREAT без O_EXCL и объект уже существует) производится проверка разрешений: 1. Проверяются биты разрешений, присвоенные объекту IPC при создании. 2. Проверяется запрошенный тип доступа (O_RDONLY, O_WRONLY, O_RDWR). 3. Проверяется действующий идентификатор пользователя вызывающего процесса, действующий групповой идентификатор процесса и дополнительные групповые идентификаторы процесса (последние могут не поддерживаться). Большинством систем Unix производятся следующие конкретные проверки: 1. Если действующий идентификатор пользователя для процесса есть 0 (привилегированный пользователь), доступ будет разрешен. 2. Если действующий идентификатор пользователя процесса совпадает с идентификатором владельца объекта IPC: если соответствующий бит разрешения для пользователя установлен, доступ разрешен, иначе в доступе отказывается. Под соответствующим битом разрешения мы подразумеваем, например, бит разрешения на чтение, если процесс открывает объект только для чтения. Если процесс открывает объект для записи, должен быть установлен соответственно бит разрешения на запись для владельца (user-write). 3. Если действующий идентификатор группы процесса или один из дополнительных групповых идентификаторов процесса совпадает с групповым идентификатором объекта IPC: если соответствующий бит разрешения для группы установлен, доступ будет разрешен, иначе в доступе отказывается. 4. Если соответствующий бит разрешения доступа для прочих пользователей установлен, доступ будет разрешен, иначе в доступе будет отказано. Эти четыре проверки производятся в указанном порядке. Следовательно, если процесс является владельцем объекта IPC (шаг 2), доступ разрешается или запрещается на основе одних только разрешений пользователя (владельца). Разрешения группы при этом не проверяются. Аналогично, если процесс не является владельцем объекта IPC, но принадлежит к нужной группе, доступ разрешается или запрещается на основе разрешений группы — разрешения для прочих пользователей при этом не проверяются.
2.5. РезюмеТри типа Posix IPC — очереди сообщений, семафоры и разделяемая память — идентифицируются их полными именами. Они могут являться или не являться реальными именами файлов в файловой системе, и это вызывает проблемы с переносимостью. Решение проблемы — использовать собственную функцию px_ipc_name. При создании или открытии объекта IPC требуется указать набор флагов, аналогичных указываемым при использовании функции open. При создании нового объекта IPC требуется указать разрешения для него, используя те же константы S_xxx, что и для функции open (табл. 2.3). При открытии существующего объекта IPC производится проверка разрешений процесса, аналогичная проверке при открытии файла. Упражнения1. Каким образом биты установки идентификатора пользователя (set-user-ID, SUID) и установки идентификатора группы (set-group-ID) (раздел 4.4 [21]) программы, использующей Posix IPC, влияют на проверку разрешений, описанную в разделе 2.4? 2. Когда программа открывает объект IPC, как она может определить, был ли создан новый объект IPC или производится обращение к существующему объекту? ГЛАВА 3System V IPC 3.1. ВведениеИз имеющихся типов IPC следующие три могут быть отнесены к System V IPC, то есть к методам взаимодействия процессов, соответствующим стандарту System V: ■ очереди сообщений System V (глава 6); ■ семафоры System V (глава 11); ■ общая память System V (глава 14). Термин «System V IPC» говорит о происхождении этих средств: впервые они появились в Unix System V. У них много общего: схожи функции, с помощью которых организуется доступ к объектам; также схожи формы хранения информации в ядре. В этой главе описываются общие для трех типов IPC черты. Информация о функциях сведена в табл. 3.1. Таблица 3.1. Функции System V IPC
3.2. Ключи типа key_t и функция ftokВ табл. 1.2 было отмечено, что в именах трех типов System V IPC использовались значения key_t. Заголовочный файл <sys/types.h> определяет тип key_t как целое (по меньшей мере 32-разрядное). Значения переменным этого типа обычно присваиваются функцией ftok. Функция ftok преобразовывает существующее полное имя и целочисленный идентификатор в значение типа key_t (называемое ключом IPC — IPC key): #include <sys/ipc.h> key_t ftok(const char *pathname, int id); //Возвращает ключ IPC либо –1 при возникновении ошибки На самом деле функция использует полное имя файла и младшие 8 бит идентификатора для формирования целочисленного ключа IPC. Эта функция действует в предположении, что для конкретного приложения, использующего IPC, клиент и сервер используют одно и то же полное имя объекта IPC, имеющее какое-то значение в контексте приложения. Это может быть имя демона сервера или имя файла данных, используемого сервером, или имя еще какого-нибудь объекта файловой системы. Если клиенту и серверу для связи требуется только один канал IPC, идентификатору можно присвоить, например, значение 1. Если требуется несколько каналов IPC (например, один от сервера к клиенту и один в обратную сторону), идентификаторы должны иметь разные значения: например, 1 и 2. После того как клиент и сервер договорятся о полном имени и идентификаторе, они оба вызывают функцию ftok для получения одинакового ключа IPC. Большинство реализаций ftok вызывают функцию stat, а затем объединяют: ■ информацию о файловой системе, к которой относится полное имя pathname (поле st_dev структуры stat); ■ номер узла (i-node) в файловой системе (поле st_ino структуры stat); ■ младшие 8 бит идентификатора (который не должен равняться нулю). Из комбинации этих трех значений обычно получается 32-разрядный ключ. Нет никакой гарантии того, что для двух различных путей с одним и тем же идентификатором получатся разные ключи, поскольку количество бит информации в трех перечисленных элементах (идентификатор файловой системы, номер узла, идентификатор IPC) может превышать число бит в целом (см. упражнение 3.5).
Если указанное полное имя не существует или недоступно вызывающему процессу, ftok возвращает значение –1. Помните, что файл, имя которого используется для вычисления ключа, не должен быть одним из тех, которые создаются и удаляются сервером в процессе работы, поскольку каждый раз при создании заново эти файлы получают, вообще говоря, другой номер узла, а это может изменить ключ, возвращаемый функцией ftok при очередном вызове. ПримерПрограмма в листинге 3.1 принимает полное имя в качестве аргумента командной строки, вызывает функции stat и ftok, затем выводит значения полей st_dev и st_ino структуры stat и получающийся ключ IPC. Эти три значения выводятся в шестнадцатеричном формате, поэтому легко видеть, как именно ключ IPC формируется из этих двух значений и идентификатора 0x57. Листинг 3.1[1]. Получение и вывод информации о файле и созданного ключа IPC//svipc/ftok.c 1 #include "unpipc.h" 2 int 3 main(int argc, char **argv) 4 { 5 struct stat stat; 6 if (argc != 2) 7 err_quit("usage: ftok <pathname>"); 8 Stat(argv[1], &stat); 9 printf("st_dev: &lx, st_ino: %Ix, key: %x\n", 10 (u_long) stat.st_dev, (u_long) stat.st_ino, 11 Ftok(argv[1], 0x57)); 12 exit(0); 13 } Выполнение этой программы в системе Solaris 2.6 приведет к следующим результатам: solaris %ftok /etc/system st_dev: 800018, st_ino: 4a1b, key: 57018a1b solaris %ftok /usr/tmp st_dev: 800015, st_ino: 10b78, key: 57015b78 solaris %ftok /home/rstevens/Mail.out st_dev: 80001f, st_ino: 3b03, key: 5702fb03 Очевидно, идентификатор определяет старшие 8 бит ключа; младшие 12 бит st_dev определяют следующие 12 бит ключа, и наконец, младшие 12 бит st_ino определяют младшие 12 бит ключа. Цель этого примера не в том, чтобы впоследствии рассчитывать на такой способ формирования ключа из перечисленной информации, а в том, чтобы проиллюстрировать алгоритм комбинации полного имени и идентификатора конкретной реализацией. В других реализациях алгоритм может быть другим.
3.3. Структура ipc_permДля каждого объекта IPC, как для обычного файла, в ядре хранится набор информации, объединенной в структуру. struct ipc_perm { uid_t uid; /*идентификатор пользователя владельца*/ gid_t gid; /*идентификатор группы владельца */ uid_t cuid; /*идентификатор пользователя создателя*/ gid_t cgid; /*идентификатор группы создателя*/ mode_t mode; /*разрешения чтения-записи*/ ulong_t seq; /*последовательный номер канала*/ key_t key; /* ключ IPC */ } Эта структура вместе с другими переименованными константами для функций System V IPC определена в файле <sys/ipc.h>. В этой главе мы расскажем о полях структуры ipc_perm более подробно. 3.4. Создание и открытие каналов IPCТри функции getXXX, используемые для создания или открытия объектов IPC (табл. 3.1), принимают ключ IPC (типа key_t) в качестве одного из аргументов и возвращают целочисленный идентификатор. Этот идентификатор отличается от того, который передавался функции ftok, как мы вскоре увидим. У приложения есть две возможности задания ключа (первого аргумента функций getXXX): 1. Вызвать ftok, передать ей полное имя и идентификатор. 2. Указать в качестве ключа константу IPCPRIVATE, гарантирующую создание нового уникального объекта IPC. Последовательность действий иллюстрирует рис. 3.1. Рис. 3.1. Вычисление идентификаторов IPC по ключам Все три функции getXXX (табл. 3.1) принимают в качестве второго аргумента набор флагов oflag, задающий биты разрешений чтения-записи (поле mode структуры ipc_perm) для объекта IPC и определяющий, создается ли новый объект IPC или производится обращение к уже существующему. Для этого имеются следующие правила. ■ Ключ IPC_PRIVATE гарантирует создание уникального объекта IPC. Никакие возможные комбинации полного имени и идентификатора не могут привести к тому, что функция ftok вернет в качестве ключа значение IPC_PRIVATE. ■ Установка бита IPC_CREAT аргумента oflag приводит к созданию новой записи для указанного ключа, если она еще не существует. Если же обнаруживается существующая запись, возвращается ее идентификатор. Одновременная установка битов IPC_CREAT и IPC_EXCL аргумента oflag приводит к созданию новой записи для указанного ключа только в том случае, если такая запись еще не существует. Если же обнаруживается существующая запись, функция возвращает ошибку EEXIST (объект IPC уже существует). Комбинация IPC_CREAT и IPC_EXCL в отношении объектов IPC действует аналогично комбинации O_CREAT и O_EXCL для функции open. Установка только бита IPC_EXCL без IPC_CREAT никакого эффекта не дает. Логическая диаграмма последовательности действий при открытии объекта IPC изображена на рис. 3.2. В табл. 3.2 показан альтернативный взгляд на этот процесс. Рис. 3.2. Диаграмма открытия объекта IPC Обратите внимание, что в средней строке табл. 3.2 для флага IPC_CREAT без IPC_EXCL мы не получаем никакой информации о том, был ли создан новый объект или получен доступ к существующему. Для большинства приложений характерно создание сервером объекта IPC с указанием IPC_CREAT (если безразлично, существует ли уже объект) или IPC_CREAT | IPC_EXCL (если требуется проверка существования объекта). Клиент вообще не указывает флагов, предполагая, что сервер уже создал объект.
Таблица 3.2. Логика создания и открытия объектов IPC
3.5. Разрешения IPCПри создании нового объекта IPC с помощью одной из функций getXXX, вызванной с флагом IPC_CREAT, в структуру ipc_perm заносится следующая информация (раздел 3.3): 1. Часть битов аргумента oflag задают значение поля mode структуры ipc_perm. В табл. 3.3 приведены биты разрешений для трех типов IPC (запись >>3 означает сдвиг вправо на три бита). 2. Поля cuid и cgid получают значения, равные действующим идентификаторам пользователя и группы вызывающего процесса. Эти два поля называются идентификаторами создателя. 3. Поля uid и gid структуры iрс_perm также устанавливаются равными действующим идентификаторам вызывающего процесса. Эти два поля называются идентификаторами владельца. Таблица 3.3. Значения mode для разрешений чтения-записи IPC
Идентификатор создателя изменяться не может, тогда как идентификатор владельца может быть изменен процессом с помощью вызова функции ctlXXX для данного механизма IPC с командой IPC_SET. Три функции ctlXXX позволяют процессу изменять биты разрешений доступа (поле mode) объекта IPC.
Когда какой-либо процесс предпринимает попытку доступа к объекту IPC, производится двухэтапная проверка: первый раз при открытии файла (функция getXXX) и затем каждый раз при обращении к объекту IPC: 1. При установке доступа к существующему объекту IPC с помощью одной из функций getXXX производится первичная проверка аргумента oflag, вызывающего функцию процесса. Аргумент не должен указывать биты доступа, не установленные в поле mode структуры ipc_perm (нижний квадрат на рис. 3.2). Например, процесс-сервер может установить значение члена mode для своей очереди входящих сообщений, сбросив биты чтения для группы и прочих пользователей. Любой процесс, попытавшийся указать эти биты в аргументе oflag функции msgget, получит ошибку. Надо отметить, что от этой проверки, производимой функциями getXXX, мало пользы. Она подразумевает наличие у вызывающего процесса информации о том, к какой группе пользователей он принадлежит: он может являться владельцем файла, может принадлежать к той же группе или к прочим пользователям. Если создающий процесс сбросит некоторые биты разрешений, а вызывающий процесс попытается их установить, функция getXXX вернет ошибку. Любой процесс может полностью пропустить эту проверку, указав аргумент oflag, равный 0, если заранее известно о существовании объекта IPC. 2. При любой операции с объектами IPC производится проверка разрешений для процесса, эту операцию запрашивающего. Например, каждый раз когда процесс пытается поместить сообщение в очередь с помощью команды msgsnd, производятся нижеследующие проверки (при получении доступа последующие этапы пропускаются). □ Привилегированному пользователю доступ предоставляется всегда. □ Если действующий идентификатор пользователя совпадает со значением uid или cuid объекта IPC и установлен соответствующий бит разрешения доступа в поле mode объекта IPC, доступ будет разрешен. Под соответствующим битом разрешения доступа подразумевается бит, разрешающий чтение, если вызывающий процесс запрашивает операцию чтения для данного объекта IPC (например, получение сообщения из очереди), или бит, разрешающий запись, если процесс хочет осуществить ее. □ Если действующий идентификатор группы совпадает со значением gid или cgid объекта IPC и установлен соответствующий бит разрешения доступа в поле mode объекта IPC, доступ будет разрешен. □ Если доступ не был разрешен на предыдущих этапах, проверяется наличие соответствующих установленных битов доступа для прочих пользователей. 3.6. Повторное использование идентификаторовСтруктура ipc_perm (раздел 3.3) содержит переменную seq, в которой хранится порядковый номер канала. Эта переменная представляет собой счетчик, заводимый ядром для каждого объекта IPC в системе. При удалении объекта IPC номер канала увеличивается, а при переполнении сбрасывается в ноль.
В счетчике возникает потребность по двум причинам. Во-первых, вспомним о дескрипторах файлов, хранящихся в ядре для каждого из открытых файлов. Они обычно представляют собой небольшие целые числа, имеющие значение только внутри одного процесса — для каждого процесса создаются собственные дескрипторы. Прочитать из файла с дескриптором 4 можно только в том процессе, в котором есть открытый файл с таким дескриптором. Есть ли открытые файлы с тем же дескриптором в других процессах — значения не имеет. В отличие от дескрипторов файлов идентификаторы System V IPC устанавливаются для всей системы, а не для процесса. Идентификатор IPC возвращается одной из функций getXXX: msgget, semget, shmget. Как и дескрипторы файлов, идентификаторы представляют собой целые числа, имеющие в отличие от дескрипторов одинаковое значение для всех процессов. Если два неродственных процесса (клиент и сервер) используют одну очередь сообщений, ее идентификатор, возвращаемый функцией msgget, должен иметь одно и то же целочисленное значение в обоих процессах, чтобы они получили доступ к одной и той же очереди. Такая особенность дает возможность какому-либо процессу, созданному злоумышленником, попытаться прочесть сообщение из очереди, созданной другим приложением, последовательно перебирая различные идентификаторы (если бы они представляли собой небольшие целые числа) и надеясь на существование открытой в текущий момент очереди, доступной для чтения всем. Если бы идентификаторы представляли собой небольшие целые числа (как дескрипторы файлов), вероятность найти правильный идентификатор составляла бы около 1/50 (предполагая ограничение в 50 дескрипторов на процесс). Для исключения такой возможности разработчики средств IPC решили расширить возможный диапазон значений идентификатора так, чтобы он включал вообще все целые числа, а не только небольшие. Расширение диапазона обеспечивается путем увеличения значения идентификатора, возвращаемого вызывающему процессу, на количество записей в системной таблице IPC каждый раз, когда происходит повторное использование одной из них. Например, если система настроена на использование не более 50 очередей сообщений, при первом использовании первой записи процессу будет возвращен идентификатор 0. После удаления этой очереди сообщений при попытке повторного использования первой записи в таблице процессу будет возвращен идентификатор 50. Далее он будет принимать значения 100, 150 и т. д. Поскольку seq обычно определяется как длинное целое без знака (ulong — см. структуру ipc_perm в разделе 3.3), возврат к уже использовавшимся идентификаторам происходит, когда запись в таблице будет использована 85899346 раз (2³²/50 в предположении, что целое является 32-разрядным). Второй причиной, по которой понадобилось ввести последовательный номер канала, является необходимость исключить повторное использование идентификаторов System V IPC через небольшой срок. Это помогает гарантировать то, что досрочно завершивший работу и впоследствии перезапущенный сервер не станет использовать тот же идентификатор. Иллюстрируя эту особенность, программа в листинге 3.2 выводит первые десять значений идентификаторов, возвращаемых msgget. Листинг 3.2. Вывод идентификатора очереди сообщений десять раз подряд//svmsg/slot.c 1 #include <unpipc.h> 2 int 3 main(int argc, char **argv) 4 { 5 int i, msqid; 6 for (i=0;i<10;i++) { 7 msqid=Msgget(IPC_PRIVATE, SVMSG_MODE|IPC_CREAT); 8 printf("msqid = %d\n", msqid); 9 Msgctl(msqid, IPC_RMID, NULL); 10 } 11 exit(0); 12 } При очередном прохождении цикла msgget создает очередь сообщений, a msgctl с командой IPC_RMID в качестве аргумента удаляет ее. Константа SVMSG_MODE определяется в нашем заголовочном файле unpipc.h (листинг В.1) и задает разрешения по умолчанию для очереди сообщений System V. Вывод программы будет иметь следующий вид: solaris %slot msqid = 0 msqid = 50 msqid = 100 msqid = 150 msqid = 200 msqid = 250 msqid = 300 msqid = 350 msqid = 400 msqid = 450 При повторном запуске программы мы увидим наглядную иллюстрацию того, что последовательный номер канала — это переменная, хранящаяся в ядре и продолжающая существовать и после завершения процесса. solaris % slot msqid = 500 msqid = 550 msqid = 600 msqid = 650 msqid = 700 msqid = 750 msqid = 800 msqid = 850 msqid = 900 msqid = 950 3.7. Программы ipcs и ipcrmПоскольку объектам System V IPC не сопоставляются имена в файловой системе, мы не можем просмотреть их список или удалить их, используя стандартные программы ls и rm. Вместо них в системах, поддерживающих эти типы IPC, предоставляются две специальные программы: ipcs, выводящая различную информацию о свойствах System V IPC, и ipcrm, удаляющая очередь сообщений System V, семафор или сегмент разделяемой памяти. Первая из этих функций поддерживает около десятка параметров командной строки, управляющих отображением информации о различных типах IPC. Второй (ipcrm) можно задать до шести параметров. Подробную информацию о них можно получить в справочной системе.
3.8. Ограничения ядраБольшинству реализаций System V IPC свойственно наличие внутренних ограничений, налагаемых ядром. Это, например, максимальное количество очередей сообщений или ограничение на максимальное количество семафоров в наборе. Характерные значения этих ограничений приведены в табл. 6.2, 11.1 и 14.1. Большая часть ограничений унаследована от исходной реализации System V.
К сожалению, некоторые из накладываемых ограничений достаточно жестки, поскольку они унаследованы от исходной реализации, базировавшейся на системе с небольшим адресным пространством (16-разрядный PDP-11). К счастью, большинство систем позволяют администратору изменять некоторые из установленных по умолчанию ограничений, но необходимые для этого действия специфичны для каждой версии Unix. В большинстве случаев после внесения изменений требуется перезагрузка ядра. К сожалению, в некоторых реализациях для хранения некоторых параметров до сих пор используются 16-разрядные целые, а это уже устанавливает аппаратные ограничения. В Solaris 2.6, например, таких ограничений 20. Их текущие значения можно вывести на экран, используя команду sysdef. Учтите, что вместо реальных значений будут выведены нули, если соответствующий модуль ядра не загружен (то есть средство не было ранее использовано). Это можно исключить, добавив к файлу /etc/system любой из нижеследующих операторов. Файл /etc/system считывается в процессе перезагрузки системы: set msgsys:msginfo_msgseg = значение set msgsys:msginfo_msgssz = значение set msgsys:msginfo_msgtql = значение set msgsys:msginfo_msgmap = значение set msgsys:msginfo_msgmax = значение set msgsys:msginfo_msgmnb = значение set msgsys:msginfo_msgmni = значение set semsys:seminfo_semopm = значение set semsys:seminfo_semume = значение set semsys:seminfo_semaem = значение set semsys:seminfo_semmap = значение set semsys:seminfo_semvmx = значение set semsys:seminfo_semmsl = значение set semsys:seminfo_semmni = значение set semsys:seminfo_semmns = значение set semsys:seminfo_semmnu = значение set shmsys:shminfo_shmmin = значение set shmsys:shminfo_shmseg = значение set shmsys:shminfo_shmmax = значение set shmsys:shminfo_shmmni = значение Последние шесть символов имени слева от знака равенства представляют собой переменные, перечисленные в табл. 6.2, 11.1 и 14.1. В Digital Unix 4.0B программа sysconfig позволяет узнать или изменить множество параметров и ограничений ядра. Ниже приведен вывод этой команды, запущенной с параметром –q. Команда выводит текущие ограничения для подсистемы ipc. Некоторые строки в выводе, не имеющие отношения к средствам IPC System V, были опущены: alpha % /sbin/sysconfig –q ipc ipc: msg-max = 8192 msg-mnb = 16384 msg-mni = 64 msg-tql = 40 shm-max = 4194304 shm-min = 1 shm-mni = 128 shm-seg = 32 sem-mni = 16 sem-msl = 25 sem-opm = 10 sem-ume = 10 sem-vmx = 32767 sem-aem = 16384 num-of-sems = 60 Для этих параметров можно указать другие значения по умолчанию, изменив файл /etc/sysconfigtab. Делать это следует с помощью программы sysconfigdb. Этот файл также считывается в процессе начальной загрузки системы. 3.9. РезюмеПервым аргументом функций msgget, semget и shmget является ключ IPC System V. Эти ключи вычисляются по полному имени файла с помощью системной функции ftok. В качестве ключа можно также указать значение IPCPRIVATE. Эти три функции создают новый объект IPC или открывают существующий и возвращают идентификатор System V IPC — целое число, которое потом используется для распознавания объекта в прочих функциях, имеющих отношение к IPC. Эти идентификаторы имеют смысл не только в рамках одного процесса (как дескрипторы файлов), но и в рамках всей системы. Они могут повторно использоваться ядром, но лишь спустя некоторое время. С каждым объектом System V IPC связана структура ipc_perm, содержащая информацию о нем, такую как идентификатор пользователя владельца, идентификатор группы, разрешения чтения и записи и др. Одним из отличий между System V и Posix IPC является то, что для объекта IPC System V эта информация доступна всегда (доступ к ней можно получить с помощью одной из функций XXXctl с аргументом IPC_STAT), а в Posix IPC доступ к ней зависит от реализации. Если объект Posix IPC хранится в файловой системе и мы знаем его имя в ней, мы можем получить доступ к этой информации, используя стандартные средства файловой системы. При создании нового или открытии существующего объекта System V IPC функции getXXX передаются два флага (IPC_CREAT и IPC_EXCL) и 9 бит разрешений. Без сомнения, главнейшей проблемой в использовании System V IPC является наличие искусственных ограничений в большинстве реализаций. Ограничения накладываются на размер объектов, причем они берут свое начало от самых первых реализаций. Это означает, что для интенсивного использования средств System V IPC приложениями требуется изменение ограничений ядра, а внесение этих изменений в каждой системе осуществляется по-разному. Упражнения1. Прочитайте о функции msgctl в разделе 6.5 и измените программу в листинге 3.2 так, чтобы выводился не только идентификатор, но и поле seq структуры ipc_perm. 2. Непосредственно после выполнения программы листинга 3.2 мы запускаем программу, создающую две очереди сообщений. Предполагая, что никакие другие приложения не использовали очереди сообщений с момента загрузки системы, определите, какие значения будут возвращены функцией msgget в качестве идентификаторов очередей сообщений. 3. В разделе 3.5 было отмечено, что функции getXXX System V IPC не используют маску создания файла. Напишите тестовую программу, создающую канал FIFO (с помощью функции mkfifо, описанной в разделе 4.6) и очередь сообщений System V, указав для обоих разрешение 666 (в восьмеричном формате). Сравните разрешения для созданных объектов (FIFO и очереди сообщений). Перед запуском программы удостоверьтесь, что значение umask отлично от нуля. 4. Серверу нужно создать уникальную очередь сообщений для своих клиентов. Что предпочтительнее: использовать какое-либо постоянное имя файла (например, имя сервера) в качестве аргумента функции ftok или использовать ключ IPC_PRIVATE? 5. Измените листинг 3.1 так, чтобы выводился только ключ IPC и путь к файлу. Запустите программу find, чтобы вывести список всех файлов вашей файловой системы, и передайте вывод вашей только что созданной программе. Скольким именам файлов будет соответствовать один и тот же ключ? 6. Если в вашей системе есть программа sar (system activity reporter — информация об активности системы), запустите команду sar –m 5 6 На экран будет выведено количество операций в секунду с очередями сообщений и семафорами, замеряемыми каждые 5 секунд 6 раз. Примечания:1 Все исходные тексты, опубликованные в этой книге, вы можете найти по адресу http://www.piter.com/download. |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|