Циклическая блокировка и семафоры

Когда два или более процесса требуют специального доступа к разделяемому ресурсу, они вынуждены устроить состязание за управление данным блоком кода. Базовой формой блокировки в Linux является циклическая блокировка.
Циклическая блокировка (spinlock) получила свое имя благодаря тому факту, что она выполняется циклически или крутится (spin), ожидая наступления блока. Благодаря такой работе циклической блокировки желательно не вставлять в циклически блокируемый код никаких повторных блокировок. Иначе может произойти ступор системы.
Перед применением циклической блокировки структура spin_lock должна быть инициализирована. Это делается с помощью вызова spin_lock_iriit ():
include/linux/spinlock.h
63 #define spin_lock_init(х) \
54 do { \
55 (x)->magic = SPINLOCK_MAGIC,- \
€6 (x)->lock = 0; \
57 (x)->babble = 5; \
68 (x)->module = FILE ; \
69 (x)->owner = NULL; \
70 (x)->oline = 0; \
71 } while (0)

Этот блок кода устанавливает spin_lock в «разблокированное» состояние или в 0 в строке 66 и инициализирует другие переменные структуры. Здесь мы коснемся переменной (х) ->1оск.
После инициализации spin_lock ее можно получить с помощью spin_lock() или spin_lock_irqsave (). Функция spin_lock_irqsave () отключает прерывания перед блокировкой, a spin_lock () - нет. Если вы используете spin_lock (), процесс может быть прерван в заблокированном разделе кода.
Для освобождения spin_lock после выполнения критической секции кода вам нужно вызвать spin_unlock() или spin_unlock_irqrestore(); spin_unlock_irqrestore () восстанавливает состояние регистров прерывания до уровня, в котором они были до вызова spin_lock_irq ().
Давайте рассмотрим вызовы spin_lock_irqsave () и spin_unlock_ irqre-store ().
flags) \

include/linux/spinlock.h
258 #define spin_lock_irqsave(lock,
259 do { \
260 local_irq_save(flags); \
261 preempt_disable(); \
262 _raw_spin_lock_flags(lock, flags); \
263 } while (0)
321 #define spin_unlock_irqrestore(lock, flags) \
322 do { \

323 _raw_spin_unlock(lock); \
324 local_irg_restore(flags); \
325 preempt_enable(); \
326 } while (0)
Обратите внимание, как во время блокировки отключается приоритетное прерывание обслуживания. После этого можно быть уверенным, что операции в критической секции не будут прерваны. Флаг IRQ сохраняется в строке 260 и восстанавливается в строке 324.
Недостатком циклической блокировки является бесполезный цикл, ожидающий снятия блокировки. Ее лучше использовать для критических секций кода, которые не требуют много времени на выполнение. Для долго выполняющихся блоков кода лучше использовать другой механизм блокировки в Linux - семафор.
Семафоры отличаются от циклической блокировки тем, что при возникновении конкурентного обращения к ресурсу задача засыпает вместо того, чтобы быть в бесконечном ожидании. Достоинством семафора является безопасность блокировки; они безопасны при выполнении на SMP и при возникновении прерываний.
include/asm-i3 8 6/semaphore.h
44 struct semaphore {
45 atomic_t count;
46 int sleepers;
47 wait_queue_head_t wait;
48 #ifdef WAITQUEUE_DEBUG
49 long magic;
50 tendif
51 };
include/asm-ppc/semaphore.h
24 struct semaphore {
25 /*
26 * Обратите внимание, что отрицательное значение счетчика
27 * эквивалентно 0, а также дополнительно означает, что процесс
28 * (процессы) должен спать или ожидать.
29 */
3 0 atomic_t count;
31 wait_queue_head_t wait;
32 #ifdef WAITQUEUE_DEBUG
33 long magic;
34 #endif
35 };

Реализации на обеих архитектурах предоставляет указатель на wait_queue и счетчик. Счетчик хранит количество процессов, которые может хранить семафор в каждый из промежутков времени. Применяя семафор, мы можем иметь несколько процессов, одновременно вошедших в код критической секции. Если счетчик инициализирован в 1, только один процесс может войти в код критической секции; семафор со счетчиком, равным 1, называется мьютексом (mutex).
Семафоры инициализируются с помощью sema_init (), а их блокировка и разблокирование производятся с помощью вызовов down () и up () соответственно. Помимо этого, существует down_interruptible (), которая возвращает 0, если семафор получен, и EINTR, если процесс был прерван при блокировке.
Когда процесс вызывает down () или down_interrruptible (), поле счетчика в семафоре уменьшается. Если значение поля меньше 0, вызывающий down () процесс блокируется и добавляется в wait_queue семафора. Если поле больше либо равно 0, процесс продолжает свою работу.
После выполнения кода критической секции процесс должен вызвать up () для сообщения семафору о завершении работы с критической секцией. С помощью вызова up () процесс увеличивает значение поля count в семафоре и, если счетчик больше или равен О, пробуждает процесс, ожидающий в wait_queue семафора.