1. 程式人生 > 其它 >《Unix/Linux系統程式設計》第十二章學習筆記

《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