Разработка ядра Linux - Роберт Лав
Шрифт:
Интервал:
Закладка:
Спин-блокировки могут использоваться в обработчиках прерываний (семафоры не могут использоваться, поскольку они переводят процесс в состояние ожидания). Если блокировка используется в обработчике прерывания, то перед тем, как захватить эту блокировку (в другом месте — не в обработчике прерывания), необходимо запретить все локальные прерывания (запросы на прерывания на данном процессоре). В противном случае может возникнуть такая ситуация, что обработчик прерывания прерывает выполнение кода ядра, Который уже удерживает данную блокировку, и обработчик прерывания также пытается захватить эту же блокировку. Обработчик прерывания постоянно проверяет (spin), не освободилась ли блокировка. С другой стороны, код ядра, который удерживает блокировку, не будет выполняться, пока обработчик прерывания не закончит выполнение. Это пример взаимоблокировки (двойной захват), который обсуждался в предыдущей главе. Следует заметить, что прерывания необходимо запрещать только на текущем процессоре. Если прерывание возникает на другом процессоре (по отношению к коду ядра, захватившего блокировку) и обработчик будет ожидать на освобождение блокировки, то это не приведет к тому, что код ядра, который захватил блокировку, не сможет никогда ее освободить.
Ядро предоставляет интерфейс, который удобным способом позволяет запретить прерывания и захватить блокировку. Использовать его можно следующим образом.
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].
В табл. 9.3 приведен полный список функций работы со спин-блокировками.
Таблица 9.3. Список функций работы со спин-блокировками
Функция Описание spin_lock() Захватить указанную блокировку spin_lock_irq() Запретить прерывания на локальном процессоре и захватить указанную блокировку spin_lock_irqsave() Сохранить текущее состояние системы прерываний, запретить прерывания на локальном процессоре и захватить указанную блокировку spin_unlock() Освободить указанную блокировку spin_unlock_irq() Освободить указанную блокировку и разрешить прерывания на локальном процессоре spin_unlock_irqrestore() Освободить указанную блокировку и восстановить состояние системы прерываний на локальном процессоре в указанное первоначальное значение spin_lock_init() Инициализировать объект типа spinlock_t в заданной области памяти spin_trylock() Выполнить попытку захвата указанной блокировки и в случае неудачи возвратить ненулевое значение spin_is_locked() Возвратить ненулевое значение, если указанная блокировка в данный момент захвачена, и нулевое значение в противном случаеСпин-блокировки и обработчики нижних половин
Как было указано в главе 7, "Обработка нижних половин и отложенные действия", при использовании блокировок в работе с обработчиками нижних половин необходимо принимать некоторые меры предосторожности. Функция spin_lock_bh() позволяет захватить указанную блокировку и запретить все обработчики нижних половин. Функция spin_unlock_bh() выполняет обратные действия.
Обработчик нижних половин может вытеснять код, который выполняется в контексте процесса, поэтому, если данные совместно используются обработчиком нижней половины и контекстом процесса, в контексте процесса эти данные необходимо защищать путем запрещения обработки нижних половин и захвата блокировки. Аналогично, поскольку обработчик прерывания может вытеснить обработчик нижней половины, необходимо запрещать прерывания и захватывать блокировку.
Вспомним, что два тасклета (tasklet) одного типа не могут выполняться параллельно. Поэтому нет необходимости защищать данные, которые используются только тасклетами одного типа.
Если данные используются тасклетами разных типов, то необходимо использовать обычную спин-блокировку перед тем, как обращаться к таким данным в обработчике нижней половины. В этом случае нет необходимости запрещать обработку нижних половин, так как тасклет никогда не вытесняет другой тасклет, выполняющийся на том же процессоре.