Обновление linux-initialization-2.md

pull/709/head
proninyaroslav 6 years ago
parent fdcd176d61
commit 6dc7698d6d

@ -194,7 +194,7 @@ static void set_intr_gate(unsigned int n, const void *addr)
}
```
Прежде всего он проверяет, что переданный номер прерывания не больше чем `255` с помощью макроса `BUG_ON`. Нам нужно сделать эту проверку, поскольку максимально возможное количество прерываний - `256`. Далее мы устаналиваем данные IDT заданными значениями. И уже после этого мы вызываем функцию `idt_setup_from_table`:
Прежде всего она проверяет, что переданный номер прерывания не больше чем `255` с помощью макроса `BUG_ON`. Нам нужно сделать эту проверку, поскольку максимально возможное количество прерываний - `256`. Далее мы устаналиваем данные IDT заданными значениями. И уже после этого мы вызываем функцию `idt_setup_from_table`:
```C
static void
@ -249,21 +249,26 @@ asm volatile("lidt %0"::"m" (*dtr));
Как говорилось ранее, мы заполнили `IDT` адресом `early_idt_handler_array`. Мы можем найти его в [arch/x86/kernel/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/head_64.S):
```assembly
.globl early_idt_handler_array
early_idt_handlers:
ENTRY(early_idt_handler_array)
i = 0
.rept NUM_EXCEPTION_VECTORS
.if (EXCEPTION_ERRCODE_MASK >> i) & 1
pushq $0
.if ((EXCEPTION_ERRCODE_MASK >> i) & 1) == 0
UNWIND_HINT_IRET_REGS
pushq $0 # Dummy error code, to make stack frame uniform
.else
UNWIND_HINT_IRET_REGS offset=8
.endif
pushq $i
pushq $i # 72(%rsp) Vector number
jmp early_idt_handler_common
UNWIND_HINT_IRET_REGS
i = i + 1
.fill early_idt_handler_array + i*EARLY_IDT_HANDLER_SIZE - ., 1, 0xcc
.endr
UNWIND_HINT_IRET_REGS offset=16
END(early_idt_handler_array)
```
Здесь мы видим создание обработчиков прерываний для первых `32` исключений. Мы проверяем, содержит ли исключение код ошибки, и ничего не делаем, если исключение не возвращает код ошибки, тогда мы помещаем в стек ноль. Мы делаем это для того чтобы стек был однородным. После этого мы помещаем номер исключения в стек и переходим на `early_idt_handler_array`, который является общим обработчиком прерываний на данный момент. Каждый девятый байт массива `early_idt_handler_array` состоит из необязательного кода ошибок, `номера вектора` и инструкции перехода. Мы можем видеть это в выводе утилиты `objdump`:
Функции, которые, как правило, не вызываются напрямую другими функциями, такие как `syscall` и обработчики прерываний, часто имеют необычные вещи вроде функции не C-типа с указателем стека. Такой код необходимо аннотировать с помощью макроса `UNWIND_HINT_IRET_REGS`, чтобы `objtool` смог его понять. Здесь мы видим создание обработчиков прерываний для первых `32` исключений. Мы проверяем, содержит ли исключение код ошибки, и ничего не делаем, если исключение не возвращает код ошибки, тогда мы помещаем в стек ноль. Мы делаем это для того чтобы стек был однородным. После этого мы помещаем номер исключения в стек и переходим на `early_idt_handler_array`, который является общим обработчиком прерываний на данный момент. Каждый девятый байт массива `early_idt_handler_array` состоит из необязательного кода ошибок, `номера вектора` и инструкции перехода. Мы можем видеть это в выводе утилиты `objdump`:
```
$ objdump -D vmlinux
@ -291,52 +296,42 @@ ffffffff81fe5014: 6a 02 pushq $0x2
| %rflags |
| %cs |
| %rip |
| rsp --> код ошибки |
| код ошибки | <-- %rsp
|--------------------|
```
Давайте посмотрим на реализацию `early_idt_handler_common`. Он находится в том же ассемблерном файле [arch/x86/kernel/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/head_64.S#L343) и первое что мы можем видеть это проверка [NMI](http://en.wikipedia.org/wiki/Non-maskable_interrupt). Нам не нужно обрабатывать их, поэтому просто игнорируем их в коде:
Давайте посмотрим на реализацию `early_idt_handler_common`. Он находится в том же ассемблерном файле [arch/x86/kernel/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/head_64.S) и прежде всего инкрементирует `early_recursion_flag`, чтобы предотвратить рекурсию в `early_idt_handler_common`:
```assembly
cmpl $2,(%rsp)
je .Lis_nmi
incl early_recursion_flag(%rip)
```
где `is_nmi`:
Далее мы сохраняем регистры общего назначения в стек:
```assembly
is_nmi:
addq $16,%rsp
INTERRUPT_RETURN
```
удаляет код ошибки и номер вектора из стека и вызывает макрос `INTERRUPT_RETURN`, который раскрывается до инструкции `iretq`. После проверки номера вектора (и это не `NMI`), мы проверяем `early_recursion_flag`, чтобы предотвратить рекурсию в `early_idt_handler_common`, и если он корректен, сохраняем регистры общего назначения в стек:
```assembly
pushq %rax
pushq %rcx
pushq %rdx
pushq %rsi
pushq %rdi
movq 8(%rsp), %rsi
movq %rdi, 8(%rsp)
pushq %rdx
pushq %rcx
pushq %rax
pushq %r8
pushq %r9
pushq %r10
pushq %r11
pushq %rbx
pushq %rbp
pushq %r12
pushq %r13
pushq %r14
pushq %r15
UNWIND_HINT_REGS
```
Мы должны сделать это, чтобы предотвратить появление неверных значений регистров при возврате из обработчика прерываний. После этого мы проверяем селектор сегмента в стеке:
Мы должны сделать это, чтобы предотвратить появление неверных значений регистров при возврате из обработчика прерываний. После этого мы проверяем номер вектора, и если он `#PF` или [ошибка страницы (Page Fault)](https://en.wikipedia.org/wiki/Page_fault), мы помещаем значение регистра `cr2` в регистр `rdi` и вызываем `early_make_pgtable` (мы скоро это увидим):
```assembly
cmpl $__KERNEL_CS,96(%rsp)
jne 11f
```
который должен быть равен сегменту кода ядра, и если нет, мы переходим к метке `11`, которая печатает сообщение `PANIC` и выводит дамп стека.
После проверки сегмента кода мы проверяем номер вектора, и если это `#PF` или [ошибка страницы (Page Fault)](https://en.wikipedia.org/wiki/Page_fault), мы помещаем значение `cr2` в регистр `rdi` и вызываем `early_make_pgtable` (мы скоро это увидим):
```assembly
cmpl $14,72(%rsp)
cmpq $14,%rsi
jnz 10f
GET_CR2_INTO(%rdi)
call early_make_pgtable
@ -344,21 +339,23 @@ is_nmi:
jz 20f
```
Если номер вектора не равен `#PF`, мы восстанавливаем регистры общего назначения из стека:
Если номер вектора не равен `#PF`, мы вызываем функцию `early_fixup_exception` с передачей указателя ядра (смотрите [соглашение о вызовах x86-64](https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions)):
```assembly
popq %r11
popq %r10
popq %r9
popq %r8
popq %rdi
popq %rsi
popq %rdx
popq %rcx
popq %rax
10:
movq %rsp,%rdi
call early_fixup_exception
```
и выходим из обработчика с помощью `iret`.
Мы увидим реализацию функции `early_fixup_exception` позже.
```assembly
20:
decl early_recursion_flag(%rip)
jmp restore_regs_and_return_to_kernel
```
После того как мы уменьшим значение параметра `initial_recursion_flag`, мы восстанавливаем регистры из стека, которые мы сохранили ранее, и возвращаемся из обработчика с помощью `iretq`.
Это конец первого обработчика прерываний. Обратите внимание, что это очень ранний обработчик прерываний, поэтому он обрабатывает только ошибку страницы. Мы увидим обработчики и для других прерываний, но пока давайте посмотрим на обработчик ошибки страницы.
@ -367,16 +364,30 @@ is_nmi:
В предыдущем разделе мы увидели первый начальный обработчик прерываний, который проверяет, что номер прерывания относится к ошибке страницы и вызывает `early_make_pgtable` для создания новых таблиц страниц. На данном этапе нам необходим обработчик `#PF`, поскольку планируется добавить способность загружать ядро выше `4G` и сделать структуру `boot_params` доступной над 4G.
Вы можете найти реализацию `early_make_pgtable` в [arch/x86/kernel/head64.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/head64.c) и он принимает только один параметр - адрес из регистра `cr2`, который вызывал ошибку страницы. Давайте посмотрим на неё более подробно:
Вы можете найти реализацию `early_make_pgtable` в [arch/x86/kernel/head64.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/head64.c) и он принимает только один параметр - адрес из регистра `cr2`, который вызывал ошибку страницы. Давайте посмотрим на неё более подробно:
```C
int __init early_make_pgtable(unsigned long address)
{
unsigned long physaddr = address - __PAGE_OFFSET;
unsigned long i;
pmdval_t pmd;
pmd = (physaddr & PMD_MASK) + early_pmd_flags;
return __early_make_pgtable(address, pmd);
}
```
Затем мы вызываем функцию `__early_make_pgtable`, которая определена в том же файле, что и функция `early_make_pgtable`, как показано ниже:
```C
int __init __early_make_pgtable(unsigned long address, pmdval_t pmd)
{
unsigned long physaddr = address - __PAGE_OFFSET;
pgdval_t pgd, *pgd_p;
p4dval_t p4d, *p4d_p;
pudval_t pud, *pud_p;
pmdval_t pmd, *pmd_p;
pmdval_t *pmd_p;
...
...
...
@ -389,7 +400,7 @@ int __init early_make_pgtable(unsigned long address)
typedef unsigned long pgdval_t;
```
Также мы будем работать с типами `*_t`, например `pgd_t` и т.д. Все эти типы определены в [arch/x86/include/asm/pgtable_types.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/pgtable_types.h) и представляют собой структуры:
Также мы будем работать с типами `*_t`, например `pgd_t` и т.д. Все эти типы определены в [arch/x86/include/asm/pgtable_types.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/pgtable_types.h) и представляют собой структуры:
```C
typedef struct { pgdval_t pgd; } pgd_t;
@ -399,22 +410,38 @@ typedef struct { pgdval_t pgd; } pgd_t;
Для примера,
```C
extern pgd_t early_level4_pgt[PTRS_PER_PGD];
extern pgd_t early_top_pgt[PTRS_PER_PGD];
```
Здесь `early_level4_pgt` представляет начальный каталог таблиц страниц верхнего уровня, который состоит из массива типа `pgd_t` и `pgd` указывает на записи страниц нижнего уровня.
Здесь `early_top_pgt` представляет начальный каталог таблиц страниц верхнего уровня, который состоит из массива типа `pgd_t` и `pgd` указывает на записи страниц нижнего уровня.
После того как мы проверили, что у нас корректный адрес, мы получаем адрес записи глобального каталога страниц, который содержит адрес `#PF`, и присваиваем его значение переменной `pgd`:
```C
pgd_p = &early_level4_pgt[pgd_index(address)].pgd;
pgd_p = &early_top_pgt[pgd_index(address)].pgd;
pgd = *pgd_p;
```
На следующем шаге мы проверяем `pgd`, если он содержит верную запись в глобальном каталоге страниц, мы помещаем физический адрес записи в `pud_p`:
Затем мы проверяем, включена ли пятиуровневая страничная организация памяти:
```C
if (!pgtable_l5_enabled())
p4d_p = pgd_p;
```
В большинстве случаев пятиуровневая страничная организация не включена, поэтому `p4d_p` скорее всего равен `pgd_p`.
После этого мы исправляем адрес `p4d` с помощью:
```C
p4d_p += p4d_index(address);
p4d = *p4d_p;
```
На следующем шаге мы проверяем `p4d`, если он содержит верную запись p4d, мы помещаем физический адрес записи p4d в `pud_p`:
```C
pud_p = (pudval_t *)((pgd & PTE_PFN_MASK) + __START_KERNEL_map - phys_base);
pud_p = (pudval_t *)((p4d & PTE_PFN_MASK) + __START_KERNEL_map - phys_base);
```
где `PTE_PFN_MASK` является макросом:
@ -426,18 +453,18 @@ pud_p = (pudval_t *)((pgd & PTE_PFN_MASK) + __START_KERNEL_map - phys_base);
который раскрывается до:
```C
(~(PAGE_SIZE-1)) & ((1 << 46) - 1)
(signed long)(~(PAGE_SIZE-1)) & ((1 << 52) - 1)
```
или
Здесь используется [расширение знака](https://en.wikipedia.org/wiki/Sign_extension). Развёрнутая форма:
```
0b1111111111111111111111111111111111111111111111
0b1111111111111111111111111111111111111111111111111111
```
состоящий из 46 бит для маскирования страницы.
т.е макрос состоит из 52 бит для маскирования фрейма страниц.
Если `pgd` не содержит верный адрес, мы проверяем что `next_early_pgt` не больше чем `EARLY_DYNAMIC_PAGE_TABLES`, который равен `64` и представляет фиксированное количество буферов для настройки новых таблиц страниц по требованию. Если `next_early_pgt` больше, чем `EARLY_DYNAMIC_PAGE_TABLES` мы сбрасываем таблицы страниц и начинаем всё заново. Если `next_early_pgt` меньше, чем `EARLY_DYNAMIC_PAGE_TABLES`, мы создаём новый указатель верхнего каталога страниц, который указывает на текущую динамическую таблицу страниц и записываем его физический адрес с правами доступа `_KERPG_TABLE` в глобальный каталог страниц:
Если `p4d` не содержит верный адрес, мы проверяем что `next_early_pgt` не больше чем `EARLY_DYNAMIC_PAGE_TABLES`, который равен `64` и представляет фиксированное количество буферов для настройки новых таблиц страниц по требованию. Если `next_early_pgt` больше, чем `EARLY_DYNAMIC_PAGE_TABLES` мы сбрасываем таблицы страниц и начинаем всё заново. Если `next_early_pgt` меньше, чем `EARLY_DYNAMIC_PAGE_TABLES`, мы создаём новый указатель верхнего каталога страниц, который указывает на текущую динамическую таблицу страниц и записываем его физический адрес с правами доступа `_KERNPG_TABLE` в p4d:
```C
if (next_early_pgt >= EARLY_DYNAMIC_PAGE_TABLES) {
@ -448,10 +475,10 @@ if (next_early_pgt >= EARLY_DYNAMIC_PAGE_TABLES) {
pud_p = (pudval_t *)early_dynamic_pgts[next_early_pgt++];
for (i = 0; i < PTRS_PER_PUD; i++)
pud_p[i] = 0;
*pgd_p = (pgdval_t)pud_p - __START_KERNEL_map + phys_base + _KERNPG_TABLE;
*p4d_p = (p4dval_t)pud_p - __START_KERNEL_map + phys_base + _KERNPG_TABLE;
```
После этого мы исправляем адрес верхнего каталога страниц:
Как уже говорили ранее, мы исправляем адрес верхнего каталога страниц:
```C
pud_p += pud_index(address);
@ -461,11 +488,96 @@ pud = *pud_p;
На следующем шаге мы делаем те же действия что и ранее, но с промежуточным каталогом страниц. В конце мы исправляем адрес промежуточного каталога страниц, который содержит отображения текста ядра+виртуальные адреса данных:
```C
pmd = (physaddr & PMD_MASK) + early_pmd_flags;
pmd_p[pmd_index(address)] = pmd;
```
После того как обработчик ошибки страницы завершён, `early_level4_pgt` содержит записи, которые указывают на корректные адреса.
После того как обработчик ошибки страницы завершён, `early_top_pgt` содержит записи, которые указывают на корректные адреса.
Другие обработчики исключений
--------------------------------------------------------------------------------
На раннем этапе прерывания, исключения, кроме ошибки страницы, обрабатываются функцией `early_fixup_exception`, которая определена в [arch/x86/mm/extable.c](https://github.com/torvalds/linux/blob/master/arch/x86/mm/extable.c) и принимает два аргумента - указатель на стек ядра, состоящий из сохраненных регистров, и номер прерывания:
```C
void __init early_fixup_exception(struct pt_regs *regs, int trapnr)
{
...
...
...
}
```
Прежде всего нам нужно проверить некоторые условия:
```C
if (trapnr == X86_TRAP_NMI)
return;
if (early_recursion_flag > 2)
goto halt_loop;
if (!xen_pv_domain() && regs->cs != __KERNEL_CS)
goto fail;
```
Здесь мы просто игнорируем [NMI](https://en.wikipedia.org/wiki/Non-maskable_interrupt). Также мы убеждаемся, что мы не в рекурсивной ситуации.
Далее:
```C
if (fixup_exception(regs, trapnr))
return;
```
Функция `fixup_exception` определена в том же файле, что и `early_fixup_exception`:
```C
int fixup_exception(struct pt_regs *regs, int trapnr)
{
const struct exception_table_entry *e;
ex_handler_t handler;
e = search_exception_tables(regs->ip);
if (!e)
return 0;
handler = ex_fixup_handler(e);
return handler(e, regs, trapnr);
}
```
`ex_handler_t` - тип указателя функции, определённый следующий образом:
```C
typedef bool (*ex_handler_t)(const struct exception_table_entry *,
struct pt_regs *, int)
```
Функция `search_exception_tables` ищет данный адрес в таблице исключений (т.е содержимое ELF-секции `__ex_table`). После этого мы получаем фактический адрес с помощью функции `ex_fixup_handler`. Наконец, мы вызываем фактический обработчик. Для получения дополнительной информации о таблице исключений смотрите [Documentation/x86/exception-tables.txt](https://github.com/torvalds/linux/blob/master/Documentation/x86/exception-tables.txt).
Вернёмся к функции `early_fixup_exception`. Следующий шаг:
```C
if (fixup_bug(regs, trapnr))
return;
```
Функция `fixup_bug` определена в [arch/x86/kernel/traps.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/traps.c). Давайте посмотрим на реализацию этой функции.
```C
int fixup_bug(struct pt_regs *regs, int trapnr)
{
if (trapnr != X86_TRAP_UD)
return 0;
switch (report_bug(regs->ip, regs)) {
case BUG_TRAP_TYPE_NONE:
case BUG_TRAP_TYPE_BUG:
break;
case BUG_TRAP_TYPE_WARN:
regs->ip += LEN_UD2;
return 1;
}
return 0;
}
```
Всё что делает эта функция - возвращает `1`, если генерируется исключение `#UD` (или [неверный опкод (Invalid Opcode)](https://wiki.osdev.org/Exceptions#Invalid_Opcode)) и функция `report_bug` вернула `BUG_TRAP_TYPE_WARN`, иначе возвращает `0`.
Заключение
--------------------------------------------------------------------------------

Loading…
Cancel
Save