Скачиваний:
90
Добавлен:
12.05.2015
Размер:
913.92 Кб
Скачать

18.8. Ограничения, накладываемые на очереди сообщений

Как отмечалось в разделе 16.7, на очереди сообщений часто накладываются системные ограничения. В табл. 17.2 приведены значения этих ограничений для ОС Linux. Первая колонка представляет собой традиционное имя System V для переменной ядра, хранящей это ограничение.

Таблица 17.2

Характерные значения ограничений для очередей сообщений

Имя

Описание

Значение

msgmax

Максимальное количество байтов в сообщении

8192

msgmnb

Максимальное количество байтов в очереди сообщений

16384

msgmni

Максимальное количество очередей сообщений в системе

16

В этом разделе мы хотели показать типичные значения ограничений, чтобы помочь в разработке переносимых программ. При выполнении приложений, активно использующих очереди сообщений, обычно требуется настройка этих параметров ядра с помощью команды sysctl(что описано в разделе 16.7).

18.9. Выводы по главе 18

Очереди сообщений System V просты в использовании: новая очередь создается (или существующая открывается) функцией msgget; очередь удаляется функциейmsgctlс аргументомIPC_RMID. Поместить сообщение в очередь можно, вызвав функциюmsgsnd, а считать сообщение из очереди можно с помощью функцииmsgrcv. Параметры очереди можно считать и установить с помощью функцииmsgctlс аргументамиIPC_STATиIPC_SETсоответственно.

Каждое сообщение в очереди обладает приоритетом. Одним из преимуществ очередей сообщений System V является наличие возможности считывания сообщений из очереди в произвольном порядке с использованием приоритетов.

18.10. Упражнения по главе 18

18.1. Почему на рис. 17.2 для сообщений, передаваемых серверу, используется тип 1?

18.2. Что произойдет с программой с рис. 17.2, если злоумышленник отправит на сервер множество сообщений, но не будет считывать ответы? Что в такой же ситуации произойдет с программой с рис. 17.3?

Глава 19. Семафоры System V

19.1. Введение

Семафор представляет собой простейшее средство синхронизации процессов и потоков. Мы рассмотрим семафоры System V, обслуживаемые ядром. Они могут использоваться для синхронизации как отдельных процессов, так и потоков одного процесса. Мы начнем с рассмотрения проблем синхронизации между разными процессами с помощью бинарного семафора, то есть такого, который может принимать только значения 0 и 1. Пример подобной схемы приведен на рис. 18.1.

рис. 18.1

На этом рисунке изображен бинарный семафор, хранящийся в ядре (семафор System V).

На рис. 18.1мы указали три операции, которые могут быть применены к семафорам:

  1. Создание семафора. При этом вызвавший процесс должен указать начальное значение (часто 1, но может быть и 0).

  2. Ожидание изменения значения семафора (wait). При этом производится проверка его значения и процесс блокируется, если значение оказывается равным 0, а при превышении 0 значение уменьшается на 1. Это может быть записано на псевдокоде как

while (semaphore_value == 0)

; /* wait; то есть поток или процесс блокируется */

semaphore_value--;

/* семафор разрешает выполнение операций */

Основным требованием является атомарность выполнения операций проверки значения в цикле while и последующего уменьшения значения семафора, (то есть как одной операции) по отношению к другим процессам. Это одна из причин, по которой семафоры System V были реализованы в середине 80-х годов прошлого века как часть ядра. Поскольку операции с ними выполнялись с помощью системных вызовов, легко было гарантировать их атомарность по отношению к другим процессам.

У этой операции есть несколько общеупотребительных имен. Изначально она называлась P, от голландского proben (проверка, попытка), – это название было введено Эдсгером Дейкстрой. Используются также термины down (поскольку значение семафора уменьшается) и lock, но мы будем следовать стандарту Posix и говорить об ожидании (wait).

  1. Установка значения семафора (post). Значение семафора увеличивается одной командой, которая может быть записана на псевдокоде как

semaphore_value++;

Если в системе имеются процессы, ожидающие изменения значения семафора до величины, превосходящей 0, один из них может быть пробужден. Как и операция ожидания, операция установки значения семафора также должна быть атомарной по отношению к другим процессам, работающим с этим семафором.

Для обозначения этой операции также имеется несколько общеупотребительных терминов. Изначально она называлась V, от голландского verhogen (увеличивать). Кроме того, ее называют up (значение семафора увеличивается) и unlock. Мы, следуя стандарту Posix, называем эту операцию post.

Очевидно, что реальный код для работы с семафором будет более сложным, чем приведенный выше. Все процессы, ожидающие изменения какого-либо семафора, должны помещаться в очередь, и один из них должен запускаться при выполнении требуемого условия. К счастью, это обеспечивается реализацией.

Обратите внимание, что приведенный псевдокод не ограничен в применении только бинарными семафорами. Код работает с семафором, инициализируемым любым неотрицательным значением. Такие семафоры называют также семафорами-счетчиками. Обычно они инициализируются некоторым значением N, которое указывает количество доступных ресурсов (например, буферов).

Бинарный семафор может использоваться в качестве средства исключения (подобно взаимному исключению). Ниже приведен пример для сравнения этих средств.

Взаимное исключение

инициализация взаимного исключения;

pthread_mutex_lock(&mutex);

критическая область

pthread_mutex_unlock(&mutex);

Семафор

инициализация семафора единицей;

sem_wait(&sem);

критическая область

sem_post(&sem);

Мы инициализируем семафор значением 1. Вызвав sem_wait, мы ожидаем, когда значение семафора окажется больше 0, а затем уменьшаем его на 1. Вызовsem_postувеличивает значение с 0 до 1 и возобновляет выполнение всех потоков, заблокированных в вызовеsem_waitдля данного семафора.

Хотя семафоры и могут использоваться в качестве взаимных исключений, они обладают некоторыми особенностями: взаимное исключение должно быть разблокировано именно тем потоком, который его заблокировал, в то время как увеличение значения семафора может быть выполнено другим потоком. Можно привести пример использования этой особенности для написания упрощенной версии решения задачи производителей и потребителей из главы 7с двумя бинарными семафорами. Нарис. 18.2приведена схема с одним производителем, помещающим объект в общий буфер, и одним потребителем, изымающим его оттуда. Для простоты предположим, что в буфер помещается ровно один объект.

рис. 18.2

Ниже приведен текст соответствующей программы на псевдокоде.

Производитель

инициализация семафора get значением 0;

инициализация семафора put значением 1;

for ( ; ; )

{

sem_wait(&put);

помещение данных в буфер;

sem_post(&get);

}

Потребитель

for ( ; ; )

{

sem_wait(&get);

обработка данных в буфере;

sem_post(&put);

}

Семафор putограничивает возможность помещения объекта в общий буфер, а семафорgetуправляет потребителем при считывании объекта из буфера. Работает эта программа в такой последовательности:

  1. Производитель инициализирует буфер и два семафора.

  2. После этого запускается потребитель. Он блокируется при вызове sem_wait, поскольку семафорgetимеет значение 0.

  3. Затем производитель входит в цикл. При вызове sem_waitзначениеputуменьшается с 1 до 0, после чего производитель помещает объект в буфер. Вызовомsem_postзначение семафораgetувеличивается с 0 до 1. Поскольку имеется поток, заблокированный в ожидании изменения значения этого семафора, этот поток помечается как готовый к выполнению. Предположим, тем не менее, что производитель продолжает выполняться. В этом случае он блокируется при вызовеsem_waitв начале цикла for, поскольку значение семафораput– 0. Производитель должен подождать, пока потребитель не извлечет данные из буфера.

  4. Потребитель возвращается из sem_wait, уменьшая значение семафораgetс 1 до 0. Затем он обрабатывает данные в буфере и вызываетsem_post, увеличивая значение семафораputс 0 до 1. Заблокированный в ожидании изменения значения этого семафора поток-производитель помечается как готовый к выполнению. Предположим опять, что выполнение потребителя продолжается. Тогда он блокируется при вызовеsem_waitв начале цикла for, поскольку семафорgetимеет значение 0.

  5. Производитель возвращается из sem_wait, помещает данные в буфер, и все повторяется.

Мы предполагали, что каждый раз при вызове sem_postпродолжалось выполнение потока, вызвавшего эту функцию, несмотря на то, что ожидающий изменения значения семафора поток помечался как готовый к выполнению. Никаких изменений в работе программы не произойдет, если вместо потока, вызвавшегоsem_post, будет выполняться другой поток, также ожидавший изменения состояния семафора.

Перечислим три главных отличия семафоров и взаимных исключений в паре с условными переменными:

  1. Взаимное исключение всегда должно разблокироваться тем потоком, который установил блокировку, тогда как увеличение значения семафора не обязательно осуществляется потоком, ожидающим изменения его значения.

  2. Взаимное исключение может быть либо заблокировано, либо разблокировано (пара состояний, аналогично бинарному семафору).

  3. Поскольку состояние семафора хранится в определенной переменной, изменение его значения оказывает влияние на процессы, которые вызовут функцию sem_waitуже после этого изменения, тогда как при отправке сигнала по условной переменной в отсутствие ожидающих его потоков сигнал будет утерян. Представьте, что при первом проходе цикла потребитель еще не вызвалsem_wait. Производитель сможет поместить объект в буфер, вызватьsem_postдля семафораget(увеличив тем самым его значение с 0 до 1), а затем он заблокируется в вызовеsem_waitдля семафораput. Через некоторое время потребитель дойдет до цикла for и вызоветsem_waitдля семафораget, что уменьшит значение этого семафора с 1 до 0, а затем потребитель приступит к обработке содержимого буфера.

Несмотря на предпочтительность применения семафоров для синхронизации совместных действий процессов, а взаимных исключений – потоков, и те и другие могут использоваться в обоих случаях. Следует пользоваться тем набором средств, который удобен в данном приложении.

Для семафоров обоих типов (бинарных и счетчиков) операция waitсостоит в ожидании изменения значения семафора с нулевого на ненулевое и последующем уменьшении этого значения на количество единиц запрашиваемых ресурсов. Операцияpostувеличивает значение семафора на количество единиц освобождаемых ресурсов, оповещая об этом все процессы, ожидающие изменения значения семафора.

Для семафоров System V определен еще один уровень сложности, а именно: набор семафоров-счетчиков – один или несколько семафоров, каждый из которых является счетчиком. На количество семафоров в наборе существует ограничение (обычно порядка 25 – раздел 18.7). Когда мы говорим о семафоре System V, мы подразумеваем именно набор семафоров-счетчиков.

Для каждого набора семафоров ядро хранит следующую информационную структуру, определенную в заголовочном файле <sys/sem.h>, основные поля которой суть следующие:

struct semid_ds

{

struct ipc_perm sem_perm; /* разрешения на операции */

unsigned long int sem_nsems; /* количество семафоров в наборе */

time_t sem_otime; /* время последнего вызова semop() */

time_t sem_ctime; /* время последнего изменения semctl(IPC_SET) */

};

Структура ipc_permбыла описана в разделе 16.3; она содержит разрешения доступа к данному семафору.

Каждый отдельный семафор представляет собой внутреннюю структуру данных ядра, используемую для хранения набора атрибутов семафора. Каждый элемент набора семафоров описывается следующим образом:

struct sem

{

unsigned short semval; /* значение семафора, неотрицательно */

short sempid; /* идентификатор последнего процесса, вызвавшего

semop(), semctl(SETVAL), semctl(SETALL) */

unsigned short semncnt; /* количество процессов (потоков), ожидающих того, что

значение семафора увеличится */

unsigned short semzcnt; /* количество процессов (потоков), ожидающих того, что

значение семафора станет равным нулю */

};

Помимо текущих значений всех семафоров набора в ядре хранятся еще три атрибута семафора: идентификатор процесса, изменившего значение семафора последним, количество процессов, ожидающих увеличения значения семафора, и количество процессов, ожидающих того, что значение семафора станет нулевым.

Любой конкретный набор семафоров в ядре мы можем воспринимать как структуру semid_ds, имеющую в своем распоряжении указатель на массив структурsem. Отдельные семафоры из набора нумеруются, начиная с 0. Если в наборе два элемента, мы получим картину, изображенную нарис. 18.3. На этом рисунке полеsem_nsemsструктурыsemid_dsимеет значение 2, а каждый из элементов набора имеет номер 0 и 1 соответственно.

рис. 18.3

Соседние файлы в папке Chapter.4