Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

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 , запрещающие и устанавливающие прерывания , окажутся недоступными для пользовательского приложения .

7 6 5 4 3 0
P DPL DT Type
P - Segment is present? (1 = Yes)
DPL - Which Ring (0 to 3)
DT - Descriptor Type
Type - Which type?
7 6 5 4 3 0
G D 0 A Seg Len. 19:16
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);
 }
 
Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье