學習筆記
本章讨論了塊裝置IO和緩沖區管理;解釋了塊裝置I/O的原理和I/O緩沖的優點;論述了Unix的緩沖區管理算法,并指出了其不足之處;還利用信号量設計了新的緩沖區管理算法,以提高IO緩沖區的緩存效率和性能;表明了簡單的PV算法易于實作,緩存效果好,不存在死鎖和饑餓問題;還提出了一個比較Unix緩沖區管理算法和PV算法性能的程式設計方案。
知識點總結
一、塊裝置I/O緩沖區
檔案系統使用一系列IO緩沖區作為塊裝置的緩存記憶體。當程序試圖讀取(dev,blk)辨別的磁盤塊時,它首先在緩沖區緩存中搜尋配置設定給磁盤塊的緩沖區。如果該緩沖區存在并且包含有效資料,那麼它隻需從緩沖區中讀取資料,而無須再次從磁盤中讀取資料塊。如果該緩沖區不存在,它會為磁盤塊配置設定一個緩沖區,将資料從磁盤讀入緩沖區,然後從緩沖區讀取資料。當某個塊被讀入時,該緩沖區将被儲存在緩沖區緩存中,以供任意程序對同一個塊的下一次讀/寫請求使用。同樣,當程序寫入磁盤塊時,它首先會擷取一個配置設定給該塊的緩沖區。然後,它将資料寫入緩沖區,将緩沖區标記為髒,以延遲寫人,并将其釋放到緩沖區緩存中。由于髒緩沖區包含有效的資料,是以可以使用它來滿足對同一塊的後續讀/寫請求,而不會引起實際磁盤L/O。髒緩沖區隻有在被重新配置設定到不同的塊時才會寫入磁盤。
二、Unix I/O緩沖區管理算法
(1)I/O緩沖區:核心中的一系列NBUF緩沖區用作緩沖區緩存。每個緩沖區用一個結構體表示。
(2)裝置表:每個塊裝置用一個裝置表結構表示。
(3)緩沖區初始化:當系統啟動時,所有I/O緩沖區都在空閑清單中,所有裝置清單和T/O隊列均為空。
(4)緩沖區清單:當緩沖區配置設定給(dev,blk)時,它會被插入裝置表的dev_list中。如果緩沖區目前正在使用,則會将其标記為BUSY(繁忙)并從空閑清單中删除。
(5) Unix getblk/brelse算法
三、PV算法
empty[s2] = m;
full[s2] = 0;
while(1)//寫程序
{
for(i = 0; i< s2; i++)
{
P(empty[i]);
}
P(mutex);
消息放入緩沖區;
V(mutex);
for(i = 0; i< s2; i++)
{
V(full[i]);
}
}
while(1)//讀程序
{
P(full[i]);
P(mutex);
讀取緩沖區;
V(mutex);
V(empty[i]);
}
【特點】:
(1)緩沖區唯一性。
(2)無重試循環。
(3)無不必要喚醒。
(4)緩存效果。
四、I/O緩沖區管理算法比較
項目分為以下幾個結構:
Box#1:使用者界面﹐這是模拟系統的使用者界面部分,提示輸人指令、顯示指令執行、顯示系統狀态和執行結果等。在開發過程中,可以手動輸入指令來執行任務。在最後測試過程中,任務應該有自己的輸入指令序列
Box#2:多任務處理系統的CPU端,模拟單處理器(單CPU)檔案系統的核心模式。當系統啟動時,它會建立并運作一個優先級最低的主任務,但它會建立ntask工作任務,所有任務的優先級都是1,并将它們輸人readyQueue。然後,主任務執行以下代碼,該代碼将任務切換為從readyQueue運作工作任務。
緩沖區管理器
磁盤驅動程式:start_io():維護裝置IO隊列,并對IO隊列中的緩沖區執行I/O操作;中斷處理程式:在每次I/O操作結束時,磁盤控制器會中斷 CPU。
磁盤控制器:Box#3:磁盤控制器,它是主程序的一個子程序。是以,它與CPU端獨立運作,除了它們之間的通信通道,通信通道是CPU和磁盤控制器之間的接口。通信通道由主程序和子程序之間的管道實作。
指令:從CPU到磁盤控制器的1/O指令。
DataOut:在寫操作中從CPU到磁盤控制器的資料輸出。
DataIn:在讀操作中從磁盤控制器到CPU的資料。
IntStatus:從磁盤控制器到CPU的中斷狀态。
IntAck:從 CPU到磁盤控制器的中斷确認。
磁盤中斷:從磁盤控制器到CPU的中斷由SIGUSR1(#10)信号實作。在每次IO操作結束時,磁盤控制器會發出 kill(ppid, SIGUSR1)系統調用,向父程序發送SIGUSR1信号,充當虛拟CPU中斷。通常,虛拟CPU會在臨界區屏蔽出/人磁盤中斷(信号)。為防止競态條件,磁盤控制器必須要從CPU接收一個中斷确認,才能再次中斷。
虛拟磁盤:Box#4:Linux檔案模拟的虛拟磁盤。使用Linux系統調用lseek()、read(和write(),支援虛拟磁盤上的任何塊I/O操作。為了簡單起見,将磁盤塊大小設定為16位元組。由于資料内容無關緊要,是以可以将它們設定為16個字元的固定序列。
實踐内容與截圖
1、setvbuf函數
改變終端原有的行緩沖為無緩沖
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **args)
{
printf("hotice0");
sleep(3);
printf(" test\n");
sleep(3);
if (setvbuf(stdout, NULL, _IONBF, 0) < 0) {
perror("setvbuf");
exit(EXIT_FAILURE);
}
printf("hotice0");
sleep(3);
printf("done\n");
return EXIT_SUCCESS;
}

另外要注意setbuf和setvbuf的差別
函數setbuf()用于将指定緩沖區與特定的檔案流相關聯,實作操作緩沖區時直接操作檔案流的功能。其原型如下:
void setbuf(FILE * stream, char * buf);
在打開檔案流後,讀取内容之前,可以調用setbuf()來設定檔案流的緩沖區(而且必須是這樣)。
函數setvbuf()用來設定檔案流的緩沖區,其原型為:
int setvbuf(FILE * stream, char * buf, int type, unsigned size);
setbuf()和setvbuf()函數的實際意義在于:使用者打開一個檔案後,可以建立自己的檔案緩沖區,而不必使用fopen()函數打開檔案時設定的預設緩沖區。這樣就可以讓使用者自己來控制緩沖區,包括改變緩沖區大小、定時重新整理緩沖區、改變緩沖區類型、删除流中預設的緩沖區、為不帶緩沖區的流開辟緩沖區等。
2、perror函數
用于将上個函數發生錯誤的原因輸出到标準裝置(stderr) 。參數 s 所指的字元串會先列印出,後面再加上出錯原因字元串。此錯誤原因依照全局變量error 的值來決定要輸出的字元串。在庫函數中有個error變量,每個 error值對應着以字元串表示的錯誤類型,可以利用strerror(int)函數将出錯字元串資訊列印出來 。當調用"某些"函數出錯時,該函數已經重新設定了error的值。perror函數隻是将你入的一些資訊和現在的error所對應的錯誤一起輸出。
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
int main()
{
FILE* fd;
fd = fopen("/src/hello","r");
if(NULL == fd)
{
perror("can not open file");
return -1;
}
return 0;
}