日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > linux >内容正文

linux

setup.s 解读——Linux-0.11 剖析笔记(三)

發(fā)布時(shí)間:2025/3/15 linux 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 setup.s 解读——Linux-0.11 剖析笔记(三) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

題目:setup.s 解讀——Linux-0.11 剖析筆記(三)

更新記錄

版本時(shí)間修訂內(nèi)容
1.02018-4-14增加了“獲取顯示模式”這一節(jié),AL取值的表格
2.02020-6-27補(bǔ)充了一些內(nèi)容

本文由 setup.s 分析—— Linux-0.11 學(xué)習(xí)筆記(二) 修改而來(lái)。

文章目錄

  • 定義符號(hào)常量
  • 獲取一些參數(shù)保存在 0x90000 處
    • 保存光標(biāo)的位置
    • 獲取從 1M 處開始的擴(kuò)展內(nèi)存大小
    • 獲取顯示模式
    • 檢查顯示方式(EGA/VGA)并獲取參數(shù)
    • 復(fù)制硬盤參數(shù)表
      • 復(fù)制 HD0 的硬盤參數(shù)表
      • 復(fù)制 HD1 的硬盤參數(shù)表
      • 檢查系統(tǒng)是否有第2個(gè)硬盤
  • 關(guān)中斷
  • 移動(dòng) system 模塊到 0x00000
  • 加載IDT
  • 加載GDT
  • 開啟A20
  • 設(shè)置8259
    • ICW1
    • ICW2
    • ICW3
    • ICW4
    • OCW1
  • 進(jìn)入保護(hù)模式
  • 總結(jié)

為了節(jié)省篇幅,完整的代碼就不貼了。

定義符號(hào)常量

INITSEG = 0x9000 ! bootsect.s 的段地址 SYSSEG = 0x1000 ! system loaded at 0x10000 SETUPSEG = 0x9020 ! 本程序的段地址

注意:以上這些參數(shù)應(yīng)該和 bootsect.s 中的相同。

獲取一些參數(shù)保存在 0x90000 處

保存光標(biāo)的位置

mov ax,#INITSEG ! INITSEG = 0x9000mov ds,ax ! ds = 0x9000mov ah,#0x03 ! 功能號(hào)=3,獲取光標(biāo)的位置xor bh,bh ! bh = 頁(yè)號(hào) = 0(輸入)int 0x10 ! 輸出: DH=行號(hào),DL=列號(hào)mov [0],dx ! 保存光標(biāo)的行號(hào)和列號(hào)到 0x90000,共占2字節(jié).

獲取從 1M 處開始的擴(kuò)展內(nèi)存大小

! 利用 BIOS 中斷 0x15 功能號(hào) ah = 0x88 取系統(tǒng)所含擴(kuò)展內(nèi)存大小,并保存在內(nèi)存 0x90002 處! 返回:ax=從0xl00000(lM)處開始的擴(kuò)展內(nèi)存大小(KB).若出錯(cuò)則CF置位,ax=出錯(cuò)碼mov ah,#0x88int 0x15mov [2],ax ! ax = 從1M處開始的擴(kuò)展內(nèi)存大小

獲取顯示模式

! 獲取顯示卡當(dāng)前的顯示模式! 調(diào)用 BIOS 中斷 0x10,功能號(hào) ah = 0x0f! 返回: ah=字符列數(shù); al=顯示模式;bh=當(dāng)前顯示頁(yè)。! 0x90004(l個(gè)字)存放當(dāng)前頁(yè);0x90006(1字節(jié))存放顯示模式;0x90007(1字節(jié))存放字符列數(shù)。mov ah,#0x0fint 0x10mov [4],bx ! bh = 當(dāng)前顯示頁(yè)mov [6],ax ! al = 顯示模式, ah = 字符列數(shù)(窗口寬度)

AL 取值的含義如下表

ALTypeFormatCellColorsAdapterAddrMonitor
0text40x258x8*16/8 (shades)CGA,EGAb800Composite
1text40x258x8*16/8CGA,EGAb800Comp,RGB,Enh
2text80x258x8*16/8 (shades)CGA,EGAb800Composite
3text80x258x8*16/8CGA,EGAb800Comp,RGB,Enh
4graphic320x2008x84CGA,EGAb800Comp,RGB,Enh
5graphic320x2008x84 (shades)CGA,EGAb800Composite
6graphic640x2008x82CGA,EGAb800Comp,RGB,Enh
7text80x259x14*3 (b/w/bold)MDA,EGAb000TTL Mono
8,9,0aHPCjr modes
0bH,0cH(reserved; internal to EGA BIOS)
0dHgraphic320x2008x816EGA,VGAa000Enh,Anlg
0eHgraphic640x2008x816EGA,VGAa000Enh,Anlg
0fHgraphic640x3508x143 (b/w/bold)EGA,VGAa000Enh,Anlg,Mono
10Hgraphic640x3508x144 or 16EGA,VGAa000Enh,Anlg
11Hgraphic640x4808x162VGAa000Anlg
12Hgraphic640x4808x1616VGAa000Anlg
13Hgraphic640x4808x16256VGAa000Anlg

Notes: With EGA, VGA, and PCjr you can add 80H to AL to initialize a video mode without clearing the screen.

The character cell size for modes 0-3 and 7 varies, depending on the hardware. On modes 0-3: CGA=8x8, EGA=8x14, and VGA=9x16. For mode 7, MDPA and EGA=9x14, VGA=9x16, LCD=8x8.

檢查顯示方式(EGA/VGA)并獲取參數(shù)

! 檢查顯示方式(EGA/VGA)并獲取參數(shù)。! 調(diào)用 BIOS 中斷 0x10,功能號(hào): ah = 0xl2,子功能號(hào): bl = 0xl0! 返回:bh=顯示狀態(tài)。 0x00-彩色模式,I/O 端口=0x3dX! 0x01-單色模式,I/O 端口=0x3bX! bl = 安裝的顯示內(nèi)存。0x00 - 64k! 0x01 - 128k! 0x02 - 192k! 0x03 - 256k! cx = 顯示卡特性參數(shù)。!mov ah,#0x12 ! 功能號(hào)mov bl,#0x10 ! 子功能號(hào)int 0x10mov [8],ax ! 我也不知道這個(gè)是什么(╯︵╰)mov [10],bx ! bh=顯示狀態(tài)(單色模式/彩色模式),bl=已安裝的顯存大小mov [12],cx ! ch=特性連接器比特位信息,cl=視頻開關(guān)設(shè)置信息

關(guān)于返回參數(shù)的詳細(xì)解釋,還是看這張圖吧,圖片來(lái)自趙炯博士的《Linux內(nèi)核完全剖析》(機(jī)械工業(yè)出版社,2006)。

BIOS 視頻中斷 0x10

復(fù)制硬盤參數(shù)表

復(fù)制 HD0 的硬盤參數(shù)表

! 復(fù)制 hd0 的硬盤參數(shù)表,參數(shù)表地址是中斷向量0x41的值,表長(zhǎng)度16B ! 中斷向量在中斷向量表中的位置 = 中斷類型號(hào)N × 4 ! (N*4)的字單元存放偏移地址; ! (N*4+2)的字單元存放段基址。mov ax,#0x0000mov ds,ax ! ds=0 ! 將內(nèi)存[4*0x41]處的低2字節(jié)(偏移地址)傳給si,高2字節(jié)(段地址)傳給dslds si,[4*0x41]mov ax,#INITSEGmov es,ax !es = 0x9000mov di,#0x0080mov cx,#0x10 !重復(fù)16次 ! ds:si --> es:di(0x9000:0x0080),共傳送16Brepmovsb

復(fù)制 HD1 的硬盤參數(shù)表

! 復(fù)制 hd1 的硬盤參數(shù)表,參數(shù)表地址是中斷向量0x46的值,表長(zhǎng)度16B ! 道理同上一小節(jié),此處不贅述mov ax,#0x0000mov ds,axlds si,[4*0x46]mov ax,#INITSEG ! INITSEG = 0x9000mov es,axmov di,#0x0090mov cx,#0x10 ! ds:si --> es:di(0x9000:0x0090),共傳送16Brepmovsb

檢查系統(tǒng)是否有第2個(gè)硬盤

! 檢查系統(tǒng)是否有第2個(gè)硬盤,如果沒有就把第2個(gè)參數(shù)表清零 ! 利用 BIOS 中斷調(diào)用 0x13 的取盤類型功能,功能號(hào) ah = 0xl5; ! 輸入: dl=驅(qū)動(dòng)器號(hào)(0x8X 是硬盤:0x80 指第 1 個(gè)硬盤,0x81 第 2 個(gè)硬盤) ! 輸出: ah=類型碼;00-沒有這個(gè)盤,CF 置位; ! 01-是軟驅(qū),沒有 change-line 支持; ! 02 -是軟驅(qū)(或其他可移動(dòng)設(shè)備),有 change-line 支持; ! 03 -是硬盤。 !mov ax,#0x01500 ! 功能號(hào) ah=0x15,讀取盤類型mov dl,#0x81 ! dl=驅(qū)動(dòng)器號(hào),0x81代表第2個(gè)硬盤int 0x13 jc no_disk1 ! CF置位,表示沒有這個(gè)盤cmp ah,#3 je is_disk1 ! ah=3表示存在第2個(gè)硬盤,跳轉(zhuǎn)到is_disk1 no_disk1: ! 清空第2個(gè)表mov ax,#INITSEGmov es,axmov di,#0x0090 ! es:di = 0x9000:0x0090mov cx,#0x10mov ax,#0x00 ! AL=0repstosb ! Store AL at address es:di is_disk1:

關(guān)中斷

! 為進(jìn)入保護(hù)模式做準(zhǔn)備cli ! no interrupts allowed !

移動(dòng) system 模塊到 0x00000

bootsect.s 引導(dǎo)程序?qū)?system 模塊讀入到 0xl0000 開始的位置。由于當(dāng)時(shí)假設(shè) system 模塊最大長(zhǎng)度不會(huì)超過 0x80000 (512KB),即其末端不會(huì)超過內(nèi)存地址 0x90000,所以 bootsect.s 會(huì)把自己移動(dòng)到0x90000 開始的地方,并把 setup 加載到它的后面。下面這段程序的用途是再把整個(gè) system 模塊移動(dòng)到 0x00000 位置,即把從 0x10000 到 0x8ffff 的內(nèi)存數(shù)據(jù)塊(共512KB)整塊地向內(nèi)存低端移動(dòng)了0x10000(64KB)。

! 從代碼實(shí)現(xiàn)來(lái)看,是一小塊(0x10000B=64KB)一小塊移動(dòng)的,共移動(dòng)8小塊。mov ax,#0x0000cld ! 'direction'=0, movs moves forward do_move:mov es,ax ! es是目的段地址add ax,#0x1000cmp ax,#0x9000 ! 當(dāng) ax==0x9000 時(shí)結(jié)束移動(dòng)jz end_movemov ds,ax ! ds是源段地址,ds比es大0x1000sub di,di ! di = 0sub si,si ! si = 0mov cx,#0x8000 ! 重復(fù) 0x8000次rep ! ds:si --> es:dimovsw ! 每次移動(dòng)2B.jmp do_move ! 本輪一共移動(dòng) 0x8000*2B = 0x10000B=64KB. 準(zhǔn)備下一輪移動(dòng)end_move:

上面的匯編代碼寫成偽C語(yǔ)言代碼如下:

ax = 0; cld;while(1){es = ax;ax += 0x1000;if(ax == 0x9000)break; //結(jié)束移動(dòng)ds = ax;di = si = 0;for(int i=0; i<0x8000; ++i){memcpy(es:di, ds:si, 2);di += 2;si += 2;} }

搬運(yùn)示意圖如下:

加載IDT

CPU 要求在進(jìn)入保護(hù)模式之前需設(shè)置 IDT 表, 這里先設(shè)置一個(gè)長(zhǎng)度為 0 的空表.

end_move:mov ax,#SETUPSEG mov ds,ax !ds = 0x9020,指向本程序段,setup.s 被加載到 0x90200! idt_48 標(biāo)號(hào)處的內(nèi)容如下! idt_48:! .word 0 ! idt 界限值=0! .word 0,0 ! idt 基地址=0Llidt idt_48 ! load idt with 0,0

加載GDT

!gdt_48 標(biāo)號(hào)處的內(nèi)容如下!gdt_48:!.word 0x800 ! 0x800 = 2048, 2048/8=256,可容納256個(gè)描述符, 其實(shí)0x7ff即可!.word 512+gdt,0x9 ! setup.s被加載到0x90200, gdt base = 0x90200+gdt = 0x90000+512+gdtlgdt gdt_48

系統(tǒng)開機(jī)時(shí),默認(rèn)進(jìn)入實(shí)模式,這時(shí)候如果想進(jìn)入保護(hù)模式,必須先設(shè)置 GDT 表,所以首先是在實(shí)模式設(shè)置GDT 表,為進(jìn)入保護(hù)模式做準(zhǔn)備。進(jìn)入保護(hù)模式后,如果不開啟分頁(yè),那么線性地址是等同于物理地址的,所以,在實(shí)模式設(shè)置 GDT 的時(shí)候,用的是物理地址。

當(dāng)進(jìn)入保護(hù)模式且啟用分頁(yè),這時(shí)候線性地址不再等同于物理地址,線性地址經(jīng)過頁(yè)部件轉(zhuǎn)換后才是物理地址。所以之前設(shè)置的物理地址,會(huì)被當(dāng)成線性地址。為了不出問題,這個(gè)線性地址就要和物理地址等價(jià),即經(jīng)過頁(yè)部件的轉(zhuǎn)換后值不變。

斜體字是我自己的理解,不知道對(duì)不對(duì)。

開啟A20

什么是A20?為什么要開啟?可以參考我的博文: 關(guān)于A20

PC 機(jī)主板上的鍵盤接口是專用接口,它可以看作是常規(guī)串行端口的一個(gè)簡(jiǎn)化版本。該接口被稱為鍵盤控制器,它使用串行通信協(xié)議接收鍵盤發(fā)來(lái)的掃描碼數(shù)據(jù)。主板上所采用的鍵盤控制器是 Intel 8042 芯片或其兼容芯片。現(xiàn)今的主板上已經(jīng)不包括獨(dú)立的 8042 芯片了,但是主板上其他集成電路會(huì)為兼容目的而模擬 8042 芯片的功能。另外,該芯片輸出端口 P2 各位被分別用于其他目的。bit 0 (P20 引腳)用于實(shí)現(xiàn) CPU 的復(fù)位操作(低電平導(dǎo)致復(fù)位),bit 1(P21 引腳)用于控制 A20 信號(hào)線的開啟與否,為 1 時(shí)就開啟(選通)A20 信號(hào)線,為 0 則禁止 A20 信號(hào)線。

P2 各個(gè)位的含義如下表:

PinNameFunction
0P20System Reset. 1: Normal;0: Reset computer
1P21Gate A20
2P22Undefined
3P23Undefined
4P24Input Buffer Full
5P25Output Buffer Empty
6P26Keyboard Clock. 1: Pull Clock low;0: High-Z
7P27Keyboard Data. 1: Pull Data low ,0: High-Z
call empty_8042 ! 等待輸入緩沖器為空mov al,#0xD1 out #0x64,al !向端口 0x64(輸入緩沖器) 寫命令,命令碼是 0xD1call empty_8042 ! 等待輸入緩沖器為空,即命令被接受mov al,#0xDF ! A20 onout #0x60,al !向端口 0x60 寫參數(shù),參數(shù)是 0xDFcall empty_8042 ! 等待輸入緩沖器為空,即參數(shù)被接受

第三行,0x64,是狀態(tài)寄存器(讀)或者輸入緩沖器(寫)

802x 的各個(gè)端口如下圖

mov al,#0xD1 out #0x64,al !向端口 0x64(輸入緩沖器) 寫命令,命令碼是 0xD1...mov al,#0xDF ! A20 onout #0x60,al !向端口 0x60 寫參數(shù),參數(shù)是 0xDF...

向 0x64 端口寫命令,可以帶一個(gè)參數(shù),參數(shù)從端口 0x60 寫入。

0xD1 是命令碼,表示寫芯片 8042 的輸出端口 P2,此命令后面帶一個(gè)字節(jié)的參數(shù),這個(gè)參數(shù)由端口 0x60 寫入。要開啟 A20,就要使參數(shù)的 b1=1,另外還要使 b0=1,否則系統(tǒng)會(huì)復(fù)位。

mov al,#0xDF

0xDF 是參數(shù),寫成二進(jìn)制是 1101_1111,其他位不理解,總之 P20 和 P21 都配成 1 了。

至于機(jī)器是否真正開啟了 A20 地址線,我們還需要在進(jìn)入保護(hù)模式之后再測(cè)試一下。這個(gè)工作放在了head.s 程序中。head.s 的代碼咱們以后再分析。

解釋一下empty_8042這個(gè)過程。

empty_8042:.word 0x00eb,0x00eb !機(jī)器碼,跳轉(zhuǎn)到下一句,為了延時(shí)in al,#0x64 ! 8042 status porttest al,#2 ! is input buffer full?jnz empty_8042 ! yes - loopret

第 3 行:讀端口 0x64 到 AL.

讀端口 0x64 就是讀 8042 的狀態(tài)寄存器(一個(gè) 8bit 的只讀寄存器),bit 1為 1 時(shí)表示輸入緩沖器滿,為 0 時(shí)表示輸入緩沖器空。要向 8042 寫命令(通過 0x64 端口寫入),必須當(dāng)輸入緩沖器為空時(shí)才可以

第 4 行:用于檢測(cè) bit 1,如果為 1,則跳轉(zhuǎn)到 empty_8042 標(biāo)號(hào)處繼續(xù)檢測(cè),直到 bit 1為 0 才返回。所以empty_8042這個(gè)過程就是為了等待輸入緩沖器為空。

這里介紹一下 test 指令。

Operation
TEMP ← SRC1 AND SRC2;
SF ← MSB(TEMP);
IF TEMP = 0
THEN ZF ← 1;
ELSE ZF ← 0;
FI:
PF ← BitwiseXNOR(TEMP[0:7]);
CF ← 0;
OF ← 0;
(AF is Undefined)

第 4 行,把 AL 寄存器的值和 0000_0010b 做“按位與”操作,如果結(jié)果是 0,那么 ZF 置位,否則,ZF 是 0;所以,這句代碼就是測(cè)試 AL 的 bit 1 是否是 1,是 1 則結(jié)果不是 0,跳轉(zhuǎn)到 empty_804;是 0 則 ZF 置位,返回。

設(shè)置8259

; ICW1 mov al,#0x11 ! initialization sequenceout #0x20,al ! send ICW1 to Master.word 0x00eb,0x00eb ! jmp $+2, jmp $+2out #0xA0,al ! send ICW1 to Slave.word 0x00eb,0x00eb;------------------------------------------------------; ICW2mov al,#0x20 ! 送主芯片ICW2命令字,設(shè)置起始中斷號(hào),要送奇端口 out #0x21,al.word 0x00eb,0x00ebmov al,#0x28 ! 送從芯片ICW2命令字,設(shè)置起始中斷號(hào),要送奇端口out #0xA1,al.word 0x00eb,0x00eb;-------------------------------------------------------; ICW3mov al,#0x04 ! 8259-1 is masterout #0x21,al.word 0x00eb,0x00ebmov al,#0x02 ! 8259-2 is slaveout #0xA1,al.word 0x00eb,0x00eb;------------------------------------------------------; ICW4mov al,#0x01 out #0x21,al.word 0x00eb,0x00ebout #0xA1,al.word 0x00eb,0x00eb;------------------------------------------------------; OCW1mov al,#0xFF ! mask off all interrupts for nowout #0x21,al.word 0x00eb,0x00ebout #0xA1,al

字(0x00eb)是直接使用機(jī)器碼表示的一條相對(duì)跳轉(zhuǎn)指令,起延時(shí)作用。0xeb是直接近跳轉(zhuǎn)指令的操作碼,帶1個(gè)字節(jié)的相對(duì)位移值。因此跳轉(zhuǎn)范圍是 -128到 +127。CPU 通過把這個(gè)相對(duì)位移值加到 EIP 寄存器中就形成一個(gè)新的有效地址。注意:執(zhí)行某條指令的時(shí)候,EIP會(huì)指向它的下一條指令。所以,CPU執(zhí)行0x00eb的時(shí)候,會(huì)把EIP的值加上 0 ,其實(shí)就是下一條指令的地址,然后跳轉(zhuǎn)到那里去執(zhí)行。

0x00eb,0x00eb這兩條指令共可提供 14~20 個(gè) CPU 時(shí)鐘周期的延遲時(shí)間。在 as86 中沒有表示相應(yīng)指令的助記符,因此 Linus 在 setup.s 等一些匯編程序中就直接使用機(jī)器碼來(lái)表示這種指令。另外,每個(gè)空操作指令 N0P 的時(shí)鐘周期數(shù)是 3 個(gè),因此若要達(dá)到相同的延遲效果就需要 6 至 7 個(gè) N0P 指令。

關(guān)于 8259A 的詳細(xì)知識(shí)可以參考我的博文 : 詳解8259A

對(duì)于每個(gè)命令字的端口,我列了一張速查表。

命令字A0主片端口地址從片端口地址備注
ICW100x200xA0D4 = 1
ICW210x210xA1
ICW310x210xA1
ICW410x210xA1
OCW110x210xA1
OCW200x200xA0D4-D3 = 00
OCW300x200xA0D4-D3 = 01

ICW1

; ICW1 mov al,#0x11 ! initialization sequence out #0x20,al ! send ICW1 to Master .word 0x00eb,0x00eb ! jmp $+2, jmp $+2 out #0xA0,al ! send ICW1 to Slave .word 0x00eb,0x00eb

2-3 行:向主片寫入 0x11 = 0001_0001b, 表示初始化命令開始,它是 ICW1 命令字。 對(duì)照表格可以知道(黑體字)——邊沿觸發(fā)、 多片8259級(jí)聯(lián)、最后要發(fā)送 ICW4 命令字。

第 5 行,向從片寫 0x11

ICW1含義
D01:需要ICW4 0:不需要ICW4
D11:單片 0:級(jí)聯(lián)
D2=0;
D31:電平觸發(fā) 0:邊沿觸發(fā)
D4=1
D7-D5=000

ICW2

; ICW2 mov al,#0x20 ! 送主芯片ICW2命令字,設(shè)置起始中斷號(hào),要送奇端口 out #0x21,al .word 0x00eb,0x00eb mov al,#0x28 ! 送從芯片ICW2命令字,設(shè)置起始中斷號(hào),要送奇端口 out #0xA1,al .word 0x00eb,0x00eb

3-4 行:送主芯片 ICW2 命令字,設(shè)置起始中斷號(hào)為 0x20,則主片 0~7 級(jí)對(duì)應(yīng)的中斷號(hào)是 0x20~0x27;

6-7 行:送從芯片 ICW2 命令字,設(shè)置起始中斷號(hào)為 0x28,則從片 8~15 級(jí)對(duì)應(yīng)的中斷號(hào)是 0x28~0x2F;

ICW3

; ICW3 mov al,#0x04 ! 8259-1 is master out #0x21,al .word 0x00eb,0x00eb mov al,#0x02 ! 8259-2 is slave out #0xA1,al .word 0x00eb,0x00eb

2-3 行:送主芯片 ICW3 命令字,0x04 = 0000_0100b,表示主芯片的 IR2 連從芯片的 INT。

5-6 行:送從芯片 ICW3 命令字,表示從芯片的 INT 連到主芯片的 IR2 引腳上。

Linux-0.11 內(nèi)核把 8259A 主片的 ICW3 設(shè)置為 0x04,即 S2=1,其余各位為0。表示主芯片的 IR2 引腳連接一個(gè)從芯片。從芯片的 ICW3 被設(shè)置為 0x02,即其標(biāo)識(shí)號(hào)為 2。表示此從片連接到主片的 IR2 引腳。 因此,中斷優(yōu)先級(jí)的排列次序?yàn)?#xff1a;0 級(jí)最高,1 級(jí)次之,接下來(lái)是從片上的 8~15 級(jí),最后是主片的 3~7 級(jí)。

ICW4

; ICW4 mov al,#0x01 out #0x21,al .word 0x00eb,0x00eb out #0xA1,al .word 0x00eb,0x00eb

送 ICW4 命令字。普通 E0I(需發(fā)送指令來(lái)復(fù)位)、非緩沖方式、非特殊全嵌套。

ICW4含義
D7-D5=0
D41:特殊全嵌套 ; 0:非特殊全嵌套
D3-D20X:非緩沖 ; 10:緩沖-從片; 11:緩沖-主片
D11:自動(dòng) EOI 0:普通 EOI
D0=1

8259A 的數(shù)據(jù)線與系統(tǒng)數(shù)據(jù)總線的連接有緩沖和非緩沖兩種方式,這里配置成后者

普通結(jié)束方式是通過在中斷服務(wù)子程序中編程寫入操作命令字 OCW2,向 8259A 傳送一個(gè)普通中斷結(jié)束(EOI,end of interrupt)命令(命令中不指定要復(fù)位的中斷級(jí))來(lái)清除 ISR 中優(yōu)先級(jí)別最高的置位。

非特殊全嵌套也叫普通嵌套,也叫做完全嵌套或者普通完全嵌套。此方式是 8259A 在初始化時(shí)默認(rèn)選擇的方式。其特點(diǎn)是:IR0 優(yōu)先級(jí)最高,IR7 優(yōu)先級(jí)最低。在 CPU 中斷服務(wù)期間,若有新的中斷請(qǐng)求到來(lái),只允許比當(dāng)前服務(wù)的優(yōu)先級(jí)更的中斷請(qǐng)求進(jìn)入,對(duì)于“同級(jí)”或“低級(jí)”的中斷請(qǐng)求則禁止響應(yīng)。

OCW1

在對(duì) 8259A 設(shè)置了初始化命令字后,芯片就已準(zhǔn)備好接收設(shè)備的中斷請(qǐng)求信號(hào)了。但在 8259A 工作期間,我們也可以利用操作命令字 OCW1~OCW3 來(lái)監(jiān)測(cè) 8259A 的工作狀況,或者隨時(shí)改變初始化時(shí)設(shè)定的 8259A 的工作方式。

需要說(shuō)明的是,與初始化命令字 ICW1~ICW4 需要按規(guī)定的順序進(jìn)行設(shè)置不同,操作命令字 OCW1~OCW3 的設(shè)置沒有規(guī)定其先后順序,使用時(shí)可根據(jù)需要靈活選擇不同的操作命令字寫入到 8259A 中。

; OCW1 mov al,#0xFF out #0x21,al ! 屏蔽主片所有中斷請(qǐng)求 .word 0x00eb,0x00eb out #0xA1,al ! 屏蔽從片所有中斷請(qǐng)求。

OCW1 用于對(duì) 8259 的中斷屏蔽寄存器進(jìn)行讀/寫操作,寫 1 則屏蔽對(duì)應(yīng)級(jí)別的中斷。

進(jìn)入保護(hù)模式

下面設(shè)置并進(jìn)入 32 位保護(hù)模式運(yùn)行。

首先加載機(jī)器狀態(tài)字(lmsw,Load Machine Status Word),也稱控制寄存器 CR0,其比特位 0 置 1 將使 CPU 切換到保護(hù)模式,并且運(yùn)行在特權(quán)級(jí)0,即當(dāng)前特權(quán)級(jí) CPL = 0。此時(shí)各個(gè)段寄存器仍然指向與實(shí)地址模式中相同的線性地址處(在實(shí)地址模式下線性地址與物理地址相同)。在設(shè)置該比特位后,隨后一條指令必須是一條段間跳轉(zhuǎn)指令,用于刷新 CPU 當(dāng)前指令隊(duì)列。因?yàn)?CPU 是在執(zhí)行一條指令之前就已從內(nèi)存讀取該指令并對(duì)其進(jìn)行譯碼。然而在進(jìn)入保護(hù)模式以后那些屬于實(shí)模式的預(yù)先取得的指令信息就變得不再有效。而一條段間跳轉(zhuǎn)指令就會(huì)刷新 CPU 的當(dāng)前指令隊(duì)列,即丟棄這些無(wú)效信息。另外,Intel 手冊(cè)上建議 80386 或以上 CPU 應(yīng)該使用指令 mov cr0,ax 切換到保護(hù)模式。lmsw 指令僅用于兼容以前的 286 CPU。

mov ax,#0x0001 ! Protection Enable (bit 0 of CR0). lmsw ax ! 實(shí)際上lmsw指令僅僅加載CR0的低4位,由低到高分別是PE,MP,EM,TS jmpi 0,8 ! jmp offset 0 of segment 8 (cs)

實(shí)際上lmsw指令僅僅加載CR0的低4位,由低到高分別是PE,MP,EM,TS. 這里我們僅關(guān)注 PE,其他的都設(shè)為 0.

jmpi 0,8 段間跳轉(zhuǎn)指令。執(zhí)行后,CS=8,IP=0.

關(guān)于這里的段間跳轉(zhuǎn),要多說(shuō)幾句。
即使是在實(shí)模式下,段寄存器的描述符高速緩存器也被用于訪問內(nèi)存,僅低 20 位有效,高 12 位是全零。當(dāng)處理器進(jìn)入保護(hù)模式后,這些內(nèi)容依然殘留著,但是,這些殘留的內(nèi)容在保護(hù)模式下是無(wú)效的,遲早會(huì)在執(zhí) 行某些指令的時(shí)候出問題。因此,比較安全的做法是盡快刷新 CS、SS、DS 、ES 、FS 和 GS 的內(nèi)容,包括它們的段選擇器和描述符高速緩存器。

在進(jìn)入保護(hù)模式之前,有很多指令已經(jīng)進(jìn)入了流水線。因?yàn)樘幚砥鞴ぷ髟趯?shí)模式下,所以它們都是按 16 位操作數(shù)和 16 位地址長(zhǎng)度進(jìn)行譯碼的,即使是那些用 bits 32 編譯的指令。進(jìn)入保護(hù)模式后,受 CS 段描述符高速緩存器中實(shí)模式殘留內(nèi)容的影響,處理器進(jìn)入 16 位保護(hù)模式工作。如果保護(hù)模式下的代碼是 16 位的,影響可能不大,但如果是用 bits 32 編譯的,那么,由于對(duì)操作數(shù)和默認(rèn)地址大小的解釋不同,指令的執(zhí)行結(jié)果可能會(huì)不正確,所以必須清空流水線。同時(shí),那些通過亂序執(zhí)行得到的中間結(jié)果也是無(wú)效的,必須清理掉,讓處理器串行化執(zhí)行,即重新按指令的自然順序執(zhí)行。

怎么辦呢?這里有一個(gè)兩全其美的方案,那就是使用段間跳轉(zhuǎn)指 jmpi。處理器最怕轉(zhuǎn)移指令,遇到這種指令,一般會(huì)淸空流水線,并串行化執(zhí)行;另一方面,段間跳轉(zhuǎn)會(huì)重新加載段選擇器 CS,并刷新描述符高速緩存器中的內(nèi)容。

jmpi 0,8 中的 “8 ”是保護(hù)模式下的段選擇子,用于選擇描述符表(GDT 或 LDT)和描述符表項(xiàng)以及所要求的特權(quán)級(jí)。段選擇子長(zhǎng)度為 16 位(2字節(jié))。

段選擇子
b1-b0請(qǐng)求特權(quán)級(jí)(RPL)
b20:全局描述符表 1:局部描述符表
b15-b3描述符表項(xiàng)的索引, 指出選擇第幾項(xiàng)描述符(從 0 開始)

位 0-1 表示請(qǐng)求特權(quán)級(jí)(RPL),Linux 操作系統(tǒng)只用到兩級(jí)——0 級(jí)(內(nèi)核級(jí))和 3 級(jí)(用戶級(jí));位 2 用于選擇全局描述符表還是局部描述符表;位 3-15 是描述符表項(xiàng)的索引,指出選擇第幾項(xiàng)描述符。所以段選擇子 8(= 0000_0000_0000_1000b)表示請(qǐng)求特權(quán)級(jí)0、使用全局描述符表 GDT 中第 1 個(gè)段描述符項(xiàng)(GDT 表在后文分析),該項(xiàng)是一個(gè)代碼段描述符,指出代碼段的基地址是 0,又因?yàn)槠浦凳?0,所以這個(gè)跳轉(zhuǎn)指令會(huì)跳轉(zhuǎn)到0地址,即運(yùn)行system模塊。

從邏輯地址到線性地址的轉(zhuǎn)換規(guī)則如下圖:

到這里,setup.s 文件就分析完了。不過還剩一個(gè)小尾巴,就是文件末尾定義的 GDT 表。

gdt:.word 0,0,0,0 ! dummy.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb).word 0x0000 ! base address=0.word 0x9A00 ! code read/exec.word 0x00C0 ! granularity=4096, 386.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb).word 0x0000 ! base address=0.word 0x9200 ! data read/write.word 0x00C0 ! granularity=4096, 386

有了這個(gè)小程序,分析段描述符再也不用發(fā)愁了,So easy !
80x86描述符總結(jié)及解析描述符的小程序

索引號(hào)描述符類型基地址段界限粒度PDPL備注選擇子
0空描述符-------
1代碼段00X7FF4KB10代碼段,非一致性,可讀0x08
2數(shù)據(jù)段00X7FF4KB10數(shù)據(jù)段,向上擴(kuò)展,可寫0x10

非一致性代碼段,這樣的代碼段可以被同級(jí)代碼段調(diào)用,或者通過門調(diào)用;

一致性代碼段,可以從低特權(quán)級(jí)的程序轉(zhuǎn)移到該段執(zhí)行(但是低特權(quán)級(jí)的程序仍然保持自身的特權(quán)級(jí))。

注意:所有的數(shù)據(jù)段都是非一致性的,即意味著它們不能被低特權(quán)級(jí)的程序或過程訪問。然而與代碼段不同,數(shù)據(jù)段可以被更高特權(quán)級(jí)的程序或過程訪問,而無(wú)需使用特殊的訪問門。

其實(shí)描述符的知識(shí)點(diǎn)比較多,以后用到再說(shuō)。

關(guān)于數(shù)據(jù)段和代碼段描述符的詳細(xì)知識(shí),可以參考我的博文:

數(shù)據(jù)段描述符和代碼段描述符

總結(jié)

setup.s 做的工作有:

  • 獲取一些參數(shù)保存在 0x90000 處
    • 保存光標(biāo)的位置
    • 獲取從 1M 處開始的擴(kuò)展內(nèi)存大小
    • 獲取顯示模式
    • 檢查顯示方式(EGA/VGA)并獲取參數(shù)
    • 復(fù)制硬盤參數(shù)表(包括檢查系統(tǒng)是否有第2個(gè)硬盤)
  • 關(guān)中斷
  • 移動(dòng) system 模塊到 0x00000
  • 加載 IDT 和 GDT
  • 開啟 A20
  • 設(shè)置 8259
  • 進(jìn)入保護(hù)模式(使 CR0 的 PE 位 = 1)
  • 跳轉(zhuǎn)到 0 地址執(zhí)行

  • 參考資料
    1《Linux內(nèi)核完全剖析》(趙炯,機(jī)械工業(yè)出版社,2006)
    2《x86匯編語(yǔ)言:從實(shí)模式到保護(hù)模式》(李忠,2013)
    3 http://webpages.charter.net/danrollins/techhelp/0114.HTM
    4 https://www.rpi.edu/dept/cis/software/g77-mingw32/info-html/as.html#Invoking

    總結(jié)

    以上是生活随笔為你收集整理的setup.s 解读——Linux-0.11 剖析笔记(三)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。