一、缓冲首部
在系统初启期间,核心按照存储器的大小及系统性能的约束条件来为若干个缓冲区分配空间。一个缓冲区由两部分组成:一个含有磁盘上的数据的存储器数组及一个用来标识缓冲区的缓冲首部。由于缓冲首部到数据数组之间为一一对应关系,所以两部分统称为“缓冲区”。
核心通过考察缓冲区首部中的标识符字段来识别缓冲区内容。在同一时间,绝不能将一个磁盘块映射到多个缓冲区中。缓冲首部由下列字段构成:设备号,块号,状态,指向数据区域的指针,指向散列队列上的下一个缓冲区的指针,指向散列队列上的前一个缓冲区的指针,指向空闲表上的下一个缓冲区的指针,指向空闲表上的前一个缓冲区的指针。其中设备号是逻辑文件系统号,一个缓冲区状态是如下条件的组合:当前为“上锁”或“忙”;包含有效数据;核心分配该缓冲区之前必须把该缓冲区内容写到磁盘上,又称为“延迟写”;核心当前正在读磁盘数据到缓冲区或将缓冲区数据写到磁盘上;一个进程正在等待缓冲区变为闲。
二、缓冲池的结构
核心按最近最少使用算法把数据缓存与缓冲池中。在他把一个缓冲区分配给磁盘块之后,只要不是所有其他缓冲区都在更近的时间内被使用过了,它就不能让另一个磁盘块使用该缓冲区。核心维护一个缓冲区的空闲表,它保存被最近使用的次序。空闲表具有一个哑缓冲区光标,以标志这个双向链表的开始和结束。当核心想要一个空闲缓冲区时,它从空闲表的头部取出一个缓冲区,但是,如果它标识出缓冲池中的一个特定块的话,它会从空闲表的中间取出一个缓冲区。在这两种情况下,它都从空闲表中摘下缓冲区。当核心把一个缓冲区还给缓冲区池时,它通常把给缓冲区附在空闲表的尾部。
当核心存取一个磁盘块时,它使用适当的设备号和块号的组合在缓冲区散列表中找相应的缓冲区。一个缓冲区可以同时既在一个散列队列中又在一个空闲队列中,所以核心由两个方法找到它:如果他要寻找一个特定的缓冲则它搜索散列队列;如果它要寻找任何一个空闲缓冲区,则它从空闲表中摘下一个缓冲区。
三、缓冲区的检索
读、写磁盘块的算法使用算法getblk来对池中的缓冲区进行分配。算法如下:
输入--文件系统号,块号
输出--现在能被磁盘块使用的上了锁的缓冲区
{
while(没找到缓冲区)
{
if(块在散列队列中)
{
if(块忙) //第五种情况
{
sleep(等候“缓冲区变为空闲”事件);
continue; //回到while循环
}
为缓冲区标记上“忙”; //第一种情况
从空闲表上摘下缓冲区;
return (缓冲区);
}
else // 块不在散列队列中
{
if(空闲表上无缓冲区) //第四种情况
{
sleep(等候“任何缓冲区变为空闲”事件);
continue; //回到while循环
}
从空闲表上摘下缓冲区;
if(缓冲区标记着延迟写) //第三种情况
{
把缓冲区异步写到磁盘上;
continue; //回到while循环
}
//第二种情况,找到一个空闲缓冲区
从旧散列队列中摘下缓冲区;
把缓冲区投入新散列队列;
return (缓冲区);
}
}
}
当核心结束使用某块缓冲区时,按照算法brelse释放该缓冲区,算法如下:
输入--上锁态的缓冲区
输出--无
{
唤醒正在等待“无论哪个缓冲区变为空闲”这一事件发生的所有进程;
唤醒正在等待“这个缓冲区变为空闲”这一事件发生的所有进程;
提高处理机执行级以封锁中断;
if(缓冲区内容有效且缓冲区非“旧”)
将缓冲区入列到空闲表尾部;
else
将缓冲区入列到空闲表头部;
降低处理机执行级以允许中断;
给缓冲区解锁;
}
当操纵空闲表时核心把处理机执行级提高以禁止磁盘中断,从而防止了由于嵌套调用brelse而引起的缓冲区指针的讹误。如果当一个进程正在执行getblk时,一个中断处理程序引用brelse,则也会发生类似的坏结果。因此,在getblk中对全局有重要意义的地方也要提高处理机执行级。
缓冲区分配算法必须是安全的:必须使进程不永远睡眠,必须使它们最终能得到一个缓冲区。当且仅当缓冲区为开锁状态时,它位于空闲表中。
四、读磁盘块与写磁盘块
读磁盘块算法bread:
输入--文件系统块号
输出--含有数据的缓冲区
{
得到该块的缓冲区(算法getblk);
if(缓冲区数据有效)
return(缓冲区);
启动磁盘读;
sleep(等待“读盘完成”事件);
return(缓冲区);
}
当较高层次的核心模块可能会预期到对另一个磁盘块的需要,因而该模块异步地请求第二个I/O,希望一旦需要这部分数据时,这部分数据将在主存中,这样可以改善性能,为了做到这一点,核心执行提前读磁盘块算法breada:
输入--立即读的文件系统块号;异步读的文件系统块号;
输出--含有可立即读的数据的缓冲区;
{
if(第一块不在高速缓冲中)
{
为第一块获得缓冲区(算法getblk);
if(缓冲区数据无效)
启动磁盘读;
}
if(第二块不在高速缓冲中)
{
为第二块获得缓冲区(算法getblk);
if(缓冲区数据有效)
释放缓冲区(算法brelse);
else
启动磁盘读;
}
if(第一块本来就在高速缓冲中)
{
读第一块(算法bread);
return(缓冲区);
}
sleep(第一个缓冲区包含有效数据的事件);
return(缓冲区);
}
写磁盘块算法bwrite:
输入--缓冲区;
输出--无;
{
启动磁盘写;
if(I/O同步)
{
sleep(等待“I/O完成”事件);
释放缓冲区(算法brelse);
}
else if(缓冲区标记着延迟写)
为缓冲区做标记以放到空闲表头部;
}