<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/css" href="http://wiki2.linuxformat.ru/skins/common/feed.css?97"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
		<id>http://wiki2.linuxformat.ru/index.php?action=history&amp;feed=atom&amp;title=LXF82%3AUnix_API</id>
		<title>LXF82:Unix API - История изменений</title>
		<link rel="self" type="application/atom+xml" href="http://wiki2.linuxformat.ru/index.php?action=history&amp;feed=atom&amp;title=LXF82%3AUnix_API"/>
		<link rel="alternate" type="text/html" href="http://wiki2.linuxformat.ru/index.php?title=LXF82:Unix_API&amp;action=history"/>
		<updated>2026-05-14T02:34:20Z</updated>
		<subtitle>История изменений этой страницы в вики</subtitle>
		<generator>MediaWiki 1.11.1</generator>

	<entry>
		<id>http://wiki2.linuxformat.ru/index.php?title=LXF82:Unix_API&amp;diff=4696&amp;oldid=prev</id>
		<title>Yaleks: Новая: {{цикл/Unix API}}  == Очереди сообщений и семафоры == ''ЧАСТЬ 3 Каналы – отнюдь не единственное средство межпр...</title>
		<link rel="alternate" type="text/html" href="http://wiki2.linuxformat.ru/index.php?title=LXF82:Unix_API&amp;diff=4696&amp;oldid=prev"/>
				<updated>2008-05-24T08:09:37Z</updated>
		
		<summary type="html">&lt;p&gt;Новая: {{цикл/Unix API}}  == Очереди сообщений и семафоры == ''ЧАСТЬ 3 Каналы – отнюдь не единственное средство межпр...&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Новая статья&lt;/b&gt;&lt;/p&gt;&lt;div&gt;{{цикл/Unix API}}&lt;br /&gt;
&lt;br /&gt;
== Очереди сообщений и семафоры ==&lt;br /&gt;
''ЧАСТЬ 3 Каналы – отнюдь не единственное средство межпроцессного взаимодействия в Unix. В этом выпуске '''Андрей Боровский''' расскажет еще о нескольких механизмах IPC: очередях сообщений, разделяемой памяти и семафорах.''&lt;br /&gt;
&lt;br /&gt;
{| align=&amp;quot;right&amp;quot;&lt;br /&gt;
 |–Я должен отметить, что основная цель компьютерных наук, –&amp;lt;br&amp;gt;&lt;br /&gt;
устранение неразберихи – так и не была выполнена&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;div align=&amp;quot;right&amp;quot;&amp;gt;Эсгар Дейкстра&amp;lt;/div&amp;gt;&lt;br /&gt;
 |}&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Мы продолжаем изучение механизмов взаимодействия между процессами в Linux. Каналы различных типов, рассмотренные в предыдущей статье, существовали в Unix практически&lt;br /&gt;
с самого начала. Позже к ним были добавлены и другие механизмы&lt;br /&gt;
межпроцессного взаимодействия. Мы остановимся на трех механизмах,&lt;br /&gt;
которые появились в Unix System V и были описаны в System V Interface&lt;br /&gt;
Definition (SVID). В настоящее время эти механизмы поддерживаются&lt;br /&gt;
почти всеми Unix-системами (очереди сообщений не поддерживаются в&lt;br /&gt;
Mac OS X 10.3 – см. список литературы 1). Интерфейсы трех механизмов SVID IPC подобны. Чтобы разные процессы могли получить доступ&lt;br /&gt;
к одному объекту системы, они должны «договориться» об идентификации этого объекта. Роль идентификатора для всех объектов System V&lt;br /&gt;
IPC выполняет ключ – уникальное число-идентификатор объекта. Чтобы&lt;br /&gt;
использовать один и тот же объект, программы должны использовать&lt;br /&gt;
один и тот же ключ. Для каждого объекта IPC предусмотрены специальные функции чтения и записи, а также управляющая функция.&lt;br /&gt;
&lt;br /&gt;
=== Сообщения ===&lt;br /&gt;
Механизм сообщений Linux похож на механизм сообщений, используемый в графических многооконных средах. Сообщения накапливаются в очередях и могут изыматься из очередей последовательно или в&lt;br /&gt;
произвольном порядке. Каждая группа процессов может создать одну&lt;br /&gt;
или несколько очередей для обмена сообщениями, а одна очередь сообщений может использоваться совместно более чем двумя процессами.&lt;br /&gt;
Сообщение определяется как «последовательность байтов, передаваемая от одного процесса другому». Система сообщений SVID обладает следующими свойствами:&lt;br /&gt;
* Возможность накопления сообщений в очереди. Приложения, использующие сообщения для обмена данными, создают свою собственную очередь сообщений, которая может (и должна) быть удалена приложением-владельцем в момент завершения его работы.&lt;br /&gt;
* Возможность произвольного выбора сообщений из очереди на основе назначенных им идентификаторов. Эта возможность позволяет организовать приоритетную обработку сообщений, а также идентифицировать сообщения, посылаемые разными приложениями, участвующими в обмене данными.&lt;br /&gt;
* Произвольная структура и размер сообщения.&lt;br /&gt;
Последний пункт требует уточнения. Максимальный размер сообщения и максимальное количество сообщений в очереди ограничены,&lt;br /&gt;
причем не существует единого для всех Unix-систем способа определить эти ограничения. В Linux максимальная длина сообщения в байтах&lt;br /&gt;
задана константой MSGMAX, определенной в фале &amp;lt;linux/msg.h&amp;gt;, а&lt;br /&gt;
максимальное число сообщений – константой MSGMNG из того же&lt;br /&gt;
файла. На платформе IA32 размер сообщения не может превышать 8&lt;br /&gt;
килобайт, а длина очереди – 16384 (16K) сообщений. Структура данных,&lt;br /&gt;
использующаяся для передачи сообщений, может быть определена&lt;br /&gt;
следующим образом:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
struct msgp&lt;br /&gt;
{&lt;br /&gt;
long mtype;&lt;br /&gt;
... // Любые другие поля&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Поле mtype является единственным обязательным полем в приведенной структуре. В этом поле хранится произвольный идентификатор&lt;br /&gt;
сообщения, который может интерпретироваться как тип передаваемых&lt;br /&gt;
данных. Кроме поля mtype, структура данных сообщения может содержать любое количество других полей любых типов.&lt;br /&gt;
&lt;br /&gt;
В качестве примера использования очередей рассмотрим совместную работу двух программ – клиента и сервера. Исходные тексты&lt;br /&gt;
вы найдете на диске (файлы msgcli.c и msgserv.c соответственно).&lt;br /&gt;
Чтобы программы могли обмениваться сообщениями, они должны&lt;br /&gt;
использовать один и тот же формат сообщений и идентификатор очереди. Эти данные, общие для клиента и сервера, удобно вынести в отдельный заголовочный файл (мы назовем его msgtypes.h). Наша структура&lt;br /&gt;
данных выглядит так:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
#define MAXLEN 512&lt;br /&gt;
struct msg_t&lt;br /&gt;
{&lt;br /&gt;
long mtype;&lt;br /&gt;
int snd_pid;&lt;br /&gt;
char body[MAXLEN];&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Помимо поля mtype, мы вводим поле snd_pid, которое будет содержать идентификатор процесса-отправителя сообщения, и поле body,&lt;br /&gt;
которое предназначено для текста сообщения. Мы могли бы определить&lt;br /&gt;
несколько разных структур для сообщений разных типов. Значение поля&lt;br /&gt;
mtype указывало бы, с какой структурой мы имеем дело. Поле mtype&lt;br /&gt;
может быть не только идентификатором типа сообщения. С его помощью можно указать, например, приоритет сообщения. Используя функцию произвольной выборки сообщений, приложение может считывать в&lt;br /&gt;
первую очередь сообщения с более высоким приоритетом.&lt;br /&gt;
&lt;br /&gt;
Кроме структуры сообщения нам следует определить ключ очереди. Для получения уникального ключа можно использовать функцию&lt;br /&gt;
ftok(3), однако руководство по работе с функциями SVID рекомендует&lt;br /&gt;
выбирать значения самостоятельно, поэтому в нашем примере мы определим ключ как константу в файле msgtypes.h:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;#define KEY 1174&amp;lt;/source&amp;gt;&lt;br /&gt;
Маловероятно, что в системе уже существует другая очередь сообщений с ключом 1174. В принципе, программа, создающая объект IPC,&lt;br /&gt;
может узнать, существует ли уже такой объект (см. ниже использование флага IPC_EXCL), однако толку от этого не много. Допустим, процесс установил, что объект с указанным идентификатором существует,&lt;br /&gt;
но что ему делать? Процесс может выбрать другой идентификатор&lt;br /&gt;
из какого-нибудь пула, однако о новом идентификаторе нужно как-то&lt;br /&gt;
оповестить другие процессы. Для оповещения можно использовать&lt;br /&gt;
именованные каналы, для которых, в свою очередь, необходим уникальный идентификатор... Уникальность идентификатора файловых&lt;br /&gt;
каналов основана на уникальности имен файловой системы (имеются в виду полные имена, начиная с корня). Функция ftok(), которую&lt;br /&gt;
мы рассмотрим ниже, тоже пытается генерировать идентификаторы,&lt;br /&gt;
основываясь на уникальности имен файловой системы. Кроме того,&lt;br /&gt;
проверка существования объекта IPC может «обмануть» процесс,&lt;br /&gt;
если существующий объект был создан предыдущим экземпляром&lt;br /&gt;
того же процесса, выгруженным из системы в результате серьезной&lt;br /&gt;
ошибки.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим теперь работу сервера. Сервер получает сообщение,&lt;br /&gt;
переданное клиентом, распечатывает сообщение на экране терминала,&lt;br /&gt;
возвращает клиенту сообщение “ОК!”, ждет подтверждения, что клиент&lt;br /&gt;
получил ответ, затем удаляет очередь и завершает работу. Программусервер следует запустить до запуска программы-клиента.&lt;br /&gt;
Текст программы (msgserv.c), как всегда, начинается с заголовочных файлов. Все типы, константы и функции, использующиеся при&lt;br /&gt;
работе с сообщениями, становятся доступны при включении в текст&lt;br /&gt;
программы файлов &amp;lt;sys/ipc.h&amp;gt; и &amp;lt;sys/msg.h&amp;gt;. Очередь сообщений&lt;br /&gt;
создается при помощи функции msgget(2):&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;msgid = msgget(KEY, 0666 | IPC_CREAT);&amp;lt;/source&amp;gt;&lt;br /&gt;
Первый параметр msgget() – ключ, гарантирующий уникальность&lt;br /&gt;
очереди. Он числовой, поэтому его можно спутать с другим числом –&lt;br /&gt;
идентификатором очереди, который присваивает система. Помните, что&lt;br /&gt;
ключ нужен только для открытия очереди, а для работы с ней используется идентификатор. Второй параметр представляет собой комбинацию маски прав доступа к создаваемой очереди (аналогичной маске прав доступа&lt;br /&gt;
к именованным каналам) и нескольких дополнительных флагов: кажется,&lt;br /&gt;
программист, писавший функции SVID IPC, сильно экономил на переменных-параметрах. Флаг IPC_CREATE указывает, что в результате вызова&lt;br /&gt;
msgget() должна быть создана новая очередь. При установке флага IPC_EXCL, функция msgget() вернет сообщение об ошибке, если очередь с указанным ключом уже существует. В случае успеха msgget() возвращает положительное значение – идентификатор созданной очереди.&lt;br /&gt;
&lt;br /&gt;
Передача и получение сообщений выполняется при помощи функций msgsnd(2) и msgrcv(2) соответственно. Первым параметром&lt;br /&gt;
обеих функций является идентификатор очереди, возвращенный функцией msgget(). Во втором параметре передается размер структуры&lt;br /&gt;
сообщения. Как было сказано выше, программа, читающая сообщения&lt;br /&gt;
из очереди, должна указать размер сообщения, соответствующий ожидаемому идентификатору, и может читать сообщения разного размера&lt;br /&gt;
в ситуации, когда программа ждет сообщений определенного типа. На&lt;br /&gt;
диске есть пример polymsgserv/polymsgcli, демонстрирующий этот&lt;br /&gt;
подход. Третьим параметром функции msgrcv() является идентификатор сообщения. Если значение этого параметра больше нуля, из очереди&lt;br /&gt;
будет извлечено сообщение с соответствующим значением поля mtype.&lt;br /&gt;
Если этот параметр равен нулю, из очереди будет извлечено первое&lt;br /&gt;
по порядку сообщение, а если параметр отрицательный, из очереди&lt;br /&gt;
будет извлечено первое сообщение, чей идентификатор меньше либо&lt;br /&gt;
равен абсолютному значению параметра. Последний параметр в функциях msgsnd() и msgrcv() позволяет задать дополнительные флаги.&lt;br /&gt;
Обычно функция, читающая сообщение из очереди, приостанавливает&lt;br /&gt;
выполнение программы до тех пор, пока извлечение сообщения не будет&lt;br /&gt;
выполнено, то есть пока в очереди не появится сообщение ожидаемого&lt;br /&gt;
типа. Именно так работает эта функция в наших программах. При указании флага IPC_NOWAIT, msgrcv() вернет сообщение об ошибке, если&lt;br /&gt;
на момент вызова в очереди отсутствует подходящее сообщение.&lt;br /&gt;
&lt;br /&gt;
В нашем примере сервер и клиент используют разные идентификаторы для посылаемых сообщений. Это сделано для того, чтобы программа, вызывающая последовательно msgsnd() и msgrcv(), не извлекала&lt;br /&gt;
из очереди свои собственные сообщения. Наш сервер записывает в&lt;br /&gt;
очередь сообщения со значением mtype, равным 1, а считывает – со&lt;br /&gt;
значением, равным 2 (у программы-клиента все наоборот).&lt;br /&gt;
&lt;br /&gt;
Для удаления очереди используется функция msgctl(2), которая,&lt;br /&gt;
как и все функции *ctl(), может выполнять множество разных действий (например, получение данных о состоянии очереди). Первый параметр этой функции, как всегда, идентификатор очереди, второй параметр – команда (IPC_STAT, IPC_SET или IPC_RMID). Третий параметр&lt;br /&gt;
используется в вызовах-запросах (то есть, когда второй параметр равен&lt;br /&gt;
IPC_STAT), а также для конфигурации очереди (команда IPC_SET). В&lt;br /&gt;
нем передается указатель на структуру msgid_ds, поля которой содержат значения различных параметров очереди. Функция возвращает статус выполнения команды. Вызов&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;msgctl(msgid, IPC_RMID, 0);&amp;lt;/source&amp;gt;&lt;br /&gt;
удаляет очередь с идентификатором msgid.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим теперь программу-клиент. Первым делом она должна&lt;br /&gt;
получить идентификатор очереди. Для этого используется функция&lt;br /&gt;
msgget() с тем же ключом очереди, что и у сервера, с маской прав&lt;br /&gt;
доступа, но без дополнительных флагов. Она возвращает или идентификатор уже существующей очереди с данным ключом или -1, если&lt;br /&gt;
очередь не существует:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
msgid = msgget(KEY, 0666);&lt;br /&gt;
if (msgid == -1)&lt;br /&gt;
{&lt;br /&gt;
printf(“Server is not running!\n”, msgid);&lt;br /&gt;
return EXIT_FAILURE;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Далее клиент считывает строку, вводимую пользователем, формирует сообщение, записывая в поле mtype значение 2, отправляет&lt;br /&gt;
сообщение и ждет ответ сервера – сообщения с идентификатором 1.&lt;br /&gt;
Скомпилируйте обе программы (можете просто скомандовать make&lt;br /&gt;
msgdemo), запустите сначала сервер, потом, в другом окне терминала,&lt;br /&gt;
клиент. Напечатайте в окне клиента строку и нажмите ввод.&lt;br /&gt;
&lt;br /&gt;
=== Разделяемая память ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=НЕАТОМАРНОСТЬ СПИН-БЛОКИРОВОК&lt;br /&gt;
|Содержание=&lt;br /&gt;
Простейший алгоритм разделения доступа (который сразу приходит в голову) можно описать схематически с помощью следующей конструкции:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
while (spin_lock == TRUE);&lt;br /&gt;
spin_lock = TRUE;&lt;br /&gt;
... // Доступ к разделяемому ресурсу&lt;br /&gt;
spin_lock = FALSE;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Проблема заключается в том, что этот алгоритм&lt;br /&gt;
простых спин-блокировок (spin locks) не только&lt;br /&gt;
прожорлив, но и не гарантирует надежного разграничения доступа. Допустим, что один процесс начал&lt;br /&gt;
проверять значение переменной spin_lock, дождался того момента, когда оно станет равно FALSE, и&lt;br /&gt;
переходит к строке&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;spin_lock = TRUE;&amp;lt;/source&amp;gt;&lt;br /&gt;
В многозадачной системе (особенно на нескольких&lt;br /&gt;
процессорах) процесс, ожидающий доступа к разделяемому ресурсу, может «вклиниться» в тот момент,&lt;br /&gt;
когда другой процесс уже проверил, но еще не изменил значение spin_lock. Второй процесс проверит&lt;br /&gt;
значение, все еще равное FALSE, и оба процесса окажутся в критической области и получат доступ к разделяемому ресурсу. Описанная проблема вызвана&lt;br /&gt;
тем, что операция «проверить значение – изменить&lt;br /&gt;
значение» неатомарна, то есть ее выполнение может&lt;br /&gt;
быть прервано другим процессом. Первый алгоритм,&lt;br /&gt;
гарантирующий синхронизацию при использовании&lt;br /&gt;
неатомарных операций, придумал Т. Деккер, а применил Э. Дейкстра в 1965 году. В 1981 году Г.Петерсон&lt;br /&gt;
предложил более простой алгоритм.&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
Спецификация SVID описывает интерфейс для работы с разделяемыми блоками памяти. Разделяемые блоки памяти представляют собой&lt;br /&gt;
область памяти, отображенную адресное пространство нескольких&lt;br /&gt;
процессов. Если один процесс запишет данные в разделяемую область,&lt;br /&gt;
другой процесс может считывать их оттуда как из собственной области&lt;br /&gt;
глобальной памяти. Мы продемонстрируем использование разделяемой памяти на уже знакомом примере клиент-сервер. Как и в случае с&lt;br /&gt;
сообщениями, нам нужно описать общие структуры данных для клиента&lt;br /&gt;
и сервера в заголовочном файле (на диске это файл shmemtypes.h):&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
#define FTOK_FILE “./shmemserv”&lt;br /&gt;
#define MAXLEN 512&lt;br /&gt;
struct memory_block&lt;br /&gt;
{&lt;br /&gt;
int server_lock;&lt;br /&gt;
int client_lock;&lt;br /&gt;
int turn;&lt;br /&gt;
int readlast;&lt;br /&gt;
char string[MAXLEN];&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Механизм разделяемой памяти не налагает никаких ограничений&lt;br /&gt;
на структуру блока памяти. Структура memory_block определенна нами,&lt;br /&gt;
исходя исключительно из наших собственных потребностей. Первые&lt;br /&gt;
четыре поля структуры memory_block – служебные, они нужны для&lt;br /&gt;
реализации модифицированного алгоритма Петерсона, о котором будет&lt;br /&gt;
сказано ниже. Последнее поле предназначено собственно для передачи данных. В нашем заголовочном файле мы не определяем ключ для&lt;br /&gt;
идентификации разделяемого блока, но указываем имя некоего файла&lt;br /&gt;
(в нашем случае – исполнимого файла сервера). Это имя будет передано функции ftok() для получения ключа. Естественно, это метод сработает только если сервер будет скомпилирован под именем shmemserv.&lt;br /&gt;
Рассмотрим исходный код инициализации сервера:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
key_t key;&lt;br /&gt;
int shmid;&lt;br /&gt;
struct memory_block * mblock;&lt;br /&gt;
key = ftok(FTOK_FILE, 1);&lt;br /&gt;
shmid = shmget(key, sizeof(struct memory_block), 0666 | IPC_CREAT);&lt;br /&gt;
mblock = (struct memory_block *) shmat(shmid, 0, 0);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Как уже отмечалось, ftok() генерирует ключ, используя в качестве «затравки» имя файла, в нашем случае – имя исполнимого файла&lt;br /&gt;
сервера. Использование имени самой программы для генерации ключа&lt;br /&gt;
до некоторой степени гарантирует уникальность ключа. Функции для&lt;br /&gt;
работы с разделяемой памятью объявлены в файлах sys/ipc.h sys/shm.h. Разделяемый блок памяти выделяется при помощи функции&lt;br /&gt;
shmget(2), которой передаются три параметра. В первом параметре передается ключ, идентифицирующий выделяемый блок памяти.&lt;br /&gt;
Второй параметр позволяет указать размер блока в байтах. В третьем&lt;br /&gt;
параметре передается маска прав доступа и флаги, аналогичные флагам msgget(). Функция shmget() возвращает идентификатор выделенного блока памяти (его не следует путать с указателем на блок).&lt;br /&gt;
Чтобы получить указатель на созданный блок разделяемой памяти, этот&lt;br /&gt;
блок нужно отобразить в локальное адресное пространство процесса.&lt;br /&gt;
Отображение блока разделяемой памяти в адресное пространство процесса выполняет функция shmat(2). У этой функции тоже три параметра. Первый параметр – идентификатор, возвращенный функцией&lt;br /&gt;
shmget(). Во втором параметре передается желательный начальный&lt;br /&gt;
адрес для отображения разделяемого блока в локальном адресном пространстве. Функция shmat() «постарается» отобразить разделяемый&lt;br /&gt;
блок в локальное пространство, начиная с указанного адреса, но успешный результат не гарантирован. Если во втором параметре shmat()&lt;br /&gt;
передать нулевое значение, функция сама выберет начальный адрес&lt;br /&gt;
области отображения. Значение желательного адреса должно быть&lt;br /&gt;
выравнено по границе страничных областей. Можно также не выравнивать адрес, но передать в третьем параметре функции флаг SHM_RND,&lt;br /&gt;
и тогда функция сама скорректирует значение адреса. Среди дополнительных флагов, которые можно передать в третьем параметре, отметим&lt;br /&gt;
флаг SHM_RDONLY, который присваивает отображаемой области статус «только для чтения». При успешном выполнении функция shmat()&lt;br /&gt;
возвращает указатель на начало области отображения, с которым мы&lt;br /&gt;
можем работать как с обычным указателем на выделенный блок памяти.&lt;br /&gt;
Чтобы понять дальнейшую работу сервера, следует иметь в виду, что&lt;br /&gt;
сами по себе объекты разделяемой памяти не предоставляют никаких&lt;br /&gt;
средств синхронизации доступа, так что нам приходится самим позаботиться об этих средствах. Для синхронизации работы клиента и сервера&lt;br /&gt;
и разграничения доступа мы используем упомянутый уже алгоритм&lt;br /&gt;
Петерсона (См. список литературы, 2), который позволяет разграничить&lt;br /&gt;
доступ к блоку разделяемой памяти, используя неатомарные операции.&lt;br /&gt;
&lt;br /&gt;
Функция shmdt(2) удаляет область отображения в локальном&lt;br /&gt;
адресном пространстве (но не удаляет блок разделяемой памяти). Блок&lt;br /&gt;
разделяемой памяти удаляется вызовом&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;shmctl(shmid, IPC_RMID, 0);&amp;lt;/source&amp;gt;&lt;br /&gt;
который и по форме, и по сути подобен приведенному выше вызову&lt;br /&gt;
msgctl(). Клиент получает доступ к блоку разделяемой памяти с помощью вызовов&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
shmid = shmget(key, sizeof(struct memory_block), 0666);&lt;br /&gt;
if (shmid == -1)&lt;br /&gt;
{&lt;br /&gt;
printf(“Server is not running!\n”);&lt;br /&gt;
return EXIT_FAILURE;&lt;br /&gt;
}&lt;br /&gt;
mblock = (struct memory_block *) shmat(shmid, 0, 0);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
При этом мы проверяем, запущен ли сервер. Далее клиент читает&lt;br /&gt;
информацию, записанную в разделяемый блок сервером, считывает&lt;br /&gt;
строку с терминала и передает строку серверу, используя механизм&lt;br /&gt;
спин-блокировок. Скомпилируйте обе программы (скомандовав make&lt;br /&gt;
shmemdemo), запустите сервер, затем клиент и набирайте строки&lt;br /&gt;
в окне клиента. Работа программ завершится, когда вы наберете q и&lt;br /&gt;
нажмете ввод. Поработав с программами, вы, конечно, обратили внимание на медлительность, с которой сервер отвечает клиенту. Кроме&lt;br /&gt;
того, вы могли заметить существенный рост потребления ресурсов процессора при работе программ. Виной всему спин-блокировки, которые&lt;br /&gt;
используются в алгоритме Петерсона. Именно из-за спин-блокировок&lt;br /&gt;
процессор проводит значительную часть времени в цикле непрерывного опроса значения переменной. Сам алгоритм Петерсона сегодня&lt;br /&gt;
можно найти только в учебниках по разработке операционных систем,&lt;br /&gt;
хотя и современные ОС его практически не используют. Вместо этого&lt;br /&gt;
ОС создают объекты синхронизации, контролирующие доступ к критическим секциям с помощью специальных атомарных операций процессора, и предоставляют пользовательским программам доступ к этим&lt;br /&gt;
объектам. Именно такими объектами являются рассмотренные далее&lt;br /&gt;
семафоры.&lt;br /&gt;
&lt;br /&gt;
=== Семафоры ===&lt;br /&gt;
Семафоры широко используются как средство синхронизации потоков&lt;br /&gt;
и процессов. В Unix-системах реализованы три типа семафоров – семафоры System V, семафоры POSIX и семафоры в разделяемой памяти.&lt;br /&gt;
Поскольку статья посвящена System V IPC, мы рассмотрим семафоры&lt;br /&gt;
System V. Подробное описание всех трех типов семафоров можно найти&lt;br /&gt;
в (См. список литературы, 3).&lt;br /&gt;
&lt;br /&gt;
Состояние семафора определяется значением некоторой внутренней переменной и переданным ему параметром. В зависимости от этих&lt;br /&gt;
значений семафор либо приостанавливает выполнение обратившегося к&lt;br /&gt;
нему потока (до тех пор, пока другой поток не переведет семафор в другое состояние), либо изменяет значение внутренней переменной, разрешив потоку дальнейшее выполнение. Следующая функция иллюстрирует логику работы семафора в зависимости от значения переменной&lt;br /&gt;
состояния (semvalue) и управляющей переменной sem_op.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
void semaphore (int sem_op)&lt;br /&gt;
{&lt;br /&gt;
static int semvalue; // Внутренняя переменная&lt;br /&gt;
if (sem_op != 0)&lt;br /&gt;
{&lt;br /&gt;
if (sem_op &amp;lt; 0) while (semvalue &amp;lt; ABS(sem_op));&lt;br /&gt;
semvalue += sem_op;&lt;br /&gt;
}&lt;br /&gt;
else while (semvalue != 0);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Отрицательное значение sem_op соответствует операции проверки доступности ресурса и вызывает приостановку потока, если доступ&lt;br /&gt;
к ресурсу заблокирован. Положительное значение сигнализирует о&lt;br /&gt;
высвобождении ресурса. Приведенная выше функция semaphore()&lt;br /&gt;
описывает поведение многозначного семафора (general semaphore).&lt;br /&gt;
Именно такие семафоры используются в System V IPC.&lt;br /&gt;
&lt;br /&gt;
Перепишем клиент и сервер из предыдущего примера, заменив&lt;br /&gt;
спин-блокировки семафорами (на диске вы найдете исходный текст&lt;br /&gt;
сервера в файле semserv.c, а исходный текст клиента – в файле&lt;br /&gt;
semcli.c. Все, что касается семафоров, определено в файле &amp;lt;sys/sem.h&amp;gt;. Программа-сервер создает семафоры с помощью вызова&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;semid = semget(key, 2, 0666|IPC_CREAT);&amp;lt;/source&amp;gt;&lt;br /&gt;
Функция semget(2) похожа на msgget() и shmget(), но у нее&lt;br /&gt;
есть дополнительный параметр – количество создаваемых семафоров.&lt;br /&gt;
Дело в том, что многим процессам, использующим семафоры, требуется более одного семафора (тогда как блоки разделяемой памяти и&lt;br /&gt;
очереди сообщений обычно существуют в единственном экземпляре).&lt;br /&gt;
Определение уникального ключа для каждого из нескольких семафоров&lt;br /&gt;
затруднительно, поэтому функция semget() позволяет несколько семафоров сразу. Нашему приложению понадобится два семафора. Нам&lt;br /&gt;
понадобилось бы три семафора, если бы... Впрочем, это уже совсем&lt;br /&gt;
другая история. Первый семафор указывает, должен ли сервер читать&lt;br /&gt;
запись, сделанную клиентом, второй – должен ли клиент читать запись,&lt;br /&gt;
сделанную сервером. Таким образом, мы приказываем semget()&lt;br /&gt;
создать сразу два семафора. В случае успешного завершения функция&lt;br /&gt;
semget() возвращает идентификатор нового массива семафоров.&lt;br /&gt;
&lt;br /&gt;
Для определения состояния семафора используется структура&lt;br /&gt;
sembuf. В ней определено много полей (конкретный набор зависит от&lt;br /&gt;
версии Unix-системы), из которых обязательными являются три:&lt;br /&gt;
* short sem_num – номер семафора (в массиве), над которым выполняется операция (нумерация начинается с нуля).&lt;br /&gt;
* short sem_op – число, изменяющее состояние семафора.&lt;br /&gt;
* short sem_flg – дополнительные флаги.&lt;br /&gt;
Как и в приведенном выше схематическом примере работы семафора, отрицательное значение sem_op соответствует операции проверки&lt;br /&gt;
доступности ресурса и вызывает приостановку потока, если ресурс&lt;br /&gt;
недоступен. Положительное значение заставляет семафор высвободить ресурс (или приблизиться к этому). Указатель на массив структур&lt;br /&gt;
sembuf (по структуре на семафор) передается как второй параметр&lt;br /&gt;
функции semop(2), которая либо изменяет состояние семафора, либо&lt;br /&gt;
приостанавливает вызывавший поток. Первый параметр этой функции –&lt;br /&gt;
идентификатор, возвращенный semget(). В третьем параметре передается число записей в массиве sembuf. Вот как, например, мы указываем, что клиент может записывать данные в разделяемую область:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
buf[1].sem_op = 1;&lt;br /&gt;
semop(semid, (struct sembuf*) &amp;amp;buf[1], 1);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
А эти строки приостановят сервер, пока клиент не изменит значение&lt;br /&gt;
первого семафора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
buf[0].sem_op = -1;&lt;br /&gt;
semop(semid, (struct sembuf*) &amp;amp;buf, 1);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Получая разрешение на доступ к разделяемой области, процесс&lt;br /&gt;
производит чтение/запись, разрешает доступ другому процессу, запрещает доступ себе и приостанавливается. Удаление семафора выполняет&lt;br /&gt;
с помощью функции semctl(), в которой, кроме прочего, нужно указывать число семафоров:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;semctl(semid, 2, IPC_RMID);&amp;lt;/source&amp;gt;&lt;br /&gt;
Скомпилируйте сервер под именем semserv, а клиент под именем&lt;br /&gt;
semcli, (или командуйте make semdemo) запустите клиент и сервер. Вы увидите, что обмен данными выполняется гораздо быстрее, а&lt;br /&gt;
процессор загружается гораздо меньше, чем в случае использования&lt;br /&gt;
спин-блокировок.&lt;br /&gt;
&lt;br /&gt;
При всем богатстве выбора средств взаимодействия между процессами в Unix/Linux, самыми популярными средствами были и остаются&lt;br /&gt;
сокеты. Ими мы и займемся в следующий раз.&lt;br /&gt;
&lt;br /&gt;
{{Врезка|center|&lt;br /&gt;
|Заголовок=РЕКОМЕНДУЕМАЯ ЛИТЕРАТУРА:&lt;br /&gt;
|Содержание=&lt;br /&gt;
# W. R. Stevens, S. A. Rago, Advanced Programming in the UNIX® Environment: Second Edition, Addison Wesley Professional, 2005&lt;br /&gt;
# Таненбаум Э. С., Вудхалл А. С., Операционные системы: разработка и реализация. – СПб.: Питер, 2005&lt;br /&gt;
# Стивенс У., UNIX: Взаимодействие процессов. – СПб.: Питер, 2003&lt;br /&gt;
|Ширина=}}&lt;/div&gt;</summary>
		<author><name>Yaleks</name></author>	</entry>

	</feed>