Ядро предоставляет интерфейс, который удобным способом позволяет запретить прерывания и захватить блокировку. Использовать его можно следующим образом.
spinlock_t mr_lock = SPIN_LOCK_UNLOCKED;
unsigned long flags;
spin_lock_irqsave(&mr_lock, flags);
/* критический участок ... */
spin_unlock_irqrestore(&mr_lock, flags);
Подпрограмма spin_lock_irqsave()сохраняет текущее состояние системы прерываний, запрещает прерывания и захватывает указанную блокировку. Функция spin_unlock_irqrestore(), наоборот, освобождает указанную блокировку и восстанавливает предыдущее состояние системы прерываний. Таким образом, если прерывания были запрещены, показанный код не разрешит их по ошибке. Заметим, что переменная flagsпередается по значению. Это потому, что указанные функции частично выполнены в виде макросов.
На однопроцессорной машине показанный пример только лишь запретит прерывания, чтобы предотвратить доступ обработчика прерывания к совместно используемым данным, а механизм блокировок скомпилирован не будет. Функции захвата и освобождения блокировки также соответственно запрещают и разрешают преемптивность ядра.
Что необходимо блокировать
Важно, чтобы каждая блокировка была четко связана с тем, что она блокирует. Еще более важно — это защищать данные , а не код . Несмотря на то что во всех примерах этой главы рассматриваются критические участки, в основе этих критических участков лежат данные, которые требуют защиты, а никак не код. Если блокировки просто блокируют участки кода, то такой код труднопонимаем и подвержен состояниям гонок. Необходимо ассоциировать данные с соответствующими блокировками. Например, структура struct fooблокируется с помощью блокировки foo_lock. С данной блокировкой также необходимо ассоциировать некоторые данные. Если к некоторым данным осуществляется доступ, то необходимо гарантировать, что этот доступ будет безопасным. Наиболее часто это означает, что перед тем, как осуществить манипуляции с данными, необходимо захватить соответствующую блокировку и освободить эту блокировку нужно после завершения манипуляций.
Если точно известно, что прерывания разрешены, то нет необходимости восстанавливать предыдущее состояние системы прерываний. Можно просто разрешить прерывания при освобождении блокировки. В этом случае оптимальным будет использование функций spin_lock_irq()и spin_unlock_irq().
spinlock_t mr_lock = SPIN_LOCK_UNLOCKED;
spin_lock_irq(&mr_lock) ;
/* критический участок ... */
spin_unlock_irq(&mr_lock);
Для любого участка кода очень сложно гарантировать, что прерывания всегда разрешены. В связи с этим не рекомендуется использовать функцию spinlock_irq(). Если стоит вопрос об использовании этих функций, то лучше быть точно уверенным, что прерывания запрещены, а не огорчаться, когда найдете, что прерывания разрешены не там, где нужно.
Отладка спин-блокировок
Параметр конфигурации ядра CONFIG_DEBUG_SPINLOCKвключает несколько отладочных проверок в коде спин-блокировок. Например, с этим параметром код спин-блокировок будет проверять использование неинициализированных спин-блокировок и освобождение блокировок, которые не были захваченными. При тестировании кода всегда необходимо включать отладку спин-блокировок.
Другие средства работы со спин-блокировками
Функция spin_lock_init()используется для инициализации спин-блокировок, которые были созданы динамически (переменная типа spinlock_t, к которой нет прямого доступа, а есть только указатель на нее).
Функция spin_try_lock()производит попытку захватить указанную спин-блокировку. Если блокировка находится в состоянии конфликта, то, вместо циклической проверки и ожидания на освобождение блокировки, эта функция возвращает ненулевое значение. Если блокировка была захвачена успешно, то функция возвращает нуль. Аналогично функция spin_is_locked()возвращает ненулевое значение, если блокировка в данный момент захвачена. В противном случае возвращается нуль. Эта функция никогда не захватывает блокировку [48] Использование этих функций может привести к тому, что код становится "грязным". Нет необходимости часто проверять значение спин-блокировок — код или всегда должен захватывать блокировку, или вызываться только, если блокировка захвачена. Однако существуют некоторые ситуации, когда такие функции логично использовать, поэтому эти интерфейсы и предоставляются.
.
Читать дальше