Как Linux ядро ​​flush_write_buffers () работает на x86? - PullRequest
4 голосов
/ 30 января 2020

Следующий код взят из include/asm-i386/io.h и вызывается из dma_map_single(). Насколько я понимаю, flush_write_buffers() должен очистить кэш памяти процессора sh перед отображением памяти для DMA. Но как эта сборка кода flu sh кэш процессора?

static inline void flush_write_buffers(void)
{
    __asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory");
}

Ответы [ 2 ]

5 голосов
/ 30 января 2020

В процессорах Intel Pentium Pro была ошибка, из-за которой хранилище в ячейке памяти типа U C могло быть переупорядочено с более ранними обращениями к памяти в ячейках типа W C, что нарушает модель целостности памяти x86. В качестве обходного пути можно использовать правильно реализованную инструкцию сериализации памяти непосредственно перед сохранением U C. На процессорах Pentium Pro любое следующее будет работать: (1) cpuid, (2) загрузка U C или (3) инструкция с префиксом lock.

flush_write_buffers в ядре Linux использует инструкцию с префиксом lock именно для этой цели. cpuid является самым дорогим и ненужным для этой цели. Загрузка AU C требует места в памяти типа U C, что в целом немного неудобно. Следовательно, выбор использования инструкции с префиксом lock.

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

Эта ошибка затрагивает только Pentium Pro, и ядро ​​нужно было скомпилировать с CONFIG_X86_PPRO_FENCE, чтобы разрешить обходной путь. Было трудно, однако, быть уверенным, что обходной путь используется во всех местах ядра, где он должен использоваться. Более того, CONFIG_X86_PPRO_FENCE влияет не только на работу flush_write_buffers, но и на другие конструкции, поэтому может привести к значительному снижению производительности. В конце концов, оно было удалено из ядра, начиная с v4.16-rc7.

4 голосов
/ 30 января 2020

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

Ограждение действует как барьер с эффектом очистки буферов ЦП (примечание: буферы, не кеш, это другое дело) потому что данные, которые ожидали записи, должны быть сразу доступны глобально, прежде чем продолжить, чтобы гарантировать, что последовательные инструкции будут извлекать правильные данные.

Эта функция была введена для Обойти проблему с оборудованием в старом семействе процессоров Intel, а именно в Pentium Pro (1995-98), из-за которого операции доступа к памяти при определенных условиях c выполнялись в неправильном порядке.

В настоящее время каноническим способом применения ограждения в x86 является использование mfence, lfence или sfence инструкции (в зависимости от типа необходимого забора), но они были добавлены только позже (с SSE и SSE2). На Pentium Pro таких инструкций не было.

Инструкция lock на самом деле является просто префиксом инструкции, так что:

lock
addl $0,0(%esp)

На самом деле "заблокирован add" .

Префикс lock используется для кодов операций, которые выполняют операцию чтения-изменения-записи, чтобы сделать их атомами c. При применении lock add $0, 0(%esp) для того, чтобы инструкция была атомарной c и, следовательно, чтобы результат был сразу виден глобально, неявно применяется ограничение load + store. Верхняя часть стека всегда читаема и доступна для записи, а добавление 0 - это запрет, поэтому нет необходимости передавать действительный адрес функции. Следовательно, этот обходной путь позволяет правильно сериализовать доступ к памяти, и это самый быстрый тип инструкций для достижения sh цели на Intel Pentium Pro.


См. Также эти другие сообщения:

...