Linux讀核日記

今天開始我的讀核罹難記.第一次讀內核,實在難度很大。

面對近123 M 的源碼,困惑是難免的所以我決定先從大面上把握,再在某一些具體的點上切入.這樣一來linux 的啓動過程便十分重要,因此我先用dmesg命令察看一下linux啓動時打出的消息.

(我想源文件應在/usr/src/linux/init/main.c)

內核的啓動最後是到 start_kernel ( in /init/main.c )也就是說啓動的過程是從 head.S ( arch/i386/boot/ ) 一直運行到 main.c(start_kernel) .它的作用是完成開機後的設置與內核的初始化,然後,系統究竟入一個無限的循環中等待用戶的輸入,調用fork來產生子進程.從而達到交互式操作系統的設計要求.

啓動系統

PC機加電開始啓動時,80X86的處理器(CPU)在實模式下自檢,開始執行物理地址 0xFFFF0ROM-BIOS的起始地址處的代碼。PC機的BIOS進行系統自檢,初始化中斷向量表到物理地址0x0。然後把引導設備的第一個扇區加 載到地址0x7C00,執行此處的指令。到這裏與linux無關,x86系列的硬件設置如此.

linux的內核本身是不能自舉的,所以liloloadlin的作用就是加載系統內核.有關lilo的原理可以參考liloreadme.從加電到內核加載的過程是:加電->執行BIOS->加載第一扇區->lilo->加載內核

Linux內核的最初部分代碼是用匯編語言寫的(文件是boot/bootsect.s)。它首先把自身這部分代碼移到絕對地址0x90000,把下面的2K代碼從引導設備加載到地址0x90200上,內核的其餘部分加載到地址 0x10000處。在加載系統時顯示“loading...”. 然後,程序控制權交給另一個實模式彙編程序(boot/Setup.S)。接下來,此程序把整個系統從地址0x10000移到地址0x1000,進入保護 模式。程序控制轉給系統的其餘部分  即地址0x1000

下一個步驟是系統內核的解壓過程,這部分代碼在地址0x1000(文件/Boot/head.S),該段程序初始 化寄存器,然後執行decompress_kernel(),這個函數源於zBoot/inflate.czBoot/unzip.c zBoot/misc.c三個文件

                             Loading ....[ bootsect.S ]             

  uncompress .....[ decompress_kernel() ] 

  main.c ---> start_kernel() 開始.       

  開始 printk(banner);                    

 

由於我主要負責bootsect.S部分,所以重點分析它。這個程序是linuxkernel的第一個程序,包括了linux自己的bootstrap程序,但是在說明這個程序前,必須先說明一般IBMPC開機時的動作(此處的開機是指"打開PC的電源" ): 
  一般PC在電源一開時,是由內存中地址FFFF:0000開始執行(這個地址一定在ROMBIOS中,ROMBIOS一般是在FEOOOhFFFFFh),而此處的內容則是一個jump指令,jump到另一個位於ROMBIOS中的位置,開始執行一系列的動作,包括了檢查RAMkeyboard,顯示器,軟硬磁盤等等,這些動作是由系統測試碼(systemtestcode)來執行的,隨着製作BIOS廠商的不同而會有些許差異,但都是大同小異。 
  緊接着系統測試碼之後,控制權會轉移給ROM中的啓動程序(ROMbootstraproutine) ,這個程序會將磁盤上的零道零扇區讀入內存中(這就是一般所謂的bootsector,因爲我曾接觸過電腦病毒,所以很早以前就聽過它的大名),至於被讀到內存的哪裏呢?--絕對位置07C0:0000(07C00h),這是IBM系列PC的特性。而位在linux開機磁盤的bootsector上的正是linuxbootsect程序,也就是說,bootsect是第一個被讀入內存中並執行的程序。現在,可以開始來看看到底bootsect做了什麼。 

第一步 
  首先,bootsect將它"自己"從被ROMBIOS載入的絕對地址0x7C00處搬到0x90000處,然後利用一個jmpi(jumpindirectly)的指令,跳到新位置的jmpi的下一行去執行,關鍵的assemblycode如下:  
                  
                    (
搬移bootsect本身
                  
                  
                   jmpigo,INITSEC 
                   go: 
                  
                   .  

                                .                                              
  表示將跳到CS0x9000IPoffset"go"的位置(CS:IP=0x9000:offsetgo),其中INITSEC=0x9000定義於程序開頭的部份,而go這個label則恰好是下一行指令所在的位置。 

第二步 
  接着,將其它segmentregisters包括DSESSS都指向0x9000這個位置,與CS看齊。另外將SPDX指向一任意位移地址(offset),這個地址等一下會用來存放磁盤參數表  (diskpara-metertable) 
  提到磁盤參數表,就必須提到BIOS中斷1Eh。先簡單的介紹一下BIOS的中斷服務:80x86將內存最低的256*4byte保留給256箇中斷向量(每個interruptvector大小爲4byte,所以一共有256*4=1024byte),而其中的第1Eh個向量指向"磁盤參數表",這個表會告訴電腦如何去讀取磁盤機,而我們所要做的事是搬移磁盤參數表到剛纔所設定的任意地址。 
  接着,改變搬移來的參數表的參數,以符合我們的需要。再將中斷向量1Eh指向我們所修改過的磁盤參數表,然後呼叫BIOSinterruptint13h(function0,即AH=0)重置磁盤控制卡及磁盤驅動器,之後磁盤機就會照我們的意思動作了。如果你曾traceDOS kernel,你會發現,上述的動作在DOS中也有類似的對應流程。 
   
現在來看看關鍵的程序碼:.  
 
              .              
              push#0         
              popfs         
              movbx,#0x78 
              .          

(使GS:SI=FS:BX,指向磁盤參數表, 再將GS:SI所指地址的內容搬移6wordES:DI所指的地址


  此段程序是將FS:BX調整成0000:0078,接着再將GS:SI的內容設成與FS:BX相同,此處0x78h即爲int1Eh的起始位置(7*16+8=120,(1*16+14)*4=120)。調整ES:DI爲剛纔所設定的任意地址,從GS:SI搬移6word(12byte)ES:DI所指的位置,顯然磁盤參數表的長度就是6word(不過事實上,磁盤參數表的確實長度是11byte)。關於磁盤參數表 ,有興趣的讀者可自行參閱講述BIOSinterruptservices的技術手冊,會有詳細的說明。 
 
  我用了debug自行觀察自家機器上DOS的磁盤參數表的起始位置(int 1Eh的內容)。以下是我做的實驗的情形(我使用的系統是MSDOS6.2): 

C:>debug 
       -d0000:0000 
       0000:00008A101601F4067000-1600CB04F4067000......p.......p. 
       0000:0010F40670000301790E-43EB00F0EBEA00F0..p...y.C....... 
       0000:002004108E340C118E34-5700CB046F00CB04...4...4W...o... 
       0000:00308700CB0408079433-B700CB04F4067000.......3......p. 
       0000:00400C01790E4DF800F0-41F800F0BA165F06..y.M...A....._. 
       0000:005039E700F01B01790E-70118E341201790E9.....y.p..4..y. 
       0000:006000E000F085175F06-6EFE00F0EE067000......_.n.....p. 
       0000:007053FF00F0A4F000F0-220500003E4600C0S......."...>F.. 
 
  
由上圖中可知,在DOS中磁盤參數表的起始位置(int1Eh的內容)0000:0522。接着觀察DOS中位置0000:0522開始的11byte,也就是磁盤參數表的內容 
     

C:>debug 
      -d0000:0520l10 
      0000:05204D53DF022502121B-FF54F60F08000000MS..%....T...... 
 
   
11byte即爲磁盤參數表的內容(分別是byte00h0Ah) 
  在程序中我們所更動的是第五個byte(byte04h),改爲18h(在上圖例子中爲12h),這 
byte的功能是定義磁軌上一個磁區的資料筆數。關鍵的程序碼如下:  
               .                             
               movb4(di),*18        
               .                           

第三步 
  接着利用BIOS中斷服務int13h的第0號功能,重置磁盤控制器,使得剛纔的設定發揮功能。  
               .                  
               xorah,ah       
               xordl,dl        
               int0x13         
               .                  

第四步 
  完成重置磁盤控制器之後,bootsect就從磁盤上讀入緊鄰着bootsectsetup程序,也就是以後將會介紹的setup.S,此讀入動作是利用BIOS中斷服務int13h的第2號功能。setupimage將會讀入至程序所指定的內存絕對地址0x90200處,也就是在內存中緊鄰着bootsect所在的位置。待setupimage讀入內存後,利用BIOS中斷服務int13h的第8號功能讀取目前磁盤機的參數。 

第五步 
  再來,就要讀入真正linuxkernel了,也就是你可以在linux的根目錄下看到的"v mlinuz"。在讀入前,將會先呼叫BIOS中斷服務int10h的第3號功能,讀取遊標位置,之後再呼叫BIOS中斷服務int10h的第13h號功能,在螢幕上輸出字符串"Loading",這個字符串在bootlinux時都會首先被看到,相信大家應該覺得很眼熟吧。 
  linuxkernel將會被讀入至內存絕對地址0x10000處,鍵關的程序碼如下:  

           .                                  
           movax,#SYSSEG        
           moves,ax                      
           callread_it                     
           callkill_motor                
           .                                  
 
  其中SYSSEG於程序開頭時定義爲0x1000,先將ES內容設爲0x1000,接着在read_it這個子程序便以ES爲目的地的節地址,將kernel讀入內存中。

 

第六步 
  接下來做的事是檢查rootdevice,之後就仿照一開始的方法,利用indirectjump跳至剛剛已讀入的setup部份,程序碼如下
           jmpi0,SETYPSEG 
  其中SETUPSEG已在先前定義爲0x9020,所以CS:IP會設定爲9020:0000,即跳到絕對地址爲0x90200,也就是setup的起點。而bootsect也大功告成了。 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章