JeffOS
Автор - jwthomp@cu-online.com
Для начала вспомним формат дескриптора gdt:
63 - 56 4-й байт базового адреса
55 Granularity Bit
54 Default Bit
53 0
52 Available for Use (free bit)
51 - 48 старшие биты ограничения
47 Segment Present Bit
46 - 45 Descriptor Privilege Level
44 System Bit
43 Data/Code Bit
42 Conforming Bit
41 Readable bit
40 Accessed bit
39 - 32 3-й байт базового адреса
31 - 24 2-й байт базового адреса
23 - 16 1-й байт базового адреса
15 - 8 2-й байт ограничения
7 - 0 1-й байт ограничения
Здесь : базовый адрес - это адрес самой таблицы.
Ограничение - число , измеряемое либо в байтах , либо в килобайтных страницах.
Если Granularity Bit=0 , то размер сегмента может быть произвольным в килобайтах.
Если Granularity Bit=1 , то размер сегмента кратен 4 килобайтам.
default bit - определяет тип кодового сегмента - 32-битный или 16-битный.
Если он равен 0 - то 16-битный.
present bit - устанавливается в единицу , если сегмент загружен.
system bit - устанавливается в 0 , если сегмент принадлежит самой ОС.
data/code bit - указывает на кодовый сегмент либо сегмент данных.
readable bit - сегмент на чтение либо выполнение
Первая стадия загрузки ядра : смотрим каталог /boot.
mainboot.c -> функция "_start" - в ее начале идет вызов kinit_MemMgmt (memboot.c).
Здесь происходит попытка определения количества памяти в системе - val = kmemcountboot();..
Далее происходит инициализация page tables - kinit_pageboot.
Пэйджинг - механизм захвата процессом памяти. Сейчас будет создана т.н. "виртуальная память".
Каждый раз , когда происходит выделение памяти , процессор смотрит в эту таблицу.
page tables организованы по следующему принципу : вначале идет т.н. page directory ,
которая разбита на 1024 частей.Каждая такая часть устроена следующим образом :
31 - 12 базовый адрес
11 - 9 не используется
8 0
7 Page Size Bit
6 0
5 Accessed Bit
4 Page Cache Disable Bit
3 Page Write Through Bit
2 User/Supervisor Bit
1 Read/Write Bit
0 Page Present Bit
базовый адрес состоит из 2-х частей - индекса и физического адреса самой таблицы.
Page Size Bit - указывает на размер страницы (Bit = 0 для 4kb или Bit = 1 для 4MB).
accessed bit - устанавливается при доступе к ней.
Page Cache Disable Bit и Page Write Bit не используются.
User/Supervisor Bit - устанавливает права доступа.
Если бит=0 , к странице имеют доступ привилегированные уровни 0, 1, 2
Если бит=1 , к странице имеют доступ привилегированные уровни 0, 1, 2, 3.
Read/Write - устанавливает право на запись для пользовательского процесса.
Present Bit - представлена ли страница в памяти.
Т.о. page directory ссылается на тысячу таблиц page tables , каждая из которых в свою очередь
имеет следующую структуру :
31 - 12 Page Base Address
11 - 9 Unused (Free bits)
8 - 7 0
6 Dirty Bit
5 Accessed Bit
4 Page Cache Disable Bit
3 Page Write Through Bit
2 User/Supervisor Bit
1 Read/Write Bit
0 Page Present Bit
Все Kernel pages установлены с правами Supervisor, Read/Write, Page Present .
Функция kinit_pageboot(memboot.c) инициализирует три page tables.
Первая - размером 1 метр - это сама page directory.
Вторая имеет размер 4 метра и будет использоваться пользовательскими процессами.
Третья является указателем на верхнюю память.
Первый адрес в page directory будет указывать на нижний адрес в пространстве этих 4 метров.
Адрес page directory помещается в регистр cr3.
В регистре cr0 устанавливается paging bit.
Первая стадия загрузки ядра завершена.
Вторая стадия начинается с фуункции _start_kernel(main.c).
Выводим на экран "Main kernel loaded.".
Инициализируем память - kinit_page()(mem.c).
Вначале проинициализируем битовое поле PMAT размером 2048 байт.
Каждый бит этого поля представляет одну страницу физической памяти.
Если бит=1 , соответствующая страница памяти выделена.
Функция kinit_tmpuser инициализирует память для пользовательских процессов,
эта память начинается с адреса 20000h.
Функция kinit_task()(task.c) инициализирует стартовый процесс ядра.
Она вызывает 2 функции : первая - kinit_gdt() - инициализирует GDT.
Инициализируются 5 сегментов : код ядра , данные ядра , данные ядра 2 ,
код пользователя , данные пользователя.
Вторая функция - kinit_ktask() - инициализирует задачу , управляемую ядром.
Обнуляется TaskList - сюда включается список всех задач в системе.
Выделяем 4 килобайта для kernel task segment.
Этот сегмент добавляем в GDT.
Этот Task segment имеет следующую структуру :
struct TSS {
ushort link; // set to 0
ushort unused0;
ulong esp0; // set to the end of the task segment page
ushort ss0; // set to SEL_KDATA (Kernel Data segment)
ushort unused1;
ulong esp1; // set to 0
ushort ss1; // set to 0
ushort unused2;
ulong esp2; // set to 0
ushort ss2; // set to 0
ushort unused3;
ulong cr3; // set to the physical address of this tasks page
// tables
ulong eip; // set to the entry point to this tasks code
ulong eflags; // set to 0x4202
ulong eax, ecx, edx, ebx, esp, ebp, esi, edi; // set to garbage values
ushort es; // set to SEL_KDATA (Kernel data segment)
ushort unused4;
ushort cs; // set to SEL_KCODE (Kernel code segment)
ushort unused5;
ushort ss; // set to SEL_KDATA
ushort unused6;
ushort ds; // set to SEL_KDATA
ushort unused7;
ushort fs; // set to SEL_KDATA
ushort unused8;
ushort gs; // set to SEL_KDATA
ushort unused9;
ushort ldt; // set to 0
ushort unused10;
ushort debugtrap; // set to 0
ushort iomapbase; // set to 0
};
Параметры espx и ssx используются для хранения указателя на стек .
Параметр cr3 используется для хранения указателя на физический адрес страницы памяти.
Понятно , что каждая задача имеет уникальный набор страниц.
Параметры eax, ecx, edx, ebx, esp, ebp, esi, edi; используют свои наборы.
В данной версии LDT не используется.
Поля из этой структуры будут загружаться каждый раз в регистры , когда процессор будет
переключаться на данную задачу.Когда процесс становится неактивным , соответственно
происходит обратное сохранение.
При создании task segment в GDT нужно проинициализировать указатель на него -
8-байтный дескриптор. Его формат :
63 - 56 Fourth Byte of Base Address
55 Granularity Bit
54 - 53 0
52 Available for use (free bit)
51 - 48 Upper Nibble of Size
47 Present in Memory Bit
46 - 45 Descriptor Privilege Level
44 System Built
43 16/32 Bit
42 0
41 Busy Bit
40 1
39 - 32 Third Byte of Base Address
31 - 24 Second Byte of Base Address
23 - 16 First Byte of Base Address
15 - 8 Second Byte of Segment Size
7 - 0 First Byte of Segment Size
Его структура похожа на кодовый сегментный дескриптор , за исключение 16/32 bit, и Busy Bit.
Создаем пользовательский процесс - task_create.
Он размещается по адресу 0x20000. Его стек размещается в 0x2107c.
Этот процесс имеет привилегии ядра и мало похож на обычный пользовательский процесс.
Процесс добавляется в список задач.
После создания 2-х процессов проинициализируем таблицу прерываний - kinit_idt(int.c).
Большинство прерываний имеет указатель на заглушку.
Таблица загружаетя в idt-регистр и разрешаются прерывания.
Врапперы прерываний лежат в jump.S.
Следующий шаг - переключение задач - swtch()(task.c).
При этом извлекается селекторный адрес и процессор переключает задачи.
Исходники лежат тут
|