Сколько бит содержится в теге ASID TLB для процессоров Intel?И как справиться с «переполнением ASID»? - PullRequest
0 голосов
/ 15 октября 2018

Согласно некоторым учебникам по операционной системе, для более быстрого переключения контекста люди добавляют ASID для каждого процесса в поле тега TLB, поэтому нам не нужно сбрасывать весь TLB в переключателе контекста.

Iслышал, что некоторые процессоры ARM и MIPS имеют ASID в TLB.Но я не уверен, что процессоры Intel x86 имеют ASID.

Между тем, кажется, что ASID обычно имеет меньше бит (например, 8 бит), чем PID (32 бита).Итак, как система справится с «переполнением ASID», если у нас в памяти больше процессов, чем 2 ^ 8 в случае 8-битного ASID, упомянутого выше?

1 Ответ

0 голосов
/ 18 октября 2018

Intel называет ASID идентификаторами контекста процесса (PCID).На всех процессорах Intel, которые поддерживают PCID, размер PCID составляет 12 бит.Они составляют биты 11: 0 регистра CR3.По умолчанию при сбросе процессора CR4.PCIDE (бит 17 в CR4) очищается, а CR3.PCID равен нулю, и поэтому, если операционная система хочет использовать идентификаторы PCID, она должна сначала установить этот CR4.PCIDE, чтобы включить эту функцию.Запись значения PCID больше нуля допускается только в том случае, если установлен CR4.PCIDE.Тем не менее, когда установлен CR4.PCIDE, также можно записать ноль в CR3.PCID.Поэтому максимальное количество идентификаторов PCID, которые могут быть использованы одновременно, составляет 2 ^ 12 = 4096.

Я расскажу, как ядро ​​Linux распределяет идентификаторы PCID.Само ядро ​​Linux на самом деле использует термин ASID даже для процессоров Intel, и поэтому я буду использовать этот термин.

В общем, существует действительно много способов управления пространством ASID, таких как:

  • Когда требуется создать новый процесс, выделите для него выделенный ASID.Если пространство ASID было исчерпано, то откажитесь создать процесс и завершится ошибкой.Это просто и эффективно, но может серьезно ограничить число процессов.
  • Вместо того, чтобы ограничивать количество процессов доступностью ASID, когда пространство ASID исчерпано, ведите себя так, как будто ASID не поддерживаются.То есть очистить весь TLB при переключении контекста процесса для всех процессов.На практике это ужасный метод, так как вы можете в конечном итоге переключаться между отключением и включением ASID по мере создания и завершения процессов.Этот метод может привести к потенциально высокой производительности.
  • Разрешить нескольким процессам использовать один и тот же ASID.В этом случае вам необходимо соблюдать осторожность при переключении между процессами, использующими один и тот же ASID, поскольку все записи TLB, помеченные этим ASID, все еще необходимо очистить.
  • Во всех предыдущих методах каждый процесс имеетASID и, следовательно, структура данных ОС, представляющая процесс, должна иметь поле, в котором хранится ASID.Альтернативным методом является сохранение выделенных в настоящее время ASID в отдельной структуре.ASID выделяются процессам динамически в то время, когда они должны выполняться.Процессы, которые не активны, не будут иметь назначенных им ASID.Это имеет два преимущества по сравнению с предыдущими методами.Во-первых, пространство ASID используется более эффективно, поскольку в основном неактивные процессы не потребляют ASID без необходимости.Во-вторых, все выделенные в настоящее время ASID хранятся в одной и той же структуре данных, которую можно сделать достаточно маленькой, чтобы поместиться в несколько строк кэша.Таким образом, поиск новых ASID может быть выполнен эффективно.

Linux использует последний метод, и я расскажу о нем более подробно.

Linux запоминает только последние 6 ASIDиспользуется на каждом ядре.Это указывается макросом TLB_NR_DYN_ASIDS .Система создает структуру данных для каждого ядра типа tlb_state , которое определяет массив следующим образом:

struct tlb_context {
    u64 ctx_id;
    u64 tlb_gen;
};

struct tlb_state {

    .
    .
    .

    u16 next_asid;
    struct tlb_context ctxs[TLB_NR_DYN_ASIDS];
};
DECLARE_PER_CPU_SHARED_ALIGNED(struct tlb_state, cpu_tlbstate);

Тип включает в себя другие поля, но для краткости я показал только два.Linux определяет следующие пространства ASID:

  • Каноническое пространство ASID: они включают в себя ASID от 0 до 6 (TLB_NR_DYN_ASIDS).Эти значения хранятся в поле next_asid и используются в качестве индексов для массива ctxs.
  • Пространство ASID ядра (kPCID): они включают в себя ASID с 1 по 7 (TLB_NR_DYN_ASIDS + 1).Эти значения фактически хранятся в CR3.PCID.
  • Пространство ASID пользователя (uPCID): они включают в себя ASID с 2048 + 1 по 2048 + 7 (2048 + TLB_NR_DYN_ASIDS + 1).Эти значения фактически сохраняются в CR3.PCID.

Каждый процесс имеет один канонический ASID.Это значение используется самой Linux.Каждый канонический ASID связан с kPCID и uPCID, которые являются значениями, которые фактически хранятся в CR3.PCID.Причиной наличия двух ASID на процесс является поддержка изоляции таблиц страниц (PTI), которая уменьшает уязвимость Meltdown.Фактически, в PTI каждый процесс имеет два виртуальных адресных пространства, каждое имеет свой собственный ASID, но два ASID имеют фиксированную арифметическую связь, как показано выше.Таким образом, хотя процессоры Intel поддерживают 4096 ASID на ядро, Linux использует только 12 на ядро.Я доберусь до массива ctxs, просто потерпите немного.

Linux назначает ASID для процессов динамически при переключении контекста, а не при создании.Один и тот же процесс может получать разные ASID на разных ядрах, и его ASID может динамически изменяться всякий раз, когда поток этого процесса планируется запустить на ядре.Это делается в функции switch_mm_irqs_off , которая вызывается всякий раз, когда планировщик переключается с одного потока на другой на ядре, даже если два потока принадлежат одному и тому же процессу.Необходимо рассмотреть два случая:

  • Поток пользователя прервался или выполнил системный вызов.В этом случае система переключается в режим ядра для обработки прерывания или системного вызова.Поскольку пользовательский поток только что запущен, его процесс должен иметь уже назначенный ASID.Если позднее ОС решила возобновить выполнение того же потока или другого потока того же процесса, она просто продолжит использовать тот же ASID.Этот случай скучен.
  • ОС решает запланировать поток другого процесса для запуска на ядре.Таким образом, ОС должна назначить ASID процессу.Этот случай очень интересен и будет подробно обсуждаться в оставшейся части этого ответа.

В этом случае ядро ​​выполняет следующий вызов функции:

choose_new_asid(next, next_tlb_gen, &new_asid, &need_flush);

Первыйаргумент next указывает на дескриптор памяти процесса, к которому принадлежит поток, выбранный планировщиком для возобновления.Этот объект содержит много вещей.Но здесь нас интересует ctx_id, которое является 64-битным значением, уникальным для каждого существующего процесса.next_tlb_gen используется, чтобы определить, требуется ли аннулирование TLB или нет, как я вскоре расскажу.Функция возвращает new_asid, который содержит ASID, назначенный процессу, и need_flush, который говорит, требуется ли аннулирование TLB.Возвращаемый тип функции: void.

static void choose_new_asid(struct mm_struct *next, u64 next_tlb_gen,
                u16 *new_asid, bool *need_flush)
{
    u16 asid;

    if (!static_cpu_has(X86_FEATURE_PCID)) {
        *new_asid = 0;
        *need_flush = true;
        return;
    }

    if (this_cpu_read(cpu_tlbstate.invalidate_other))
        clear_asid_other();

    for (asid = 0; asid < TLB_NR_DYN_ASIDS; asid++) {
        if (this_cpu_read(cpu_tlbstate.ctxs[asid].ctx_id) !=
            next->context.ctx_id)
            continue;

        *new_asid = asid;
        *need_flush = (this_cpu_read(cpu_tlbstate.ctxs[asid].tlb_gen) <
                   next_tlb_gen);
        return;
    }

    /*
     * We don't currently own an ASID slot on this CPU.
     * Allocate a slot.
     */
    *new_asid = this_cpu_add_return(cpu_tlbstate.next_asid, 1) - 1;
    if (*new_asid >= TLB_NR_DYN_ASIDS) {
        *new_asid = 0;
        this_cpu_write(cpu_tlbstate.next_asid, 1);
    }
    *need_flush = true;
}

Логически функция работает следующим образом.Если процессор не поддерживает идентификаторы PCID, то все процессы получают значение ASID, равное нулю, и всегда требуется очистка TLB.Я пропущу проверку invalidate_other, так как она не актуальна.Затем цикл перебирает все 6 канонических ASID и использует их в качестве индексов для ctxs.Процессу с контекстным идентификатором cpu_tlbstate.ctxs[asid].ctx_id в настоящий момент назначено значение ASID asid.Таким образом, цикл проверяет, назначен ли процессу ASID.В этом случае используется тот же ASID, и need_flush обновляется на основе next_tlb_gen.Причина, по которой нам может потребоваться очистить записи TLB, связанные с ASID, даже если ASID не был переработан, связана с ленивым механизмом аннулирования TLB, который выходит за рамки вашего вопроса.

Если ни один из используемых в настоящее время ASID не был назначен процессу, то нам нужно выделить новый.Вызов this_cpu_add_return просто увеличивает значение в next_asid на 1. Это дает нам значение kPCID.Затем, когда вычитается 1, мы получаем канонический ASID.Если мы превысили максимальное каноническое значение ASID (TLB_NR_DYN_ASIDS), то мы возвращаемся к каноническому нулю ASID и записываем соответствующий kPCID (равный 1) в next_asid.Когда это происходит, это означает, что некоторому другому процессу был назначен тот же канонический ASID, и поэтому мы определенно хотим сбросить записи TLB, связанные с этим ASID, в ядре.Затем, когда choose_new_asid возвращается к switch_mm_irqs_off, массив ctxs и CR3 обновляются соответственно.Запись в CR3 заставит ядро ​​автоматически сбросить записи TLB, связанные с этим ASID.Если процесс, ASID которого был переназначен другому процессу, все еще жив, то при следующем запуске одного из его потоков ему будет назначен новый ASID на этом ядре.Весь этот процесс происходит на ядро.В противном случае, если этот процесс не работает, то в какой-то момент в будущем его ASID будет переработан.

Причина, по которой Linux использует ровно 6 ASID на ядро, состоит в том, что он имеет размер tlb_stateдостаточно маленький, чтобы поместиться в две 64-байтовые строки кэша.Как правило, в системе Linux могут существовать одновременно десятки процессов.Однако большинство из них, как правило, бездействуют.Таким образом, способ, которым Linux управляет пространством ASID, практически очень эффективен.Хотя было бы интересно увидеть экспериментальную оценку влияния значения TLB_NR_DYN_ASIDS на производительность.Но я не знаю ни о каком таком опубликованном исследовании.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...