2021SC@SDUSC
inode.c(1)
今天来分析inode.c文件。有了前面对ext4_jbd2.h、acl.h等头文件的分析做基础,今天的分析将相对简单。
在看代码之前,首先要说一下inode数据结构。inode是Linux内核文件系统中最重要的数据结构之一,里面保存了文件的大小、文件块的大小、创建时间等参数,可以说,一个inode就代表了一个文件。因为软连接、硬连接的存在,指向一个文件的路径可能有多个,即一个文件可以有多个dentry,但是一个文件只能有一个inode。
ext4_inode 定义于/fs/ext4/ext4.h,大小为256字节,也就是说一个4KB的块可以保存16个inode。
/*
* Structure of an inode on the disk
*/
struct ext4_inode {
__le16 i_mode; /*文件模式 */
__le16 i_uid; /* 低16位的owner Uid */
__le32 i_size_lo; /* 文件大小的字节数 */
__le32 i_atime; /* 存取时间 */
__le32 i_ctime; /* inode改变时间 */
__le32 i_mtime; /* 修改时间 */
__le32 i_dtime; /* 删除时间 */
__le16 i_gid; /* 低16位 group id */
__le16 i_links_count; /* 链接数 */
__le32 i_blocks_lo; /* 块数目 */
__le32 i_flags; /* 文件标志 */
union {
struct {
__le32 l_i_version;
} linux1;
struct {
__u32 h_i_translator;
} hurd1;
struct {
__u32 m_i_reserved1;
} masix1;
} osd1; /* OS dependent 1 */
__le32 i_block[EXT4_N_BLOCKS];/* 块指针 */
__le32 i_generation; /* 文件版本(适用于NFS) */
__le32 i_file_acl_lo; /* 文件的ACL */
__le32 i_size_high;
__le32 i_obso_faddr; /* 弃用片段的地址 */
union {
struct {
__le16 l_i_blocks_high; /* were l_i_reserved1 */
__le16 l_i_file_acl_high;
__le16 l_i_uid_high; /* these 2 fields */
__le16 l_i_gid_high; /* were reserved2[0] */
__le16 l_i_checksum_lo;/* crc32c(uuid+inum+inode) LE */
__le16 l_i_reserved;
} linux2;
struct {
__le16 h_i_reserved1; /* 在ext4中被删除的残片号/大小 */
__u16 h_i_mode_high;
__u16 h_i_uid_high;
__u16 h_i_gid_high;
__u32 h_i_author;
} hurd2;
struct {
__le16 h_i_reserved1; /* 在ext4中被删除的残片号/大小 */
__le16 m_i_file_acl_high;
__u32 m_i_reserved2[2];
} masix2;
} osd2; /* OS dependent 2 */
__le16 i_extra_isize;
__le16 i_checksum_hi; /* crc32c(uuid+inum+inode) BE */
__le32 i_ctime_extra; /* 额外的变化(change)时间 (nsec << 2 | epoch) */
__le32 i_mtime_extra; /* 额外的修改(Modification)时间 (nsec << 2 | epoch) */
__le32 i_atime_extra; /* 额外的访问(Access)时间 (nsec << 2 | epoch) */
__le32 i_crtime; /* 文件创建时间 */
__le32 i_crtime_extra; /* 额外的文件创建时间 (nsec << 2 | epoch) */
__le32 i_version_hi; /* 高32位(64位版本) */
__le32 i_projid; /* Project ID */
};
由此可见字段i_block的大小为60个字节,即__le32 i_block[EXT4_N_BLOCKS]且EXT4_N_BLOCKS=15。其中前12个字节为extent头,保存的是extent的基本信息;后48个字节可以保存4个extent节点,每个extent节点为12字节大小。
以下是inode.c文件的代码分析:
/*
*测试一个索引节点是否为快速符号链接。
*快速符号链接的符号链接数据存储在ext4_inode_info->i_data中。
*/
int ext4_inode_is_fast_symlink(struct inode *inode)
{
if (!(EXT4_I(inode)->i_flags & EXT4_EA_INODE_FL)) {
int ea_blocks = EXT4_I(inode)->i_file_acl ?
EXT4_CLUSTER_SIZE(inode->i_sb) >> 9 : 0;
if (ext4_has_inline_data(inode))
return 0;
return (S_ISLNK(inode->i_mode) && inode->i_blocks - ea_blocks == 0);
}
return S_ISLNK(inode->i_mode) && inode->i_size &&
(inode->i_size < EXT4_N_BLOCKS * 4);
}
/*
* 在i_nlink为零的最后一个iput()函数中调用。
*/
void ext4_evict_inode(struct inode *inode)
{
handle_t *handle;
int err;
/*
*最终的inode清理和释放:
*sb + inode (ext4_orphan_del()),块位图(block bitmap),组描述符(xattr块释放),
位图(bitmap),组描述符(inode释放)
*/
int extra_credits = 6;
struct ext4_xattr_inode_array *ea_inode_array = NULL;
bool freeze_protected = false;
trace_ext4_evict_inode(inode);
if (inode->i_nlink) {
/*
*当记录数据脏缓冲区只在日志中跟踪。
因此,尽管mm认为一切就绪,可以获取inode,
但在运行的事务中仍然可能有一些页面需要写入,或者等待被检查点。
因此,调用jbd2_journal_invalidatepage()(通过truncate_inode_pages())来丢弃这些缓冲区可能会导致数据丢失。
而且,即使我们没有丢弃这些缓冲区,在获取inode之后,我们也无法找到它们,
因此,如果用户试图在事务被检查点之前读取这些缓冲区,就可能看到过期的数据。
所以要小心,把所有东西都放到圆盘上。
我们使用ei->i_datasync_tid来存储包含inode数据的最新事务。
*
*注意目录没有这个问题,因为它们不使用页面缓存。
*/
if (inode->i_ino != EXT4_JOURNAL_INO &&
ext4_should_journal_data(inode) &&
(S_ISLNK(inode->i_mode) || S_ISREG(inode->i_mode)) &&
inode->i_data.nrpages) {
journal_t *journal = EXT4_SB(inode->i_sb)->s_journal;
tid_t commit_tid = EXT4_I(inode)->i_datasync_tid;
jbd2_complete_transaction(journal, commit_tid);
filemap_write_and_wait(&inode->i_data);
}
truncate_inode_pages_final(&inode->i_data);
goto no_delete;
}
if (is_bad_inode(inode))
goto no_delete;
dquot_initialize(inode);
if (ext4_should_order_data(inode))
ext4_begin_ordered_truncate(inode, 0);
truncate_inode_pages_final(&inode->i_data);
/*
对于带有日志数据的inode,事务提交可能已经污染了inode。
Flush worker因为I_FREEING标志而忽略了它,
但是我们仍然需要从writeback列表中移除这个inode。
*/
if (!list_empty_careful(&inode->i_io_list)) {
WARN_ON_ONCE(!ext4_should_journal_data(inode));
inode_io_list_del(inode);
}
/*
保护我们不被冻结- iput()调用者不需要有任何保护。
但是,当我们处于运行的事务中时,我们已经得到了防止冻结的保护,
而且由于锁排序约束,我们无法获取进一步的保护。
*/
if (!ext4_journal_current_handle()) {
sb_start_intwrite(inode->i_sb);
freeze_protected = true;
}
if (!IS_NOQUOTA(inode))
extra_credits += EXT4_MAXQUOTAS_DEL_BLOCKS(inode->i_sb);
/*
ext4_blocks_for_truncate()和extra_credits中都包含块位图、组描述符和inode,所以减去3。
*/
handle = ext4_journal_start(inode, EXT4_HT_TRUNCATE,
ext4_blocks_for_truncate(inode) + extra_credits - 3);
if (IS_ERR(handle)) {
ext4_std_error(inode->i_sb, PTR_ERR(handle));
/*
如果我们要跳过正常的清理,我们仍然需要确保核内孤立链表被正确清理。
*/
ext4_orphan_del(NULL, inode);
if (freeze_protected)
sb_end_intwrite(inode->i_sb);
goto no_delete;
}
if (IS_SYNC(inode))
ext4_handle_sync(handle);
/*
在调用 ext4_truncate() 之前将 inode->i_size 设置为 0。
这里我们需要对符号链接进行特殊处理,
因为 i_size 用于确定 ext4_inode_info->i_data 是否包含符号链接数据或块映射。
将 i_size 设置为 0 将删除其快速符号链接状态。
擦除 i_data 使其成为有效的空块映射。
*/
if (ext4_inode_is_fast_symlink(inode))
memset(EXT4_I(inode)->i_data, 0, sizeof(EXT4_I(inode)->i_data));
inode->i_size = 0;
err = ext4_mark_inode_dirty(handle, inode);
if (err) {
ext4_warning(inode->i_sb,
"couldn't mark inode dirty (err %d)", err);
goto stop_handle;
}
if (inode->i_blocks) {
err = ext4_truncate(inode);
if (err) {
ext4_error_