【版權所有,轉載請註明出處。出處:http://www.cnblogs.com/joey-hua/p/5596746.html 】 首先看main.c里的初始化函數main函數裡面有個函數是對進程調度的初始化,sched_init()函數,次函數在sched.c中實現: 首先初始化任務0的TTS,F ...
【版權所有,轉載請註明出處。出處:http://www.cnblogs.com/joey-hua/p/5596746.html 】
首先看main.c里的初始化函數main函數裡面有個函數是對進程調度的初始化,sched_init()函數,次函數在sched.c中實現:
// 調度程式的初始化子程式。 void sched_init (void) { int i; struct desc_struct *p; // 描述符表結構指針。 if (sizeof (struct sigaction) != 16) // sigaction 是存放有關信號狀態的結構。 panic ("Struct sigaction MUST be 16 bytes"); // 設置初始任務(任務0)的任務狀態段描述符和局部數據表描述符(include/asm/system.h,65)。 set_tss_desc (gdt + FIRST_TSS_ENTRY, &(init_task.task.tss)); set_ldt_desc (gdt + FIRST_LDT_ENTRY, &(init_task.task.ldt)); // 清任務數組和描述符表項(註意i=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; p++; } /* Clear NT, so that we won't have troubles with that later on */ /* 清除標誌寄存器中的位NT,這樣以後就不會有麻煩 */ // NT 標誌用於控製程序的遞歸調用(Nested Task)。當NT 置位時,那麼當前中斷任務執行 // iret 指令時就會引起任務切換。NT 指出TSS 中的back_link 欄位是否有效。 __asm__ ("pushfl ; andl $0xffffbfff,(%esp) ; popfl"); // 複位NT 標誌。 ltr (0); // 將任務0 的TSS 載入到任務寄存器tr。 lldt (0); // 將局部描述符表載入到局部描述符表寄存器。 // 註意!!是將GDT 中相應LDT 描述符的選擇符載入到ldtr。只明確載入這一次,以後新任務 // LDT 的載入,是CPU 根據TSS 中的LDT 項自動載入。 // 下麵代碼用於初始化8253 定時器。 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); }
首先初始化任務0的TTS,FIRST_TSS_ENTRY為4,表示在描述符表的索引是4。因為gdt是desc_struct類型為8個位元組,剛好是一個描述符的長度,所以這裡的gdt+4可以理解為gdt[4]。剛好對應的是TSS0。
描述符表的內容如下:
0-沒有用nul,1-代碼段cs,2-數據段ds,3-系統段syscall,4-任務狀態段TSS0,5-局部表LTD0,6-任務狀態段TSS1,等。
//// 在全局表中設置任務狀態段/局部表描述符。 // 參數:n - 在全局表中描述符項n 所對應的地址;addr - 狀態段/局部表所在記憶體的基地址。 // type - 描述符中的標誌類型位元組。 // %0 - eax(地址addr);%1 - (描述符項n 的地址);%2 - (描述符項n 的地址偏移2 處); // %3 - (描述符項n 的地址偏移4 處);%4 - (描述符項n 的地址偏移5 處); // %5 - (描述符項n 的地址偏移6 處);%6 - (描述符項n 的地址偏移7 處); #define _set_tssldt_desc(n,addr,type) \ __asm__ ( "movw $104,%1\n\t" \ // 將TSS 長度放入描述符長度域(第0-1 位元組)。 "movw %%ax,%2\n\t" \ // 將基地址的低字放入描述符第2-3 位元組。 "rorl $16,%%eax\n\t" \ // 將基地址高字移入ax 中。 "movb %%al,%3\n\t" \ // 將基地址高字中低位元組移入描述符第4 位元組。 "movb $" type ",%4\n\t" \ // 將標誌類型位元組移入描述符的第5 位元組。 "movb $0x00,%5\n\t" \ // 描述符的第6 位元組置0。 "movb %%ah,%6\n\t" \ // 將基地址高字中高位元組移入描述符第7 位元組。 "rorl $16,%%eax" \ // eax 清零。 ::"a" (addr), "m" (*(n)), "m" (*(n + 2)), "m" (*(n + 4)), "m" (*(n + 5)), "m" (*(n + 6)), "m" (*(n + 7))) //// 在全局表中設置任務狀態段描述符。 // n - 是該描述符的指針(向量);addr - 是描述符中的基地址值。任務狀態段描述符的類型是0x89。 #define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr, "0x89") //// 在全局表中設置局部表描述符。 // n - 是該描述符的指針(向量);addr - 是描述符中的基地址值。局部表描述符的類型是0x82。 #define set_ldt_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr, "0x82")
因為TSS最小尺寸是104位元組,所以第一句是把長度104賦值給TTS0描述符的第0-1位元組,描述符的格式如 描述符格式 ;第二句是把ax也就是addr也就是任務聯合的第一個任務的tss地址賦值給*(n+2)處,因為是movw字,所以也就是描述符的第2-3位元組處。接下來填充第4位元組,然後把類型type填充到第5位元組,最後把剩餘的位元組填充。
初始化任務0的ldt的方法也是類似,好了,這裡初始化完成任務0的TSS和LDT。
sched_init接下來是清空除了任務0的所有任務的數組和對應的描述符,這個好理解。
下麵是載入任務0的TSS到任務寄存器tr,載入ldt到局部描述符表寄存器ldtr,sched.h:
/* * 尋找第1 個TSS 在全局表中的入口。0-沒有用nul,1-代碼段cs,2-數據段ds,3-系統段syscall * 4-任務狀態段TSS0,5-局部表LTD0,6-任務狀態段TSS1,等。見head.s */ // 全局表中第1 個任務狀態段(TSS)描述符的選擇符索引號。 #define FIRST_TSS_ENTRY 4 // 全局表中第1 個局部描述符表(LDT)描述符的選擇符索引號。 #define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1) // 巨集定義,計算在全局表中第n 個任務的TSS 描述符的索引號(選擇符)。 #define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3)) // 巨集定義,計算在全局表中第n 個任務的LDT 描述符的索引號。 #define _LDT(n) ((((unsigned long) n)<<4)+(FIRST_LDT_ENTRY<<3)) // 巨集定義,載入第n 個任務的任務寄存器tr。 #define ltr(n) __asm__( "ltr %%ax":: "a" (_TSS(n))) // 巨集定義,載入第n 個任務的局部描述符表寄存器ldtr。 #define lldt(n) __asm__( "lldt %%ax":: "a" (_LDT(n)))
LDTR局部描述符寄存器:16位,高13為存放LDT在GDT中的索引值。
所以FIRST_LDT_ENTRY要左移3位,(((unsigned long) n)<<4)不太好理解,因為先要去掉左移的3位,所以實際值是n<<1,也就是2n。最終的值相當於FIRST_LDT_ENTRY+2n。這樣就好理解了,因為每個任務都有兩個描述符項。
這裡要註意:只明確載入這一次,以後新任務LDT 的載入,是CPU 根據TSS 中的LDT 項自動載入。
接下來是初始化定時器,沒什麼好說的。
接下來兩句最關鍵了,進程調度的引發的誘因就是在下麵初始化的:
// 設置時鐘中斷處理程式句柄(設置時鐘中斷門)。 set_intr_gate (0x20, &timer_interrupt); // 修改中斷控制器屏蔽碼,允許時鐘中斷。 outb (inb_p (0x21) & ~0x01, 0x21);
第一句在系統調用機制分析中有講到,是設置中斷門的,所以這裡就是把system_call.s中的函數timer_interrupt和中斷號0x20關聯起來,下麵一句代碼參考 時鐘中斷 開啟了時鐘中斷也就是0x20號中斷,也就是說時鐘每滴答(10ms)一下就會調用timer_interrupt函數。
到這裡,進程調度的初始化就結束了。