diff --git a/Initialization/linux-initialization-2.md b/Initialization/linux-initialization-2.md index 42ee841..d494313 100644 --- a/Initialization/linux-initialization-2.md +++ b/Initialization/linux-initialization-2.md @@ -1,145 +1,152 @@ Kernel initialization. Part 2. ================================================================================ -Early interrupt and exception handling +Начальная обработка прерываний и исключений -------------------------------------------------------------------------------- -In the previous [part](https://proninyaroslav.gitbooks.io/linux-insides-ru/content/Initialization/linux-initialization-1.html) we stopped before setting of early interrupt handlers. At this moment we are in the decompressed Linux kernel, we have basic [paging](https://en.wikipedia.org/wiki/Page_table) structure for early boot and our current goal is to finish early preparation before the main kernel code will start to work. +В предыдущей [части](linux-initialization-1.md) мы остановились перед настройкой начальных обработчиков прерываний. На данный момент мы находимся в распакованном ядре Linux, у нас есть базовая структура [подкачки](https://en.wikipedia.org/wiki/Page_table) для начальной загрузки, и наша текущая цель - завершить начальную подготовку до того, как основной код ядра начнёт свою работу. -We already started to do this preparation in the previous [first](https://proninyaroslav.gitbooks.io/linux-insides-ru/content/Initialization/linux-initialization-1.html) part of this [chapter](https://proninyaroslav.gitbooks.io/linux-insides-ru/content/Initialization/index.html). We continue in this part and will know more about interrupt and exception handling. +Мы уже начали эту подготовку в предыдущей [первой](linux-initialization-1.md) части этой [главы](README.md). Мы продолжим в этой части и узнаем больше об обработке прерываний и исключений. -Remember that we stopped before following loop: +Как вы можете помнить, мы остановились перед этим циклом: ```C for (i = 0; i < NUM_EXCEPTION_VECTORS; i++) set_intr_gate(i, early_idt_handler_array[i]); ``` -from the [arch/x86/kernel/head64.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/head64.c) source code file. But before we started to sort out this code, we need to know about interrupts and handlers. +из файла [arch/x86/kernel/head64.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/head64.c). Но прежде чем начать разбирать этот код, нам нужно знать о прерываниях и обработчиках. -Some theory +Некоторая теория -------------------------------------------------------------------------------- -An interrupt is an event caused by software or hardware to the CPU. For example a user have pressed a key on keyboard. On interrupt, CPU stops the current task and transfer control to the special routine which is called - [interrupt handler](https://en.wikipedia.org/wiki/Interrupt_handler). An interrupt handler handles and interrupt and transfer control back to the previously stopped task. We can split interrupts on three types: +Прерывание - это событие, вызванное программным или аппаратным обеспечением в CPU. Например, пользователь нажал клавишу на клавиатуре. Во время прерывания, CPU останавливает текущую задачу и передаёт управление специальной процедуре - [обработчику прерываний](https://en.wikipedia.org/wiki/Interrupt_handler). Обработчик прерываний обрабатывает прерывания и передаёт управление обратно к ранее остановленной задаче. Мы можем разделить прерывания на три типа: -* Software interrupts - when a software signals CPU that it needs kernel attention. These interrupts are generally used for system calls; -* Hardware interrupts - when a hardware event happens, for example button is pressed on a keyboard; -* Exceptions - interrupts generated by CPU, when the CPU detects error, for example division by zero or accessing a memory page which is not in RAM. +* Программные прерывания - когда программное обеспечение сигнализирует CPU, что ему нужно обратиться к ядру. Эти прерывания обычно используются для системных вызовов; +* Аппаратные прерывания - когда происходит аппаратное событие, например нажатие кнопки на клавиатуре; +* Исключения - прерывания, генерируемые процессором, когда CPU обнаруживает ошибку, например деление на ноль или доступ к странице памяти, которая не находится в ОЗУ. -Every interrupt and exception is assigned a unique number which called - `vector number`. `Vector number` can be any number from `0` to `255`. There is common practice to use first `32` vector numbers for exceptions, and vector numbers from `32` to `255` are used for user-defined interrupts. We can see it in the code above - `NUM_EXCEPTION_VECTORS`, which defined as: +Каждому прерыванию и исключению присваивается уникальный номер - `номер вектора`. `Номер вектора` может быть любым числом от `0` до `255`. Существует обычная практика использовать первые `32` векторных номеров для исключений, а номера от `32` до `255` для пользовательских прерываний. Мы можем видеть это в коде выше - `NUM_EXCEPTION_VECTORS`, определённый как: ```C #define NUM_EXCEPTION_VECTORS 32 ``` -CPU uses vector number as an index in the `Interrupt Descriptor Table` (we will see description of it soon). CPU catch interrupts from the [APIC](http://en.wikipedia.org/wiki/Advanced_Programmable_Interrupt_Controller) or through it's pins. Following table shows `0-31` exceptions: - -``` ----------------------------------------------------------------------------------------------- -|Vector|Mnemonic|Description |Type |Error Code|Source | ----------------------------------------------------------------------------------------------- -|0 | #DE |Divide Error |Fault|NO |DIV and IDIV | -|--------------------------------------------------------------------------------------------- -|1 | #DB |Reserved |F/T |NO | | -|--------------------------------------------------------------------------------------------- -|2 | --- |NMI |INT |NO |external NMI | -|--------------------------------------------------------------------------------------------- -|3 | #BP |Breakpoint |Trap |NO |INT 3 | -|--------------------------------------------------------------------------------------------- -|4 | #OF |Overflow |Trap |NO |INTO instruction | -|--------------------------------------------------------------------------------------------- -|5 | #BR |Bound Range Exceeded|Fault|NO |BOUND instruction | -|--------------------------------------------------------------------------------------------- -|6 | #UD |Invalid Opcode |Fault|NO |UD2 instruction | -|--------------------------------------------------------------------------------------------- -|7 | #NM |Device Not Available|Fault|NO |Floating point or [F]WAIT | -|--------------------------------------------------------------------------------------------- -|8 | #DF |Double Fault |Abort|YES |An instruction which can generate NMI | -|--------------------------------------------------------------------------------------------- -|9 | --- |Reserved |Fault|NO | | -|--------------------------------------------------------------------------------------------- -|10 | #TS |Invalid TSS |Fault|YES |Task switch or TSS access | -|--------------------------------------------------------------------------------------------- -|11 | #NP |Segment Not Present |Fault|NO |Accessing segment register | -|--------------------------------------------------------------------------------------------- -|12 | #SS |Stack-Segment Fault |Fault|YES |Stack operations | -|--------------------------------------------------------------------------------------------- -|13 | #GP |General Protection |Fault|YES |Memory reference | -|--------------------------------------------------------------------------------------------- -|14 | #PF |Page fault |Fault|YES |Memory reference | -|--------------------------------------------------------------------------------------------- -|15 | --- |Reserved | |NO | | -|--------------------------------------------------------------------------------------------- -|16 | #MF |x87 FPU fp error |Fault|NO |Floating point or [F]Wait | -|--------------------------------------------------------------------------------------------- -|17 | #AC |Alignment Check |Fault|YES |Data reference | -|--------------------------------------------------------------------------------------------- -|18 | #MC |Machine Check |Abort|NO | | -|--------------------------------------------------------------------------------------------- -|19 | #XM |SIMD fp exception |Fault|NO |SSE[2,3] instructions | -|--------------------------------------------------------------------------------------------- -|20 | #VE |Virtualization exc. |Fault|NO |EPT violations | -|--------------------------------------------------------------------------------------------- -|21-31 | --- |Reserved |INT |NO |External interrupts | ----------------------------------------------------------------------------------------------- -``` - -To react on interrupt CPU uses special structure - Interrupt Descriptor Table or IDT. IDT is an array of 8-byte descriptors like Global Descriptor Table, but IDT entries are called `gates`. CPU multiplies vector number on 8 to find index of the IDT entry. But in 64-bit mode IDT is an array of 16-byte descriptors and CPU multiplies vector number on 16 to find index of the entry in the IDT. We remember from the previous part that CPU uses special `GDTR` register to locate Global Descriptor Table, so CPU uses special register `IDTR` for Interrupt Descriptor Table and `lidt` instruction for loading base address of the table into this register. - -64-bit mode IDT entry has following structure: +CPU использует номер вектора как индекс в `таблице векторов прерываний` (мы рассмотрим её позже). Для перехвата прерываний CPU использует [APIC](http://en.wikipedia.org/wiki/Advanced_Programmable_Interrupt_Controller). В следующей таблице показаны исключения `0-31`: + +``` +------------------------------------------------------------------------------------------------------- +|Вектор|Мнемоника|Описание |Тип |Код ошибки|Источник | +------------------------------------------------------------------------------------------------------- +|0 | #DE |Деление на ноль |Ошибка |Нет |DIV и IDIV | +|------------------------------------------------------------------------------------------------------ +|1 | #DB |Зарезервировано |О/Л |Нет | | +|------------------------------------------------------------------------------------------------------ +|2 | --- |Немаск. прервания |Прерыв.|Нет |Внешние NMI | +|------------------------------------------------------------------------------------------------------ +|3 | #BP |Исключение отладки |Ловушка|Нет |INT 3 | +|------------------------------------------------------------------------------------------------------ +|4 | #OF |Переполнение |Ловушка|Нет |Иснтрукция INTO | +|------------------------------------------------------------------------------------------------------ +|5 | #BR |Вызод за границы |Ошибка |Нет |Инструкция BOUND | +|------------------------------------------------------------------------------------------------------ +|6 | #UD |Неверный опкод |Ошибка |Нет |Инструкция UD2 | +|------------------------------------------------------------------------------------------------------ +|7 | #NM |Устройство недоступно |Ошибка |Нет |Плавающая точка или [F]WAIT | +|------------------------------------------------------------------------------------------------------ +|8 | #DF |Двойная ошибка |Авария |Да |Инструкция, которую могут генерировать NMI | +|------------------------------------------------------------------------------------------------------ +|9 | --- |Зарезервировано |Ошибка |Нет | | +|------------------------------------------------------------------------------------------------------ +|10 | #TS |Неверный TSS |Ошибка |Да |Смена задачи или доступ к TSS | +|------------------------------------------------------------------------------------------------------ +|11 | #NP |Сегмент отсутствует |Ошибка |Нет |Доступ к регистру сегмента | +|------------------------------------------------------------------------------------------------------ +|12 | #SS |Ошибка сегмента стека |Ошибка |Да |Операции со стеком | +|------------------------------------------------------------------------------------------------------ +|13 | #GP |Общее нарушение защиты|Ошибка |Да |Ссылка на память | +|------------------------------------------------------------------------------------------------------ +|14 | #PF |Ошибка страницы |Ошибка |Да |Ссылка на память | +|------------------------------------------------------------------------------------------------------ +|15 | --- |Зарезервировано | |Нет | | +|------------------------------------------------------------------------------------------------------ +|16 | #MF |Ошибка x87 FPU |Ошибка |Нет |Плавающая точка или [F]WAIT | +|------------------------------------------------------------------------------------------------------ +|17 | #AC |Проверка выравнивания |Ошибка |Да |Ссылка на данные | +|------------------------------------------------------------------------------------------------------ +|18 | #MC |Проверка машины |Авария |Нет | | +|------------------------------------------------------------------------------------------------------ +|19 | #XM |Исключение SIMD |Ошибка |Нет |Инструкции SSE[2,3] | +|------------------------------------------------------------------------------------------------------ +|20 | #VE |Искл. виртуализации |Ошибка |Нет |Гипервизор | +|------------------------------------------------------------------------------------------------------ +|21-31 | --- |Зарезервировано |Прерыв.|Нет |Внешние прерывания | +------------------------------------------------------------------------------------------------------- +``` + +Исключения делятся на три типа: + +* Ошибки (Faults) - исключения, по окончании обработки которых прерванная команда повторяется; +* Ловушки (Traps) - исключения, при обработке которых CPU сохраняет состояние, следующее за командой, вызвавшей исключение; +* Аварии (Aborts) - исключения, при обработке которых CPU не сохраняет состояния и не имеет возможности вернуться к месту +исключения + +Для реагирования на прерывание CPU использует специальную структуру - таблицу векторов прерываний (Interrupt Descriptor Table, IDT). IDT является массивом 8-байтных дескрипторов, наподобие глобальной таблицы дескрипторов, но записи в IDT называются `шлюзами` (gates). CPU умножает номер вектора на 8 для того чтобы найти индекс записи IDT. Но в 64-битном режиме IDT представляет собой массив 16-байтных дескрипторов и CPU умножает номер вектора на 16. Из предыдущей части мы помним, что CPU использует специальный регистр `GDTR` для поиска глобальной таблицы дескрипторов, поэтому CPU использует специальный регистр `IDTR` для таблицы векторов прерываний и инструкцию `lidt` для загрузки базового адреса таблицы в этот регистр. + +Запись IDT в 64-битном режиме имеет следующую структуру: ``` 127 96 -------------------------------------------------------------------------------- | | -| Reserved | +| Зарезервировано | | | -------------------------------------------------------------------------------- 95 64 -------------------------------------------------------------------------------- | | -| Offset 63..32 | +| Смещение 63..32 | | | -------------------------------------------------------------------------------- 63 48 47 46 44 42 39 34 32 -------------------------------------------------------------------------------- | | | D | | | | | | | -| Offset 31..16 | P | P | 0 |Type |0 0 0 | 0 | 0 | IST | +| Смещение 31..16 | P | P | 0 |Тип |0 0 0 | 0 | 0 | IST | | | | L | | | | | | | -------------------------------------------------------------------------------- 31 16 15 0 -------------------------------------------------------------------------------- | | | -| Segment Selector | Offset 15..0 | +| Селектор сегмента | Смещение 15..0 | | | | -------------------------------------------------------------------------------- ``` -Where: +где: -* `Offset` - is offset to entry point of an interrupt handler; -* `DPL` - Descriptor Privilege Level; -* `P` - Segment Present flag; -* `Segment selector` - a code segment selector in GDT or LDT -* `IST` - provides ability to switch to a new stack for interrupts handling. +* `Смещение` - смещение к точки входа обработчика прерывания; +* `DPL` - Уровень привилегий сегмента (Descriptor Privilege Level); +* `P` - флаг присутствия сегмента; +* `Селектор сегмента` - селектор сегмента кода в GDT или LDT +* `IST` - обеспечивает возможность переключения на новый стек для обработки прерываний. -And the last `Type` field describes type of the `IDT` entry. There are three different kinds of handlers for interrupts: +И последнее поле `Тип` описывает тип записи `IDT`. Существует три различных типа обработчиков для прерываний: -* Task descriptor -* Interrupt descriptor -* Trap descriptor +* Дескриптор задачи +* Дескриптор прерывания +* Дескриптор ловушки -Interrupt and trap descriptors contain a far pointer to the entry point of the interrupt handler. Only one difference between these types is how CPU handles `IF` flag. If interrupt handler was accessed through interrupt gate, CPU clear the `IF` flag to prevent other interrupts while current interrupt handler executes. After that current interrupt handler executes, CPU sets the `IF` flag again with `iret` instruction. +Дескрипторы прерываний и ловушек содержат дальний указатель на точку входа обработчика прерываний. Различие между этими типами заключается в том, как CPU обрабатывает флаг `IF`. Если обработчик прерываний был вызван через шлюз прерывания, CPU очищает флаг `IF` чтобы предотвратить другие прерывания, пока выполняется текущий обработчик прерываний. После выполнения текущего обработчика прерываний CPU снова устанавливает флаг `IF` с помощью инструкции `iret`. -Other bits in the interrupt gate reserved and must be 0. Now let's look how CPU handles interrupts: +Остальные биты в шлюзе прерывания зарезервированы и должны быть равны 0. Теперь давайте посмотрим, как CPU обрабатывает прерывания: -* CPU save flags register, `CS`, and instruction pointer on the stack. -* If interrupt causes an error code (like `#PF` for example), CPU saves an error on the stack after instruction pointer; -* After interrupt handler executed, `iret` instruction used to return from it. +* CPU сохраняет регистр флагов, `CS`, и указатель на инструкцию в стеке. +* Если прерывание вызывает код ошибки (например, `#PF`), CPU сохраняет ошибку в стеке после указателя на инструкцию; +* После выполнения обработчика прерываний для возврата из него используется инструкция `iret`. -Now let's back to code. +Теперь вернёмся к коду. -Fill and load IDT +Заполнение и загрузка IDT -------------------------------------------------------------------------------- We stopped at the following point: @@ -220,7 +227,7 @@ As I mentioned above, we fill gate descriptor in this function. We fill three pa #define PTR_HIGH(x) ((unsigned long long)(x) >> 32) ``` -With the first `PTR_LOW` macro we get the first `2` bytes of the address, with the second `PTR_MIDDLE` we get the second `2` bytes of the address and with the third `PTR_HIGH` macro we get the last `4` bytes of the address. Next we setup the segment selector for interrupt handler, it will be our kernel code segment - `__KERNEL_CS`. In the next step we fill `Interrupt Stack Table` and `Descriptor Privilege Level` (highest privilege level) with zeros. And we set `GAT_INTERRUPT` type in the end. +With the first `PTR_LOW` macro we get the first `2` bytes of the address, with the second `PTR_MIDDLE` we get the second `2` bytes of the address and with the third `PTR_HIGH` macro we get the last `4` bytes of the address. Next we setup the segment selector for interrupt handler, it will be our kernel code segment - `__KERNEL_CS`. In the next step we fill `Interrupt Stack Table` and `Descriptor Privilege Level` (highest privilege level) with zeros. And we set `GAT_INTERRUPT` type in the end. Now we have filled IDT entry and we can call `native_write_idt_entry` function which just copies filled `IDT` entry to the `IDT`: @@ -453,7 +460,7 @@ if (next_early_pgt >= EARLY_DYNAMIC_PAGE_TABLES) { reset_early_page_tables(); goto again; } - + pud_p = (pudval_t *)early_dynamic_pgts[next_early_pgt++]; for (i = 0; i < PTRS_PER_PUD; i++) pud_p[i] = 0;