Как синхронизируется кэш инструкций x86?

Мне нравятся примеры, поэтому я написал немного самоизменяющегося кода на C ...

#include <stdio.h>
#include <sys/mman.h> // linux

int main(void) {
    unsigned char *c = mmap(NULL, 7, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|
                            MAP_ANONYMOUS, -1, 0); // get executable memory
    c[0] = 0b11000111; // mov (x86_64), immediate mode, full-sized (32 bits)
    c[1] = 0b11000000; // to register rax (000) which holds the return value
                       // according to linux x86_64 calling convention 
    c[6] = 0b11000011; // return
    for (c[2] = 0; c[2] < 30; c[2]++) { // incr immediate data after every run
        // rest of immediate data (c[3:6]) are already set to 0 by MAP_ANONYMOUS
        printf("%d ", ((int (*)(void)) c)()); // cast c to func ptr, call ptr
    }
    putchar('\n');
    return 0;
}

... который работает, по-видимому:

>>> gcc -Wall -Wextra -std=c11 -D_GNU_SOURCE -o test test.c; ./test
0 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

Но, честно говоря, я неexpect это работать на всех. Я ожидал инструкции, содержащейc[2] = 0 быть кэшированным при первом вызовеcпосле чего все последовательные звонкиc будет игнорировать повторные изменения, внесенные вc (если я каким-то образом явно не сделал кэш недействительным). К счастью, мой процессор оказался умнее этого.

Я думаю, что процессор сравнивает оперативную память (при условии,c даже находится в ОЗУ) с кешем инструкций всякий раз, когда указатель инструкций совершает скачок большого размера (как в случае вызова mmapped memory выше), и делает кэш недействительным, когда он не совпадает (все это?), но я Я надеюсь получить более точную информацию об этом. В частности, я хотел бы знать, можно ли считать такое поведение предсказуемым (за исключением каких-либо различий в оборудовании и операционной системе) и на которое можно положиться?

(Вероятно, мне следует обратиться к руководству Intel, но эта страница насчитывает тысячи страниц, и я, как правило, теряюсь в ней ...)

Ответы на вопрос(5)

Решение Вопроса

То, что вы делаете, обычно называютself-modifying code, Платформы Intel (и, вероятно, AMD) также выполняют всю работу по поддержаниюi/d cache-coherency, как указано в руководстве (Руководство 3А, Системное программирование)

11.6 SELF-MODIFYING CODE

A write to a memory location in a code segment that is currently cached in the processor causes the associated cache line (or lines) to be invalidated.

Но это утверждение действует до тех пор, пока один и тот же линейный адрес используется для изменения и извлечения, что не относится кdebuggers а такжеbinary loaders поскольку они не работают в одном и том же адресном пространстве:

Applications that include self-modifying code use the same linear address for modifying and fetching the instruction. Systems software, such as a debugger, that might possibly modify an instruction using a different linear address than that used to fetch the instruction, will execute a serializing operation, such as a CPUID instruction, before the modified instruction is executed, which will automatically resynchronize the instruction cache and prefetch queue.

Например, операция сериализации всегда запрашивается многими другими архитектурами, такими как PowerPC, где она должна выполняться явно (Руководство по E500 Core):

3.3.1.2.1 Self-Modifying Code

When a processor modifies any memory location that can contain an instruction, software must ensure that the instruction cache is made consistent with data memory and that the modifications are made visible to the instruction fetching mechanism. This must be done even if the cache is disabled or if the page is marked caching-inhibited.

Интересно отметить, что PowerPC требует выдачи инструкции синхронизации контекста, даже когда кэши отключены; Я подозреваю, что это вызывает сброс более глубоких блоков обработки данных, таких как буферы загрузки / хранения.

Код, который вы предложили, ненадежен на архитектурах безsnooping или продвинутыйcache-coherency объекты, и, следовательно, скорее всего, не сможет

Надеюсь, это поможет.

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

Я только что добрался до этой страницы в одном из моих поисков и хочу поделиться своими знаниями в этой области ядра Linux!

Ваш код выполняется должным образом, и для меня здесь нет сюрпризов. Системный протокол mmap () и протокол Cache Coherency делают это за вас. Флаги & quot; PROT_READ | PROT_WRITE | PROT_EXEC & quot; просит mmamp () правильно установить значения iTLB, dTLB для кэша L1 и TLB для кэша L2 этой физической страницы. Этот код ядра для архитектуры низкого уровня делает это по-разному в зависимости от архитектуры процессора (x86, AMD, ARM, SPARC и т. Д.). Любая ошибка в ядре может испортить вашу программу!

Это только для объяснения. Предположим, что ваша система не делает много, и нет никаких переключений процессов между & quot; a [0] = 0b01000000; & quot; и начало & quot; printf (& quot; \ n & quot;): & quot; ... Кроме того, предположим, что у вас есть 1 Кбайт iCache L1, 1 Кбайт dCache в вашем процессоре и некоторый кеш L2 в ядре. (Теперь дни это порядка нескольких МБ)

  1. mmap() sets up your virtual address space and iTLB1, dTLB1 and TLB2s.
  2. "a[0]=0b01000000;" will actually Trap(H/W magic) into kernel code and your physical address will be setup and all Processor TLBs will be loaded by the kernel. Then, You will be back into user mode and your processor will actually Load 16 bytes(H/W magic a[0] to a[3]) into L1 dCache and L2 Cache. Processor will really go into Memory again, only when you refer a[4] and and so on(Ignore the prediction loading for now!). By the time you complete "a[7]=0b11000011;", Your processor had done 2 burst READs of 16 bytes each on the eternal Bus. Still no actual WRITEs into physical memory. All WRITEs are happening within L1 dCache(H/W magic, Processor knows) and L2 cache so for and the DIRTY bit is set for the Cache-line.
  3. "a[3]++;" will have STORE Instruction in the Assembly code, but the Processor will store that only in L1 dCache&L2 and it will not go to Physical Memory.
  4. Let's come to the function call "a()". Again the processor do the Instruction Fetch from L2 Cache into L1 iCache and so on.
  5. Result of this user mode program will be the same on any Linux under any processor, due to correct implementation of low level mmap() syscall and Cache coherency protocol!
  6. If You are writing this code under any embedded processor environment without OS assistance of mmap() syscall, you will find the problem you are expecting. This is because your are not using either H/W mechanism(TLBs) or software mechanism(memory barrier instructions).

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

Это довольно просто; запись по адресу, который находится в одной из строк кэша в кэше команд, делает его недействительным из кэша команд. Нет "синхронизации" вовлечен.

Ваш ответ на вопрос