xv6——文件系统:磁盘的I/O操作和内存缓存机制
目录
目录
作者:殷某人
更新时间:2022/07/03
相关源码文件
buf.h
bio.c
ide.c
架构图
数据结构
内存的缓存块
缓存块用于缓存磁盘上的一个block, 大小为512字节, 在内存中,缓存块采用数组+双向链表的数据结构,使用链表的目的是进行LRU优化, 链表的顺序按照最近使用顺序进行排列, 提高复用率,减少IO操作。
每一个buffer块都有自己的一把锁,一个buffer块同一时间只能有一个进程拥有。
- buffer块的数据结构定义如下:
struct buf {
int flags; // 标志位,b_valid和b_dirty.
uint dev; // 缓存块对应的磁盘设备号
uint blockno; // 缓存块对应的block块号
struct sleeplock lock; // 睡眠锁, 保证一个buffer同一时间只可能被一个进程拥有
uint refcnt; // 引用次数
struct buf *prev; // LRU双向链表
struct buf *next; // LRU双向链表
struct buf *qnext; // 当Buffer块需要与磁盘间进行同步时,Buffer块之间组成的单向同步队列
uchar data[BSIZE]; // 512字节的数据缓存区
};
- buffer Table的数据结构定义如下:
struct {
struct spinlock lock; // 锁
struct buf buf[NBUF]; // buffer块数组,一大块连续的buffer
struct buf head; // 它可以看出一个哨兵,目的方便双向链表的操作
// head.next 是第一个buffer块, 它是最近使用过的!
} bcache;
函数实现
IDE磁盘的读写操作
磁盘串口读写操作函数 void idestart()
- 计算要主读取的sector块数,通常512字节对应一个sector.
- 写操作:CPU只负责写到磁盘的串口寄存器, 磁盘自己会执行真正的写到磁盘的操作.完成之后,会通过中断告诉CPU
- 读操作:CPU通知磁盘读取512字节内容到磁盘寄存器,完成之后,通过中断告诉CPU,CPU之后会把内容复制到内存中
static void idestart(struct buf *b)
{
if(b == 0)
panic("idestart");
if(b->blockno >= FSSIZE)
panic("incorrect blockno");
int sector_per_block = BSIZE/SECTOR_SIZE;
int sector = b->blockno * sector_per_block;
int read_cmd = (sector_per_block == 1) ? IDE_CMD_READ : IDE_CMD_RDMUL;
int write_cmd = (sector_per_block == 1) ? IDE_CMD_WRITE : IDE_CMD_WRMUL;
if (sector_per_block > 7) panic("idestart");
idewait(0);
outb(0x3f6, 0); // generate interrupt
outb(0x1f2, sector_per_block); // number of sectors
outb(0x1f3, sector & 0xff);
outb(0x1f4, (sector >> 8) & 0xff);
outb(0x1f5, (sector >> 16) & 0xff);
outb(0x1f6, 0xe0 | ((b->dev&1)<<4) | ((sector>>24)&0x0f));
if(b->flags & B_DIRTY){
outb(0x1f7, write_cmd);
outsl(0x1f0, b->data, BSIZE/4);
} else {
outb(0x1f7, read_cmd);
}
}
磁盘中断处理函数void ideintr()
- 在收到中断处理函数时, 如果是磁盘读取工作完成,会负责把内容从寄存器读取到内存缓存区。
- 刷新标志位,清掉dirdy, 置位valid.
void ideintr(void)
{
struct buf *b;
// First queued buffer is the active request.
acquire(&idelock);
if((b = idequeue) == 0){
release(&idelock);
return;
}
idequeue = b->qnext;
// Read data if needed.
if(!(b->flags & B_DIRTY) && idewait(1) >= 0)
insl(0x1f0, b->data, BSIZE/4);
// Wake process waiting for this buf.
b->flags |= B_VALID;
b->flags &= ~B_DIRTY;
wakeup(b);
// Start disk on next buf in queue.
if(idequeue != 0)
idestart(idequeue);
release(&idelock);
}
磁盘的读写处理函数void iderw()
- 把需要同步的buffer块添加到操作队列中
- 如果队列内只有它自己,说明队列任务未启动, 启动磁盘同步。
void iderw(struct buf *b)
{
struct buf **pp;
if(!holdingsleep(&b->lock))
panic("iderw: buf not locked");
if((b->flags & (B_VALID|B_DIRTY)) == B_VALID)
panic("iderw: nothing to do");
if(b->dev != 0 && !havedisk1)
panic("iderw: ide disk 1 not present");
acquire(&idelock); //DOC:acquire-lock
// Append b to idequeue.
b->qnext = 0;
for(pp=&idequeue; *pp; pp=&(*pp)->qnext) //DOC:insert-queue
;
*pp = b;
// Start disk if necessary.
if(idequeue == b)
idestart(b);
// Wait for request to finish.
while((b->flags & (B_VALID|B_DIRTY)) != B_VALID){
sleep(b, &idelock);
}
release(&idelock);
}
内存缓存区操作函数
Buffer块初始化函数void binit()
- 初始化锁
- 初始化双向链表
void binit(void)
{
struct buf *b;
initlock(&bcache.lock, "bcache");
//PAGEBREAK!
// Create linked list of buffers
bcache.head.prev = &bcache.head;
bcache.head.next = &bcache.head;
for(b = bcache.buf; b < bcache.buf+NBUF; b++){
b->next = bcache.head.next;
b->prev = &bcache.head;
initsleeplock(&b->lock, "buffer");
bcache.head.next->prev = b;
bcache.head.next = b;
}
}
查找指定的磁盘block块对应的buffer缓存块函数struct buff* bget()
- 如果该bock块已经缓存过,直接返回缓存的buffer块
- 如果没有缓存过,查找一块空闲的buffer块,然后返回。
static struct buf*
bget(uint dev, uint blockno)
{
struct buf *b;
acquire(&bcache.lock);
// Is the block already cached?
for(b = bcache.head.next; b != &bcache.head; b = b->next){
if(b->dev == dev && b->blockno == blockno){
b->refcnt++;
release(&bcache.lock);
acquiresleep(&b->lock);
return b;
}
}
// Not cached; recycle an unused buffer.
// Even if refcnt==0, B_DIRTY indicates a buffer is in use
// because log.c has modified it but not yet committed it.
for(b = bcache.head.prev; b != &bcache.head; b = b->prev){
if(b->refcnt == 0 && (b->flags & B_DIRTY) == 0) {
b->dev = dev;
b->blockno = blockno;
b->flags = 0;
b->refcnt = 1;
release(&bcache.lock);
acquiresleep(&b->lock);
return b;
}
}
panic("bget: no buffers");
}
读取指定的磁盘的block块, 返回读取到的buffer块bread()
- 如果该bock块已经缓存过,直接返回缓存的buffer块,它当前数据是最新的。
- 如果没有缓存过,需要把磁盘上的block块的内容同步到缓存块中,然后返回。
struct buf*
bread(uint dev, uint blockno)
{
struct buf *b;
b = bget(dev, blockno);
if((b->flags & B_VALID) == 0) {
iderw(b);
}
return b;
}
写缓存块操作 bwrite()
- 写之前,该buffer块一定是已经加锁的。
- 写完成之后,会执行缓存区到磁盘的写操作。
void
bwrite(struct buf *b)
{
if(!holdingsleep(&b->lock))
panic("bwrite");
b->flags |= B_DIRTY;
iderw(b);
}
释放指定的buffer块 void brelse()
- 释放锁, 减少引用次数
- 若该buffer没有进程使用时,把它移动到链接头部,LRU。
// Release a locked buffer.
// Move to the head of the MRU list.
void brelse(struct buf *b)
{
if(!holdingsleep(&b->lock))
panic("brelse");
releasesleep(&b->lock);
acquire(&bcache.lock);
b->refcnt--;
if (b->refcnt == 0) {
// no one is waiting for it.
b->next->prev = b->prev;
b->prev->next = b->next;
b->next = bcache.head.next;
b->prev = &bcache.head;
bcache.head.next->prev = b;
bcache.head.next = b;
}
release(&bcache.lock);
}