linuxlinux0.11源碼學習——bootsect.s學習


  由於一直想寫一個自己的操作系統,網上推薦了《linux內核完全注釋》。自學了一個星期,感覺這本書還是很好的,同時寫下關於內核代碼的理解,如果有什么不對的對方,歡迎大家一起來交流。

   在內核引導啟動程序中,有3個文件,bootsec.s,setup.s head.s。關於這3個源代碼,網上有很多人都有詳細的解釋,但是有很多人的文章中都是對每行代碼的解釋,但是關於整個代碼的整體框架沒有很多的解釋。在這里我想提出自己對代碼的理解,我不會每行都解釋,只是對很重要的部分做出自己的理解。

關於bootsec.s主要做了如下的幾件事:

  1. 由於PC開機后,BIOS將可移動的設備的第一個扇區,讀入到了0x7c00處,總共512B,bootsec.s就在這里,它將自己移到了ox9000處來執行;
  2. 初始化堆棧;關於這個堆棧我對它的理解就是在后面的程序,比如read_track中就用到了pop和push之類的指令,因此在這設置了‘
  3. 將setup.s的模塊裝載在bootsec.s的后面;
  4. 獲取驅動器的參數,這里應該是值軟盤,主要獲取到每個磁道的扇區數量;
  5. 在屏幕中輸出“loading system.....”,然后裝載system模塊即內核模塊;
  6. 確定根文件系統設備;
  7. 段間跳轉,到setup.s中執行;

下面是我對bootsec.s中部分代碼的理解。

  • bootsec.s代碼移動到ox9000中

    [html]  view plain  copy
    1. mov ax,#BOOTSEG  
    2. mov ds,ax  
    3. mov ax,#INITSEG  
    4. mov es,ax  
    5. mov cx,#256  
    6. sub si,si  
    7. sub di,di  
    8. rep  
    9. movsw  
    在這里主要是提醒大家,關於cx的值,由於是要移動一個扇區的數據,一個扇區的大小是512B,這里的cx=256,但是movsw表示移動兩個B,所以256*2=512B,這里大家會忽略,沒注意;
  • 關於load_setup

    [html]  view plain  copy
    1. mov dx,#0x0000  
    2. mov cx,#0x0002  
    3. mov bx,#0x0200  
    4. mov ax,#0200+SETUPLEN  
    5. int ox13  
    仔細看int 0x13這個中斷的各個參數,這里比較重要的是es:bx 將指向setup模塊的在內存中的位置。在移動bootsec.s的代碼最后有一個jmpi go,INITSEG這個指令,此時的cs=0x900不再時0x7c00了,es=cs,於是es:bx指向了0x900:0200處,從而實現了裝載setup模塊的功能。
  • 關於ok_load_setup

    [html]  view plain  copy
    1. seg cs  
    2. mov sectors,cx  
    這里主要是來談談這兩條指令。從代碼的第241行中可以看出sectors是個標量指向一個word長的地址。seg cs表明了sectors的段地址是cs,而不是ds。而且seg cs 的作用范圍只有下一行,不會延生到其他地方。


  • 最后介紹這篇文章中最重要的read_it

    [html]  view plain  copy
    1. read_it:  
    2.     mov ax,es  
    3.     test ax,#0x0fff  
    4. die:    jne die         ! es must be at 64kB boundary  
    5.     xor bx,bx       ! bx is starting address within segment  
    6. rp_read:  
    7.     mov ax,es  
    8.     cmp ax,#ENDSEG      ! have we loaded all yet?  
    9.     jb ok1_read  
    10.     ret  
    11. ok1_read:  
    12.     seg cs  
    13.     mov ax,sectors  
    14.     sub ax,sread  
    15.     mov cx,ax  
    16.     shl cx,#9  
    17.     add cx,bx  
    18.     jnc ok2_read  
    19.     je ok2_read  
    20.     xor ax,ax  
    21.     sub ax,bx  
    22.     shr ax,#9  
    23. ok2_read:  
    24.     call read_track  
    25.     mov cx,ax  
    26.     add ax,sread  
    27.     seg cs  
    28.     cmp ax,sectors  
    29.     jne ok3_read  
    30.     mov ax,#1  
    31.     sub ax,head  
    32.     jne ok4_read  
    33.     inc track  
    34. ok4_read:  
    35.     mov head,ax  
    36.     xor ax,ax  
    37. ok3_read:  
    38.     mov sread,ax  
    39.     shl cx,#9  
    40.     add bx,cx  
    41.     jnc rp_read  
    42.     mov ax,es  
    43.     add ax,#0x1000  
    44.     mov es,ax  
    45.     xor bx,bx  
    46.     jmp rp_read  
    47.   
    48. read_track:  
    49.     push ax  
    50.     push bx  
    51.     push cx  
    52.     push dx  
    53.     mov dx,track  
    54.     mov cx,sread  
    55.     inc cx  
    56.     mov ch,dl  
    57.     mov dx,head  
    58.     mov dh,dl  
    59.     mov dl,#0  
    60.     and dx,#0x0100  
    61.     mov ah,#2  
    62.     int 0x13  
    63.     jc bad_rt  
    64.     pop dx  
    65.     pop cx  
    66.     pop bx  
    67.     pop ax  
    68.     ret  
    69. bad_rt: mov ax,#0  
    70.     mov dx,#0  
    71.     int 0x13  
    72.     pop dx  
    73.     pop cx  
    74.     pop bx  
    75.     pop ax  
    76.     jmp read_track  
    不知道大家在讀這段代碼的時候是怎么理解的,反正我理解了很長時間,不知道它到底想干什么。網上很多都是注釋了每一行,但是沒有解釋總的在干什么,整體的流程是什么。
  • 標量間代碼作用

  1. ok1_read:主要的功能是確定了ax的值,其實嚴格的說是確定了al的值,因為al的值是代表了要讀取的扇區的值,關於最后的幾行代碼
    [html]  view plain  copy
    1. xor ax,ax  
    2. sub ax,bx  
    3. shr ax,#9  
    這幾行代碼是我當時極度不能理解的因為當時我認為ax=0,bx=0,最后不都是0嗎?:),我想說這幾行代碼是以后執行的,到時后bx的值不再是0,而是代表了一個段內讀取了的數據,sub是不帶進位的0-bx正好是64k-段內已讀的數據(不得不佩服linus的基本功),從而得到了段內剩余的空間,從而可以求出還可以讀取最大的扇區數。
  2. ok2_read:調用了read_track(),read_track()的功能其實可以看成read_track(ax),根據ax,主要是al來確定一個磁道內從哪個扇區開始讀數據,從而讀取一個磁道的數據;然后ax=read_track(ax)(偽代碼而已),ax表示讀取的扇區的值,然后再加上已讀的扇區數后比較是否等於每個磁道的扇區數,如果不等,則調用ok3_read(),否則讀取下一個磁頭1,(軟盤有兩個磁頭,0和1,這和硬盤不一樣,硬盤磁頭比較多)
  3. ok4_read:這里為什么不從ok3_read寫起呢,主要是因為linus的代碼能力的厲害之處,大家慢慢也會懂的,只可意會。ok_read4的主要的功能是重新賦值磁頭和ax即扇區的起始地址。
  4. ok3_read:主要是確定了bx的值和es的值。
    [html]  view plain  copy
    1. mov sread,ax  
    2. shl cx,#9  
    3. add bx,cx  
    4. jnc rp_read  
    5. mov ax,es  
    6. add ax,#0x1000  
    7. mov es,ax  
    8. xor bx,bx  
    9. jmp rp_read  
    這里add bx,cx 是確定了bx的值,即段內已經讀取的數據大小,如果bx沒有溢出即超過0xffff,則跳轉到開始的循環,否則段基址es+=0x1000,再從新循環。
  • 整個代碼流程

                            接下了主要講一下關於我對這段代碼的理解。由於軟盤只有兩個磁頭0和1,且只有18個扇區,每個扇區的大小是512B從而代碼開始處的流程應該是如下的

  1. 在ok1_read中從0磁頭讀取的數目最多(18-6)*512B,不會溢出,則會進入到ok2_read()中;如果bx不等於0,且數值比較接近0xffff時會溢出,則卻確定該段內剩余的地址可以讀取最大的扇區數;
  2. 到ok2_read中,從0磁頭讀取從能夠ax開始的整個磁道剩余的扇區數,cx=讀取的扇區數,在加上已讀的扇區數確定是否全部讀完,若是則下一步,否則到4;
  3. 到ok4_read,磁頭變成了1,(只有兩個磁頭,如果原磁頭是1,則增加磁道track),ax=0。
  4. 到ok3_read中,此時ok3_read的操作是針對下一次循環的,根據這一次的循環設置下一次循環的一些變量,bx=段內已經讀取的數據的大小,如果超出64k則增加段基址,由於add是無進位的(這里又體現了linus的高明了);
  5. 從新返回到1,執行循環,知道es的值>ENDSEG;


    到這可以理解為什么我要先介紹ok4_read了,而不是ok3_read了,如果在源代碼中把ok3_read放在ok4_read之前那么代碼會多加幾個jmp命令,雖然便於我們理解,但是代碼長度變長了,原來的代碼體現了代碼的魅力。


轉載的時候請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM