当前位置: 首页 > news >正文

Linux jbd2_journal_recover日志恢复与superblock标记

Linux jbd2_journal_recover日志恢复与superblock标记

jbd2日志恢复在文件系统mount时由jbd2_journal_recover()触发,其核心任务是从日志磁盘区域中扫描已提交但尚未写入文件系统元数据区域的事务,并重放这些事务的元数据块。

日志恢复的入口函数是jbd2_journal_load(),它在mount流程中调用:

int jbd2_journal_load(journal_t *journal)
{
int err;
journal_superblock_t *sb;

err = jbd2_journal_read_superblock(journal, GFP_KERNEL);
if (err)
return err;

sb = journal->j_superblock;
journal->j_tail = be32_to_cpu(sb->s_start);
journal->j_tail_sequence = be32_to_cpu(sb->s_sequence);
journal->j_first = be32_to_cpu(sb->s_first);
journal->j_last = be32_to_cpu(sb->s_maxlen);

if (journal->j_tail_sequence != be32_to_cpu(sb->s_sequence)) {
err = jbd2_journal_recover(journal);
if (err)
return err;
}

err = jbd2_journal_bmap(journal, 0, &journal->j_blk_offset);
...
return 0;
}

函数首先读取journal superblock,从中获取以下关键字段:s_start(日志起始块的物理块号)、s_sequence(当前日志序列号)、s_first和s_maxlen(日志区域的物理范围)。如果s_sequence不等于j_tail_sequence,表示日志不为空,需要执行恢复。

jbd2_journal_recover()调用do_one_pass()执行实际的扫描和重放操作:

int jbd2_journal_recover(journal_t *journal)
{
struct recovery_info info;
int ret;

memset(&info, 0, sizeof(info));

ret = do_one_pass(journal, &info, PASS_SCAN);
if (ret)
return ret;

jbd2_clear_revoke(journal, &info);

ret = do_one_pass(journal, &info, PASS_REPLAY);
if (ret)
return ret;

jbd2_clear_revoke(journal, &info);

ret = do_one_pass(journal, &info, PASS_REVOKE);
if (ret)
return ret;

jbd2_clear_revoke(journal, &info);

return 0;
}

恢复过程分三个阶段(pass)。PASS_SCAN:从日志尾部开始向前扫描所有日志块,构建事务结构,收集revoke记录。PASS_REPLAY:按事务顺序重放所有已提交的元数据块。PASS_REVOKE:处理revoke记录,跳过被撤销的日志块重放。

do_one_pass()的扫描逻辑如下:

static int do_one_pass(journal_t *journal,
struct recovery_info *info,
enum passtype pass)
{
unsigned int first_commit_ID;
unsigned int next_commit_ID;
unsigned long long next_log_block;
int err, success = 0;
journal_superblock_t *sb;
struct buffer_head *bh;
unsigned int sequence;
int blocktype;

sb = journal->j_superblock;
next_commit_ID = info->end_commit_id;
next_log_block = info->end_transaction;

for (;;) {
bh = __jbd2_journal_read_next_block(journal, &next_log_block,
&next_commit_ID);
if (!bh)
break;

sequence = be32_to_cpu(bh->b_blocknr);
blocktype = be32_to_cpu(((journal_header_t *)bh->b_data)->h_blocktype);

switch (blocktype) {
case JBD2_DESCRIPTOR_BLOCK:
if (pass == PASS_REPLAY) {
err = jbd2_journal_replay_descriptor(journal, bh,
next_log_block);
}
break;

case JBD2_COMMIT_BLOCK:
info->end_commit_id = next_commit_ID;
break;

case JBD2_REVOKE_BLOCK:
if (pass == PASS_REVOKE) {
err = jbd2_journal_scan_revoke_record(journal, bh,
next_log_block);
}
break;
}
brelse(bh);
}
return 0;
}

扫描器从日志中逐个读取块,通过h_blocktype字段判断块类型。JBD2_DESCRIPTOR_BLOCK是描述符块,包含本次事务中所有元数据块的UUID、块号列表和校验信息。JBD2_COMMIT_BLOCK是提交块,包含事务的checksum和flags,标记事务成功提交。JBD2_REVOKE_BLOCK是撤销块,列出了不需要重放的块号列表。

在实际重放阶段,jbd2_journal_replay_descriptor()根据描述符块中的条目逐个处理:

int jbd2_journal_replay_descriptor(journal_t *journal,
struct buffer_head *descriptor,
unsigned long long next_log_block)
{
int i;
int err = 0;
struct buffer_head *bh;
unsigned long blocknr;
journal_block_tag_t *tag;
int flags;

tag = (journal_block_tag_t *)&descriptor->b_data[sizeof(journal_header_t)];
for (i = 0; i < journal->j_blocksize / sizeof(journal_block_tag_t); i++) {
blocknr = be32_to_cpu(tag->t_blocknr);
flags = tag->t_flags;

if (flags & JBD2_FLAG_SAME_UUID) {
bh = __jbd2_journal_get_descriptor_buffer(journal, blocknr);
if (!bh)
return -EIO;

err = jbd2_journal_replay_block(journal, bh, next_log_block);
if (err) {
brelse(bh);
return err;
}

if (flags & JBD2_FLAG_LAST_TAG)
break;
brelse(bh);
}
tag++;
}
return 0;
}

每个描述符块包含多个tag条目,每个tag对应一个日志数据块。t_blocknr是目标块号(文件系统元数据块的物理位置),t_flags包含JBD2_FLAG_SAME_UUID和JBD2_FLAG_LAST_TAG等标志。jbd2_journal_replay_block()将日志块的数据复制到目标元数据块:

int jbd2_journal_replay_block(journal_t *journal,
struct buffer_head *obh,
unsigned long long next_log_block)
{
struct buffer_head *nbh;
int err;

nbh = __getblk(journal->j_fs_dev, next_log_block,
journal->j_blocksize);
lock_buffer(nbh);
memcpy(nbh->b_data, obh->b_data, journal->j_blocksize);
set_buffer_uptodate(nbh);
mark_buffer_dirty(nbh);
unlock_buffer(nbh);
sync_dirty_buffer(nbh);
brelse(nbh);
return 0;
}

恢复完成后,jbd2_journal_recover()更新journal superblock的s_start和s_sequence字段,标记日志为空:

void jbd2_journal_update_sb_log_tail(journal_t *journal,
tid_t tail_tid,
unsigned long long tail_block,
int write_op)
{
journal_superblock_t *sb = journal->j_superblock;
sb->s_sequence = cpu_to_be32(tail_tid);
sb->s_start = cpu_to_be32(tail_block);
jbd2_journal_write_superblock(journal, write_op);
}

s_start被设置为0表示日志已清空,s_sequence更新为最后一个已提交事务的序列号。此时文件系统可以直接挂载,不需要再次恢复。

jbd2的superblock还包含了s_errno字段用于记录上一次mount时的错误码,以及s_feature_compat/s_feature_incompat/s_feature_ro_compat三个特性字段,用于控制日志格式的向后兼容性。当jbd2检测到未知的incompat特性位时,拒绝mount以防止数据损坏。

http://www.rkmt.cn/news/1518391.html

相关文章:

  • UUV Simulator终极指南:快速构建高保真水下机器人仿真系统
  • 2026年6月南通劳保手套工厂排行:服务与品质双维度深度盘点 - 奔跑123
  • Python 高手编程系列三千三百七十八:构建自己的文档集
  • 2026年6月国内松木镜框油画布框套装定制服务商排行top5,资质与专业评测推荐 - 奔跑123
  • 2026 青岛汽车音响改装靠谱度榜首:鼎峰汇汽车音响,被低估的技术标杆 - 汽车音响改装
  • 如何快速部署AI模型到嵌入式设备:5大实用技巧与RKNN-Toolkit2终极指南
  • 2026石家庄翡翠回收深度实测:七家机构种水色工专项横评 - 薛定谔的梨花猫
  • DLSS Swapper终极指南:智能游戏性能优化方案
  • 别再乱用快照了!QEMU磁盘快照和检查点快照的保姆级区别与实战(Windows+Debian)
  • 华浙培训・浙经院高复班(下沙)电话号码给我一下 - 弱书讲升学
  • Visual Syslog Server:为Windows系统打造的专业级集中日志管理解决方案
  • 2026西安钻石回收翘楚,本地赛道顶流王机构测评 - 讯息早知道
  • Aider
  • 2026 年 6 月深圳卫生间阳台屋顶漏水修缮测评 本地三家防水工艺材料质保全方位对比 - 吉修匠
  • 去除水印工具推荐:软件小程序都好用的去水印神器 - 工具软件使用方法推荐
  • 2026年油莎豆加工成套设备深度选型指南:如何为你的生产项目匹配最佳方案? - 速递信息
  • 国产化项目实战:手把手教你为若依(Ruoyi-Vue)系统剥离Redis依赖(附完整代码)
  • 3G/LTE PDU安全处理实战:从协议原理到NXP SEC硬件加速实现
  • 如何高效管理Switch游戏文件:NSC_BUILDER实用指南
  • CANN/asc-devkit数学API示例介绍
  • 2025最简单IDM激活教程:永久免费解锁下载神器终极指南
  • ReadCat小说阅读器:5个步骤打造你的纯净数字书房
  • LITIENGINE社区生态解析:插件、工具与第三方资源完全指南 [特殊字符]
  • PacketEvents事件系统完全指南:从基础监听器到高级事件处理
  • BthPS3驱动技术指南:解决PS3手柄在Windows系统的蓝牙连接难题
  • MES与ERP的区别和联系到底是什么?
  • zsh-async社区最佳实践:来自开源项目的10个实用技巧
  • 云顶之弈策略博弈中信息优势的构建:TFT Overlay实战深度解析
  • 2026大型不锈钢雕塑厂家选型指南及实力排行 - 曲阳嘉华园林
  • 广州卡地亚做深度保养是否必须拆卸表盘!广州卡地亚机芯检修逻辑,亨得利区分简易维保与全拆保养差异 - 亨得利官方维修中心