linux0.11啟動與初始化

來源:互聯網
上載者:User

linux0.11啟動與初始化 簡單描述Linux0.11的啟動與初始化過程。  www.2cto.com  啟動過程中需要關註:IDT, GDT, LDT, TSS, 頁表, 堆棧這些資料。 一:啟動過程啟動的代碼檔案為bootsect.s、setup.s、head.s bootsect.s也就是開機磁區的代碼。這段代碼主要是將setup.s和head.s中的內容讀入記憶體的相應地區。然後開始執行setup.s  www.2cto.com  setup.s1:使用BIOS中斷來獲得相關係統資訊:記憶體大小,硬碟分區資訊,顯示卡資訊2:將head.s代碼拷貝到記憶體位址為0X0000的地方。3:載入idt表和gdt表地址4:開啟A20地址線,只有開啟它了才能訪問高於1M地址的記憶體5:重新設定中斷控制器。這之後以前的BIOS中斷號就沒用了。6:置位CR0寄存器的最後一位進入保護模式, 然後用jmpi 0, 8指令跳轉到地址0x08:0x0000處開始執行,也就是head.s的起始程式碼處。 這裡設定的idt表全部為空白,也即這時並不處理任何中斷。gdt表中有三個描述符:0--NULL, 1--核心程式碼片段, 2--核心資料區段描述符。此時核心程式碼片段與核心資料區段:基地址為0x00000000, 限長為:8MB head.s1:將堆棧設定在static_stack處,堆棧大小為1KB2:重新設定設定IDT和GDT,此時全部IDT的都設定為ignore_int,即仍然忽略中斷。    GDT中包含256個描述符。    0--NULL, 1--核心程式碼片段, 2--核心資料區段, 3--保留, 4--進程0的TSS段, 5--進程0的LDT段, 6--進程1的TSS段, 7--進程1的LDT段......    可見系統的GDT中為每個進程都設定了一個TSS和LDT段。    核心程式碼片段和核心資料區段:基地址(0x00000000),限長(16MB)3:設定分頁(由於記憶體管理部分目前沒看到,因此關於進程的頁表如何設定暫不明白,這裡是核心的頁表)    在0x00000000處的第一頁存放“頁目錄”,隨後存放4頁“頁表”,每個頁表對應於頁目錄中的一項。    設定的頁表,要求線性地址等於物理地址。    4個頁表能映射16MB的物理空間,因此這16MB的實體記憶體地址與線性地址是相同的。(0.11的核心沒有PAGE_OFFSET)4:開始執行init/main.c中的main函數。    方式如下:pushl $_main                          # 將main函數地址入棧jmp setup_paging                   # 開始分頁......setup_paging:  .......  ret                                      # 分頁完成後,用ret指令彈出堆棧中的main函數入口地址,並開始執行main函數。 好像核心代碼中常用這種彈出堆棧的方式來執行其他的函數  二:main函數在啟動過程中設定好GDT和頁表後,開始在main函數中設定其他的內容。主要是:設定IDT,裝置初始化,建立進程0,fork出進程1,用進程1來執行init main.c中主要的初始化函數是:trap_init,sched_inittrap_init中調用set_trap_gate來設定相應的中斷描述符表。 下面以0號中斷為例,描述其實現過程1:調用set_trap_gate(0, &divide_error)這個宏定義用來設定IDT表中的第0項的陷阱門描述符。#define set_trap_gate(n, addr)   _set_gate(&idt[n], 15, 0, addr)在_set_gate中:&idt[n]為第n個描述符的地址, 15為描述符的類型(陷阱門),0為描述符的許可權(最高許可權),addr為要調用的代碼的地址。_set_gate宏會調用相應的彙編指令,在&idt[n]處寫入8位元組的描述符。 2:divide_error的實現該函數是以彙編碼的形式實現。與該函數對應有一個處理函數do_divide_error(用C語言實現)對其他的多數陷阱門處理方式也是如此,有一個彙編方式實現的,還有一個C語言實現的處理函數。當中斷0發生時,先調用divide_error,該函數再調用do_divide_error。 void do_divide_error(long esp, long error_code)上面為函數原型。第一個參數為出錯時的代碼地址的指標,第二個參數是錯誤碼。(有些中斷不產生錯誤碼,則錯誤碼設成0)因此在divide_error的彙編代碼中,主要的功能就是將出錯地址的指標和錯誤碼這兩個參數傳遞給do_divide_error函數,同時將目前的資料區段設定為核心資料區段。  sched_init函數該函數設定了進程0的TSS和LDT描述符,並將它們的選擇子載入進了TR和LDTR寄存器。另外該函數設定了時鐘中斷和系統調用 這裡主要說下系統調用的執行,以fork函數為例。 1:在sched_init中初始化系統調用set_system_gate(0x80, &system_call)#define set_system_gate(0x80, &system_call)  _set_gate(&idt[n], 15, 3, addr)可見系統調用也是一個陷阱門。區別是許可權值為3, 因此使用者進程能通過int 0x80的中斷進入核心,執行系統調用。 2:每個系統調用都有一個對應的編號,fork為第二個系統調用,因此fork的調用號為2。當執行fork函數的時候,它會用int 0x80來調用system_call函數。此時fork的調用號被放入eax寄存器中。 3:全部的系統調用函數指標都儲存在數組sys_call_table中。在system_call函數中會執行指令call _sys_call_table(,%eax, 4)來跳轉到eax指定的系統函數代碼上,對fork來說就是sys_fork函數。 4:system_calli) system_call首先將相應的寄存器入棧。pushl %edxpushl %ecxpushl %ebx             這三個寄存器對應了相應的系統調用函數的3個參數                            因此0.11中,系統函數最多隻能有3個參數。ii)將ds和es設定為核心資料區段,將fs設定為使用者進程的資料區段,需要使用者進程的資料時,可使用fs來訪問iii)用call _sys_call_table(,%eax, 4)來執行系統調用iii)檢查當前進程是否處於可執行狀態,檢查當前進程的時間片是否用完,相應的執行scheduleiv)最後是對進程訊號的處理。(訊號機制沒看完)  三:進入使用者態在main函數中相關初始化後,main以進程0的身份進入使用者態。然後調用fork函數,建立進程1,進程1調用init函數init函數載入根檔案系統,運行初始化配置命令,然後執行shell程式,這樣便進入了命令列視窗。 0.11核心中,每個進程都有一個TSS段和一個LDT段,它們儲存在進程描述符strut task_struct結構中。相應段的描述符儲存在GDT表中。在LDT段中,有3個LDT描述符,0--NULL, 1--進程程式碼片段, 2--進程資料區段。進程n的程式碼片段和資料區段:基地址=n*64MB,限長=64MB。(進程0和1的限長為640KB)因此系統中最多有64個進程。 進程0的task_struct為INIT_TASK,進程0的TSS和LDT描述符在sched_init中設定。 main函數調用move_to_user_mode函數來執行進程0,進入使用者態。 0.11核心中所有進程都是屬於使用者態,不像之後的Linux核心裡有核心線程。 move_to_user_mode函數此函數使用iret返回的方式,從核心態進入使用者態。+------------++   ss      +             pushl    $0x17+------------++   esp    +             pushl %%eax                #eax中儲存了esp+------------++  eflags  +             pushfl+------------++  cs       +             pushl $0x0f+------------++   eip     +             pushl $1f                       #目的代碼的位移地址+------------+ 首先採用上面的push指令,將相關的資料壓入堆棧,然後執行iret將它們彈出堆棧。於是進程0從堆棧中的cs:eip指向的代碼開始執行。 四:fork函數進程0執行fork函數建立出進程1.1:調用get_free_page為進程描述符分配記憶體。p = (struct task_struct *) get_free_page();這一頁記憶體,前面儲存task_struct記憶體, 頁尾為進程的核心棧,當一個使用者程式調用系統函數進入核心態後,系統函數執行時使用的棧就是這個。2:設定進程的task_struct結構體3:記憶體拷貝,將父親進程的記憶體拷貝給新進程。4:設定新進程在GDT中的TSS和LDT描述符 有關fork最主要的是弄明白了,為什麼它可以“返回”兩次。1:調用fork時,CPU自動將父進程的返回地址入棧(即eip寄存器入棧)2:建立子進程的task_struct後,將TSS段中的eax欄位設成0,eip欄位設成父進程的返回地址。3:將子進程的狀態設成TASK_RUNNING(就緒狀態)4:fork函數以子進程的pid返回。5:等到執行schedule,調度到子進程時。會自動將子進程的TSS內容載入進寄存器。因此這時CPU中eax寄存器值為0, eip為父進程的返回地址。所以子進程從fork函數的下一條指令開始執行,傳回值在eax中,為0。 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.