Так как ядро является вытесняемым, процесс, работающий в режиме ядра, может прекратить выполнение в любой момент, чтобы позволить выполняться более высокоприоритетному процессу. Это означает, что новое задание может начать выполняться в том же критическом участке, в котором выполнялось вытесненное задание. Для того чтобы предотвратить такую возможность, код, который отвечает за преемптивность ядра, использует спин-блокировки в качестве маркеров, чтобы отмечать участки "непреемптивности". Если спин-блокировка захвачена, то ядро является невытесняемым. Так как проблемы, связанные с параллелизмом, в случае SMP и преемптивного ядра одинаковы, то, если ядро уже является безопасным для SMP-обработки, такое простое дополнение позволяет также сделать ядро безопасным и при вытеснении.
Будем надеяться, что это действительно так. На самом деле возникают некоторые ситуации, в которых нет необходимости использовать спин-блокировки, но нужно запрещать преемптивность ядра. Наиболее часто ситуация такого рода возникает из-за данных, привязанных к определенным процессорам (per-processor data). Если используются данные, уникальные для каждого процессора, то может быть необязательным защищать их с помощью спин-блокировок, потому что только один процессор может получать доступ к этим данным. Если никакая спин-блокировка не захвачена и ядро является преемптивным, то появляется возможность доступа к тем же переменным для вновь запланированного задания, как показано в следующем примере.
задание А манипулирует переменной foo
задание А вытесняется
задание В планируется на выполнение
задание В манипулирует переменной foo
задание В завершается
задание А планируется на выполнение
задание А манипулирует переменной foo
Следовательно, даже для однопроцессорного компьютера к некоторой переменной может псевдопараллельно обращаться несколько процессов. В обычной ситуации для такой переменной требуется спин-блокировка (для защиты при истинном параллелизме на многопроцессорной машине). Если эта переменная связана с одним процессором, то для нее не требуется блокировка.
Для решения указанной проблемы преемптивность ядра можно запретить с помощью функции preempt_disable()
preempt_enable(). Последний вызов функции preempt_enable() разрешает преемптивность, как показано в следующем примере.preempt_disable();
/* преемптивность запрещена ... */
preempt_enable();
Счетчик преемптивности текущего процесса содержит значение, равное количеству захваченных этим процессом блокировок плюс количество вызовов функции preempt_disable()
preempt_count() возвращает значение данного счетчика. В табл. 9.9 показан полный список функций управления преемптивностью.Таблица 9.9
. Функции управления преемптивностью ядра| Функция | Описание |
|---|---|
preempt_disable() | Запретить вытеснение кода ядра |
preempt_enable() | Разрешить вытеснение кода ядра |
preempt_enable_no_resched() | Разрешить вытеснение кода ядра, но не перепланировать выполнение процесса |
preempt count() | Возвратить значение счетчика преемптивности |
Более полное решение задачи работы с данными, связанными с определенным процессором, — это получение номера процессора (который используется в качестве индекса для доступа к данным, связанным с определенным процессором) с помощью функции get_cpu()
int cpu = get_cpu();
/* работаем с данными, связанными с текущим процессором ... */
/* работа закончена, снова разрешаем вытеснение кода ядра */
put_cpu();
Барьеры и порядок выполнения