Linux Kernel (Ядро линукса) (часть 1)


Барьеры оптимизации и барьеры памяти

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

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

Барьер оптимизации гарантирует, что ассемблерные инструкции, соответствующие операторам языка С, помещенным до примитива, не будут перемешаны компилятором с инструкциями, соответствующими операторам языка С, поставленным после примитива. В Linux макрос barrier о, который расширяется в asm volatile (ffff::: "memory"), действует как барьер оптимизации. Инструкция asm заставляет компилятор вставить ассемблерный фрагмент (в данном случае пустой). Ключевое слово volatile запрещает компилятору переставлять инструкцию asm по отношению к другим инструкциям программы. Ключевое слово memory вынуждает компилятор предполагать, что все ячейки памяти были изменены ассемблерными инструкциями, и он поэтому не может оптимизировать код, используя содержимое ячеек, сохраненное в регистрах процессора до выполнения инструкции asm. Обратите внимание: барьер оптимизации не гарантирует, что ассемблерные инструкции не будут "перетасованы" процессором, это задача барьера памяти.

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

В процессорах 80x86 следующие ассемблерные инструкции обеспечивают "сериализацию", поскольку они действуют как барьеры памяти:

- все инструкции, работающие с портами ввода/вывода;
- все инструкции, имеющие байт lock в качестве префикса
- все инструкции, которые пишут в управляющие регистры, системные регистры и регистры отладки (например, инструкции cli и sti, изменяющие состояние флага if в регистре efiags);
- ассемблерные инструкции lfence, sfence и mfence, появившиеся в микропроцессорах Pentium 4 для эффективной реализации барьеров чтения памяти, записи в память и чтения/записи соответственно;
- несколько специальных ассемблерных инструкций, в том числе инструкция iret, завершающая обработчик прерывания или исключения.
Эти примитивы работают и как барьеры оптимизации, поскольку они должны не позволить компилятору перемещать ассемблерные инструкции вокруг барьера. Барьеры "чтения памяти" действуют только на инструкции, читающие данные из памяти, а барьеры "записи в память" влияют только на инструкции, пишущие в память. Барьеры памяти могут быть полезны как в однопроцессорных, так и в многопроцессорных системах. Примитивы smp_xxx применяются, когда барьер памяти должен предотвратить состояния гонки, возникающие только в многопроцессорных системах; в однопроцессорных системах эти инструкции эффекта не имеют. Остальные барьеры памяти используются для предотвращения конфликтов одновременного обращения, возникающих как в однопроцессорных, так и в многопроцессорных системах.

Реализация барьеров памяти зависит от архитектуры системы. В архитектуре 80x86 макрос mb о обычно развертывается в оператор asm volatile!"ifепсе"), если процессор поддерживает ассемблерную инструкцию lfence, ИЛИ В asm volatile("lock;addl $0,0 (esp)":::"memory") в противном случае. Оператор asm вставляет ассемблерный фрагмент в код, сгенерированный компилятором, и действует как барьер оптимизации. Ассемблерная инструкция lock; addl $0,0(esp) прибавляет ноль к верхнему элементу стека. Сама по себе она бесполезна, но префикс lock превращает ее в барьер памяти для процессора.

Макрос wmb , в принципе, проще, поскольку расширяется в макрос barrier о. Это сделано, потому что существующие микропроцессоры Intel не переупорядочивают операции записи в память, и, следовательно, нет необходимости вставлять в код сериализующую ассемблерную инструкцию. Однако этот макрос не позволяет компилятору перетасовывать инструкции.

Заметим, что в многопроцессорных системах все атомарные операции, описанные ранее в этой главе, работают барьерами памяти, поскольку имеют префиксный байт lock.

Предыдущая страница | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | Следующая страница




Возможно, Вас также заинтересует:

ОС Knoppix - это Linux без проблем

ВведениеЕсли вы цените свое время, умеете считать деньги и знаете стоимость информации, то эта книга для вас. А так как к книге прилагается компакт- диск с готовой к работе операционной системой Knoppix Live CD, то лишь достаточно вставить его в привод и перегрузить компьютер,...

Linux Kernel (Ядро линукса) (часть 1)

Спин-блокировкаСпин-блокировка необходима в многопроцессорной системе, потому что могут возникнуть другие прерывания того же типа, и другие процессоры могут приступить к их обработке. Без спин-блокировки к главному дескриптору прерывания могли бы обратиться сразу несколько процессоров. Как мы...

Linux Kernel (Ядро линукса) (часть 2)

Копирование при записи В системах Unix первых поколений создание процесса было реализовано довольно неуклюже: получив системный вызов fork о, ядро в буквальном смысле дублировало все адресное пространство родителя и присваивало копию процессу-потомку. Такая операция...

Linux Kernel (Ядро линукса) (часть 3)

Буферы блоков и головы буферовУ каждого буфера есть дескриптор голова буфера, имеющий тип buffer head. Этот дескриптор содержит всю информацию, необходимую ядру для работы с блоком, так что перед обработкой блока ядро обязательно проверяет голову...