(莱昂氏unix源代码分析导读-36) 缓存管理(下)

本文详细解析了Unix系统中缓存管理的相关函数,包括brelse、binit、notavail、clrbuf、incore和getblk等。讨论了缓存的分配、释放、初始化以及读写操作,特别是“延迟写”和“预读”技术。通过对缓存状态标志的处理,实现了高效的数据读写流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

                                      by cszhao1980

理解了上述内容,下面的这些程序就不难理解了。

首先是函数brelse(buf bp),该函数将传入的缓存归还到AV队列中,函数采用尾插法,

即缓存会插到AV队列的队尾——这样做显然有助于提高“延迟写”技术的效率。莱昂

特别指出,brelse没有清理B_DONE标志,这一点非常重要,读完本章后大家就会明白。

 

接下来是binit()函数,完成初始化:

(1)         每个缓存都被放入到空闲设备队列(bfreelist b队列)中;(有趣的是,使用头插法)

(2)         每个缓存都通过调用brelse放入到AV队列中;

(3)         初始化每个设备的b list——都为空(仅有头结点)。

 

还有notavail()函数,其作用是将传入的缓存从AV队列中取下来,并为缓存设置B_BUSY

志,表示该缓存已经被某设备占用,not available了。

 

clrbuf()函数就比较简单了,它将缓存区清0

 

incore()有两个参数:设备号(adev)和块号(blkno),它会Loop该设备的任务队列(b队列),

看是否已经为此块分配了缓存(b_blkno == blkno)。

 

getblk(dev, block)相对比较复杂,它的作用是为指定设备的指定块分配一个缓存。

(1)     它首先检查该设备的b队列,看是否已经为此block分配过缓存,如果有,则检查B_BUSY标记;

                   i.     如置位,则设置B_WANTED标记后睡眠;醒来后,跳回程序开头;

                  ii.     否则,调用notavail占用此缓存(注意,如果B_BUSY未置位,则此缓存已经被归还到AV队列了);

(2)     如果没有这样的缓存,则需要直接从(bfreelist的)AV队列中分配。

                如果该队列为空,则B_WANTED置位后睡眠;

(3)     否则,调用notavail(bfreelist->av_forw)占用AV队列的第一个缓存;

(4)     如该缓存的B_DELWRI置位(“延迟写”),则调用bwrite进行实际io,然后跳回程序开头;

            【思考题】:该缓存被从AV队列中摘下来,谁负责归还呢?

 

(5)         如否,则将该缓存从原设备b队列中摘下来,放入新设备的b队列中。

 

getblk还有个简单的用法,即getblk(NODEV)NODEV定义为-1getblk发现dev为负数时,就直接

AV队列里取一个缓存下来(经过放后,它仍在bfreelistb队列中)。由于此种情况下没有

使用block参数,故调用时,可省略第二个参数的输入。

 

下面看一下读写相关的函数,首先是bread(dev, block),它将指定设备的指定块读入缓存。

首先,它调用getblk来获取一个缓存;

然后,检查该缓存,如果B_DONE标记已经设置,则表示走了大运,无需再读磁盘了,直接return即可;

如果没这么走运,则需要启动设备进行实际的读操作,然后调用iowait等待操作完成。

【注】:记得末,brelse没有清理B_DONE标志。

【思考题】:这样的盘块从何而来?

 

令人疑惑的是,b_wcount的值是一个负数:-256Why?因为这个值会被直接设置给这与RK设备的rkcs

存器,而该寄存器的内容是要传输word数的补码。系统的这种写法其实破坏了其一直努力实现的device

 independent性,rkcs寄存器的特殊要求应该由更底层的函数来实现。

 

bwrite()进行真实io,将指定缓存的内容写入设备,对同步写,它会等待io结束,然后调用brelse()

将缓存归还AV队列。

 

bflush()函数大家都比较熟悉了,它会Loop指定设备的任务(b)队列,调用bwrite()函数将“延迟写”

的缓存写入物理设备。

 

现在,让我们看一下正常的读过程。根据前面的描述,其过程大致如下:

(1)         当进程要读取磁盘盘块时,会首先获取一个buffer,启动调用bread启动设备操作,

                   bread会调用iowait睡眠(以此buffer地址为原因);

(2)         设备操作完成后,会启动rkintr中断处理程序,该程序会唤醒等待的进程;

(3)         进程读取buffer的内容,然后调用brelse释放该buffer

 

对于写操作,也有类似的过程,其根本特点是进程会等待io完成,然后再进行下面的工作。

因此,它们也称为同步io操作。

 

除了同步io之外,在读码过程中,我们还遇到了有关异步(B_ASYNC)的代码,它们是做什么的呢?

对于写操作,比较好理解。异步写操作用于“延迟写”技术——在真正写时,也会调用bwrite()

该函数启动设备操作后,直接return即可,无需调用iowait等待。

 

读操作的情形稍微复杂一些,它用于“预读”。预读用以提高效率,如果我们能预测下一次要读取的盘块

的话,我们可以预先读取它,以提高效率。

 

breada()函数提供了预读的功能,相比bread它多指定了一个参数:read ahead块号,即要“提前/额外”读

入的块的块号。它首先会以同步方式启动“正选”块的读入,然后会以异步方式去启动设备读“提前”

块——这很容易理解,因为在启动读操作时,还没有进程等待此盘块,因此,无法使用同步方式。

 

rkintr在处理异步读入的盘块时,会直接调用brelse将其释放到AV队列(B_DONE仍设置),但在其被再

次分配之前,如果遇到相同盘块的读取请求,会直接返回该buffer,无需再次读入——参见对bread函数的描述。

 

在这里顺便谈一下unix v6的预读算法。在inode结构中有一个变量i_lastr,记录的是上一次读的盘块号,如

果本次读的盘块号正好为i_lastr+1,我们有理由相信,下次读操作有很大机会是读取i_lastr+2号盘块,因此,

就可以调用breada进行预读。

 

我们没有讨论所谓的原始(raw)输入输出函数physio——它的解析就留给读者吧。

 

博客地址:http://blog.youkuaiyun.com/cszhao1980

博客专栏地址:http://blog.youkuaiyun.com/column/details/lions-unix.html

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值