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_err(inode->i_sb, -err,"couldn't truncate inode %lu (err %d)",inode->i_ino, err);goto stop_handle;}}/* 删除 xattr 引用。 */err = ext4_xattr_delete_inode(handle, inode, &ea_inode_array,extra_credits);if (err) {ext4_warning(inode->i_sb, "xattr delete (err %d)", err);
stop_handle:ext4_journal_stop(handle);ext4_orphan_del(NULL, inode);if (freeze_protected)sb_end_intwrite(inode->i_sb);ext4_xattr_inode_array_free(ea_inode_array);goto no_delete;}/*删除 ext4_truncate 创建的孤立记录。注意 ext4_orphan_del() 必须能够处理不存在的孤儿的删除——这是因为我们不知道 ext4_truncate() 是否真的创建了一个孤儿记录。*/ext4_orphan_del(handle, inode);EXT4_I(inode)->i_dtime = (__u32)ktime_get_real_seconds();/*一个微妙的排序要求:如果出现任何问题(事务中止、IO 错误等等),那么我们仍然可以执行这些后续步骤(fs 将已经被标记为有错误),但是如果 mark_dirty 失败,我们就不能释放 inode。*/if (ext4_mark_inode_dirty(handle, inode))/* 如果失败,只需清除所需的核心 inode。 */ext4_clear_inode(inode);elseext4_free_inode(handle, inode);ext4_journal_stop(handle);if (freeze_protected)sb_end_intwrite(inode->i_sb);ext4_xattr_inode_array_free(ea_inode_array);return;
no_delete:if (!list_empty(&EXT4_I(inode)->i_fc_list))ext4_fc_mark_ineligible(inode->i_sb, EXT4_FC_REASON_NOMEM);ext4_clear_inode(inode);  /* 我们必须保证清除inode... */
}/** 在 i_data_sem down 时调用,这很重要,因为我们可以从这里调用 ext4_discard_preallocations()。*/
void ext4_da_update_reserve_space(struct inode *inode,int used, int quota_claim)
{struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);struct ext4_inode_info *ei = EXT4_I(inode);spin_lock(&ei->i_block_reservation_lock);trace_ext4_da_update_reserve_space(inode, used, quota_claim);if (unlikely(used > ei->i_reserved_data_blocks)) {ext4_warning(inode->i_sb, "%s: ino %lu, used %d ""with only %d reserved data blocks",__func__, inode->i_ino, used,ei->i_reserved_data_blocks);WARN_ON(1);used = ei->i_reserved_data_blocks;}/* 更新每个 inode 的预留 */ei->i_reserved_data_blocks -= used;percpu_counter_sub(&sbi->s_dirtyclusters_counter, used);spin_unlock(&ei->i_block_reservation_lock);/* 更新数据块的配额子系统 */if (quota_claim)dquot_claim_block(inode, EXT4_C2B(sbi, used));else {/*我们确实使用已经延迟分配的偏移量进行了错误分配。 因此,在延迟分配的回写时,我们不应该收回分配块的配额。*/dquot_release_reservation_block(inode, EXT4_C2B(sbi, used));}/** 如果我们已经完成了所有挂起的块分配,并且如果 inode 上没有任何写入者,我们可以丢弃 inode 预分配。*/if ((ei->i_reserved_data_blocks == 0) &&!inode_is_open_for_write(inode))ext4_discard_preallocations(inode, 0);
}/*
* ext4_map_blocks() 函数尝试查找请求的块,如果块已经被映射则返回。* 否则获取 i_data_sem 的写锁并分配块并将分配的块存储在结果缓冲区头中并将其标记为映射。* 如果文件类型是extents based,则调用ext4_ext_map_blocks(),否则调用ext4_ind_map_blocks()处理基于间接映射的文件* 成功时,它返回被映射或分配的块数。 如果 create==0 并且块已预先分配且未写入,则生成的 @map 将标记为未写入。 如果 create == 1,它会将 @map 标记为已映射。* 如果简单查找失败(块尚未分配),则返回 0,在这种情况下,@map 返回为未映射,但我们仍然填充 map->m_len 以指示从 map->m_lblk 开始的孔的长度。* 它在分配失败的情况下返回false。*/
int ext4_map_blocks(handle_t *handle, struct inode *inode,struct ext4_map_blocks *map, int flags)
{struct extent_status es;int retval;int ret = 0;
#ifdef ES_AGGRESSIVE_TESTstruct ext4_map_blocks orig_map;memcpy(&orig_map, map, sizeof(*map));
#endifmap->m_flags = 0;ext_debug(inode, "flag 0x%x, max_blocks %u, logical block %lu\n",flags, map->m_len, (unsigned long) map->m_lblk);/** ext4_map_blocks 返回一个整数,而 m_len 是一个无符号整数*/if (unlikely(map->m_len > INT_MAX))map->m_len = INT_MAX;/* 我们可以处理小于 EXT_MAX_BLOCKS 的块数 */if (unlikely(map->m_lblk >= EXT_MAX_BLOCKS))return -EFSCORRUPTED;/* 首先查找范围状态树 */if (!(EXT4_SB(inode->i_sb)->s_mount_state & EXT4_FC_REPLAY) &&ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es)) {if (ext4_es_is_written(&es) || ext4_es_is_unwritten(&es)) {map->m_pblk = ext4_es_pblock(&es) +map->m_lblk - es.es_lblk;map->m_flags |= ext4_es_is_written(&es) ?EXT4_MAP_MAPPED : EXT4_MAP_UNWRITTEN;retval = es.es_len - (map->m_lblk - es.es_lblk);if (retval > map->m_len)retval = map->m_len;map->m_len = retval;} else if (ext4_es_is_delayed(&es) || ext4_es_is_hole(&es)) {map->m_pblk = 0;retval = es.es_len - (map->m_lblk - es.es_lblk);if (retval > map->m_len)retval = map->m_len;map->m_len = retval;retval = 0;} else {BUG();}
#ifdef ES_AGGRESSIVE_TESText4_map_blocks_es_recheck(handle, inode, map,&orig_map, flags);
#endifgoto found;}/** 尝试看看我们是否可以在不请求新的文件系统块的情况下获得该块。*/down_read(&EXT4_I(inode)->i_data_sem);if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) {retval = ext4_ext_map_blocks(handle, inode, map, 0);} else {retval = ext4_ind_map_blocks(handle, inode, map, 0);}if (retval > 0) {unsigned int status;if (unlikely(retval != map->m_len)) {ext4_warning(inode->i_sb,"ES len assertion failed for inode ""%lu: retval %d != map->m_len %d",inode->i_ino, retval, map->m_len);WARN_ON(1);}status = map->m_flags & EXT4_MAP_UNWRITTEN ?EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN;if (!(flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE) &&!(status & EXTENT_STATUS_WRITTEN) &&ext4_es_scan_range(inode, &ext4_es_is_delayed, map->m_lblk,map->m_lblk + map->m_len - 1))status |= EXTENT_STATUS_DELAYED;ret = ext4_es_insert_extent(inode, map->m_lblk,map->m_len, map->m_pblk, status);if (ret < 0)retval = ret;}up_read((&EXT4_I(inode)->i_data_sem));found:if (retval > 0 && map->m_flags & EXT4_MAP_MAPPED) {ret = check_block_validity(inode, map);if (ret != 0)return ret;}/* 如果它只是一个块查找 */if ((flags & EXT4_GET_BLOCKS_CREATE) == 0)return retval;/** 如果块已经分配,则返回* 请注意,如果块已被预分配 ext4_ext_get_block() 返回 create = 0 且缓冲区头未映射。*/if (retval > 0 && map->m_flags & EXT4_MAP_MAPPED)/** 如果我们需要将范围转换为未写,我们继续并在 ext4_ext_map_blocks() 中进行实际工作*/if (!(flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN))return retval;/** 这里我们清除 m_flags 因为在分配了一个新的范围后,它将再次设置。*/map->m_flags &= ~EXT4_MAP_FLAGS;/** 新块分配和/或写入未写入的范围可能会导致更新 i_data,*因此我们获取 i_data_sem 的写锁,并使用 create == 1 标志调用 get_block()。*/down_write(&EXT4_I(inode)->i_data_sem);/** 我们需要在这里检查 EXT4,因为 migrate 可能已经改变了两者之间的 inode 类型*/if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) {retval = ext4_ext_map_blocks(handle, inode, map, flags);} else {retval = ext4_ind_map_blocks(handle, inode, map, flags);if (retval > 0 && map->m_flags & EXT4_MAP_NEW) {/** 我们分配了新的块,这将导致 i_data 的格式发生变化。 通过清除迁移标志强制迁移失败*/ext4_clear_inode_state(inode, EXT4_STATE_EXT_MIGRATE);}/*延迟至今的成功块分配后更新保留块/元数据块。 我们不支持非扩展文件的 fallocate。 所以我们可以在这里更新预留空间。*/if ((retval > 0) &&(flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE))ext4_da_update_reserve_space(inode, retval, 1);}if (retval > 0) {unsigned int status;if (unlikely(retval != map->m_len)) {ext4_warning(inode->i_sb,"ES len assertion failed for inode ""%lu: retval %d != map->m_len %d",inode->i_ino, retval, map->m_len);WARN_ON(1);}/*我们必须在将块插入到范围状态树之前将它们清零。 否则有人可以在那里查找它们并在它们真正归零之前使用它们。 我们还必须在归零之前取消映射元数据,否则回写可能会用来自块设备的陈旧数据覆盖零。*/if (flags & EXT4_GET_BLOCKS_ZERO &&map->m_flags & EXT4_MAP_MAPPED &&map->m_flags & EXT4_MAP_NEW) {ret = ext4_issue_zeroout(inode, map->m_lblk,map->m_pblk, map->m_len);if (ret) {retval = ret;goto out_sem;}}/** 如果extent 已经被清零,我们不需要更新extent 状态树。*/if ((flags & EXT4_GET_BLOCKS_PRE_IO) &&ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es)) {if (ext4_es_is_written(&es))goto out_sem;}status = map->m_flags & EXT4_MAP_UNWRITTEN ?EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN;if (!(flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE) &&!(status & EXTENT_STATUS_WRITTEN) &&ext4_es_scan_range(inode, &ext4_es_is_delayed, map->m_lblk,map->m_lblk + map->m_len - 1))status |= EXTENT_STATUS_DELAYED;ret = ext4_es_insert_extent(inode, map->m_lblk, map->m_len,map->m_pblk, status);if (ret < 0) {retval = ret;goto out_sem;}}out_sem:up_write((&EXT4_I(inode)->i_data_sem));if (retval > 0 && map->m_flags & EXT4_MAP_MAPPED) {ret = check_block_validity(inode, map);if (ret != 0)return ret;/** 事务提交后内容可见的新分配块的索引节点必须在事务的有序数据列表中。*/if (map->m_flags & EXT4_MAP_NEW &&!(map->m_flags & EXT4_MAP_UNWRITTEN) &&!(flags & EXT4_GET_BLOCKS_ZERO) &&!ext4_is_quota_file(inode) &&ext4_should_order_data(inode)) {loff_t start_byte =(loff_t)map->m_lblk << inode->i_blkbits;loff_t length = (loff_t)map->m_len << inode->i_blkbits;if (flags & EXT4_GET_BLOCKS_IO_SUBMIT)ret = ext4_jbd2_inode_add_wait(handle, inode,start_byte, length);elseret = ext4_jbd2_inode_add_write(handle, inode,start_byte, length);if (ret)return ret;}ext4_fc_track_range(handle, inode, map->m_lblk,map->m_lblk + map->m_len - 1);}if (retval < 0)ext_debug(inode, "failed with err %d\n", retval);return retval;
}/** 更新 bh->b_state 中的 EXT4_MAP_FLAGS。 对于附加到页面的缓冲区头,我们必须小心,因为其他人也可能在操纵 b_state。*/
static void ext4_update_bh_state(struct buffer_head *bh, unsigned long flags)
{unsigned long old_state;unsigned long new_state;flags &= EXT4_MAP_FLAGS;/* 虚拟buffer_head  非原子设置。 */if (!bh->b_page) {bh->b_state = (bh->b_state & ~EXT4_MAP_FLAGS) | flags;return;}/*其他人可能正在修改 b_state。 这很恶心,但是一旦我们摆脱了使用 bh 作为映射信息的容器以传递到 get_block 函数/从 get_block 函数传递的信息,这种情况就会消失。*/do {old_state = READ_ONCE(bh->b_state);new_state = (old_state & ~EXT4_MAP_FLAGS) | flags;} while (unlikely(cmpxchg(&bh->b_state, old_state, new_state) != old_state));
}/** 如果我们需要在未分配块的情况下创建未写入的范围,则在准备缓冲写入时使用获取块函数。 * IO 完成后,extent 将转换为写入。*/
int ext4_get_block_unwritten(struct inode *inode, sector_t iblock,struct buffer_head *bh_result, int create)
{ext4_debug("ext4_get_block_unwritten: inode %lu, create flag %d\n",inode->i_ino, create);return _ext4_get_block(inode, iblock, bh_result,EXT4_GET_BLOCKS_IO_CREATE_EXT);
}/** 如果 create 为零,handle 可以为 NULL*/
struct buffer_head *ext4_getblk(handle_t *handle, struct inode *inode,ext4_lblk_t block, int map_flags)
{struct ext4_map_blocks map;struct buffer_head *bh;int create = map_flags & EXT4_GET_BLOCKS_CREATE;int err;ASSERT((EXT4_SB(inode->i_sb)->s_mount_state & EXT4_FC_REPLAY)|| handle != NULL || create == 0);map.m_lblk = block;map.m_len = 1;err = ext4_map_blocks(handle, inode, &map, map_flags);if (err == 0)return create ? ERR_PTR(-ENOSPC) : NULL;if (err < 0)return ERR_PTR(err);bh = sb_getblk(inode->i_sb, map.m_pblk);if (unlikely(!bh))return ERR_PTR(-ENOMEM);if (map.m_flags & EXT4_MAP_NEW) {ASSERT(create != 0);ASSERT((EXT4_SB(inode->i_sb)->s_mount_state & EXT4_FC_REPLAY)|| (handle != NULL));/** 既然我们并不总是记录数据,我们应该记住这是否应该总是将新缓冲区记录为元数据。 * 目前,常规文件写入使用 ext4_get_block 代替,所以这不是问题。*/lock_buffer(bh);BUFFER_TRACE(bh, "call get_create_access");err = ext4_journal_get_create_access(handle, bh);if (unlikely(err)) {unlock_buffer(bh);goto errout;}if (!buffer_uptodate(bh)) {memset(bh->b_data, 0, inode->i_sb->s_blocksize);set_buffer_uptodate(bh);}unlock_buffer(bh);BUFFER_TRACE(bh, "call ext4_handle_dirty_metadata");err = ext4_handle_dirty_metadata(handle, inode, bh);if (unlikely(err))goto errout;} elseBUFFER_TRACE(bh, "not a new buffer");return bh;
errout:brelse(bh);return ERR_PTR(err);
}/** 为了保持顺序,必须将孔实例化和数据写入封装在单个事务中。我们不能在 ext4_get_block() 和 commit_write() 之间关闭一个事务并开始一个新的事务。所以在 prepare_write() 开始时做 jbd2_journal_start 是正确的地方。* 此外,该函数可以嵌套在 ext4_writepage() 中。在这种情况下,我们知道 ext4_writepage() 已生成足够的缓冲区信用来完成整个页面。所以在这种情况下我们不会阻塞日志,这很好,因为调用者可能是 PF_MEMALLOC。* 偶然地,当通过配额文件写入打开事务时,可以重新进入 ext4。如果我们在重新进入时提交事务,可能会出现死锁——我们将持有一个配额锁,如果另一个线程打开一个事务并且阻塞在配额锁上,提交将永远不会完成——违反排名。* 所以我们所做的是依赖于 jbd2_journal_stop/journal_start 在这些情况下不会_运行提交的事实,因为 handle->h_ref 被提升了。我们仍然有足够的积分用于微小的配额文件写入。*/
int do_journal_get_write_access(handle_t *handle,struct buffer_head *bh)
{int dirty = buffer_dirty(bh);int ret;if (!buffer_mapped(bh) || buffer_freed(bh))return 0;/*__block_write_begin() 可能弄脏了一些缓冲区。 清理脏位,因为 jbd2_journal_get_write_access() 可能会抱怨 fs 完整性问题。 通过 __block_write_begin() 设置脏位在这里并不是真正的问题,因为我们在释放页锁之前清除了该位,因此回写永远无法写入缓冲区。*/if (dirty)clear_buffer_dirty(bh);BUFFER_TRACE(bh, "get write access");ret = ext4_journal_get_write_access(handle, bh);if (!ret && dirty)ret = ext4_handle_dirty_metadata(handle, NULL, bh);return ret;
}static int ext4_write_begin(struct file *file, struct address_space *mapping,loff_t pos, unsigned len, unsigned flags,struct page **pagep, void **fsdata)
{struct inode *inode = mapping->host;int ret, needed_blocks;handle_t *handle;int retries = 0;struct page *page;pgoff_t index;unsigned from, to;if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))return -EIO;trace_ext4_write_begin(inode, pos, len, flags);/** 多保留一个块以添加到孤儿列表,以防我们分配块但由于某种原因写入失败*/needed_blocks = ext4_writepage_trans_blocks(inode) + 1;index = pos >> PAGE_SHIFT;from = pos & (PAGE_SIZE - 1);to = from + len;if (ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA)) {ret = ext4_try_to_write_inline_data(mapping, inode, pos, len,flags, pagep);if (ret < 0)return ret;if (ret == 1)return 0;}/*如果系统由于内存压力而抖动,或者页面正在被写回,grab_cache_page_write_begin() 可能需要很长时间。 所以在我们开始事务句柄之前先抓住它。 这也允许我们在不使用 GFP_NOFS 的情况下分配页面(如果需要)。*/
retry_grab:page = grab_cache_page_write_begin(mapping, index, flags);if (!page)return -ENOMEM;unlock_page(page);retry_journal:handle = ext4_journal_start(inode, EXT4_HT_WRITE_PAGE, needed_blocks);if (IS_ERR(handle)) {put_page(page);return PTR_ERR(handle);}lock_page(page);if (page->mapping != mapping) {/* 页面从我们下面被截断 */unlock_page(page);put_page(page);ext4_journal_stop(handle);goto retry_grab;}/* 如果在页面解锁时开始写回 */wait_for_stable_page(page);#ifdef CONFIG_FS_ENCRYPTIONif (ext4_should_dioread_nolock(inode))ret = ext4_block_write_begin(page, pos, len,ext4_get_block_unwritten);elseret = ext4_block_write_begin(page, pos, len,ext4_get_block);
#elseif (ext4_should_dioread_nolock(inode))ret = __block_write_begin(page, pos, len,ext4_get_block_unwritten);elseret = __block_write_begin(page, pos, len, ext4_get_block);
#endifif (!ret && ext4_should_journal_data(inode)) {ret = ext4_walk_page_buffers(handle, page_buffers(page),from, to, NULL,do_journal_get_write_access);}if (ret) {bool extended = (pos + len > inode->i_size) &&!ext4_verity_in_progress(inode);unlock_page(page);/*__block_write_begin 可能在 i_size 之外实例化了几个块,再把这些剪掉。 不需要 i_size_read 因为我们持有 i_mutex。将 inode 添加到孤立列表,以防我们在截断完成之前崩溃*/if (extended && ext4_can_truncate(inode))ext4_orphan_add(handle, inode);ext4_journal_stop(handle);if (extended) {ext4_truncate_failed_write(inode);/** 如果早期截断失败,inode 可能仍然在孤儿列表中; * 在这种情况下,我们需要确保从孤立列表中删除 inode。*/if (inode->i_nlink)ext4_orphan_del(NULL, inode);}if (ret == -ENOSPC &&ext4_should_retry_alloc(inode->i_sb, &retries))goto retry_journal;put_page(page);return ret;}*pagep = page;return ret;
}/** 我们需要获取 generic_commit_write 给我们文件的新 inode 大小可以为 NULL - 例如,当从 page_symlink() 调用时。* ext4 从不在 inode->i_mapping->private_list 上放置缓冲区。 元数据缓冲区在内部进行管理。*/
static int ext4_write_end(struct file *file,struct address_space *mapping,loff_t pos, unsigned len, unsigned copied,struct page *page, void *fsdata)
{handle_t *handle = ext4_journal_current_handle();struct inode *inode = mapping->host;loff_t old_size = inode->i_size;int ret = 0, ret2;int i_size_changed = 0;int inline_data = ext4_has_inline_data(inode);bool verity = ext4_verity_in_progress(inode);trace_ext4_write_end(inode, pos, len, copied);if (inline_data) {ret = ext4_write_inline_data_end(inode, pos, len,copied, page);if (ret < 0) {unlock_page(page);put_page(page);goto errout;}copied = ret;} elsecopied = block_write_end(file, mapping, pos,len, copied, page, fsdata);/** 在保持页面锁定的同时更新 i_size 很重要:否则页面写出可能会进入并且超过 i_size 为零。* 如果 FS_IOC_ENABLE_VERITY 在此 inode 上运行,则 Merkle 树块将被写入超过 EOF,因此跳过 i_size 更新。*/if (!verity)i_size_changed = ext4_update_inode_size(inode, pos + copied);unlock_page(page);put_page(page);if (old_size < pos && !verity)pagecache_isize_extended(inode, old_size, pos);/*不要在页面锁定下标记 inode dirty。 首先,它不必要地延长了页锁的保持时间。 其次,它强制对日志文件系统进行页锁和事务启动的锁排序。*/if (i_size_changed || inline_data)ret = ext4_mark_inode_dirty(handle, inode);if (pos + len > inode->i_size && !verity && ext4_can_truncate(inode))/* 如果我们分配了更多的块并减少了复制,我们将在 inode->i_size 之外分配块。所以截断它们*/ext4_orphan_add(handle, inode);
errout:ret2 = ext4_journal_stop(handle);if (!ret)ret = ret2;if (pos + len > inode->i_size && !verity) {ext4_truncate_failed_write(inode);/** 如果早期截断失败,inode 可能仍然在孤儿列表中; * 在这种情况下,我们需要确保从孤立列表中删除 inode。*/if (inode->i_nlink)ext4_orphan_del(NULL, inode);}return ret ? ret : copied;
}/** 为单个集群预留空间*/
static int ext4_da_reserve_space(struct inode *inode)
{struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);struct ext4_inode_info *ei = EXT4_I(inode);int ret;/** 我们将在写出时收取元数据配额; 这使我们免于高估元数据,尽管我们最终可能会略有偏差。 这里我们只保留数据。*/ret = dquot_reserve_block(inode, EXT4_C2B(sbi, 1));if (ret)return ret;spin_lock(&ei->i_block_reservation_lock);if (ext4_claim_free_clusters(sbi, 1, 0)) {spin_unlock(&ei->i_block_reservation_lock);dquot_release_reservation_block(inode, EXT4_C2B(sbi, 1));return -ENOSPC;}ei->i_reserved_data_blocks++;trace_ext4_da_reserve_space(inode);spin_unlock(&ei->i_block_reservation_lock);return 0;       /* success */
}void ext4_da_release_space(struct inode *inode, int to_free)
{struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);struct ext4_inode_info *ei = EXT4_I(inode);if (!to_free)return;       /* Nothing to release, exit */spin_lock(&EXT4_I(inode)->i_block_reservation_lock);trace_ext4_da_release_space(inode, to_free);if (unlikely(to_free > ei->i_reserved_data_blocks)) {/** 如果没有足够的保留块,那么计数器就会在某处搞砸。 * 由于这个函数是从invalidate页面调用的,所以不做任何操作就返回是无害的。*/ext4_warning(inode->i_sb, "ext4_da_release_space: ""ino %lu, to_free %d with only %d reserved ""data blocks", inode->i_ino, to_free,ei->i_reserved_data_blocks);WARN_ON(1);to_free = ei->i_reserved_data_blocks;}ei->i_reserved_data_blocks -= to_free;/* 更新 fs dirty数据块计数器 */percpu_counter_sub(&sbi->s_dirtyclusters_counter, to_free);spin_unlock(&EXT4_I(inode)->i_block_reservation_lock);dquot_release_reservation_block(inode, EXT4_C2B(sbi, to_free));
}/** 延迟分配*/struct mpage_da_data {struct inode *inode;struct writeback_control *wbc;pgoff_t first_page;  /* 要写入的第一页 */pgoff_t next_page; /* 当前要检查的页面 */pgoff_t last_page;    /* 最后一页检查 *//** 映射范围 - 这可以在 first_page 之后,因为它可以被完全映射。 我们有点滥用 m_flags 来存储范围是 delalloc 还是未写入。*/struct ext4_map_blocks map;struct ext4_io_submit io_submit; /* IO提交数据 */unsigned int do_map:1;unsigned int scanned_until_end:1;
};/** ext4_insert_delayed_block - 将延迟块添加到范围状态树,增加保留的集群/块计数或在需要时进行挂起的保留* @inode - 包含新添加块的文件* @lblk - 要添加的逻辑块* 成功返回 0,失败返回错误代码。*/
static int ext4_insert_delayed_block(struct inode *inode, ext4_lblk_t lblk)
{struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);int ret;bool allocated = false;/*如果包含 lblk 的集群与 bigalloc 文件系统中的延迟、写入或未写入范围共享,则它已经被考虑在内,不需要保留。如果集群与写入或未写入的范围共享并且还没有,则必须为该集群进行挂起预留。 如果系统处于内存压力下,可以从范围状态树中清除已写入和未写入的范围,因此如果对范围状态树的搜索没有匹配,则有必要检查范围树。*/if (sbi->s_cluster_ratio == 1) {ret = ext4_da_reserve_space(inode);if (ret != 0)   /* ENOSPC */goto errout;} else {   /* bigalloc */if (!ext4_es_scan_clu(inode, &ext4_es_is_delonly, lblk)) {if (!ext4_es_scan_clu(inode,&ext4_es_is_mapped, lblk)) {ret = ext4_clu_mapped(inode,EXT4_B2C(sbi, lblk));if (ret < 0)goto errout;if (ret == 0) {ret = ext4_da_reserve_space(inode);if (ret != 0)   /* ENOSPC */goto errout;} else {allocated = true;}} else {allocated = true;}}}ret = ext4_es_insert_delayed_block(inode, lblk, allocated);errout:return ret;
}/** 该函数是从 ext4_map_blocks 一开始就抓取代码,但假设调用者来自延迟写入时间。 * 该函数在 i_data_sem 的保护下查找请求的块并设置缓冲区延迟位。*/
static int ext4_da_map_blocks(struct inode *inode, sector_t iblock,struct ext4_map_blocks *map,struct buffer_head *bh)
{struct extent_status es;int retval;sector_t invalid_block = ~((sector_t) 0xffff);
#ifdef ES_AGGRESSIVE_TESTstruct ext4_map_blocks orig_map;memcpy(&orig_map, map, sizeof(*map));
#endifif (invalid_block < ext4_blocks_count(EXT4_SB(inode->i_sb)->s_es))invalid_block = ~0;map->m_flags = 0;ext_debug(inode, "max_blocks %u, logical block %lu\n", map->m_len,(unsigned long) map->m_lblk);/* 首先查找范围状态树 */if (ext4_es_lookup_extent(inode, iblock, NULL, &es)) {if (ext4_es_is_hole(&es)) {retval = 0;down_read(&EXT4_I(inode)->i_data_sem);goto add_delayed;}/** 延迟范围可以通过 fallocate 分配。 所以我们需要检查一下。*/if (ext4_es_is_delayed(&es) && !ext4_es_is_unwritten(&es)) {map_bh(bh, inode->i_sb, invalid_block);set_buffer_new(bh);set_buffer_delay(bh);return 0;}map->m_pblk = ext4_es_pblock(&es) + iblock - es.es_lblk;retval = es.es_len - (iblock - es.es_lblk);if (retval > map->m_len)retval = map->m_len;map->m_len = retval;if (ext4_es_is_written(&es))map->m_flags |= EXT4_MAP_MAPPED;else if (ext4_es_is_unwritten(&es))map->m_flags |= EXT4_MAP_UNWRITTEN;elseBUG();#ifdef ES_AGGRESSIVE_TESText4_map_blocks_es_recheck(NULL, inode, map, &orig_map, 0);
#endifreturn retval;}/** 尝试看看我们是否可以在不请求新的文件系统块的情况下获得该块。*/down_read(&EXT4_I(inode)->i_data_sem);if (ext4_has_inline_data(inode))retval = 0;else if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))retval = ext4_ext_map_blocks(NULL, inode, map, 0);elseretval = ext4_ind_map_blocks(NULL, inode, map, 0);add_delayed:if (retval == 0) {int ret;/** XXX: __block_prepare_write() 取消映射传递的块*/ret = ext4_insert_delayed_block(inode, map->m_lblk);if (ret != 0) {retval = ret;goto out_unlock;}map_bh(bh, inode->i_sb, invalid_block);set_buffer_new(bh);set_buffer_delay(bh);} else if (retval > 0) {int ret;unsigned int status;if (unlikely(retval != map->m_len)) {ext4_warning(inode->i_sb,"ES len assertion failed for inode ""%lu: retval %d != map->m_len %d",inode->i_ino, retval, map->m_len);WARN_ON(1);}status = map->m_flags & EXT4_MAP_UNWRITTEN ?EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN;ret = ext4_es_insert_extent(inode, map->m_lblk, map->m_len,map->m_pblk, status);if (ret != 0)retval = ret;}out_unlock:up_read((&EXT4_I(inode)->i_data_sem));return retval;
}/** 这是一个特殊的 get_block_t 回调,由 ext4_da_write_begin() 使用。 它将返回映射块或为单个块保留空间。* 对于延迟的buffer_head,我们设置了BH_Mapped、BH_New、BH_Delay。 我们也正确初始化了 b_blocknr = -1 和 b_bdev* 对于未写入的 buffer_head,我们设置了 BH_Mapped、BH_New、BH_Unwritten。 我们也有 b_blocknr = physicalblock mapping unwritten extent 和 b_bdev 正确初始化。*/
int ext4_da_get_block_prep(struct inode *inode, sector_t iblock,struct buffer_head *bh, int create)
{struct ext4_map_blocks map;int ret = 0;BUG_ON(create == 0);BUG_ON(bh->b_size != inode->i_sb->s_blocksize);map.m_lblk = iblock;map.m_len = 1;/** 首先,我们需要知道块是否已分配已经预分配的块未映射但应与已分配的块一样对待。*/ret = ext4_da_map_blocks(inode, iblock, &map, bh);if (ret <= 0)return ret;map_bh(bh, inode->i_sb, map.m_pblk);ext4_update_bh_state(bh, map.m_flags);if (buffer_unwritten(bh)) {/* 延迟写入未写入的 bh 应标记为新的并已映射。 Mapped 确保我们不会在写入相同偏移量时多次执行 get_block,而 new 确保我们对部分写入执行正确的清零。*/set_buffer_new(bh);set_buffer_mapped(bh);}return 0;
}

未完待续……

Linux内核文件系统10相关推荐

  1. Linux内核5.10编译 与调试

    Linux内核5.10编译 与调试 Linux 5.10 编译 下载内核 准备编译环境 配置模板 编译 安装新内核 qemu 调试 busybox 根文件系统制作 qemu 运行 脚本二 方法三 目的 ...

  2. DM365 linux内核文件系统的烧写步骤及其uboot参数配置

    DM365 linux内核&文件系统的烧写步骤及其uboot参数配置     目录 源文档下载:http://download.csdn.net/detail/zhangjikuan/6443 ...

  3. 索引节点inode: Linux内核文件系统之(inode)

    inode译成中文就是索引节点,它用来存放档案及目录的基本信息,包含时间.档名.使用者及群组等. static int eachreg_open(struct inode *inode, struct ...

  4. 全面解析Linux 内核 3.10.x - 内核进程

    From: 全面解析Linux 内核 3.10.x - 进程管理 三千大世界,五千小世界,却是由生命组成 - XXXX 进程类似于人类,它们被产生,有或多或少的有效生命.可以产生一个或者多个子进程,最 ...

  5. 【技术分享篇】Linux内核——手把手带你实现一个Linux内核文件系统丨Linux内核源码分析

    手把手带你实现一个Linux内核文件系统 1. 内核文件系统架构分析 2. 行行珠玑,代码实现 [技术分享篇]Linux内核--手把手带你实现一个Linux内核文件系统丨Linux内核源码分析 更多L ...

  6. Linux内核文件系统

    Linux 内核文件系统 概述 文件系统这一词在不同上下文时有不同的含义: 指一种具体的文件格式.例如Linux的文件系统是Ext2,MSDOS的文件系统是FAT16,而Windows NT的文件系统 ...

  7. linux 内核 4.10.0,Bodhi Linux 4.2.0发布下载,运行Linux内核4.10

    基于Ubuntu的Linux发行版Bodhi Linux的新版本可供下载.Bodhi Linux 4.2是4.x系列中的第二个次要更新,因此具有相对较小的更改日志来匹配. 关于Bodhi Linux? ...

  8. 全面解析Linux 内核 3.10.x - 如何开始

    万事开头难 - 如何开始? 人总是对未知的事物充满恐惧!就像航海一样,在面对危难的时候,船员和船长是一样心中充满恐惧的!只是船员始终充满恐惧,而船长却能压抑恐惧并从当前找出突破口!  我没有船长之能, ...

  9. 2020年10月linux内核,Windows 10 May 2020现已提供更新,内置Linux内核和Cortana

    微软今天发布其Windows 10 May 2020更新.它是Windows 10的最新"主要"更新,其主要功能包括Linux 2的Windows子系统和Cortana更新.微软上 ...

最新文章

  1. Mac 设置文件默认打开方式
  2. iOS开发--使用OpenSSL生成私钥和公钥的方法
  3. 并发编程-volatile和synchronized的区别
  4. 服务端第八次上课:mongodb,redis
  5. [APIO2015]巴厘岛的雕塑[按位贪心+dp]
  6. 蓝色版苹果iPhone 12开箱上手视频流出;谷歌回应司法部反垄断诉讼:存在严重漏洞;​Git 2.29 稳定版发布|极客头条
  7. 为CListBox加上智能水平滚动条
  8. 【029】获取选择要素的属性
  9. Java中多线程访问冲突的解决方式
  10. 亚马逊 kindle 刷机 过程记录
  11. 简化“复杂”的层级管理,实现团队作战式的目标协同
  12. opencv: 颜色通道 探究(图示+源码)
  13. C#操作MicroSoft Word的代码
  14. 使用video.js 播放youtube视频——踩坑记1
  15. Three.js显示物体的运动轨迹
  16. 华为云数据库实验-openGauss金融场景化实验出现的问题
  17. S2B2C模式学习总结
  18. python制作题库网站_制作一个在线测试的题库网站需要掌握哪些知识?
  19. 一文详解8种异常检测算法(附Python代码)
  20. window服务安装、卸载

热门文章

  1. 使用springboot访问本地电脑资源,并解决中文路径无法访问的问题
  2. Gridview 换行
  3. 【Android】Bluetooth(蓝牙)连接与数据传输(一)
  4. php印章类,印章风格类别
  5. 透明度与十六进制代码转换#ffffffff
  6. oracle erp 汇率表,ERP中汇率设置
  7. 司徒正美写给前端开发者的算法书(文末抽奖送书)
  8. 关于jxls2.6.0后的版本使用的jexl3学习使用
  9. ActiveSync Startup Server
  10. 代码实践:基于LSTM网络的DEAP情感数据集情感分类