Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 OS
 osjournal 
 Protected Mode 
 Hardware 
 Kernels
  Dark Fiber
  BOS
  QNX
  OS Dev
  Lecture notes
  MINIX
  OS
  Solaris
  История UNIX
  История FreeBSD
  Сетунь
  Эльбрус
NEWS
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 Linux Kernel 2.6...3653 
 Trees...489 
 Clickhouse...460 
 Go Web ...455 
 Ethreal 4...452 
 Максвелл 3...420 
 Ext4 FS...411 
 C++ Patterns 3...402 
 Rodriguez 6...396 
 William Gropp...390 
 Ethreal 1...385 
 Secure Programming for Li...382 
 Steve Pate 1...381 
 Gary V.Vaughan-> Libtool...380 
 Ethreal 3...371 
 Assembler...360 
 DevFS...359 
 Стивенс 9...353 
 Ulrich Drepper...348 
 Стивенс 10...323 
 
  01.01.2024 : 3621733 посещений 

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);
 }
 
Оставьте свой комментарий !

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

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