《Unix/Linux系統程式設計》第十二章學習筆記
第十二章 塊裝置I/O和緩衝區管理
知識點總結
本章討論了塊裝置 I/O和緩衝區管理;解釋了塊裝置I/O的原理和I/O緩衝的優點;論述了Unix 的緩衝區管理演算法,並指出了其不足之處;還利用訊號量設計了新的緩衝區管理演算法,以提高 I/O緩衝區的快取效率和效能;表明了簡單的PV演算法易於實現,快取效果好,不存在死鎖和飢餓問題;還提出了一個比較 Unix 緩衝區管理演算法和 PV演算法效能的程式設計方案。程式設計專案還可以幫助讀者更好地理解檔案系統中的I/O操作。
塊裝置I/O緩衝區
I/O緩衝的基本原理非常簡單。檔案系統使用一系列I/O緩衝區作為塊裝置的快取記憶體。當程序試圖讀取(dev,blk)標識的磁碟塊時。它首先在緩衝區快取中搜索分配給磁碟塊的緩衝區。如果該緩衝區存在並且包含有效資料、那麼它只需從緩衝區中讀取資料、而無須再次從磁碟中讀取資料塊。如果該緩衝區不存在,它會為磁碟塊分配一個緩衝區,將資料從磁碟讀人緩衝區,然後從緩衝區讀取資料。當某個塊被讀入時、該緩衝區將被儲存在緩衝區快取中,以供任意程序對同一個塊的下一次讀/寫請求使用。同樣,當程序寫入磁碟塊時,它首先會獲取一個分配給該塊的緩衝區。然後,它將資料寫入緩衝區,將緩衝區標記為髒,以延遲寫入,並將其釋放到緩衝區快取中。由於髒緩衝區包含有效的資料,因此可以使用它來滿足對同一塊的後續讀/寫請求,而不會引起實際磁碟I/O。髒緩衝區只有在被重新分配到不同的塊時才會寫人磁碟。
Unix I/O緩衝區管理演算法
I/O緩衝區:核心中的一系列NBUF 緩衝區用作緩衝區快取。每個緩衝區用一個結構體表示。
typdef struct buf[ struct buf*next__free;// freelist pointer struct buf *next__dev;// dev_list pointer int dev.,blk; // assigmed disk block;int opcode; // READ|wRITE int dirty; // buffer data modified int async; // ASYNC write flag int valid; //buffer data valid int buay; // buffer is in use int wanted; // some process needs this buffer struct semaphore lock=1; / // buffer locking semaphore; value=1 struct semaphore iodone=0;// for process to wait for I/0 completion;// block data area char buf[BLKSIZE];) } BUFFER; BUFFER buf[NBUF],*freelist;// NBUF buffers and free buffer list
裝置表:每個塊裝置用一個裝置表結構表示。
struct devtab{
u16 dev;
// major device number // device buffer list BUFFER *dev_list;BUFFER*io_queue
// device I/0 queue ) devtab[NDEV];
Unix演算法的優點:1.資料的一致性;2.快取效果;3.臨界區;
Unix演算法的缺點:1.效率低下;2.快取效果不可預知;3.可能會出現飢餓;4.該演算法使用只適用於單處理系統的休眠/喚醒操作。
PV演算法:
BUFFER *getb1k(dev,blk): while(1){ (1). P(free); //get a free buffer first if (bp in dev_1ist){ (2). if (bp not BUSY){ remove bp from freelist;P(bp); // lock bp but does not wait (3).return bp; // bp in cache but BUSY V(free); // give up the free buffer (4).P(bp); // wait in bp queue return bp;v // bp not in cache,try to create a bp=(dev,blk) (5).bp = frist buffer taken out of freelist;P(bp); // lock bp,no wait (6).if(bp dirty){ awzite(bp); // write bp out ASYNC,no wait continue; // continue from (1) (7).reassign bp to(dev,blk);1/ mark bp data invalid,not dir return bp;- // end of while(1); brelse(BUFFER *bp), { (8).iF (bp queue has waiter)( V(bp); return; ] (9).if(bp dirty && free queue has waiter){ awrite(bp);zeturn;}(10).enter bp into(tail of) freelist;V(bp);V(free); }
新的I/O緩衝區管理演算法
- 訊號量的主要優點是:
(1)計數訊號量可用來表示可用資源的數量,例如:空閒緩衝區的數量。
(2)當多個程序等待一個資源時,訊號量上的V操作只會釋放一個等待程序,該程序不必重試,因為它保證擁有資源。
Box#1:使用者介面﹐這是模擬系統的使用者介面部分,提示輸人命令、顯示命令執行、顯示系統狀態和執行結果等。在開發過程中,可以手動輸入命令來執行任務。在最後測試過程中,任務應該有自己的輸入命令序列 - Box#2:多工處理系統的CPU端,模擬單處理器(單CPU)檔案系統的核心模式。當系統啟動時,它會建立並執行一個優先順序最低的主任務,但它會建立ntask工作任務,所有任務的優先順序都是1,並將它們輸人readyQueue。然後,主任務執行以下程式碼,該程式碼將任務切換為從readyQueue執行工作任務。
- Box#3:磁碟控制器,它是主程序的一個子程序。因此,它與CPU端獨立執行,除了它們之間的通訊通道,通訊通道是CPU和磁碟控制器之間的介面。通訊通道由主程序和子程序之間的管道實現。
- 磁碟中斷:從磁碟控制器到CPU的中斷由SIGUSR1(#10)訊號實現。在每次IO操作結束時,磁碟控制器會發出 kill(ppid, SIGUSR1)系統呼叫,向父程序傳送SIGUSR1訊號,充當虛擬CPU中斷。通常,虛擬CPU會在臨界區遮蔽出/人磁碟中斷(訊號)。為防止競態條件,磁碟控制器必須要從CPU接收一箇中斷確認,才能再次中斷。
- 虛擬磁碟:Box#4:Linux檔案模擬的虛擬磁碟。使用Linux系統呼叫lseek()、read(和write(),支援虛擬磁碟上的任何塊I/O操作。為了簡單起見,將磁碟塊大小設定為16位元組。由於資料內容無關緊要,所以可以將它們設定為16個字元的固定序列。
實踐部分
點選檢視程式碼
include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#define N 100
#define true 1
#define producerNum 10
#define consumerNum 5
#define sleepTime 1000
typedef int semaphore;
typedef int item;
item buffer[N] = {0};
int in = 0;
int out = 0;
int proCount = 0;
semaphore mutex = 1, empty = N, full = 0, proCmutex = 1;
void * producer(void * a){
while(true){
while(proCmutex <= 0);
proCmutex--;
proCount++;
printf("produce a product: ID %d, buffer location:%d\n",proCount,in);
proCmutex++;
while(empty <= 0){
printf("buffer is full\n");
}
empty--;
while(mutex <= 0);
mutex--;
buffer[in] = proCount;
in = (in + 1) % N;
mutex++;
full++;
sleep(sleepTime);
}
}
void * consumer(void *b){
while(true){
while(full <= 0){
printf("buffer is empty\n");
}
full--;
while(mutex <= 0);
mutex--;
int nextc = buffer[out];
buffer[out] = 0;//消費完將緩衝區設定為0
out = (out + 1) % N;
mutex++;
empty++;
printf("produce a product: ID %d, buffer location:%d\n", nextc,out);
sleep(sleepTime);
}
}
int main()
{
pthread_t threadPool[producerNum+consumerNum];
int i;
for(i = 0; i < producerNum; i++){
pthread_t temp;
if(pthread_create(&temp, NULL, producer, NULL) == -1){
printf("ERROR, fail to create producer%d\n", i);
exit(1);
}
threadPool[i] = temp;
}//建立生產者程序放入執行緒池
for(i = 0; i < consumerNum; i++){
pthread_t temp;
if(pthread_create(&temp, NULL, consumer, NULL) == -1){
printf("ERROR, fail to create consumer%d\n", i);
exit(1);
}
threadPool[i+producerNum] = temp;
}//建立消費者程序放入執行緒池
void * result;
for(i = 0; i < producerNum+consumerNum; i++){
if(pthread_join(threadPool[i], &result) == -1){
printf("fail to recollect\n");
exit(1);
}
}//執行執行緒池
return 0;
}
點選檢視程式碼
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <errno.h>
#define total 20
sem_t remain, apple, pear, mutex;
static unsigned int vremain = 20, vapple = 0, vpear = 0;
void *father(void *);
void *mather(void *);
void *son(void *);
void *daughter(void *);
void print_sem();
int main()
{
pthread_t fa, ma, so, da;
sem_init(&remain, 0, total);//總數初始化為20
sem_init(&apple, 0, 0);//盆子中蘋果數, 開始為0
sem_init(&pear, 0, 0);//盆子中梨子數, 開始為0
sem_init(&mutex, 0, 1);//互斥鎖, 初始為1
pthread_create(&fa, NULL, &father, NULL);
pthread_create(&ma, NULL, &mather, NULL);
pthread_create(&so, NULL, &son, NULL);
pthread_create(&da, NULL, &daughter, NULL);
for(;;);
}
void *father(void *arg)
{
while(1)
{
sem_wait(&remain);
sem_wait(&mutex);
printf("before A: remain=%u, apple_num=%u\n", vremain--, vapple++);
printf("after A: remain=%u, apple_num%u\n", vremain, vapple);
sem_post(&mutex);
sem_post(&apple);
sleep(1);
}
}
void *mather(void *arg)
{
while(1)
{
sem_wait(&remain);
sem_wait(&mutex);
printf("before B: remain=%u, pear_num=%u\n", vremain--, vpear++);
printf("after B: remain=%u, pear_num%u\n", vremain, vpear);
sem_post(&mutex);
sem_post(&pear);
sleep(2);
}
}
void *son(void *arg)
{
while(1)
{
sem_wait(&pear);
sem_wait(&mutex);
printf("before C: remain=%u, pear_num=%u\n", vremain++, vpear--);
printf("after C: remain=%u, pear_num=%u\n", vremain, vpear);
sem_post(&mutex);
sem_post(&remain);
sleep(3);
}
}
void *daughter(void *arg)
{
while(1)
{
sem_wait(&apple);
sem_wait(&mutex);
printf("before D remain=%u, apple_num=%u\n", vremain++, vapple--);
printf("after D: remain=%u, apple_num=%u\n", vremain, vapple);
sem_post(&mutex);
sem_post(&remain);
sleep(3);
}
}
void print_sem()
{
int val1, val2, val3;
sem_getvalue(&remain, &val1);
sem_getvalue(&apple, &val2);
sem_getvalue(&pear, &val3);
printf("Semaphore: remain:%d, apple:%d, pear:%d\n", val1, val2, val3);
}
問:setbuf()函式和setvbuf()函式的區別是什麼?
答:setbuf()和setvbuf()函式的實際意義在於:使用者開啟一個檔案後,可以建立自己的檔案緩衝區,而不必使用fopen()函式開啟檔案時設定的預設緩衝區。這樣就可以讓使用者自己來控制緩衝區,包括改變緩衝區大小、定時重新整理緩衝區、改變緩衝區型別、刪除流中預設的緩衝區、為不帶緩衝區的流開闢緩衝區等。
https://www.jb51.net/article/71720.htm