Роберт Лав - Разработка ядра Linux
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(). Рассмотрим следующий пример.
static DECLARE_RWSEM(mr_rwsem);
/* попытка захватить семафор для чтения */
down_read(&mr_rwsem);
/* критический участок (только чтение) ... */
/* освобождаем семафор */
up_read(&mr_rwsem);
/* ... * /
/* попытка захватить семафор на запись */
down_write(&mr_rwsem);
/* освобождаем семафор */
/* критический участок (чтение и запись) ... */
up write(&mr_rwsem);
Для семафоров есть реализации функций down_read_trylock() и down_write_trylock(). Каждая из них принимает один параметр — указатель на семафор чтения-записи. Обе функции возвращают ненулевое значение, если блокировка захвачена успешно, и нуль, если блокировка находится в состоянии конфликта. Следует быть внимательными — поведение этих функций противоположно поведению аналогичных функций для обычных семафоров, причем без всякой на то причины!
Семафоры чтения-записи имеют уникальную функцию, аналога которой нет для спин-блокировок чтения-записи. Это функция downgrade_writer(), которая автоматически превращает блокировку, захваченную на запись, в блокировку, захваченную на чтение.
Семафоры чтения-записи, так же как и спин-блокировки аналогичного типа, должны использоваться, только если есть четкое разделение между участками кода, которые осуществляют чтение, и участками кода, которые осуществляют запись. Использование механизмов блокировок чтения-записи приводит к дополнительным затратам, поэтому их стоит использовать, только если код можно четко разделить на участки чтения и записи.
Сравнение спин-блокировок и семафоров
Понимание того, когда использовать спин-блокировки, а когда семафоры является важным для написания оптимального кода. Однако во многих случаях выбирать очень просто. В контексте прерывания могут использоваться только спин-блокировки, и только семафор может удерживаться процессом, который находится в состоянии ожидания. В табл. 9.6 показан обзор требований того, какой тип блокировок использовать.
Таблица 9.6. Что следует использовать: семафоры или спин-блокировки
Требование Рекомендуемый тип блокировки Блокировка с малыми накладными затратами (low overhead) Спин-блокировки более предпочтительны Малое время удержания блокировки Спин-блокировки более предпочтительны Длительное время удержания блокировки Семафоры более предпочтительны Необходимо использовать блокировку в контексте прерывания Необходима спин-блокировка Необходимо переходить в состояние ожидания (steep) при захваченной блокировке Необходимо использовать семафорыУсловные переменные
Условные переменные (conditional variable, completion variable) — простое средство синхронизации между двумя заданиями, которые работают в режиме ядра, когда необходимо, чтобы одно задание послало сигнал другому о том, что произошло некоторое событие. При этом одно задание ожидает на условной переменной, пока другое задание не выполнит некоторую работу. Когда другое задание завершит выполнение своей работы, оно использует условную переменную для того, чтобы возвратить к выполнению все ожидающие на ней задания. Если это кажется похожим на работу семафора, то именно так оно и есть, идея та же. В действительности, условные переменные просто обеспечивают простое решение проблемы, для которой в других ситуациях используются семафоры. Например, в системном вызове vfork() условная переменная используется для возврата к выполнению родительского процесса при завершении порожденного.