20201317 LYX 第12章 块设备I/O和缓冲区管理

第12章 块设备I/O和缓冲区管理

知识总结

  1. 解释块设备I/O的原理和I/O缓冲的优点
  2. 介绍Unix的缓冲区管理算法
  3. 利用信号量设计新的缓冲区管理算法,以提高I/O缓冲区的缓存效率和性能
  4. 介绍简单的PV算法及其特点

基本概念
读写普通文件的算法依赖于两个关键操作,即get_block和put_block,这两个操作将磁盘块读写到内存缓冲区中。由于与内存访问相比,磁盘I/O速度较慢,所以不希望在每次执行读写文件操作时都执行磁盘I/O。因此、大多数文件系统使用I/O缓冲来减少进出存储设备的物理I/O数量。

合理设计的I/O缓冲方案可显著提高文件I/O效率并增加系统吞吐量。

I/O缓冲的基本原理非常简单。文件系统使用系列I/O缓冲区作为块设备的缓存内存。当进程试图读取(dev,blk)标识的磁盘块时。它首先在缓冲区缓存中搜索分配给磁盘块的缓冲区。

如果该缓冲区存在并且包含有效数据、那么它只需从缓冲区中读取数据、而无须再次从磁盘中读取数据块。如果该缓冲区不存在,它会为磁盘块分配一个缓冲区,将数据从磁盘读人缓冲区,然后从缓冲区读取数据。当某个块被读入时,该缓冲区将被保存在缓冲区缓存中。以供任意进程对同一个块的下一次读/写请求使用。同样。当进程写入磁盘块时。它首先会获取一个分配给该块的缓冲区。然后,它将数据写入缓冲区,将缓冲区标记为脏、以延迟写入,并将其释放到缓冲区缓存中。由于脏缓冲区包含有效的数据,因此可以使用它来满足对同一块的后续读/写请求,而不会引起实际磁盘I/O。脏缓冲区只有在被重新分配到不同的块时才会写入磁盘。

在read file/write file中,我们假设它们从内存中的一个专用缓冲区进行读/写。
对于I/O缓冲,将从缓冲区缓存中动态分配缓冲区。假设BUFFER是缓冲区的结构类型,而且getblk(dev,blk)从缓冲区缓存中分配一个指定给(dev,blk)的缓冲区。定义一个bread(dev,blk)函数,它会返回一个包含有效数据的缓冲区(指针)。

从缓冲区读取数据后,进程通过brelse(bp)将缓冲区释放会缓冲区缓存。同理,定义一个write_block(dev, blk, data)函数。

同步写入操作等待写操作完成,用于顺序块或可移动块设备。

当I/O操作完成后,设备中断处理程序会完成当前缓冲去上的I/O操作,并启动I/O队列中下一个缓冲区的I/O。

Unix I/O缓冲区管理算法

实践过程

  1. 信号同步

    生产者消费者问题

    “生产者—消费者”问题 (producer/consumer problem) 是最著名的进程同步问题。
    该问题描述了共享固定大小缓冲区的两个线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。
    与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

    它是许多相互合作进程的抽象,如输入进程与计算进程;计算进程与打印进程等。

    要解决该问题,就必须让生产者在缓冲区满时休眠,等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。

    设置两个资源信号量及一个互斥信号量。
    资源信号量 empty:说明空缓冲区的数目,其初值为有界缓冲池的大小n。
    资源信号量 full:说明满缓冲区的数目(即产品数目),其初值为 0。full+ empty= n。
    互斥信号量 s: 说明该有界缓冲池是一个临界资源,必须互斥使用,其初值为 1。

    //生产者消费者问题
    #include<stdio.h>
    #include<unistd.h>
    #include<semaphore.h>
    #include<pthread.h>
    sem_t empty,full,s;
    int buffer[10]={-1};
    int fill=0;
    int use=0;
    void put(int value){
    	buffer[fill]=value;
    	fill=(fill+1)%11;
    }
    int get(){
    	int tmp=buffer[use];
    	use=(use+1)%11;
    	return tmp;
    }
    void *producer(void *arg){
    	printf("producer\n");
    
    	int i=0;
    	for(i=0;i<=20;i++){
    		//sleep(3);
    		sem_wait(&empty);
    		sem_wait(&s);
    		
    		put(i);
    		sem_post(&s);
    		sem_post(&full);
    		printf("producer put:%d\n",i);
    	}
    
    	//pthread_exit(0);
    }
    void *consumer(void *arg){
    	printf("consumer\n");
    	
    	int i=0;
    	for(i=0;i<=20;i++){
    		//sleep(3);
    		sem_wait(&full);
    		sem_wait(&s);
    		
    		int tmp=get();
    		
    		sem_post(&s);
    		sem_post(&empty);
    		printf("consumer get:%d\n",tmp);
    	}
    	//pthread_exit(0);
    }
    int main(int argv,char * args[]){
    	sem_init(&empty,0,10);
    	sem_init(&full,0,0);
    	sem_init(&s,0,1);
    	pthread_t pro,con;
    	pthread_create(&pro,NULL,producer,NULL);
    	pthread_create(&con,NULL,consumer,NULL);
    	pthread_join(con,NULL);
    	pthread_join(pro,NULL);
    	
    	
    
    }
    

    image-20221102192010352

  2. 吃水果问题

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>

//定义信号量
//盘子空信号量
sem_t empty;//初始值为1,表示当前盘子为空
//苹果信号量
sem_t apple;//初始值为0,表示当前盘子没有苹果
//橘子信号量
sem_t orange;//初始值为0,表示当前盘子没有橘子
//函数声明
void* father(void* arg);//父亲线程执行函数
void* mother(void* arg);//母亲线程执行函数
void* son(void* arg);//儿子线程执行函数
void* daughter(void* arg);//女儿线程执行函数

void* father(void* arg) {
while(1) {
sem_wait(&empty);
// 放入一个苹果
printf("father --> apple\n");
sem_post(&apple);
        sleep(rand() % 10); // 随机休眠一段时间
    }
}
void* mother(void* arg)
{
	while(1)
	{
		sem_wait(&empty);
		//放入一个橘子
		printf("mother --> orange\n");
		sem_post(&orange);
		sleep(rand() % 10); // 随机休眠一段时间
	}
}
void* son(void* arg)
{
	while(1)
	{   //等盘子不为空才开始执行下面的,盘子不为空说明有橘子或者苹果
        sem_wait(&apple);//当苹果为1时,做减法,0则不做
		printf("son --> apple\n");
		//把盘子信号量释放,表示盘子为空,信号量加1
		sem_post(&empty);
        sleep(rand() % 10); // 随机休眠一段时间
		}
}
void* daughter(void* arg)
{
	while(1)
	{   //等盘子不为空才开始执行下面的,盘子不为空说明有橘子或者苹果
        sem_wait(&orange);//当橘子为1时,做减法,0则不做
		printf("daughter --> orange\n");
		//把盘子信号量释放,表示盘子为空,信号量加1
		sem_post(&empty);
        sleep(rand() % 10); // 随机休眠一段时间
		}
}
int main()
{   //定义父亲,母亲,儿子,女儿线程
	pthread_t fatherThread,motherThread,sonThread,daughterThread;
	//初始化信号量,empty 1,orange 0,apple 0;
	sem_init(&empty,0,1);
	sem_init(&orange,0,0);
	sem_init(&apple,0,0);
	//创建线程
	pthread_create(&fatherThread, NULL, father, NULL);
	pthread_create(&motherThread, NULL, mother, NULL);
	pthread_create(&sonThread, NULL, son, NULL);
	pthread_create(&daughterThread, NULL, daughter, NULL);
	pthread_join(fatherThread, NULL);
    pthread_join(motherThread, NULL);
	pthread_join(sonThread, NULL);
	pthread_join(daughterThread, NULL);
	
}

image-20221102192322918

  1. setbuf()和setvbuf()函数的实际意义在于:用户打开一个文件后,可以建立自己的文件缓冲区,而不必使用fopen()函数打开文件时设定的默认缓冲区。这样就可以让用户自己来控制缓冲区,包括改变缓冲区大小、定时刷新缓冲区、改变缓冲区类型、删除流中默认的缓冲区、为不带缓冲区的流开辟缓冲区等。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
   char buff[1024];

   memset( buff, '\0', sizeof( buff ));
 
   fprintf(stdout, "启用全缓冲\n");
   setvbuf(stdout, buff, _IOFBF, 1024);
 
   fprintf(stdout, "这里是 runoob.com\n");
   fprintf(stdout, "该输出将保存到 buff\n");
   fflush( stdout );
 
   fprintf(stdout, "这将在编程时出现\n");
   fprintf(stdout, "最后休眠五秒钟\n");
 
   sleep(5);
 
   return(0);
}

image-20221102192602599

posted @ 2022-11-02 19:29  B1smarck  阅读(24)  评论(0编辑  收藏  举报