Для решения указанной проблемы преемптивность ядра можно запретить с помощью функции 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();
Барьеры и порядок выполнения
В случае, когда необходимо иметь дело с синхронизацией между разными процессорами или разными аппаратными устройствами, иногда возникает требование, чтобы чтение памяти (load) или запись в память (save) выполнялись в том же порядке, как это указано в исходном программном коде. При работе с аппаратными устройствами часто необходимо, чтобы некоторая указанная операция чтения была выполнена перед другими операциями чтения или записи. В дополнение к этому, на симметричной многопроцессорной системе может оказаться необходимым, чтобы операции записи выполнялись строго в том порядке, как это указано в исходном программном коде (обычно для того, чтобы гарантировать, что последовательные операции чтения получают данные в том же порядке). Эти проблемы усложняются тем, что как компилятор, так и процессор могут менять порядок операций чтения и записи [52] Процессоры Intel x86 никогда не переопределяют порядок операций записи, т.е. выполняют запись всегда в указанном порядке. Тем не менее другие процессоры могут нести себя и по-другому,
для повышения производительности. К счастью, все процессоры, которые переопределяют порядок операций чтения или записи предоставляют машинные инструкции, которые требуют выполнения операций чтения-записи памяти в указанном порядке. Также существует возможность дать инструкцию компилятору, что нельзя изменять порядок выполнения операций при переходе через определенную точку программы. Эти инструкции называются барьерами ( barrier ).
Рассмотрим следующий код.
а = 1;
b = 2;
На некоторых процессорах запись нового значения в область памяти, занимаемую переменной b
, может выполниться до того, как будет записано новое значение в область памяти переменной а
. Компилятор может выполнить такую перестановку статически и внести в файл объектного кода, что значение переменной b
должно быть установлено перед переменной a
. Процессор может изменить порядок выполнения динамически путем предварительной выборки и планирования выполнения внешне вроде бы независимых инструкций для повышения производительности. В большинстве случаев такая перестановка операций будет оптимальной, так как между переменными a
и b
нет никакой зависимости. Тем не менее иногда программисту все-таки виднее.
Хотя в предыдущем примере и может быть изменен порядок выполнения, ни процессор, ни компилятор никогда не будут менять порядок выполнения следующего кода, где переменные а
и b
являются глобальными.
Читать дальше