内存管理时,不让多进程的程序出现内存冲突的一解决方案是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是0x 26f 800到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(0x 9a ):系统专用,可执行,可读,不可写
11110010(0xf2):应用程序使用,可读写,不可执行。
11111010(0xfa):应用程序可用,可执行,可读,不可写。
属性规定了系统和应用程序使用的读写执行权限。系统模式称为Ring0,应用程序模式称为Ring3,中间的Ring1和Ring2模式是系统服务。Ring0管理Ring3,例如,Ring3的应用程序在请求Load LGDT时,操作系统将否决该请求,以保证系统的安全性。下面是新增加的源代码文件。
/*boot.h*/
#define AR_DATA32_RW 0x4092
#define AR_CODE32_ER 0x 409a
#define ADR_BOTPAK 0x00280000
#define LIMIT_BOTPAK 0x0007ffff
#define LIMIT_GDT 0x0000ffff
#define ADR_IDT 0x 0026f 800
#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) & 0x 0f ) | ((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相同。