阅读材料
- xv6代码:log.c
- 教材第8章 8.4-8.6节
日志层的作用
- 系统崩溃恢复:当系统重启的时候,会通过日志恢复未完成的事务,保证文件系统的一致性
- 通过减少磁盘写入次数,提升文件系统性能:日志层可以将多个文件系统操作的修改批量写入日志区域,减少磁盘 I/O 的次数
日志层事务
当进行日志层事务的时候,日志层API调用顺序如下:
begin_op()函数
该函数用于标记一个事务(transaction)的开始,执行的操作如下:
- 等待日志系统不处于提交状态
- 等待有足够的日志空间来容纳本次调用的写入操作
- 如果上述两个条件都通过,则增加outstanding标记这次系统调用
void begin_op(void)
{
acquire(&log.lock);
while (1)
{
if (log.committing)
{
sleep(&log, &log.lock);
}
else if (log.lh.n + (log.outstanding + 1) * MAXOPBLOCKS > LOGSIZE)
{
// this op might exhaust log space; wait for commit.
sleep(&log, &log.lock);
}
else
{
log.outstanding += 1;
release(&log.lock);
break;
}
}
}
log_write()函数
当对缓存中的数据进行修改后,需要调用本函数来更新日志系统,记录本次修改。该函数功能如下:
- 获取日志锁。
- 检查日志大小是否超出限制,若超出则引发异常。
- 检查事务状态是否合法,若不合法则引发异常。
- 遍历日志记录,查找是否存在相同的块号。
- 若未找到,则将新块号添加到日志并增加日志长度,同时锁定块。
- 释放日志锁。
void log_write(struct buf *b)
{
int i;
acquire(&log.lock);
if (log.lh.n >= LOGSIZE || log.lh.n >= log.size - 1)
panic("too big a transaction");
if (log.outstanding < 1)
panic("log_write outside of trans");
for (i = 0; i < log.lh.n; i++)
{
if (log.lh.block[i] == b->blockno) // log absorption
break;
}
log.lh.block[i] = b->blockno;
if (i == log.lh.n)
{ // Add new block to log?
bpin(b);
log.lh.n++;
}
release(&log.lock);
}
end_op()函数
该函数用于标记事务的结束,执行的操作如下:
- 减少待处理的操作计数
log.outstanding
- 若无待处理操作,则设置
do_commit
为 1,并标记正在提交log.committing
- 唤醒可能因等待日志空间而阻塞的
begin_op(),或者因日志正在提交而阻塞的begin_op()
- 若
do_commit
为 1,则执行提交操作并重置log.committing
void end_op(void)
{
int do_commit = 0;
acquire(&log.lock);
log.outstanding -= 1;
if (log.committing)
panic("log.committing");
if (log.outstanding == 0)
{
do_commit = 1;
log.committing = 1;
}
else
{
// begin_op() may be waiting for log space,
// and decrementing log.outstanding has decreased
// the amount of reserved space.
wakeup(&log);
}
release(&log.lock);
if (do_commit)
{
// call commit w/o holding locks, since not allowed
// to sleep with locks.
commit();
acquire(&log.lock);
log.committing = 0;
wakeup(&log);
release(&log.lock);
}
}
commit()函数
该函数用于提交当前事务,将日志中的修改应用到磁盘数据区域,并清空日志
static void commit()
{
if (log.lh.n > 0)
{
write_log(); // Write modified blocks from cache to log
write_head(); // Write header to disk -- the real commit
install_trans(0); // Now install writes to home locations
log.lh.n = 0;
write_head(); // Erase the transaction from the log
}
}
日志恢复
initlog()函数
初始化日志结构,读取日志超级块信息,设置日志的起始地址和大小
void initlog(int dev, struct superblock *sb)
{
if (sizeof(struct logheader) >= BSIZE)
panic("initlog: too big logheader");
initlock(&log.lock, "log");
log.start = sb->logstart;
log.size = sb->nlog;
log.dev = dev;
recover_from_log();
}