Linux Device Drivers 3 / Драйверы устройств Линукс (третье издание): 5.5. Spinlocks

Оглавление
Английский оригинал Перевод на русский

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

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

hardorsoft 06.12.10 в 04:23

0

Спин-блокировки - простая концепция. Спин-блокировка является взаимным исключением устройства, которое может иметь только два значения: "заблокировано" и "разблокировано". Это обычно реализуется как один бит в целом числе. Код, желающий забрать какую-либо определённую блокировку, проверяет соответствующий бит. Если блокировка доступна, "блокирующий" бит устанавливается и этот код продолжает работу в критической секции. Вместо этого, если блокировка уже захвачена кем-то другим, код переходит в короткий цикл, где постоянно проверяет блокировку, пока она не станет доступна. Этот цикл является "спин" частью спин-блокировки.

hardorsoft 06.12.10 в 04:23

0

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

hardorsoft 06.12.10 в 04:23

0

Спин-блокировки, в силу их особенности, предназначены для использования на многопроцессорных системах, хотя однопроцессорная рабочая станция, работающая на вытесняющем ядре, ведёт себя в отношении конкуренции как SMP. Если бы невытесняющая однопроцессорная система когда-нибудь вошла в блокировке в цикл, она бы осталась там навсегда; никакой другой поток никогда не смог бы получить процессор для снятия блокировки. По этой причине операции спин-блокировки на однопроцессорных системах без разрешённого вытеснения оптимизированы, чтобы ничего не делать, за исключением изменения статуса маскировки прерываний. Из-за вытеснения, даже если вы никогда не ожидаете работы своего кода на SMP системах, вам всё равно придётся осуществлять корректную блокировку.

hardorsoft 06.12.10 в 04:23

0

5.5.1. Знакомство с API спин-блокировки

hardorsoft 06.12.10 в 04:24

0

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

hardorsoft 06.12.10 в 04:24

0

spinlock_t my_lock = SPIN_LOCK_UNLOCKED;

hardorsoft 06.12.10 в 04:25

0

или во время работы:

hardorsoft 06.12.10 в 04:25

0

void spin_lock_init(spinlock_t *lock);

hardorsoft 06.12.10 в 04:25

0

Перед входом в критическую секцию ваш код должен получить необходимую блокировку:

hardorsoft 06.12.10 в 04:25

0

void spin_lock(spinlock_t *lock);

hardorsoft 06.12.10 в 04:26

0

Заметим, что все ожидания в спин-блокировках непрерываемы по своей природе. После вызова spin_lock вы будете крутиться в цикле, пока блокировка не станет доступной.

hardorsoft 06.12.10 в 04:26

0

Для освобождения полученной блокировки передайте её в:

hardorsoft 06.12.10 в 04:26

0

<code>void spin_unlock(spinlock_t *lock);</code>

hardorsoft 06.12.10 в 04:27

0

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

hardorsoft 06.12.10 в 04:27

0

<b>5.3.2. Спин-блокировки и контекст атомарности</b>

hardorsoft 06.12.10 в 04:29

0

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

hardorsoft 06.12.10 в 04:29

0

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

hardorsoft 06.12.10 в 04:29

0

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

hardorsoft 06.12.10 в 04:30

0

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

hardorsoft 06.12.10 в 04:30

0

Вот ещё один сценарий: ваш драйвер исполняется и только что забрал блокировку, которая контролирует доступ к его устройству. Пока удерживается блокировка, устройство вызывает прерывание, которая запускает ваш обработчик прерываний. Обработчик прерываний перед обращением к устройству также должен получить блокировку. Получение спин-блокировки в обработчике прерывания является законной вещью; это является одной из причин того, что операции спин-блокировки не засыпают. Но что произойдёт, если подпрограмма прерывания выполняется тем же процессором, что и код, который до этого забрал спин-блокировку? Пока обработчик прерываний вращается в цикле, непрерываемый код не будет иметь возможность запуститься для снятия блокировки. Этот процессор зациклится навсегда.

hardorsoft 06.12.10 в 04:30

0

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

hardorsoft 06.12.10 в 04:30

0

Последнее важное правило для использования спин-блокировок: спин-блокировки всегда должны удерживаться минимально возможное время. Чем дольше вы держите блокировку, тем больше другой процессор может вращаться в ожидании, пока вы освободите её, и вероятность получения цикла увеличивается. Длительное время удержания блокировки также отключает текущий процессор от планировщика задач, это означает, что процесс с более высоким приоритетом, который действительно должен иметь возможность получить процессор, возможно, вынужден ждать. Разработчики ядра приложили много усилий на сокращение латентности ядра (времени, которое процессу придётся ждать до переключения на него) во время разработки версий 2.5. Плохо написанный драйвер может уничтожить все эти достижения просто слишком долго удерживая блокировку. Чтобы избежать подобной проблемы, считайте обязательным для себя удерживать блокировку минимальное время.

hardorsoft 06.12.10 в 04:31

0

<b>5.5.3. Функции спин-блокировки</b>

hardorsoft 06.12.10 в 04:32

0

Мы уже видели две функции, spin_lock и spin_unlock, которые манипулируют спин-блокировками. Однако, есть несколько других функций с похожими именами и назначением. Сейчас мы представим полный набор. Эта обсуждение даст нам основу для ещё нескольких глав, пока мы не будем в состоянии должным образом полностью разобраться в вопросе; полное понимание API спин-блокировки требует понимания обработки прерываний и связанных с ним понятий.

hardorsoft 06.12.10 в 06:24

0

Фактически, есть четыре функции, которые могут блокировать спин-блокировку:

hardorsoft 06.12.10 в 06:25

0

void spin_lock(spinlock_t *lock);
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
void spin_lock_irq(spinlock_t *lock);
void spin_lock_bh(spinlock_t *lock);

hardorsoft 06.12.10 в 06:26

0

Мы уже видели, как работает spin_lock. spin_lock_irqsave запрещает прерывания (только на местном процессоре) до снятия блокировки; предыдущее состояние прерывания запоминается во флагах. Если вы абсолютно уверены, что никто другой не запрещал прерывания на вашем процессоре (или, другими словами, вы уверены, что должны разрешить прерывания, когда освободите вашу спин-блокировку), вы можете использовать взамен spin_lock_irq и сохранять флаги. Наконец, spin_lock_bh перед получением блокировки запрещает программные прерывания, но оставляет включёнными аппаратные прерывания.

hardorsoft 06.12.10 в 06:26

0

Если у вас есть спин-блокировка, которая может быть получена кодом, который работает в контексте (аппаратного или программного) прерывания, необходимо использовать одну из форм spin_lock, которая запрещает прерывания. Если поступить иначе, рано или поздно в системе случится взаимоблокировка. Если вы не обращаетесь к вашей блокировке в обработчике аппаратного прерывания, а делаете это с помощью программных прерываний (например, в коде, который запускается тасклетами, тема рассматривается в Главе 7), вы можете  использовать spin_lock_bh для безопасного избегания взаимоблокировок, разрешая в то же время обслуживание аппаратных прерываний.

hardorsoft 06.12.10 в 06:26

0

Есть также четыре способа освободить блокировку; он должен соответствовать функции, которую вы использовали для получения блокировки:

hardorsoft 06.12.10 в 06:27

0

void spin_unlock(spinlock_t *lock);
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);

hardorsoft 06.12.10 в 06:27

0

Каждый вариант spin_unlock отменяет работу, выполненную соответствующей функцией spin_lock. Аргумент flags, передаваемый в spin_unlock_irqrestore, должен быть той же переменной, переданной в spin_lock_irqsave. Вы должны также вызвать spin_lock_irqsave и spin_unlock_irqrestore в той же самой функции; в противном случае, на некоторых архитектурах ваш код может сломаться.

hardorsoft 06.12.10 в 06:28

0

Существует также набор неблокирующих операций спин-блокировки:

hardorsoft 06.12.10 в 06:28

0

int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);

hardorsoft 06.12.10 в 06:28

0

Эти функции возвращают ненулевое значение при успехе (блокировка была получена), иначе - 0. Не существует версии "попробовать" ("try"), которая запрещает прерывания.

hardorsoft 06.12.10 в 06:28

0

5.5.4. Чтение/Запись спин-блокировок

hardorsoft 06.12.10 в 06:29

0

Ядро предоставляет формы для чтения/записи спин-блокировок, которые полностью аналогичны чтению/записи семафоров, рассмотренному ранее в этой главе. Эти блокировки разрешают в критической секции одновременно любое количество читателей, но писатели должны иметь эксклюзивный доступ. Блокировки чтения/записи имеют тип rwlock_t, определённый в <linux></linux>spinlock.h>. Они могут быть объявлены и проинициализированы двумя способами:

hardorsoft 06.12.10 в 06:29

0

rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* Статический способ */
rwlock_t my_rwlock;
rwlock_init(&my_rwlock); /* Динамический способ */

hardorsoft 06.12.10 в 06:51

0

Список доступных функций должен выглядеть похожим на уже знакомые функции. Для читателей доступны следующие функции:

hardorsoft 06.12.10 в 06:52

0

void read_lock(rwlock_t *lock);
void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
void read_lock_irq(rwlock_t *lock);
void read_lock_bh(rwlock_t *lock);
void read_unlock(rwlock_t *lock);
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void read_unlock_irq(rwlock_t *lock);
void read_unlock_bh(rwlock_t *lock);

hardorsoft 06.12.10 в 06:52

0

Интересно, что нет read_trylock.

hardorsoft 06.12.10 в 06:52

0

Функции для записи аналогичны:

hardorsoft 06.12.10 в 06:53

0

void write_lock(rwlock_t *lock);
void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
void write_lock_irq(rwlock_t *lock);
void write_lock_bh(rwlock_t *lock);
int write_trylock(rwlock_t *lock);
void write_unlock(rwlock_t *lock);
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void write_unlock_irq(rwlock_t *lock);
void write_unlock_bh(rwlock_t *lock);

hardorsoft 06.12.10 в 06:54

0

Блокировки чтения/записи могут оставить читателей голодными, так же как и rwsem-ы. Такое поведение является редкой проблемой; однако, если есть достаточно спорящих за блокировку, чтобы вызвать зависание, производительность в любом случае плохая.

hardorsoft 06.12.10 в 06:54

0
Минутку...