bkerndev - kernel
Механизм защиты - protection- реализован с помощью Global Descriptor Table, или GDT.
GDT определяет права и привилегии на использование памяти .
С помощью GDT можно сгенерировать исключение в том случае , если пользовательский
процесс делает что-то не то . Следующим шагом является реализация пэйджинга .
GDT разбивает память на сектора кода и сектора данных .
На GDT ложится ответственность за функционирование Task State Segments , которые
реализуют многозадачность .
GDT представляет из себя список 64-битных адресов . Этот адрес включает в себя :
стартовый адрес региона памяти , размер региона , битовые права доступа .
Первый адрес в GDT называется NULL descriptor . Тем не менее , ни один из сегментных
регистров не может быть установлен в 0 , ибо это вызовет General Protection fault .
Адрес GDT включает в себя бит уровня исполнения - System Use (Ring 0) или Application Use (Ring 3).
Этот бит управляет специальными инструкциями процессора , имеющими повышенный приоритет.
Например , инструкции cli и sti , запрещающие и устанавливающие прерывания ,
окажутся недоступными для пользовательского приложения .
|
|
P - Segment is present? (1 = Yes)
DPL - Which Ring (0 to 3)
DT - Descriptor Type
Type - Which type?
|
|
|
|
|
G - Granularity (0 = 1byte, 1 = 4kbyte)
D - Operand Size (0 = 16bit, 1 = 32-bit)
0 - Always 0
A - Available for System (Always set to 0)
|
|
В нашем примере мы создадим GDT всего с 3-мя дескрипторами .
Первый - нулевой . Второй дескриптор - для кодового сегмента , третий - для сегмента данных .
Для инициализации GDT нужно использовать команду процессора lgdt .
В файле gdt.c мы создаем структуру gdt_entry , создаем из нее массив из 3 элементов ,
а также указатель gp . После вызова команды lgdt мы обязаны сделать т.н. far jump
для загрузки кодового сегмента .
Файл gdt.c :
struct gdt_entry
{
unsigned short limit_low;
unsigned short base_low;
unsigned char base_middle;
unsigned char access;
unsigned char granularity;
unsigned char base_high;
} __attribute__((packed));
struct gdt_ptr
{
unsigned short limit;
unsigned int base;
} __attribute__((packed));
/* массив и указатель на массив */
struct gdt_entry gdt[3];
struct gdt_ptr gp;
/* Определим прототип функции , которая находится в start.asm. */
extern void gdt_flush();
/* Инициализация дескриптора в Global Descriptor Table */
void gdt_set_gate(int num, unsigned long base, unsigned long limit,
unsigned char access, unsigned char gran)
{
/* Setup the descriptor base address */
gdt[num].base_low = (base & 0xFFFF);
gdt[num].base_middle = (base >> 16) & 0xFF;
gdt[num].base_high = (base >> 24) & 0xFF;
/* Setup the descriptor limits */
gdt[num].limit_low = (limit & 0xFFFF);
gdt[num].granularity = ((limit >> 16) & 0x0F);
/* Finally, set up the granularity and access flags */
gdt[num].granularity |= (gran & 0xF0);
gdt[num].access = access;
}
/* Эта функция вызывается из main. Генерируется указатель на GDT ,
* настраиваются 3 дескриптора ,
* вызывается gdt_flush() для настройки процессора
* и настройки сегментных регистров
*/
void gdt_install()
{
/* Setup the GDT pointer and limit */
gp.limit = (sizeof(struct gdt_entry) * 3) - 1;
gp.base = (int)&gdt;
/* Первый дескриптор - NULL */
gdt_set_gate(0, 0, 0, 0, 0);
/* 2-й дескриптор - Code Segment. Базовый адрес - 0,
ограничение на размер - 4 гига,
4 килобайта на одну страницу памяти
использование 32-bit opcodes,
Code Segment descriptor.
*/
gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);
/* 3-й дескриптор - Data Segment.
Он практически не отличается от 2-го дескриптора ,
с той лишь разницей , что тип дескриптора - Data Segment */
gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);
gdt_flush();
}
Interrupt Descriptor Table, или IDT , будет создана для управления прерываниями
с помощью Interrupt Service Routine (ISR) . Дескриптор IDT похож на дескриптор GDT .
Разница : базовый адрес дескриптора IDT указывает на процедуру Interrupt Service Routine ,
которая вызывается процессором в случае исключения . Вместо ограничения в дескрипторе
IDT нужно указать сегмент , в котором лежит процедура .
Флаги доступа в IDT похожи на GDT . Имеется бит Descriptor Privilege Level (DPL) .
Нижние 5 бит байта доступа всегда установлены в 01110 (14 в десятичном исчислении).
Загружается IDT с помощью команды lidt .
Вы можете добавить свою собственную ISR в IDT уже после загрузки .
Файл idt.c :
struct idt_entry
{
unsigned short base_lo;
unsigned short sel;
unsigned char always0;
unsigned char flags;
unsigned short base_hi;
} __attribute__((packed));
struct idt_ptr
{
unsigned short limit;
unsigned int base;
} __attribute__((packed));
/* IDT будет состоять из 256 дескрипторов.
На самом деле мы будем использовать только первые 32
*/
struct idt_entry idt[256];
struct idt_ptr idtp;
/* Строка из 'start.s' */
extern void idt_load();
void idt_set_gate(unsigned char num, unsigned long base,
unsigned short sel, unsigned char flags)
{
/* The interrupt routine's base address */
idt[num].base_lo = (base & 0xFFFF);
idt[num].base_hi = (base >> 16) & 0xFFFF;
/* The segment or 'selector' that this IDT entry will use
* is set here, along with any access flags */
idt[num].sel = sel;
idt[num].always0 = 0;
idt[num].flags = flags;
}
/* Installs the IDT */
void idt_install()
{
/* Sets the special IDT pointer up, just like in 'gdt.c' */
idtp.limit = (sizeof (struct idt_entry) * 256) - 1;
idtp.base = (int)&idt;
/* Clear out the entire IDT, initializing it to zeros */
memset(&idt, 0, sizeof(struct idt_entry) * 256);
/* Add any new ISRs to the IDT here using idt_set_gate */
/* Points the processor's internal register to the new IDT */
idt_load();
}
Interrupt Service Routines, или ISR , используются при исключениях для того ,
чтобы сохранить текущее состояние процессора и сегментных регистров .
Нам понадобятся указатели для их вызова .
Исключение - ситуация , при которой процессор не может продолжать работу , например
при делении на ноль . При этом ядро останавливает процесс , вызвавший исключение .
Если например исключение вызвано тем , что произошла попытка доступа к несуществующей памяти ,
будет вызван General Protection Fault.
Первые 32 дескриптора в IDT зарезервированы за стандартными ISR .
Некоторые исключения могут использовать стек . Для упрощения данная версия ядра реализована
так , что любое исключение будет использовать стек , записывая туда код 0 , а также номер исключения.
Процедура isr_common_stub записывает статус процессора в стек
Файл isrs.c :
extern void isr0();
...
extern void isr31();
/* Инициализация превых 32 дескрипторов таблицы IDT
Флаг доступа устанавливается в 0x8E.
Это значит , что дескриптор выполняется на уровне 0 (kernel level)
И нижние 5 бит установлены в '14'- 'E' в 16-ричном исчислении
*/
void isrs_install()
{
idt_set_gate(0, (unsigned)isr0, 0x08, 0x8E);
idt_set_gate(1, (unsigned)isr1, 0x08, 0x8E);
...
idt_set_gate(31, (unsigned)isr31, 0x08, 0x8E);
}
/* Простой символьный массив сообщений для каждого исключения*/
unsigned char *exception_messages[] =
{
"Division By Zero",
"Debug",
"Non Maskable Interrupt",
"Breakpoint",
"Into Detected Overflow",
"Out of Bounds",
"Invalid Opcode",
"No Coprocessor",
...
"Reserved",
...
};
/* В каждой Interrupt Service Routines есть указатель на эту функцию
В случае исключения просто все вырубается .
*/
void fault_handler(struct regs *r)
{
if (r->int_no < 32)
{
puts(exception_messages[r->int_no]);
puts(" Exception. System Halted!\n");
for (;;);
}
}
Аргументом fault_handler является структура , прдставляющая стековый фрейм.
С его помощью в стек можно положить указатель самого стека .
Interrupt Requests , или IRQ - это прерывания от железа . Они могут быть сгенерированы
при чтении или записи данных на устройства .
На pc-шках есть 2 чипа для управления IRQ - Programmable Interrupt Controllers или PIC или 8259 .
Первый 'Master' IRQ controller, второй - 'Slave' IRQ controller.
Второй коннектится через IRQ2 на первого .
Первый коннектится к процессору , посылая тому сигналы .
Каждый PIC может управлять восемью IRQ , первый - с 0 по 7-й , второй - с 8 по 15-й .
Как только устройство посылает сигнал , процессор останавливается и вызывает
соответствующую ISR . После этого процессор может читать данные от устройства .
После этого процессор должен передать управление 1-му PIC - для этого он
должен записать 0x20 в порт I/O 0x20.
IRQ с 0-го по 7-й соответственно по умолчанию прикручены к IDT к дескрипторам с 8 по 15-й.
IRQ с 8 по 15-й - в IDT с 0x70 по 0x78 . Мы это немного переделаем :
IRQ с 0-го по 7-й мы прикручиваем к IDT к дескрипторам с 32 по 47-й.
Файл irq.c :
extern void irq0();
...
extern void irq15();
/* Массив указателей на функции на IRQ */
void *irq_routines[16] =
{
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
void irq_install_handler(int irq, void (*handler)(struct regs *r))
{
irq_routines[irq] = handler;
}
void irq_uninstall_handler(int irq)
{
irq_routines[irq] = 0;
}
/* По умолчанию IRQ с 0 по 7 маппированы с 8 по 15.
Дескриптор IDT под номером 8 - Double Fault!
Это означает , что при каждом нулевом прерывании
мы будем получать несуществующий Double Fault Exception.
Поэтому мы меняем порядок маппирования :
IRQ 0 : 15 -> IDT 32 : 47
*/
void irq_remap(void)
{
outportb(0x20, 0x11);
outportb(0xA0, 0x11);
outportb(0x21, 0x20);
outportb(0xA1, 0x28);
outportb(0x21, 0x04);
outportb(0xA1, 0x02);
outportb(0x21, 0x01);
outportb(0xA1, 0x01);
outportb(0x21, 0x0);
outportb(0xA1, 0x0);
}
void irq_install()
{
irq_remap();
idt_set_gate(32, (unsigned)irq0, 0x08, 0x8E);
idt_set_gate(33, (unsigned)irq1, 0x08, 0x8E);
idt_set_gate(34, (unsigned)irq2, 0x08, 0x8E);
...
idt_set_gate(47, (unsigned)irq15, 0x08, 0x8E);
}
/* Each of the IRQ ISRs point to this function, rather than
* the 'fault_handler' in 'isrs.c'. The IRQ Controllers need
* to be told when you are done servicing them, so you need
* to send them an "End of Interrupt" command (0x20). There
* are two 8259 chips: The first exists at 0x20, the second
* exists at 0xA0. If the second controller (an IRQ from 8 to
* 15) gets an interrupt, you need to acknowledge the
* interrupt at BOTH controllers, otherwise, you only send
* an EOI command to the first controller. If you don't send
* an EOI, you won't raise any more IRQs */
void irq_handler(struct regs *r)
{
/* This is a blank function pointer */
void (*handler)(struct regs *r);
/* Find out if we have a custom handler to run for this
* IRQ, and then finally, run it */
handler = irq_routines[r->int_no - 32];
if (handler)
{
handler(r);
}
/* If the IDT entry that was invoked was greater than 40
* (meaning IRQ8 - 15), then we need to send an EOI to
* the slave controller */
if (r->int_no >= 40)
{
outportb(0xA0, 0x20);
}
/* In either case, we need to send an EOI to the master
* interrupt controller too */
outportb(0x20, 0x20);
}
|