Linux內核學習1-1——啟動(bootsect.s)

  好了,終於來到具體的講解了,其實這個彙編文件,我已經看過好幾次了,每次看都很新鮮,每次看都會理解得更深入(可能這就是所謂的"溫故而知新"吧)。本人的記性十分差,看過的東西、寫過的代碼都很快就被忘記,正如這彙編文件,每次看都很新鮮。所以我有必要把每個點都寫得清清楚楚,一來可以讓自己能記錄下容易被忘記的部份,而且還可以讓初學者很基本地瞭解整個過程(那怕只是一句彙編語句)。

  正如我前面一章提到過,bootsect.s主要是做搬運工,即把setup.s和內核部份(內核部份在編譯時把head.s也編譯到裡頭了,在內核部份的前端)搬到內存中。由於整個文件比較長,所以我將會將其拆分為一段一段講解,也可以當作是功能劃分。下邊代碼注釋中一部份來自本人理解而寫出的,一部份來自《Linux内核完全注释》。

宏定義:

也許這名稱不太貼切,不過這裡確實是定義了一些後面會用到,卻不會被當作變量的數據,就像C語言的寵定義,編譯時,宏會被替換成具體數據。

 1 !
 2 ! SYS_SIZE is the number of clicks (16 bytes) to be loaded.
 3 ! 0x3000 is 0x30000 bytes = 196kB, more than enough for current
 4 ! versions of linux
 5 !
 6 SYSSIZE = 0x3000  ! 指編譯連接后的system 模組大小。SYS_SIZE 是段地址。
             ! 0x3000 實際地址為 0x30000(即 192kb),足夠存放當前版本內核。

7 ! 8 ! bootsect.s (C) 1991 Linus Torvalds 9 ! 10 ! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves 11 ! iself out of the way to address 0x90000, and jumps there. 12 ! 13 ! It then loads 'setup' directly after itself (0x90200), and the system 14 ! at 0x10000, using BIOS interrupts. 15 ! 16 ! NOTE! currently system is at most 8*65536 bytes long. This should be no 17 ! problem, even in the future. I want to keep it simple. This 512 kB 18 ! kernel size should be enough, especially as this doesn't contain the 19 ! buffer cache as in minix 20 ! 21 ! The loader has been made as simple as possible, and continuos 22 ! read errors will result in a unbreakable loop. Reboot by hand. It 23 ! loads pretty fast by getting whole sectors at a time whenever possible. 24 25 .globl begtext, begdata, begbss, endtext, enddata, endbss 26 .text 27 begtext: 28 .data 29 begdata: 30 .bss 31 begbss: 32 .text 33 34 SETUPLEN = 4 ! nr of setup-sectors            !setup.s 所占的扇區數 35 BOOTSEG = 0x07c0 ! original address of boot-sector     !bootsect.s 起始地址(段地址,下同)(被BIOS搬到此地址) 36 INITSEG = 0x9000 ! we move boot here - out of the way   !bootsect.s 將會被搬到的內存起始地址 37 SETUPSEG = 0x9020 ! setup starts here             !setup.s 起始地址 38 SYSSEG = 0x1000 ! system loaded at 0x10000 (65536).   !內核被載入的內存地址 39 ENDSEG = SYSSEG + SYSSIZE ! where to stop loading           !內核載入結束地址 40 41 ! ROOT_DEV: 0x000 - same type of floppy as boot. 42 ! 0x301 - first partition on first drive etc 43 ROOT_DEV = 0x306      ! 指定根文件系统设备是第2个硬盘的第1个分区。这是Linux老式的硬盘命名方式,具体值的含义如下:                  ! 设备号=主设备号*256 + 次设备号(也即dev_no = (major << 8) + minor)                  ! (主设备号:1-内存, 2-磁盘, 3-硬盘, 4-ttyx, 5-tty, 6-并行口, 7-非命名管道)                  ! 0x300 - /dev/hd0 - 代表整个第1 个硬盘;                  ! 0x301 - /dev/hd1 - 第1 个盘的第1 个分区;                  ! ...                  ! 0x304 - /dev/hd4 - 第1 个盘的第4 个分区;                  ! 0x305 - /dev/hd5 - 代表整个第2 个硬盘;                  ! 0x306 - /dev/hd6 - 第2 个盘的第1 个分区;                  ! ...                  ! 0x309 - /dev/hd9 - 第2 个盘的第4 个分区;                  ! 从linux 内核0.95 版后已经使用与现在相同的命名方法了。

以上的都只是一些定義所以沒有什麽好說的,不過這些定義后面都要用,所以得先知道是怎麼一回事,才能更好得往下看。

bootsect模塊搬遷:

   這裡除了代碼搬運外,也做了一定段寄存器的設置,以及棧的設置。

 1 entry start    !告知连接程序,程序从start 标号开始执行。
 2 start:
 3     mov    ax,#BOOTSEG
 4     mov    ds,ax        !把BOOTSEG(0x07c0)寫入DS(數據段寄存器)
 5     mov    ax,#INITSEG
 6     mov    es,ax        !把INITSEG(0x9000)寫入ES(附加段寄存器)
 7     mov    cx,#256        !把cx寫為256(用於循環,由於寄存器是16位的,所以循環256次,可以移動512 bytes,即一個扇區的大小) 
 !清除數據及其標誌(網上有人說這操作 比直接賦0 再清標誌要快,其實這裡所說的標誌我還不太知道哪裡,只知道在CR中的某個或某些標誌)
8 sub si,si        !源地址 ds:si = 0x07c0:0x0000 9 sub di,di        !目的地址 es:di = 0x9000,:0x0000 10 rep      !這兩句語句很簡單,卻很難找到明確的說明,其實可以寫成rep movw,表示重複操作movw ,cx次(256次)。 11 movw      !在網上movw也是有參數的,如果不帶參數,表示有默認值,即 movw es:di, ds:si,
            !意思是把ds:si的數據移動到es:di中,並且每次操作后di,si會自動加1
12 jmpi go,INITSEG     !把bootsect移動到0x90000后,把IP指針指向go.(由於數據是移動過去的,
                   !所以偏移地址是一樣的,這裡就跳到移動后的go的偏移地址)
13 go: mov ax,cs 14 mov ds,ax 15 mov es,ax         16 ! put stack at 0x9ff00. 17 mov ss,ax        !將ds、es和ss都置成移動后的代碼所在的段處(0x9000) 18 mov sp,#0xFF00 ! arbitrary value >>512
                ! 由于代码段移动过了,所以要重新设置堆栈段的位置。
                !
sp 只要指向远大于512 偏移(即地址0x90200)处都可以。
                ! 因为从0x90200 地址开始处还要放置setup 程序,
                ! 而此时setup 程序大约为4 个扇区,因此sp 要指向大
                ! 于(0x200 + 0x200 * 4 + 堆栈大小)处。

                ! 這裡可能需要說明一下棧的結構。由於內存的高地址是棧底,
                ! 低地址是棧頂,而sp存放的是棧頂指針,由於現在棧沒有數據,                
                ! 所以,棧底等於棧頂,實際的棧空間為0xFF00 - (0x200 +
                ! 0x200 * 4) = 0xF500

setup模塊讀取:

 1 ! load the setup-sectors directly after the bootblock.
 2 ! Note that 'es' is already set up.
 3 
 4 load_setup:
 5     mov    dx,#0x0000            ! drive 0, head 0
 6     mov    cx,#0x0002            ! sector 2, track 0
 7     mov    bx,#0x0200            ! address = 512, in INITSEG  ! 0x90200(0x9000:0x0200)
 8     mov    ax,#0x0200+SETUPLEN   ! service 2, nr of sectors
 9     int    0x13                  ! read it
                     ! int 0x13是BIOS的磁盤服務中斷。這里使用了Read Disk Sectors(AH = 2),用於讀取磁盤扇區。操作配置如下:
                     !
AH = 02 
                     ! AL = number of sectors to read (1-128 dec.) 
                     ! CH = track/cylinder number (0-1023 dec., see below) 
                     ! CL = sector number (1-17 dec.) 
                     ! DH = head number (0-15 dec.) 
                     ! DL = drive number (0=A:, 1=2nd floppy, 80h=drive 0, 81h=drive 1) 
                     ! ES:BX = pointer to buffer
                     ! on return: 
                     ! AH = status (see INT 13,STATUS) 
                     ! AL = number of sectors read 
                     ! CF = 0 if successful 
                     !  = 1 if error
                     
! |F|E|D|C|B|A|9|8|7|6|5-0| CX
                     ! | | | | | | | | | |  `----- sector number
                     ! | | | | | | | | `--------- high order 2 bits of track/cylinder
                     ! `------------------------ low order 8 bits of track/cyl number
10     jnc    ok_load_setup         ! ok - continue  ! 如果CF不為1,跳到ok_load_setup處執行,否則執行下邊的操作。
11     mov    dx,#0x0000
12     mov    ax,#0x0000            ! reset the diskette
13     int    0x13           ! Reset Disk System(AH = 0)
                     !
AH = 00 
                     ! DL = drive number (0=A:, 1=2nd floppy, 80h=drive 0, 81h=drive 1) 
                     ! on return: 
                     ! AH = disk operation status (see INT 13,STATUS) 
                     ! CF = 0 if successful 
                     !  = 1 if error
14     j    load_setup        ! 跳回load_setup,重新讀取扇區(如果沒有磁盤或磁盤損壞,將進入死循環)

 載入內核: 

 1 ok_load_setup:
 2 
 3 ! Get disk drive parameters, specifically nr of sectors/track
 4 
 5     mov    dl,#0x00
 6     mov    ax,#0x0800        ! AH=8 is get drive parameters
 7     int    0x13        ! Get Current Drive Parameters
                   ! AH = 08
                   ! DL = drive number (0=A:, 1=2nd floppy, 80h=drive 0, 81h=drive 1)
                   ! on return:
                   ! AH = status  (see INT 13,STATUS)
                   !
BL = CMOS drive type  
                   !   01 - 5¬ 360K 03 - 3« 720K  
                   !   02 - 5¬ 1.2Mb 04 - 3« 1.44Mb
                   ! CH = cylinders (0-1023 dec. see below) 
                   ! CL = sectors per track (see below) 
                   ! DH = number of sides (0 based) 
                   ! DL = number of drives attached 
                   ! ES:DI = pointer to 11 byte Disk Base Table (DBT) 
                   ! CF = 0 if successful 
                   ! = 1 if error
 8     mov    ch,#0x00
 9     seg cs           ! 表示下一條語句的操作數在cs段寄存器所指的段中
10     mov    sectors,cx     ! 暫存cx(ch被置0,cl數據【扇區/磁道】來自上一操作)的數據到變量sectors中
11     mov    ax,#INITSEG
12     mov    es,ax        ! 把es段寄存器設為INITSEG(0x9000)
13 
14 ! Print some inane message
15 
16     mov    ah,#0x03          ! read cursor pos
17     xor    bh,bh        ! 異或清零
18     int    0x10        ! 獲取指針位置
                   ! AH = 03
                  
! BH = video page
 
                  ! on return:
 
                  ! CH = cursor starting scan line (low order 5 bits)
 
                  ! CL = cursor ending scan line (low order 5 bits)
 
                  ! DH = row
 
                  ! DL = column
19 20 mov cx,#24       ! 設置24個字符串長度 21 mov bx,#0x0007 ! page 0, attribute 7 (normal) 22 mov bp,#msg1      ! 把字符串msg1(msg1是一個變量)地址賦給bp 23 mov ax,#0x1301 ! write string, move cursor 24 int 0x10        ! 顯示字符串
                   !
AH = 13h 
                   ! AL = write mode (see bit settings below) 
                   ! = 0 string is chars only, attribute in BL, cursor not moved 
                   ! = 1 string is chard only, attribute in BL, cursor moved  
                   ! = 2 string contains chars and attributes, cursor not moved 
                   ! = 3 string contains chars and attributes, cursor moved 
                   ! BH = video page number 
                   ! BL = attribute if mode 0 or 1 (AL bit 1=0) 
                   ! CX = length of string (ignoring attributes) 
                   ! DH = row coordinate 
                   ! DL = column coordinate 
                   ! ES:BP = pointer to string
                  
! Bit settings for write mode (register AL): 
                   !  |7|6|5|4|3|2|1|0| AL 
                   !  | | | | | | | `---- 0=don't move cursor, 1=move cursor 
                   !  | | | | | | `----- 0=BL has attributes, 1=string has attributes 
                   !  `---------------- unused
                   ! returns nothing
25 
26 ! ok, we've written the message, now
27 ! we want to load the system (at 0x10000)
28 
29     mov    ax,#SYSSEG
30     mov    es,ax           ! segment of 0x010000
31     call   read_it      ! 讀取磁盤上的system模塊(包括head和內核),es為輸入參數
32     call   kill_motor     ! 關閉驅動器馬達,就可以知道驅動器狀態了

 獲取根設備,并轉向setup模塊: 

 1 ! After that we check which root-device to use. If the device is
 2 ! defined (!= 0), nothing is done and the given device is used.
 3 ! Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending
 4 ! on the number of sectors that the BIOS reports currently.
 5 
 6     seg cs
 7     mov    ax,root_dev
 8     cmp    ax,#0        ! 判斷root_dev是否為0,在前面定義部份有說明
 9     jne    root_defined    ! 如果不等於,跳到root_defined處執行。
10     seg cs
11     mov    bx,sectors     ! 把扇區數(扇區/磁道)放入bx中。
12     mov    ax,#0x0208        ! /dev/ps0 - 1.2Mb  !把0x0208寫入ax,用於過后保存到root_dev(前提是判斷條件成立),下同
13     cmp    bx,#15       ! 如果扇區數等於15,跳到root_defined,下同
14     je    root_defined
15     mov    ax,#0x021c        ! /dev/PS0 - 1.44Mb
16     cmp    bx,#18
17     je    root_defined
18 undef_root:
19     jmp undef_root       ! 如果是未定義root,將進入死循環
20 root_defined:
21     seg cs
22     mov    root_dev,ax    ! 把ax的值賦給root_dev變量。
23 
24 ! after that (everyting loaded), we jump to
25 ! the setup-routine loaded directly after
26 ! the bootblock:
27 
28     jmpi    0,SETUPSEG    ! 跳到SETUPSEG(0x9000),偏移值為0處,運行。

   好了到這里主流程已經結束了,下面將講bootsect.s剩下的部份,主要是“函數”的定義及變量的定義。

內核載入“函數”定義:

  這裡可複雜了,不是因為太長,而是因為跳轉得比較多,會很容易被搞混亂了 。

 1 ! This routine loads the system at address 0x10000, making sure
 2 ! no 64kB boundaries are crossed. We try to load it as fast as
 3 ! possible, loading whole tracks whenever we can.
 4 !
 5 ! in:    es - starting address segment (normally 0x1000)
 6 !
  ! “局部變量”定義
7 sread: .word 1+SETUPLEN ! sectors read of current track  ! 已讀扇區數,由於一開始已經讀出了bootsect和setup模塊,所以初值要賦為1 + SETUPLEN 8 head: .word 0 ! current head            ! 磁頭號 9 track: .word 0 ! current track            ! 磁道號 10 11 read_it: 
! 测试输入的段值。从盘上读入的数据必须存放在位于内存地址64KB 的边界开始处,否则进入死循环。
! 清bx 寄存器,用于表示当前段内存放数据的开始位置。  

12 mov ax,es         ! 用ax獲取es寄存器的值。 13 test ax,#0x0fff 14 die: jne die ! es must be at 64kB boundary 15 xor bx,bx    ! bx is starting address within segment 16 rp_read:
! 判断是否已经读入全部数据。比较当前所读段是否就是系统数据末端所处的段(#ENDSEG),
! 如果不是就跳转至下面ok1_read 标号处继续读数据。否则退出子程序返回。

17 mov ax,es        ! 由於一個段地址只能放64kb(0xffff),所以這裡會跨多個段操作。
                 ! SYSSIZE = 0x3000,所以需要3次迭代(es每次累加0x1000,執行到這裡,內存地址應為0x1000)
18 cmp ax,#ENDSEG ! have we loaded all yet?  ! 是否已经加载了全部数据? 19 jb ok1_read       ! 如果未載入完畢,跳到ok1_read 20 ret           ! 返回調用處 21 ok1_read: 22 seg cs 23 mov ax,sectors     ! 用ax獲取每磁道的扇區數 24 sub ax,sread      ! sread為已經讀扇區數(一個磁道內),ax - sread 為未讀扇區數 25 mov cx,ax 26 shl cx,#9        ! cx × 512(0x200是扇區大小,一個扇區512bytes),表示剩餘未讀的字節數 27 add cx,bx        ! bx 是當前偏移地址(來自上一次操作)。結果是指 已經讀取的數量 + 未讀取的數量
                 ! 用於判斷當前段的剩餘空間是否能夠把剩餘數據讀入,(因為段只有64kb,system卻有192kb)
                 ! 以便段地址的變化。
28 jnc ok2_read      ! 如果沒有溢出,或者 29 je ok2_read      ! 如果為0(65536 = 0x10000,在16位無符號數表達時為0),跳到ok2_read 30 xor ax,ax        ! ax清零 31 sub ax,bx        ! 由於寄存器是16位無符號的,所以0 - bx = 65536 - bx,結果為段內剩餘內存數 32 shr ax,#9        ! ax ÷ 512 ,表示段內剩餘內存可以存放的扇區數 33 ok2_read: 34 call read_track    ! 讀取一個磁道 35 mov cx,ax        ! 把段內剩餘可存放扇區數賦給cx 36 add ax,sread      ! ax加上已讀扇區數 37 seg cs 38 cmp ax,sectors     ! 與每磁道的扇區數作比較 39 jne ok3_read      ! 如果不等就跳到ok3_read 40 mov ax,#1      ! ax置0,用於磁頭號轉換, 41 sub ax,head      ! 由於軟盤只有兩面,所以磁頭號只有0和1兩個 42 jne ok4_read    ! 如果被設磁頭號與當磁頭號不同,并跳到ok4_read 43 inc track   ! 磁道號加1 44 ok4_read: 45 mov head,ax      ! 把ax賦給變量head 46 xor ax,ax     ! ax清0 47 ok3_read: 48 mov sread,ax      ! 當前的ax為 已讀扇區數 + 段內還可存放扇區數 49 shl cx,#9     ! 把cx轉成要存放字節數 50 add bx,cx       ! cx + 當前段內偏移地址 51 jnc rp_read      ! 如果沒有溢出,跳到rp_read執行 52 mov ax,es       ! ax獲取當前段地址 53 add ax,#0x1000    ! ax + 0x1000,加0x1000就是指向下一段(因為段內只能尋址64kb,即0~0xffff,只能把段寄存器指向下一段才能被尋址) 54 mov es,ax       ! es段地址被修改 55 xor bx,bx       ! bx(段內偏移地址)清0 56 jmp rp_read     ! 跳到rp_read繼續執行 57 58 read_track: 59 push ax      ! 數據保護,入棧 60 push bx 61 push cx 62 push dx 63 mov dx,track   ! dx獲取當前磁道號 64 mov cx,sread   ! cx獲取當前已讀扇區數(扇區號) 65 inc cx       ! 已讀扇區數自增1 66 mov ch,dl     ! 把磁道號放到ch中 67 mov dx,head    ! 把磁頭號放到dx中 68 mov dh,dl     ! 把dl(磁頭號)放到dh中 69 mov dl,#0     ! 把dl置0 70 and dx,#0x0100  ! 對dx進行與操作 71 mov ah,#2     ! 置功能號為2,即為讀取扇區 72 int 0x13     ! 前面這裡有提到,或在文章最后的網址中查找 73 jc bad_rt     ! 如果讀取失敗,跳到bad_rt 74 pop dx 75 pop cx 76 pop bx 77 pop ax       ! 恢復數據,出棧 78 ret        ! 返回調用處 79 bad_rt: 80 mov ax,#0     81 mov dx,#0 82 int 0x13     ! 復位磁盤系統,前面這裡有提到,或在文章最后的網址查找 83 pop dx 84 pop cx 85 pop bx 86 pop ax      ! 恢復數據,出棧 87 jmp read_track  ! 執行完畢,跳到read_track重讀扇區

   不知道這樣注釋夠不夠清晰,這裡基本把最簡單的語句也注釋了(希望不會被說大累贅吧),上面的理解大概就這樣,如果有不理解的可以提出哦(有錯請指正),這裡就注釋都挺耗時的,不過學習本來就是個耗時的過程,如果找到我方法可以把所耗的時間縮短。

停止驅動電機及變量聲明:

 1 /*
 2  * This procedure turns off the floppy drive motor, so
 3  * that we enter the kernel in a known state, and
 4  * don't have to worry about it later.
 5  */
 6 kill_motor:
 7     push dx      ! dx數據保護
 8     mov dx,#0x3f2   ! 软驱控制卡的驱动端口,只写。
 9     mov al,#0     ! A 驱动器,关闭FDC,禁止DMA 和中断请求,关闭马达。
10     outb         ! 将al中的内容输出到dx指定的端口去。(不知道爲什麽這個指令沒找到具體的說明,
              ! 不知道是否已經被修改了,out倒是有)
11 pop dx       ! dx數據恢復 12 ret        ! 返回調用 13 14 sectors: 15 .word 0      ! sectors聲明為16位的變量 16 17 msg1: 18 .byte 13,10            ! (字節),換行、回車,下同 19 .ascii "Loading system ..."   ! ascii碼值為"Loading system ...",其中占了18個字節 20 .byte 13,10,13,10         ! 21
! 寫入根設備號(0x901FC地址為如下數據,這代碼空間(0x90000~0x901FF)將會被覆蓋,剩下被覆蓋部份在setup模塊被執行,可見下一節一開始的表 22 .org 508                ! 表示下面语句从地址508(0x1FC)开始,所以root_dev在启动扇区的第508开始的2个字节中。 23 root_dev: 24 .word ROOT_DEV          ! root_dev聲明為16位的變量 25 boot_flag: 26 .word 0xAA55           ! 硬盤有效標誌(與MBR有關),要在這里瞭解一下MBR, 27                     ! http://zh.wikipedia.org/wiki/MBR 28 .text 29 endtext: 30 .data 31 enddata: 32 .bss 33 endbss:

  

  到此,bootsect.s文件的講述完成。下一步將進入setup.s的講述。

  由於方便瞭解,本文的分段是按順序劃分的,所以可以把各段連接回去。本文使用分段及注釋的方式講述,這樣可以更好地瞭解各個小區域的功能,由於採用了此方式,所以分段也沒有做總結(因為段標題基本就是其總結了)。如果有任何不瞭解及錯處,請提出,謝謝。

BIOS中斷指令網址:http://stanislavs.org/helppc/idx_interrupt.html

原文地址:https://www.cnblogs.com/bakasen/p/linuxStudy1-1.html