內存管理時,不讓多進程的程序出現內存衝突的一解決方案是Segmentation。4GB的內存可以任何分割,每塊的初始地址都是0。另外還有一種複雜的內存管理方案,既Paging,目前主流的操作系統都是採用這種方式。本文的OS爲了實現簡單,只採用Segmentation方案。
我們規定1個Segmentation的信息有:
l Size
l 初始地址
l 屬性(讀寫權限等)。
每個Segementation信息佔8Byte,既64bit。我們把所有的Segment編號,並把編號保存在CPU內的16位segment register裏。Segment register可以處理0到8191個segment,本來16位的register應該可以處理0到65535個segment,但是因爲register的低3爲不能使用,所以最多隻到8191。爲了保存8192個segment(每個8bytes),需要64KB的容量,CPU是無法保存這麼多數據的,因此需要內存的幫助。GDT是[gobal (segment) descriptor table]的縮寫,它保存所有segment的信息。Segment的初始地址和有效設定個數保存在CPU的GDTR寄存器內。另外一個IDT是[interrupt descriptor table]的縮寫,用來處理一些內部或外部的中斷。例如鍵盤,鼠標,軟驅,硬盤,CDROM,網卡等。IDT可以處理0到255編號的中斷,例如當123號中斷髮生時,調用XXX函數。IDT的信息和GDT相似,也是每個8bytes。本系統的GDT分配在內存的0x27000到0x27ffff。IDT是0x26f800到0x26ffff,佔2KB。
我們將Segment定義爲:
struct SEGMENT_DESCRIPTOR {
short limit_low, base_low;
char base_mid, access_right;
char limit_high, base_high;
};
Segment信息的初始地址由32bit組成,成爲Base地址,分爲3個部分,low(2byte),mid(1byte),high(1byte),如此分割是爲了兼容80286 CPU。
Segment信息的大小成爲Limit,Limit最大可以爲4GB,當然是在32位機。這樣就需要4Byte,和Base地址一共需要8個byte,但這樣就沒有保存屬性的位置。爲了留給屬性空間,只能給Limit 20bit,最大能表示1MB,當然這樣是不能滿足目前主流的32bit機的。Intel爲了解決這個問題,規定了在屬性裏留有1位稱爲G bit(G表示granularity,粒度),limit的單位不是Byte,而是Page,PC的CPU的1個Page表示4KB。這樣4KB*1MB=4GB。爲了能表示20bit的Limit,我們使用2個Byte。其中多出的4bit用來表示屬性。
Segment的屬性由Limit多出的4bit加上剩下的8個bit表示,也稱訪問權限。訪問權限的構成:
XXXX0000XXXXXXXX(X代表0或1)
上4bit是386以後使用的擴張訪問屬性,由[GD00]構成,G表示上面提到的粒度,D表示Segment mode,1表示32位機,0表示16位機。下8bit繼承80286時代的Segement屬性。8bit的屬性內容非常多,在這裏,我們常用的有如下幾個:
00000000(0x00):未使用的Descriptor table
10010010(0x92):系統專用,可讀寫,不能執行
10011010(0x9a):系統專用,可執行,可讀,不可寫
11110010(0xf2):應用程序使用,可讀寫,不可執行。
11111010(0xfa):應用程序可用,可執行,可讀,不可寫。
屬性規定了系統和應用程序使用的讀寫執行權限。系統模式稱爲Ring0,應用程序模式稱爲Ring3,中間的Ring1和Ring2模式是系統服務。Ring0管理Ring3,例如,Ring3的應用程序在請求Load LGDT時,操作系統將否決該請求,以保證系統的安全性。下面是新增加的源代碼文件。
/*boot.h*/
#define AR_DATA32_RW 0x4092
#define AR_CODE32_ER 0x409a
#define ADR_BOTPAK 0x00280000
#define LIMIT_BOTPAK 0x0007ffff
#define LIMIT_GDT 0x0000ffff
#define ADR_IDT 0x0026f800
#define LIMIT_IDT 0x000007ff
struct SEGMENT_DESCRIPTOR {
short limit_low, base_low;
char base_mid, access_right;
char limit_high, base_high;
};
struct GATE_DESCRIPTOR {
short offset_low, selector;
char dw_count, access_right;
short offset_high;
};
void init_gdtidt(void);
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar);
void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar);
/*gldt.c*/
#include "boot.h"
void init_gdtidt(void)
{
struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;
struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR *) ADR_IDT;
int i;
/*GDT Init*/
for (i = 0; i <= LIMIT_GDT / 8; i++) {
set_segmdesc(gdt + i, 0, 0, 0);
}
/*set all memory into data segement*/
set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, AR_DATA32_RW);
/*set this segemnt to code segment that can be executed by system*/
set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER);
load_gdtr(LIMIT_GDT, ADR_GDT);
/*IDT Init*/
for (i = 0; i <= LIMIT_IDT / 8; i++) {
set_gatedesc(idt + i, 0, 0, 0);
}
/*implemented in func.s*/
load_idtr(LIMIT_IDT, ADR_IDT);
return;
}
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)
{
if (limit > 0xfffff) {
ar |= 0x8000; /* G_bit = 1 */
limit /= 0x1000;
}
sd->limit_low = limit & 0xffff;
sd->base_low = base & 0xffff;
sd->base_mid = (base >> 16) & 0xff;
sd->access_right = ar & 0xff;
sd->limit_high = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0);
sd->base_high = (base >> 24) & 0xff;
return;
}
void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar)
{
gd->offset_low = offset & 0xffff;
gd->selector = selector;
gd->dw_count = (ar >> 8) & 0xff;
gd->access_right = ar & 0xff;
gd->offset_high = (offset >> 16) & 0xffff;
return;
}
;func.s
;Colimas Simple OS
[BITS 32]
GLOBAL _io_hlt, _io_cli, _io_sti, io_stihlt
GLOBAL _io_in8, _io_in16, _io_in32
GLOBAL _io_out8, _io_out16, _io_out32
GLOBAL _io_load_eflags, _io_store_eflags
GLOBAL _load_gdtr, _load_idtr
segment .text
_io_hlt: ; void io_hlt(void);
HLT
RET
_io_cli: ; void io_cli(void);
CLI
RET
_io_sti: ; void io_sti(void);
STI
RET
_io_stihlt: ; void io_stihlt(void);
STI
HLT
RET
_io_in8: ; int io_in8(int port);
MOV EDX,[ESP+4] ; port
MOV EAX,0
IN AL,DX
RET
_io_in16: ; int io_in16(int port);
MOV EDX,[ESP+4] ; port
MOV EAX,0
IN AX,DX
RET
_io_in32: ; int io_in32(int port);
MOV EDX,[ESP+4] ; port
IN EAX,DX
RET
_io_out8: ; void io_out8(int port, int data);
MOV EDX,[ESP+4] ; port
MOV AL,[ESP+8] ; data
OUT DX,AL
RET
_io_out16: ; void io_out16(int port, int data);
MOV EDX,[ESP+4] ; port
MOV EAX,[ESP+8] ; data
OUT DX,AX
RET
_io_out32: ; void io_out32(int port, int data);
MOV EDX,[ESP+4] ; port
MOV EAX,[ESP+8] ; data
OUT DX,EAX
RET
_io_load_eflags: ; int io_load_eflags(void);
PUSHFD ; PUSH EFLAGS
POP EAX
RET
_io_store_eflags: ; void io_store_eflags(int eflags);
MOV EAX,[ESP+4]
PUSH EAX
POPFD ; POP EFLAGS
RET
;09/03
_load_gdtr: ; void load_gdtr(int limit, int addr);
MOV AX,[ESP+4] ; limit
MOV [ESP+6],AX
LGDT [ESP+6]
RET
_load_idtr: ; void load_idtr(int limit, int addr);
MOV AX,[ESP+4] ; limit
MOV [ESP+6],AX
LIDT [ESP+6]
RET
_load_gdtr函數的參數是Limit和Base地址,GDTR是48位寄存器,不能直接使用Mov指令,這個寄存器的後16bit,也就是內存的最初2byte表示Limit,剩下的4個byte表示Base地址。[ESP+4]表示limit,[ESP+8]表示base地址。因爲我們不需要Limit的[ESP+4]到[ESP+6]的值,只需要將[ESP+6]代入LGDT。_load_idtr與_load_gdtr相同。