我們提到cpu的主要作用之一就是控制設備之間的數據交互。這其中自然也包括了硬盤。系統的所有數據基本都在硬盤中,所以知道怎么讀寫硬盤,對程序來說非常重要,所以我們先來探索下傳說中的pio模式。
cpu要想直接訪問設備里的數據,必須對設備存儲空間進行編址。而硬盤數據數據太大,直接編址數據線成本太高,於是設計上在這類設備和總線之間加了一個控制器。這個控制器里有少量寄存器可以被cpu訪問,而這個控制器又能控制硬盤驅動器讀寫數據,所以,cpu通過對硬盤控制器里的少量io寄存器的讀寫來控制對整個硬盤的讀寫。cpu告訴磁盤控制器讀哪些地址的數據,磁盤控制器讀好之后放入能被 cpu訪問的數據寄存器中,再將這里的數據傳給內存,這確實是個不錯的辦法。
首先,我們先找到硬盤控制器io端口地址的分配,0×170 — 0×177 IDE 硬盤控制器 1
0x1F0 — 0x1F7 IDE 硬盤控制器 0。更詳細的說明如下:
端口號 讀還是寫 具體含義
1F0H 讀/寫 用來傳送讀/寫的數據(其內容是正在傳輸的一個字節的數據)
1F1H 讀 用來讀取錯誤碼
1F2H 讀/寫 用來放入要讀寫的扇區數量
1F3H 讀/寫 用來放入要讀寫的扇區號碼
1F4H 讀/寫 用來存放讀寫柱面的低8位字節
1F5H 讀/寫 用來存放讀寫柱面的高2位字節(其高6位恆為0)
1F6H 讀/寫 用來存放要讀/寫的磁盤號及磁頭號
第7位 恆為1
第6位 恆為0
第5位 恆為1
第4位 為0代表第一塊硬盤、為1代表第二塊硬盤
第3~0位 用來存放要讀/寫的磁頭號
1f7H 讀 用來存放讀操作后的狀態
第7位 控制器忙碌
第6位 磁盤驅動器准備好了
第5位 寫入錯誤
第4位 搜索完成
第3位 為1時扇區緩沖區沒有准備好
第2位 是否正確讀取磁盤數據
第1位 磁盤每轉一周將此位設為1,
第0位 之前的命令因發生錯誤而結束
寫 該位端口為命令端口,用來發出指定命令
為50h 格式化磁道
為20h 嘗試讀取扇區
為21h 無須驗證扇區是否准備好而直接讀扇區
為22h 嘗試讀取長扇區(用於早期的硬盤,每扇可能不是512字節,而是128字節到1024之間的值)
為23h 無須驗證扇區是否准備好而直接讀長扇區
為30h 嘗試寫扇區
為31h 無須驗證扇區是否准備好而直接寫扇區
為32h 嘗試寫長扇區
為33h 無須驗證扇區是否准備好而直接寫長扇區
當然看完這個表你會發現,這種讀寫端口的方法其實是基於磁頭、柱面、扇區的硬盤讀寫方法,也就是你要告訴硬盤控制器你要讀硬盤里的哪個磁頭下的哪個柱面里的哪個扇區,然后下達讀命令,硬盤控制器將扇區內容緩存起來,然后依次將每16位的數據放入一個寄存器(1F0H)中, cpu就可以通過io指令來讀取這個數據了。
我們可以再看一段示例代碼:
mov dx,1f6h ; 要讀入的磁盤號及磁頭號
mov al,0a0h ;磁盤0,磁頭0
out dx,al
mov dx,1f2h ;要讀入的扇區數量
mov al,1 ;讀一個扇區
out dx,al
mov dx,1f3h ;要讀的扇區號
mov al,1 ;扇區號為1
out dx,al
mov dx,1f4h ;要讀的柱面的低8位
mov al,0 ; 柱面低8位為0
out dx,al
mov dx,1f5h ; 柱面高2位
mov al,0 ; 柱面高2位為0(通過1F4H和1F5H端口我們可以確定
; 用來讀的柱面號是0)
out dx,al
mov dx,1f7h ;命令端口
mov al,20h ; 嘗試讀取扇區
out dx,al
still_going:
in al,dx
test al,8 ;扇區緩沖是否准備好
jz still_going ;如果扇區緩沖沒有准備好的話則跳轉,直到准備好才向下執行。
mov cx,512/2 ;設置循環次數(512/2次)
mov di,offset buffer
mov dx,1f0h ;將要傳輸的一個字節的數據
rep insw ;傳輸數據
有成就感吧,一切盡在掌握中。其實我們可以直接調用bios里的int 13h中斷,就可以直接讀寫扇區,只不過通過更底層的代碼,我們可以對計算機的體系結構有一個更深刻的認識。知道cpu和其他設備是如何交互數據的。
注意到上面的指令rep insw,這里解釋一下。INS指令可從DX指出的外設端口輸入一個字節或字到由ES: DI指定的存儲器中。 輸入字節還是字,則由ES: DI目標操作數的屬性決定,且根據方向標志位DF和目標操作數的屬性來修改DI的值:若(DF)= 0,則DI加1(或加2);否則DI減1(或減2)。與INS指令相似,INSB 和INSW指令也分別從DX指出的外設端口輸入一個字節或字到由ES: DI指定的存儲器中,且根據方 向標志位DF和串操作的類型來修改DI的值。上述三種格式的指令均可加重復前綴REP,以實現連續的串操 。此時CX寄存器中的內容為重復操作的次數。
這里我們看到,cpu讀取硬盤數據不是讀入到cpu內部,而是讀入到內存中,這可能是因為在cpu中根本就沒有這么大空間來存儲硬盤里的數據,所以在指令設計中就直接讓其往內存中放,並且考慮到帶寬限制,硬盤一個最小的數據單元–扇區就要分很多次才能讀入內存,所以設計了想rep insw這樣的指令來提高效率。磁盤控制器每次從扇區數據緩沖區中取出1個字長的數據放入1F0h(也稱作pio數據端口)供insw指令獲取,循環執行insw,直到讀完整個緩沖區。
CPU讀取磁盤數據的操作時序(CPU和磁盤控制器)
CPU
檢查ready,確認磁盤控制器空閑,可以接受新的I/O命令;
將接收操作結果的內存單元的起始地址送入內存地址寄存器;
數據在內存中的起始扇區號送入數據起始地址寄存器,將待傳送的數據的長度(以字節為單位)送入數據長度寄存器;
置命令/狀態寄存器:
go置1;
r/w置1;(r/w置1表示讀取數據,置0表示寫入數據)
ready置0;
磁盤控制器
磁盤控制器定位到指定扇區(通過數據起始地址寄存器),然后將該扇區中的所有內容送入數據緩沖區;
從數據緩沖區中取一個字節的數據存入內存地址寄存器所指向的內存單元;內存地址寄存器的值+1;數據長度寄存器的值-1;
重復第二步,直到將數據緩沖區中的所有數據全部送入指定內存單元;如果此時數據長度寄存器的值非零,則重復上述第一步,繼續讀取下一個扇區,直到數據長度寄存器的值為零為止;
最后,所有的數據全部送入內存中,置ready為1,向CPU發送中斷信號;
中斷控制程序
中斷控制程序在接收到中斷信號后,喚醒等待IO結束的進程,該進程上台后,它所需要訪問的所有的文件內容就已經出現在內存中了!