static journal_t *ext4_get_dev_journal(struct super_block *sb, dev_t j_dev) { struct buffer_head *bh; journal_t *journal; ext4_fsblk_t start; ext4_fsblk_t len; int hblock, blocksize; ext4_fsblk_t sb_block; unsigned long offset; struct ext4_super_block *es; struct block_device *bdev;
//检查这个分区是不是有兼容功能,如果没有,并且有日志,那就报错。
BUG_ON(!EXT4_HAS_COMPAT_FEATURE(sb, EXT4_FEATURE_COMPAT_HAS_JOURNAL));
// 这个函数是根据设备号获得设备,这个函数也是相当复杂的,这里就不讲了。
// 如果要详细讲来,10篇blog也写不完。它就是根据设备编号获得一个描述设备
//的结构体
bdev = ext4_blkdev_get(j_dev); if (bdev == NULL) //当然如果得到的空,就是出错了,我们也就不费劲,直接返回。
return NULL;
/* 从那个bd_claim的函数名,我们就可以宣称这个设备或设备分区的的所有都就是sb, 如果当前设备已经属于其它的所有者,并且不是当然sb,那么就出错了。然后调用 blkdev_put来释放当前设备。为什么不直接free掉呢,因为在linux内核分配的内存 不像我们做一般的开发,有可能还有其它的使用者,在这里调用就把它的bd_openers 减1,如果为0,表示没有使用者才真正释放。然后函数返回。 */ if (bd_claim(bdev, sb)) { printk(KERN_ERR "EXT4-fs: failed to claim external journal device.\n"); blkdev_put(bdev, FMODE_READ|FMODE_WRITE); return NULL; }
/* 以下的代码是获得设备分区文件系统块大小和设备的物理块大小。在设备物理特性中, 能处理的单元最小一般是512字节,当然也有不按常理出牌的家伙。 */ blocksize = sb->s_blocksize; hblock = bdev_hardsect_size(bdev); //如果文件系统的块比设备的物理扇区还小,那就没法操作了,因为硬盘无法操作
//比扇区还小的单元。那么就退出。
if (blocksize < hblock) { printk(KERN_ERR "EXT4-fs: blocksize too small for journal device.\n"); goto out_bdev; }
sb_block = EXT4_MIN_BLOCK_SIZE / blocksize; offset = EXT4_MIN_BLOCK_SIZE % blocksize; set_blocksize(bdev, blocksize); /* __bread 函数就是从设备指定块,读取指定大小的数据,这里是从第一个块读取 blocksize(通常是4K)的数据,在放在一个buffer_head的结构体中,这个结构 体在include/linux/buffer_head.h里定义,就是用来存放块设备数据的。 */ if (!(bh = __bread(bdev, sb_block, blocksize))) { printk(KERN_ERR "EXT4-fs: couldn't read superblock of " "external journal\n"); goto out_bdev; }
/* 然后再用刚才读出来的数据转换成一个ext4_super_block结构体,是ext4超级块在硬盘上 的表示。这个结构体包含了分区的详细信息来用构造VFS块的super_block。 */ es = (struct ext4_super_block *) (((char *)bh->b_data) + offset); // 检查这个超级块是不是ext4的超级块,并且检查它的兼容性。
if ((le16_to_cpu(es->s_magic) != EXT4_SUPER_MAGIC) || !(le32_to_cpu(es->s_feature_incompat) & EXT4_FEATURE_INCOMPAT_JOURNAL_DEV)) { printk(KERN_ERR "EXT4-fs: external journal has " "bad superblock\n"); //如果出错,那么就释放,掉刚读出来的buffer_head,和刚才bdev一样,不能直接free.
brelse(bh); goto out_bdev; }
// 光是前面的检查不够,还要检查这个分区的ext4_super_block里的s_journal_uuid
// 和刚读出来的(日志分区的)ext4_super_block里的s_uuid是否一样。如果一样才
// 表示这个分区是正确的journal就是这个日志分区.
if (memcmp(EXT4_SB(sb)->s_es->s_journal_uuid, es->s_uuid, 16)) { printk(KERN_ERR "EXT4-fs: journal UUID does not match\n"); brelse(bh); goto out_bdev; }
// 获得分区的block数,和分区数据开始的block号。因为第一个block用来存放ext4_super_block
// 结构体所以要加1
len = ext4_blocks_count(es); start = sb_block + 1; brelse(bh); /* we're done with the superblock */
// 这是这个函数的里的核心内容,主要就是初始化这个分区为日志分区,因为在之前已经做好一切
// 准备,下一节再做详细的说明。
journal = jbd2_journal_init_dev(bdev, sb->s_bdev, start, len, blocksize); //如果初始没有得到一个真正的journal,那么我们也是返回去了。
if (!journal) { printk(KERN_ERR "EXT4-fs: failed to create device journal\n"); goto out_bdev; } // 把super_block放在journal的private数据里,以供以后用。
journal->j_private = sb; // 从设备中读取journal的super_block的数据。读一个buffer_head结构表示的数据,
// ll_rw_block可谓是骨灰的函数,从linux 0.1开始就存在。
ll_rw_block(READ, 1, &journal->j_sb_buffer); // 因为硬盘读数据是异步的,所以要等待,不要看这个函数很简单,它可涉及linux进程
// 管理最精华的思想,因为这里不是主要讲它的,所以知道ll_rw_block和这个wait_on_buffer
// 干的事情就是把读的请求通过request提交到这块硬盘的驱动,然后把当前进程挂起,然后等待
// 数据读完到断续执行。
wait_on_buffer(journal->j_sb_buffer); // 如果等待超时,或是读取有错,那j_sb_buffer就不会把b_state置位BH_Uptodate,说明出错了,
// 当然退出了,还有什么好办法。
if (!buffer_uptodate(journal->j_sb_buffer)) { printk(KERN_ERR "EXT4-fs: I/O error on journal device\n"); goto out_journal; } // 还要检查是不是只当前进程在用,如果其它进程也在用,或是根本没进程用,那么就出错了,
// 退出。
if (be32_to_cpu(journal->j_superblock->s_nr_users) != 1) { printk(KERN_ERR "EXT4-fs: External journal has more than one " "user (unsupported) - %d\n", be32_to_cpu(journal->j_superblock->s_nr_users)); goto out_journal; } // 设置当前分区的日志分区设备描述符。
EXT4_SB(sb)->journal_bdev = bdev; // 初始化journal的参数,主要把 journal通过这个分区的super_block与这个分区关系起来。
ext4_init_journal_params(sb, journal); return journal; // 到这里就大事大吉,收工。
out_journal: jbd2_journal_destroy(journal); out_bdev: ext4_blkdev_put(bdev); return NULL; }
|