Чтение онлайн

ЖАНРЫ

Разработка ядра Linux (Второе издание)
Шрифт:

Последняя полезная функция семафоров — это то, что они позволяют иметь любое количество потоков, которые одновременно удерживают семафор. В то время как спин-блокировки позволяют удерживать блокировку только одному заданию в любой момент времени, количество заданий, которым разрешено одновременно удерживать семафор, может быть задано при декларации семафора. Это значение называется счетчиком использования (usage count) или просто счетчиком (count). Наиболее часто встречается ситуация, когда разрешенное количество потоков, которые одновременно могут удерживать семафор, равно одному, как и для спин-блокировок. В таком случае счетчик использования равен единице и семафоры называются бинарными семафорами (binary semaphore) (потому что он может удерживаться только одним заданием или совсем никем не удерживаться) или взаимоисключающими блокировками (mutex, мьютекс) (потому

что он гарантирует взаимоисключающий доступ — mutual exclusion). Кроме того, счетчику при инициализации может быть присвоено значение, большее единицы. В этом случае семафор называется счетным семафором (counting semaphore, семафор-счетчик), и он допускает количество потоков, которые одновременно удерживают блокировку, не большее чем значение счетчика использования. Семафоры-счетчики не используются для обеспечения взаимоисключающего доступа, так как они позволяют нескольким потокам выполнения одновременно находиться в критическом участке. Вместо этого они используются для установки лимитов в определенном коде. В ядре они используются мало. Если вы используете семафор, то, скорее всего, вы используете взаимоисключающую блокировку (семафор со счетчиком, равным единице).

Семафоры были формализованы Эдсгером Вайбом Дейкстрой [50] (Edsger Wybe Dijkstra) в 1968 году как обобщенный механизм блокировок. Семафор поддерживает две атомарные операции

P
и
V
, название которых происходит от голландских слов Proben (тестировать) и Verhogen (выполнить инкремент). Позже эти операции начали называть
down
и
up
соответственно.

В операционной системе Linux они имеют такое же название. Операция

down
используется для того, чтобы захватить семафор путем уменьшения его счетчика на единицу. Если значение этого счетчика больше или равно нулю, то блокировка захватывается успешно и задание может входить в критический участок. Если значение счетчика меньше нуля, то задание помещается в очередь ожидания и процессор переходит к выполнению каких-либо других операций. Об использовании этой функции говорят в форме глагола— семафор опускается (down) для того, чтобы его захватить. Метод
up
используется для того, чтобы освободить семафор после завершения выполнения критического участка. Эту операцию называют поднятием (upping) семафора.

50

Доктор Дейкстра (1930–2002 г.) один из самых талантливых ученых за всю (конечно, не очень долгую) историю существования вычислительной техники как области науки. Его многочисленные труды включают работы но проектированию операционных систем и по теории алгоритмов, сюда же входит концепция семафоров. Он родился в городе Роттердам, Нидерланды, и преподавал в университете штата Техас в течение 15 лет. Тем не менее, он был бы не очень доволен большим количеством директив

GOTO
в ядре Linux.

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

Создание и инициализация семафоров

Реализация семафоров зависит от аппаратной платформы и определена в файле

<asm/semaphore.h>
. Структура
struct semaphore
представляет объекты типа семафор. Статическое определение семафоров выполняется следующим образом.

static DECLARE_SEMAPHORE_GENERIC(name, count);

где

name
— имя переменной семафора, a
count
— счетчик семафора. Более короткая запись для создания взаимоисключающей блокировки (mutex), которая используются наиболее часто, имеет следующий вид.

static DECLARE_MUTEX(name);

где

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

sema_init(sem, count);

где

sem
— это указатель, a
count
— счетчик использования семафора. Аналогично для инициализации динамически создаваемой взаимоисключающей блокировки можно использовать функцию

init_MUTEX(sem);

Неизвестно, почему слово "mutex" в имени функции

init_MUTEX
выделено большими буквами и почему слово "init" идет перед ним, в то время как имя функции
sema_init
таких особенностей не имеет. Тем не менее ясно, что это выглядит не логично, и я приношу свои извинения за это несоответствие. Надеюсь, что после прочтения главы 7 ни у кого уже не будет вызывать удивление то, какие имена придумывают символам ядра.

Использование

семафоров

Функция

down_interruptible
выполняет попытку захватить данный семафор. Если эта попытка неудачна, то задание переводится в состояние ожидания с флагом
TASK_INTERRUPTIBLE
. Из материала главы 3 следует вспомнить, что такое состояние процесса означает, что задание может быть возвращено к выполнению с помощью сигнала и что такая возможность обычно очень ценная. Если сигнал приходит в тот момент, когда задание ожидает на освобождение семафора, то задание возвращается к выполнению, а функция
down_interruptible
возвращает значение
– EINTR
. Альтернативой рассмотренной функции выступает функция
down
, которая переводит задание в состояние ожидания с флагом
TASK_UNINTERRUPTIBLE
. В большинстве случаев это нежелательно, так как процесс, который ожидает на освобождение семафора, не будет отвечать на сигналы. Поэтому функция
down_interruptible
используется значительно более широко, чем функция
down
. Да, имена этих функций, конечно, далеки от идеала.

Функция

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

Для освобождения захваченного семафора необходимо вызвать функцию

up
. Рассмотрим следующий пример.

/* объявление и описание семафора с именем mr_sem и

первоначальным значением счетчика, равным 1 */

static DECLARE_MUTEX(mr_sem);

...

if (down_interruptible(&mr_sem))

 /* получен сигнал и семафор не захвачен */

/* критический участок ... */

/* освободить семафор */

up(&mr_sem);

Полный список функций работы с семафорами приведен в табл. 9.5.

Таблица 9.5. Список функций работы с семафорами

Функция Описание
sema_init(struct semaphore*, int)
Инициализация динамически созданного семафора и установка для него указанного значения счетчика использования
init_MUTEX(struct semaphore*)
Инициализация динамически созданного семафора и установка его счетчика использования в значение 1
init_MUTEX_LOCKED (struct semaphore*)
Инициализация динамически созданного семафора и установка его счетчика использования в значение 0 (т.е. семафор изначально заблокирован)
down_interruptible(struct semaphore *)
Выполнить попытку захватить семафор и перейти в прерываемое состояние ожидания, если семафор находится в состоянии конфликта при захвате (contended)
down(struct semaphore*)
Выполнить попытку захватить семафор и перейти в непрерываемое состояние ожидания, если семафор находится в состоянии конфликта при захвате (contended)
down_trylock(struct semaphore*)
Выполнить попытку захватить семафор и немедленно возвратить ненулевое значение, если семафор находится в состоянии конфликта при захвате (contended)
up(struct semaphore*)
Освободить указанный семафор и возвратить к выполнению ожидающее задание, если такое есть

Семафоры чтения-записи

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

Семафоры чтения-записи представляются с помощью структуры

struct rw_semaphore
, которая определена в файле
<asm/rwsem.h>
. Статически определенный семафор чтения-записи может быть создан с помощью функции

static DECLARE_RWSEM(name);

где

name
— это имя нового семафора.

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

init_rwsem(struct rw_semaphore *sem);

Все семафоры чтения-записи являются взаимоисключающими (mutex), т.е. их счетчик использования равен единице. Любое количество потоков чтения может одновременно удерживать блокировку чтения, если при этом нет ни одного потока записи. И наоборот, только один поток записи может удерживать блокировку, захваченную на запись, если нет ни одного потока чтения. Все семафоры чтения-записи используют непрерываемое состояние ожидания, поэтому существует только одна версия функции

down.
Рассмотрим следующий пример.

Поделиться с друзьями: