Главная » Ядро Linux » Средства синхронизации в ядре – ЧАСТЬ 2

0

Иногда  бывает так,  что  вашему  коду  необходимо нечто  большее,  например операция  чтения всегда  выполняется  перед  ожидающей операцией записи.  Это называется  не атомарностью, а порядком выполнения (ordering). Атомарность  гарантирует, что инструкции выполняются  не прерываясь  и что они либо выполняются  полностью,  либо не выполняются  совсем.  Порядок  выполнения  же гарантирует, что две или более инструкций, даже если они выполняются  разными потоками  или разными  процессами, всегда  выполняются  в нужном  порядке.

Атомарные  операции, которые  обсуждаются в этом  разделе,  гарантируют только атомарность. Порядок  выполнения  гарантируется с  помощью  операций   барьеров  (barrier), которые  будут рассмотрены дальше в текущей  главе.

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

Битовые атомарные операции

В дополнение к атомарным операциям с  целыми числами, ядро  также  предоставляет  семейство функций,  которые  позволяют работать   на  уровне  отдельных битов. Не удивительно,  что  эти  операции зависят  от  аппаратной платформы и  определены в  файле   <asm/bitops.h> .

Тем  не  менее  может  вызвать  удивление то,  что  функции,  которые реализуют битовые  операции,  работают  с  обычными адресами памяти. Аргументами функций  являются  указатель  и  номер  бита.  Бит  0 — это  наименее значащий бит  числа, которое находится  по  указанному адресу.  На  32-разрядных машинах бит  31 — это  наиболее значащий бит, а бит 0 — наименее значащий бит машинного слова.  Нет  ограничений на значение номера  бита, которое передается в функцию, хотя  большинство пользователей  работают  с машинными словами и номерами битов  от 0 до  31  (или  до 63 для

64-битовых  машин).

Так  как  функции работают  с  обычными указателями, то в этом  случае  нет  аналога типу  atomic_t , который используется для  операций с целыми числами. Вместо  этого можно  использовать указатель  на  любые  данные.  Рассмотрим следующий пример.

unsigned long word = 0;

set_bit(0,&word);    /* атомарно устанавливается бит 0 */ set_bit(l, &word);     /* атомарно устанавливается бит 1 */ printk("%ul\n", word);  /* будет напечатано "З" */ clear_bit(1, &word);   /* атомарно очищается бит 1 */

change_bit(0, &word); /* атомарно изменяется значение бита 1,

теперь он очищен */

/* атомарно устанавливается бит нуль и возвращается предыдущее значение этого бита (нуль) */

if (test_and_set_bit(0, &word)) {

/* условие никогда не выполнится … */

}

Список  стандартных атомарных битовых  операций  приведен в табл.  9.2.

Для  удобства  работы   также   предоставляются  неатомарные  версии   всех  битовых операций. Эти  операции работают  так же, как  и их атомарные аналоги, но  они  не гарантируют атомарности выполнения операций,  и имена  этих  функций начинаются с двух символов подчеркивания. Например,  неатомарная форма  функции test_bi t () будет  иметь  имя   __test_bi t  () .  Если  нет  необходимости  в  том,  чтобы  операции были  атомарными, например, когда  данные уже  защищены с помощью блокировки, неатомарные  операции могут  выполняться быстрее.

Таблица 9.2. Список стандартных атомарных битовых операций

Атомарная битовая операция                                 Описание

void set_bit (int nr, void *addr)             Атомарно установить n r -й бит в области памяти, которая начинается с адреса add r

void clear_bit (int nr, void *addr)          Атомарно очистить n r -й бит в области памяти, которая начинается с адреса add r

void change_bit (in t nr, void *addr)         Атомарно изменить значение n r -го бита в области памяти, которая начинается с адреса addr , на инвертированное

in t test_and_set_bit(in t nr ,  voi d *addr)         Атомарно установить значение nr-г о бита в области памяти, которая начинается с адреса  addr ,

и возвратить предыдущее значение этого бита

in t test_and_clear_bit (int nr, void *addr)    Атомарно очистить значение nr -го бита в области памяти, которая начинается с адреса addr , и  возвратить предыдущее значение этого бита

in t test_and_change_bit (in t nr, void *addr)   Атомарно изменить значение nr -го бита в области памяти, которая начинается с адреса addr , на инвертированное и возвратить предыдущее значение этого бита

int test_bit (int nr, void *addr)            Атомарно возвратить значение n r -го бита в области памяти, которая начинается с адреса add r

Функция                                            Описание

spin_iock( )                         Захватить указанную блокировку

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)  одного  типа  не  могут  выполняться параллельно.  Поэтому нет необходимости защищать данные, которые  используются только тасклетами одного  типа.

2   Использовани е этих функций может привест и к тому,  что код становится "грязным".  Не т необходимост и часто  проверят ь значени е спин-блокировок код  или  всегда долже н захватывать  блокировку,  или  вызыватьс я только ,  если  блокировка захвачена.   Однак о существуют  некоторы е ситуации , когда такие  функции логичн о использовать, поэтому  эти  интерфейс ы и предоставляются.

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

В случае  отложенных прерываний  (softirq), независимо от  того, это  отложенные прерывания  одного   типа  или  разных, данные,  совместно используемые обработчиками отложенных прерываний,  необходимо защищать с  помощью блокировки. Вспомним,  что  обработчики отложенных прерываний,  даже  одного  типа, могут  выполняться одновременно на  разных  процессорах системы. Обработчик отложенного прерывания никогда не  вытесняет другие  обработчики отложенных прерываний,  которые  выполняются на  одном  процессоре с ним, поэтому запрещать обработку нижних  половин в этом  случае  не  нужно.

Источник: Лав,  Роберт. Разработка ядра  Linux, 2-е  издание. : Пер.  с англ.  — М.  : ООО  «И.Д.  Вильяме» 2006. — 448 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии