<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                之前已經做了一些理論上的鋪墊,這次我們就可以看代碼了。 # 一、代碼清單 ~~~ ;代碼清單11-1 ;文件名:c11_mbr.asm ;文件說明:硬盤主引導扇區代碼 ;創建日期:2011-5-16 19:54 ;設置堆棧段和棧指針 mov ax,cs mov ss,ax mov sp,0x7c00 ;計算GDT所在的邏輯段地址 mov ax,[cs:gdt_base+0x7c00] ;低16位 mov dx,[cs:gdt_base+0x7c00+0x02] ;高16位 mov bx,16 div bx mov ds,ax ;令DS指向該段以進行操作 mov bx,dx ;段內起始偏移地址 ;創建0#描述符,它是空描述符,這是處理器的要求 mov dword [bx+0x00],0x00 mov dword [bx+0x04],0x00 ;創建#1描述符,保護模式下的代碼段描述符 mov dword [bx+0x08],0x7c0001ff mov dword [bx+0x0c],0x00409800 ;創建#2描述符,保護模式下的數據段描述符(文本模式下的顯示緩沖區) mov dword [bx+0x10],0x8000ffff mov dword [bx+0x14],0x0040920b ;創建#3描述符,保護模式下的堆棧段描述符 mov dword [bx+0x18],0x00007a00 mov dword [bx+0x1c],0x00409600 ;初始化描述符表寄存器GDTR mov word [cs: gdt_size+0x7c00],31 ;描述符表的界限(總字節數減一) lgdt [cs: gdt_size+0x7c00] in al,0x92 ;南橋芯片內的端口 or al,0000_0010B out 0x92,al ;打開A20 cli ;保護模式下中斷機制尚未建立,應 ;禁止中斷 mov eax,cr0 or eax,1 mov cr0,eax ;設置PE位 ;以下進入保護模式... ... jmp dword 0x0008:flush ;16位的描述符選擇子:32位偏移 ;清流水線并串行化處理器 [bits 32] flush: mov cx,00000000000_10_000B ;加載數據段選擇子(0x10) mov ds,cx ;以下在屏幕上顯示"Protect mode OK." mov byte [0x00],'P' mov byte [0x02],'r' mov byte [0x04],'o' mov byte [0x06],'t' mov byte [0x08],'e' mov byte [0x0a],'c' mov byte [0x0c],'t' mov byte [0x0e],' ' mov byte [0x10],'m' mov byte [0x12],'o' mov byte [0x14],'d' mov byte [0x16],'e' mov byte [0x18],' ' mov byte [0x1a],'O' mov byte [0x1c],'K' ;以下用簡單的示例來幫助闡述32位保護模式下的堆棧操作 mov cx,00000000000_11_000B ;加載堆棧段選擇子 mov ss,cx mov esp,0x7c00 mov ebp,esp ;保存堆棧指針 push byte '.' ;壓入立即數(字節) sub ebp,4 cmp ebp,esp ;判斷壓入立即數時,ESP是否減4 jnz ghalt pop eax mov [0x1e],al ;顯示句點 ghalt: hlt ;已經禁止中斷,將不會被喚醒 ;------------------------------------------------------------------------------- gdt_size dw 0 gdt_base dd 0x00007e00 ;GDT的物理地址 times 510-($-$$) db 0 db 0x55,0xaa ~~~ 上面就是配書源碼。我們一點一點看。 # 二、源碼分析 ### (一)設置堆棧和棧指針 ~~~ ;設置堆棧段和棧指針 mov ax,cs mov ss,ax mov sp,0x7c00 ~~~ 這個沒有什么好說的,就是初始化棧。這三行執行后,SS=0; SP=0x7c00; 需要注意的是,這樣設置后,棧的區域從0x0000_7c00向下擴展(不含0x0000_7c00這個字節),該區域包含了很多BIOS數據,包括實模式下的中斷向量表,所以一定要小心。 ### (二)安裝段描述符 ~~~ ;計算GDT所在的邏輯段地址 mov ax,[cs:gdt_base+0x7c00] ;低16位 mov dx,[cs:gdt_base+0x7c00+0x02] ;高16位 mov bx,16 div bx mov ds,ax ;令DS指向該段以進行操作 mov bx,dx ;段內起始偏移地址 ~~~ 怎么理解這段代碼呢? 首先,在代碼清單的95、96行,有 ~~~ gdt_size dw 0 gdt_base dd 0x00007e00 ;GDT的物理地址 ~~~ 作者在這里聲明了標號gdt_base,還初始化了一個雙字——0x0000_7e00; 作者的意圖是從這個地方開始建立全局描述符表GDT。我們的程序就是一個引導扇區,占用了512(=0x200)字節。程序加載的物理地址是0x7c00, 0x7c00+0x200 = 0x7e00. 可見,在物理地址的安排上,引導程序后面緊跟著就是GDT。 目前我們還是處在實模式下,所以要建立GDT,必須將GDT的線性地址(物理地址)轉換成實模式下使用的“段地址:偏移地址”的形式。 mov ax,[cs:gdt_base+0x7c00] ; 這句使了段超越前綴“cs”,表明訪問代碼段中的數據;因為CS=0,所以就把物理地址(0x7c00+gdt_base)處的0x7e00傳送給了ax; 同樣地,將0x0000傳送給dx; 為了把線性地址轉換成邏輯地址,我們用DX:AX除以16,得到的商(AX)就是段地址,余數(DX)就是偏移地址。 mov bx,16??????? ??????? div bx??????????? ??????? mov ds,ax????????????????????????? ;令DS指向該段以進行操作 ??????? mov bx,dx????????????????????????? ;段內起始偏移地址 這幾行執行之后,GDT的邏輯地址就是 DS:BX. ~~~ ;創建0#描述符,它是空描述符,這是處理器的要求 mov dword [bx+0x00],0x00 mov dword [bx+0x04],0x00 ~~~ **處理器規定,GDT中的第一個描述符必須是空描述符。**這是什么原因呢?因為很多時候,寄存器和內存單元的初始值都會為0,再加上程序設計有問題,就會在無意中用全0的索引來選擇描述符,這當然是不好的。因此,處理器要求將第一個描述符定義成空描述符。所以,上面兩行代碼定義了一個空描述符。 ~~~ ;創建#1描述符,保護模式下的代碼段描述符 mov dword [bx+0x08],0x7c0001ff mov dword [bx+0x0c],0x00409800 ~~~ 這兩行用來創建第二個描述符。之前的博文我們已經掌握了數據段和代碼段描述符的格式,所以對這個描述符就不難理解了。 還記得我上一篇博文中寫了一個小程序嗎?[http://blog.csdn.net/longintchar/article/details/50507218](http://blog.csdn.net/longintchar/article/details/50507218) 趕緊用它來分析一下吧: [![seg_des2](https://box.kancloud.cn/2016-02-29_56d3a8fce537b.jpg "seg_des2")](http://img.blog.csdn.net/20160113223951465) 線性基地址:0x0000_7c00 段界限為0x001FF,因為G=0,所以該段的長度是512(2的9次方)字節; 特權級:0 其他字段就不逐個說明了,相信你一定能懂。很明顯,這個描述符定義的段,就是主引導程序所在的區域。 接著看代碼。 ~~~ ;創建#2描述符,保護模式下的數據段描述符(文本模式下的顯示緩沖區) mov dword [bx+0x10],0x8000ffff mov dword [bx+0x14],0x0040920b ~~~ 程序分析的結果是: seg_base = 0XB8000 seg_limit = 0XFFFF S = 1 DPL = 0 G = 0 D/B = 1 TYPE = 2 數據段: 可讀可寫 看來這個段是指向顯存的。 ~~~ ;創建#3描述符,保護模式下的堆棧段描述符 mov dword [bx+0x18],0x00007a00 mov dword [bx+0x1c],0x00409600 ~~~ 這是創建棧段的描述符。程序分析的結果是: ----------------------- seg_base = 0 seg_limit = 0X7A00 S = 1 DPL = 0 G = 0 D/B = 1 TYPE = 6 數據段: 向下擴展,可讀可寫 ------------------------ 正如作者所說:段界限的值0x7a00加上1(0x7a01),就是ESP寄存器所允許的最小值。當執行隱式的棧操作(如PUSH、CALL)時,處理器會檢查ESP的值,一旦發現它小于0x7a01,就會引發異常中斷。如果你還不理解,那么可以把書翻到215頁。作者說在棧操作時,必須符合以下規則: **實際使用的段界限+1 <= (ESP的內容減操作數的長度) <= 0xFFFF_FFFF** 就拿這個例子來說,因為G=0,所以段界限就是0x7a00. 假設現在ESP的內容是0x7a04,此時執行下面的指令: push edx 因為壓入的是雙字,所以處理器會先將ESP的值減去4,于是ESP=0x7a00. 因為0x7a00小于0x7a01,因此會引發異常中斷。 ### (三)LGDT指令 好了,現在描述符已經安裝完畢,接下來的工作是加載描述符表的線性基地址和界限到GDTR寄存器。相關的指令是lgdt. 該指令的格式為: lgdt m48 也就是說,該指令的操作數內存操作數。注意,該指令在實模式和保護模式下都可以執行,也不影響任何標志位。 這個內存操作數指向一個6字節的內存區域,要求低16位是GDT的界限值(表的總字節數減去1),高32位是GDT的線性基地址。 ~~~ gdt_size dw 0 gdt_base dd 0x00007e00 ;GDT的物理地址 ~~~ 還記得嗎,這是代碼中事先定義了6字節的空間。前兩個字節就是為了保存GDT的界限值。 ~~~ ;初始化描述符表寄存器GDTR mov word [cs: gdt_size+0x7c00],31 ;描述符表的界限(總字節數減一) lgdt [cs: gdt_size+0x7c00] ~~~ 第一句寫入界限值,第二句把6字節加載到GDTR寄存器。 注意,到目前為止,我們依然在實模式下。 ### (四)關于A20 ### 1.A20 GATE 起源[1] 在8086/8088中,只有20根地址線,所以可以訪問的地址是2^20=1M。但由于8086/8088是16位地址模式,能夠表示的地址范圍是0-64K,所以為了訪問1M內存,Intel采取了分段的模式。 即:物理地址=16位段地址*16 + 16位偏移 但這種方式引起了新的問題,通過上述分段模式,能夠表示的最大內存為:FFFFh:FFFFh=FFFF0h+FFFFh=10FFEFh 但8086/8088只有20位地址線,所以當訪問100000h~10FFEFh之間的內存時,系統并不認為訪問越界而產生異常,而是自動從重新0開始計算,也就是說系統計算實際地址的時候是按照對1M求模的方式進行的,這種技術被稱為wrap-around(回繞)。 到了80286,系統的地址總線發展為24根,這樣能夠訪問的內存可以達到2^24=16M。為了兼容,Intel在設計80286時提出的目標是:在實模式下,系統所表現的行為應該和8086/8088所表現的完全一樣。但最終,80286芯片卻存在一個BUG:如果程序員訪問100000H~10FFEFH之間的內存,系統將實際訪問這塊內存,而不是象過去一樣重新從0開始。 為了解決上述問題,IBM使用鍵盤控制器上剩余的一些輸出線來管理第21根地址線(從0開始數是第20根),被稱為A20Gate;如果A20 Gate打開,則當程序員給出100000H~10FFEFH之間的地址的時候,系統將真正訪問這塊內存區域;如果A20Gate被禁止,則當程序員給出100000H~10FFEFH之間的地址的時候,系統仍然使用8086/8088的方式。絕大多數IBM PC兼容機默認的A20Gate是被禁止的。由于在當時沒有更好的方法來解決這個問題,所以IBM使用了鍵盤控制器來操作A20 Gate,但是這種操作太麻煩了,要使用一大堆指令。 ### 2.Alt_A20_GATE Alt_A20_GATE ,又稱Fast A20. 通過端口0x92的bit1來打開A20,具體方法是:先從端口讀出原數據,接著將bit1置1,然后再寫入該端口,這樣就打開了A20. 正如代碼所示 ~~~ in al,0x92 ;南橋芯片內的端口 or al,0000_0010B out 0x92,al ;打開A20 ~~~ ? 一次學太多會不會覺得累呢?我們就說到這里,下次繼續… ? 【參考資料】 [1] 如煙海的專欄. [http://blog.csdn.net/ruyanhai/article/details/7181842](http://blog.csdn.net/ruyanhai/article/details/7181842 "http://blog.csdn.net/ruyanhai/article/details/7181842")
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看