首先當程式需要一塊不存在的記憶體時(也即頁表項中已經標記出相應的頁面不在記憶體中),CPU通過80386的頁錯誤異常中斷來實現。當一個進程引用一個不存在的記憶體頁面時就會產生一個頁錯誤中斷,並把引起中斷的線性地址儲存在CR2寄存器中(便於高效快速存取),因此處理該中斷的程式就可以知道頁異常的確切地址,從而可以把進程要求的頁面從二級儲存空間(比如硬碟)載入到記憶體中。如果此時實體記憶體已經被佔用,則可以藉助二級空間的一部分作為緩衝區把記憶體中暫時不使用的頁面交換到二級儲存空間中,然後把進程需求的頁加入到記憶體中。這就是記憶體的缺頁載入機制。
1.實模式下定址:定址一個地址需要使用一個段和位移值。段值儲存在段寄存器中(ds)且長度固定為64KB,段內位移值儲存在可定址的寄存器中例如(si)。根據段基址和段內位移就可以找到對應的物理地址。
2.保護模式下定址:主要將段寄存器值轉換成了段描述符表中的段索引值以及段表表示位和特權級,成為段選擇符。位移值的概念不變。
每個段描述符佔8個位元組,其中含有所描述段線上性地址空間中的起始地址(基址)、段的長度、段的類型(程式碼片段or資料區段)、段得特權層級等。一個段最多定義的最大長度是4GB。
儲存描述符項的描述符表有3中類型,GDT、IDT、LDT。GDT通用描述元表是主要的基本描述符表,該表可被所有程式引用訪問一個記憶體段。IDT中斷描述符表儲存有定義中斷或異常處理過程的段描述符。LDT局部描述符表,應用於多任務系統中,通常每個任務使用一個LDT表,為每個任務提供了可定址記憶體空間的範圍。為了讓CPU能定位GDT表、IDT表和當前LDT表,需要為CPU分別設定GDTR IDTR LDTR三個特殊寄存器。
從中可以看出,LDT本身也是GDT的一部分,該段中存放著任務的資料區段和程式碼片段。中斷描述符IDT儲存在核心代碼中。任務狀態段TSS用於在任務切換時CPU自動儲存或回複相關任務的當前上下文。
從以上兩圖可以看出,任務0的代碼資料儲存在核心代碼中,每個進程的虛擬位址空間可定址範圍是64MB。
全域以及中斷描述符資料結構:
typedef struct desc_struct {
unsigned long a,b;
}desc_table[256];
在32位的機器中,由於每個描述符佔用了8位元組,所以用兩個long型數表示一個描述符。
下面看下sched_init程式。
void sched_init(void)
{
int i;
struct desc_struct * p; //這裡定義一個描述符結構
if (sizeof(struct sigaction) != 16)
panic("Struct sigaction MUST be 16 bytes");
//下面兩句參考本文的圖一,FIRST_TSS_ENTRY和FIRST_LDT_ENTRY是在sched.h中定義的宏,為任務零的狀態段描述符和局部描述符,為4和5。所以下面兩句用了設定任務0的狀態和ldt。
set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
//讓p指向任務1的狀態和ldt描述符表項的基地址,從任務1開始初始化後續各任務的資料結構
p = gdt+2+FIRST_TSS_ENTRY;
for(i=1;i<NR_TASKS;i++) {
task[i] = NULL;
p->a=p->b=0;//狀態段清零
p++;
p->a=p->b=0;//ldt段清零
p++;
}
/* Clear NT, so that we won't have troubles with that later on */
__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
ltr(0);
lldt(0);
outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */
outb_p(LATCH & 0xff , 0x40); /* LSB */
outb(LATCH >> 8 , 0x40); /* MSB */
set_intr_gate(0x20,&timer_interrupt);
outb(inb_p(0x21)&~0x01,0x21);
set_system_gate(0x80,&system_call);
}