mirror of
https://github.com/0xAX/linux-insides
synced 2024-11-05 21:20:59 +00:00
Update linux-bootstrap-2.md
This commit is contained in:
parent
53d5ba7301
commit
3e9a63b633
@ -6,10 +6,10 @@
|
||||
|
||||
Мы начали изучение внутренностей Linux в предыдущей [части](linux-bootstrap-1.md) и увидели начальную часть кода настройки ядра. Мы остановились на вызове функции `main` (это первая функция, написанная на C) из [arch/x86/boot/main.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/main.c).
|
||||
|
||||
В этой части мы продолжим исследовать код установки ядра и
|
||||
В этой части мы продолжим исследовать код установки ядра, а именно
|
||||
|
||||
* увидим `защищённый режим`,
|
||||
* некоторую подготовку для перехода в него,
|
||||
* `защищённый режим`,
|
||||
* переход в него,
|
||||
* инициализацию кучи и консоли,
|
||||
* обнаружение памяти, проверку CPU, инициализацию клавиатуры
|
||||
* и многое другое.
|
||||
@ -32,7 +32,7 @@
|
||||
* Сегментация
|
||||
* Страничная организация
|
||||
|
||||
Здесь мы будем рассматривать только сегментацию. Страничная организация будет обсуждаться в следующих разделах.
|
||||
Здесь мы только о сегментации. Страничная организация будет обсуждаться в следующих разделах.
|
||||
|
||||
Как вы можете помнить из предыдущей части, адреса в режиме реальных адресов состоят из двух частей:
|
||||
|
||||
@ -126,7 +126,7 @@ lgdt gdt
|
||||
* Если W (бит 41) (для сегмента данных) равен 1, то запись в сегмент разрешена. Обратите внимание, что право на чтение всегда разрешено для сегментов данных.
|
||||
* A (бит 40) - было ли обращение процессора к сегменту.
|
||||
* C (бит 43) - бит подчинения (для сегмента кода). Если C равен 1, то сегмент кода может быть выполнен из более низкого уровня привилегий, например, из уровня пользователя. Если C равно 0, то сегмент может быть выполнен только из того же уровня привилегий.
|
||||
* R (бит 41) (для сегмента кода). Если он равен 1, то чтение сегмента разрешено. Право на запись всегда запрещено для сегмента кода.
|
||||
* R (бит 41) контролирует доступ чтения сегментам кода; когда он равен 1, то чтение сегмента разрешено. Право на запись всегда запрещено для сегмента кода.
|
||||
|
||||
4. DPL [2 бита] (уровень привилегий сегмента (Descriptor Privilege Level)) находится в 45-46 битах. Определяет уровень привилегий сегмента от 0 до 3, где 0 является самым привилегированным.
|
||||
|
||||
@ -155,14 +155,14 @@ lgdt gdt
|
||||
|
||||
Каждый сегментный регистр имеет видимую и скрытую часть.
|
||||
|
||||
* Видимая - здесь хранится селектор сегмента
|
||||
* Скрытая - дескриптор сегмента (базовый адрес, предел, атрибуты, флаги)
|
||||
* Видимая - здесь хранится селектор сегмента.
|
||||
* Скрытая - здесь хранится дескриптор сегмента, который содержит базовый адрес, предел, атрибуты, флаги.
|
||||
|
||||
Необходимы следующие шаги, чтобы получить физический адрес в защищённом режиме:
|
||||
|
||||
* Селектор сегмента должен быть загружен в один из сегментных регистров
|
||||
* CPU пытается найти дескриптор сегмента по адресу GDT + Index из селектора и загрузить дескриптор в *скрытую* часть сегментного регистра
|
||||
* Базовый адрес (из дескриптора сегмента) + смещение будет линейным адресом сегмента, который является физическим адресом (если страничная организация отключена).
|
||||
* CPU пытается найти дескриптор сегмента по адресу `GDT + Index` из селектора и загрузить дескриптор в *скрытую* часть сегментного регистра
|
||||
* Если страничная организация памяти отключена, линейный адрес сегмента или его физический адрес задается формулой: Базовый адрес (найденный в дескрипторе, полученном на предыдущем шаге) + Смещение.
|
||||
|
||||
Схематично это будет выглядеть следующим образом:
|
||||
|
||||
@ -212,7 +212,7 @@ ENDPROC(memcpy)
|
||||
|
||||
Да, мы только что перешли в C-код и снова вернулись к ассемблеру :) Прежде всего мы видим, что `memcpy` и другие подпрограммы, расположенные здесь, начинаются и заканчиваются двумя макросами: `GLOBAL` и `ENDPROC`. Макрос `GLOBAL` описан в [arch/x86/include/asm/linkage.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/linkage.h) и определяет директиву `globl`, а так же метку для него. `ENDPROC` описан в [include/linux/linkage.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/linux/linkage.h); отмечает символ `name` в качестве имени функции и заканчивается размером символа `name`.
|
||||
|
||||
Реализация `memcpy` достаточно проста. Во-первых, она помещает значения регистров `si` and `di` в стек для их сохранения, так как они будут меняться в течении работы. `memcpy` (как и другие функции в copy.S) использует `fastcall` соглашения о вызовах. Таким образом, она получает свои входные параметры из регистров `ax`, `dx` и `cx`. Вызов `memcpy` выглядит следующим образом:
|
||||
Реализация `memcpy` достаточно проста. Во-первых, она помещает значения регистров `si` и `di` в стек для их сохранения, так как они будут меняться в течении работы. `memcpy` (как и другие функции в copy.S) использует `fastcall` соглашения о вызовах. Таким образом, она получает свои входные параметры из регистров `ax`, `dx` и `cx`. Вызов `memcpy` выглядит следующим образом:
|
||||
|
||||
```c
|
||||
memcpy(&boot_params.hdr, &hdr, sizeof hdr);
|
||||
@ -308,7 +308,7 @@ ENDPROC(memset)
|
||||
|
||||
Как правило, реализация `memset` подобна реализации `memcpy`. Она сохраняет значение регистра `di` в стеке и помещает значение `ax` в `di`, которое является адресом структуры `biosregs`. Далее идёт инструкция `movzbl`, которая копирует значение `dl` в нижние 2 байта регистра `eax`. Оставшиеся 2 верхних байта `eax` будут заполнены нулями.
|
||||
|
||||
Следующая инструкция умножает `eax` на `0x01010101`. Это необходимо, так как `memset` будет копировать 4 байта одновременно. Например, нам нужно заполнить структуру значением `0x7` с помощью `memset`. В этом случае `eax` будет содержать значение `0x00000007`. Так что если мы умножим `eax` на `0x01010101`, мы получим `0x07070707` и теперь мы можем скопировать эти 4 байта в структуру. `memset` использует инструкцию `rep; stosl` для копирования `eax` в `es:di`.
|
||||
Следующая инструкция умножает `eax` на `0x01010101`. Это необходимо, так как `memset` будет копировать 4 байта одновременно. Например, нам нужно заполнить структуру, размер которой составляет 4 байта, значением `0x7` с помощью `memset`. В этом случае `eax` будет содержать значение `0x00000007`. Так что если мы умножим `eax` на `0x01010101`, мы получим `0x07070707` и теперь мы можем скопировать эти 4 байта в структуру. `memset` использует инструкцию `rep; stosl` для копирования `eax` в `es:di`.
|
||||
|
||||
Остальная часть `memset` делает почти то же самое, что и `memcpy`.
|
||||
|
||||
@ -343,7 +343,7 @@ ENDPROC(memset)
|
||||
Проверка CPU
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Следующим шагом является проверка CPU с помощью `validate_cpu` из [arch/x86/boot/cpu.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/cpu.c).
|
||||
Следующим шагом является проверка CPU с помощью функции `validate_cpu` из [arch/x86/boot/cpu.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/cpu.c).
|
||||
|
||||
Она вызывает функцию [`check_cpu`](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/cpucheck.c#L112) и передаёт ей два параметра: уровень CPU и необходимый уровень CPU; `check_cpu` проверяет, запущено ли ядро на нужном уровне CPU.
|
||||
|
||||
@ -362,7 +362,7 @@ if (cpu_level < req_level) {
|
||||
|
||||
Следующим шагом является обнаружение памяти с помощью функции `detect_memory`. `detect_memory` в основном предоставляет карту доступной оперативной памяти для CPU. Она использует различные программные интерфейсы для обнаружения памяти, такие как `0xe820`, `0xe801` и `0x88`. Здесь мы будем рассматривать только реализацию **0xE820**.
|
||||
|
||||
Давайте посмотрим на реализацию `detect_memory_e820` в [arch/x86/boot/memory.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/memory.c). Прежде всего, функция `detect_memory_e820` инициализирует структуру `biosregs`, как мы видели выше, и заполняет регистры специальными значениями для вызова `0xe820`:
|
||||
Давайте посмотрим на реализацию фуцнкции `detect_memory_e820` в [arch/x86/boot/memory.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/memory.c). Прежде всего, функция `detect_memory_e820` инициализирует структуру `biosregs`, как мы видели выше, и заполняет регистры специальными значениями для вызова `0xe820`:
|
||||
|
||||
```assembly
|
||||
initregs(&ireg);
|
||||
@ -389,7 +389,7 @@ if (cpu_level < req_level) {
|
||||
|
||||
* начало сегмента памяти
|
||||
* размер сегмента памяти
|
||||
* тип сегмента памяти (зарезервированый, используемый и т.д).
|
||||
* тип сегмента памяти (может ли конкретный сегмент быть использован или он зарезервирован).
|
||||
|
||||
Вы можете увидеть результат в выводе `dmesg`, что-то вроде:
|
||||
|
||||
@ -403,10 +403,28 @@ if (cpu_level < req_level) {
|
||||
[ 0.000000] BIOS-e820: [mem 0x00000000fffc0000-0x00000000ffffffff] reserved
|
||||
```
|
||||
|
||||
Затем вы видим вызов функции `set_bios_mode`. Эта функция реализована только для режима `x86_64`:
|
||||
|
||||
```C
|
||||
static void set_bios_mode(void)
|
||||
{
|
||||
#ifdef CONFIG_X86_64
|
||||
struct biosregs ireg;
|
||||
|
||||
initregs(&ireg);
|
||||
ireg.ax = 0xec00;
|
||||
ireg.bx = 2;
|
||||
intcall(0x15, &ireg, NULL);
|
||||
#endif
|
||||
}
|
||||
```
|
||||
|
||||
Функция `set_bios_mode` выполняет прерывание `0x15`, чтобы сообщить BIOS, что будет использоваться [long mode](https://en.wikipedia.org/wiki/Long_mode) (если `bx == 2`).
|
||||
|
||||
Инициализация клавиатуры
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Следующим шагом является инициализация клавиатуры с помощью вызова функции [`keyboard_init()`](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/main.c#L65). Вначале `keyboard_init` инициализирует регистры с помощью функции `initregs` и вызова прерывания [0x16](http://www.ctyme.com/intr/rb-1756.htm) для получения статуса клавиатуры.
|
||||
Следующим шагом является инициализация клавиатуры с помощью вызова функции [`keyboard_init()`](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/main.c#L65). Вначале `keyboard_init` инициализирует регистры с помощью функции `initregs`. Затем он вызывает прерывание [0x16](http://www.ctyme.com/intr/rb-1756.htm) для получения статуса клавиатуры.
|
||||
|
||||
```c
|
||||
initregs(&ireg);
|
||||
@ -426,81 +444,13 @@ if (cpu_level < req_level) {
|
||||
|
||||
Следующие несколько шагов - запросы для различных параметров. Мы не будем погружаться в подробности этих запросов, но вернёмся к этому в последующих частях. Давайте коротко взглянем на эти функции:
|
||||
|
||||
Функция [query_mca](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/mca.c#L18) вызывает BIOS прерывание [0x15](http://www.ctyme.com/intr/rb-1594.htm) для получения машинного номера модели, номера субмодели, номера ревизии BIOS, а также других, аппаратно-ориентированных атрибутов:
|
||||
|
||||
```c
|
||||
int query_mca(void)
|
||||
{
|
||||
struct biosregs ireg, oreg;
|
||||
u16 len;
|
||||
|
||||
initregs(&ireg);
|
||||
ireg.ah = 0xc0;
|
||||
intcall(0x15, &ireg, &oreg);
|
||||
|
||||
if (oreg.eflags & X86_EFLAGS_CF)
|
||||
return -1; /* MCA отсутствует */
|
||||
|
||||
set_fs(oreg.es);
|
||||
len = rdfs16(oreg.bx);
|
||||
|
||||
if (len > sizeof(boot_params.sys_desc_table))
|
||||
len = sizeof(boot_params.sys_desc_table);
|
||||
|
||||
copy_from_fs(&boot_params.sys_desc_table, oreg.bx, len);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Функция заполняет регистр `ah` значением `0xc0` и вызывает BIOS прерывание `0x15`. После выполнения прерывания она проверяет [флаг переноса](http://en.wikipedia.org/wiki/Carry_flag) и если он установлен в 1, то это означает, что BIOS не поддерживает [**MCA**](https://en.wikipedia.org/wiki/Micro_Channel_architecture). Если флаг переноса установлен в 0, `ES:BX` будет содержать указатель на таблицу системной информации, которая выглядит следующим образом:
|
||||
|
||||
```
|
||||
Смещение Размер Описание
|
||||
00h СЛОВО количество следующих байт
|
||||
02h БАЙТ модель (смотрите #00515)
|
||||
03h БАЙТ субмодель (смотрите #00515)
|
||||
04h БАЙТ ревизия BIOS: 0 для первой ревизии, 1 для второй и т.д
|
||||
05h БАЙТ байт свойства 1 (смотрите #00510)
|
||||
06h БАЙТ байт свойства 2 (смотрите #00511)
|
||||
07h БАЙТ байт свойства 3 (смотрите #00512)
|
||||
08h БАЙТ байт свойства 4 (смотрите #00513)
|
||||
09h БАЙТ байт свойства 5 (смотрите #00514)
|
||||
---AWARD BIOS---
|
||||
0Ah N БАЙТ Уведомление об авторских правах AWARD
|
||||
---Phoenix BIOS---
|
||||
0Ah БАЙТ ??? (00h)
|
||||
0Bh БАЙТ мажорная версия
|
||||
0Ch БАЙТ минорная версия (BCD)
|
||||
0Dh 4 БАЙТА ASCIZ-строка "PTL" (Phoenix Technologies Ltd)
|
||||
---Quadram Quad386---
|
||||
0Ah 17 БАЙТ ASCII-строка подписи "Quadram Quad386XT"
|
||||
---Toshiba (По крайней мере Satellite Pro 435CDS)---
|
||||
0Ah 7 БАЙТ подпись "TOSHIBA"
|
||||
11h БАЙТ ??? (8h)
|
||||
12h БАЙТ ??? (E7h) ID продукта??? (предположительно)
|
||||
13h 3 БАЙТА "JPN"
|
||||
```
|
||||
|
||||
Далее мы вызываем функцию `set_fs` и передаём ей значение регистра `es`. Реализация `set_fs` довольно проста:
|
||||
|
||||
```c
|
||||
static inline void set_fs(u16 seg)
|
||||
{
|
||||
asm volatile("movw %0,%%fs" : : "rm" (seg));
|
||||
}
|
||||
```
|
||||
|
||||
Функция содержит ассемблерную вставку, которая получает значение параметра `seg` и помещает его в регистр `fs`. Существует множество функций в [boot.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/boot.h), похожих на `set_fs`, например, `set_gs`, `fs`, `gs` для чтения значения в нём и т.д.
|
||||
|
||||
В завершении функция `query_mca` просто копирует таблицу, на которую указывает `es:bx`, в `boot_params.sys_desc_table`.
|
||||
|
||||
Следующим шагом является получение информации [Intel SpeedStep](http://en.wikipedia.org/wiki/SpeedStep) с помощью вызова функции `query_ist`. В первую очередь она проверяет уровень CPU, и если он верный, вызывает прерывание `0x15` для получения информации и сохраняет результат в `boot_params`.
|
||||
Первым шагом является получение информации [Intel SpeedStep](http://en.wikipedia.org/wiki/SpeedStep) с помощью вызова функции `query_ist`. Она проверяет уровень CPU, и если он верный, вызывает прерывание `0x15` для получения информации и сохраняет результат в `boot_params`.
|
||||
|
||||
Следующая функция - [query_apm_bios](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/apm.c#L21) получает из BIOS информацию об [Advanced Power Management](http://en.wikipedia.org/wiki/Advanced_Power_Management). `query_apm_bios` также вызывает BIOS прерывание `0x15`, но с `ah = 0x53` для проверки поддержки `APM`. После выполнения `0x15`, функция `query_apm_bios` проверяет сигнатуру `PM` (она должна быть равна `0x504d`), флаг переноса (он должен быть равен 0, если есть поддержка `APM`) и значение регистра `cx` (оно должено быть равным 0x02, если имеется поддержка защищённого режима).
|
||||
|
||||
Далее она снова вызывает `0x15`, но с `ax = 0x5304` для отсоединения от интерфейса `APM` и подключению к интерфейсу 32-битного защищённого режима. В итоге она заполняет `boot_params.apm_bios_info` значениями, полученными из BIOS.
|
||||
|
||||
Обратите внимание: `query_apm_bios` будет выполняться только в том случае, если в конфигурационном файле установлен `CONFIG_APM` или `CONFIG_APM_MODULE`:
|
||||
Обратите внимание: `query_apm_bios` будет выполняться только в том случае, если в конфигурационном файле установлен флаг времени компиляции `CONFIG_APM` или `CONFIG_APM_MODULE`:
|
||||
|
||||
```C
|
||||
#if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
|
||||
|
Loading…
Reference in New Issue
Block a user