网上讲解ext4的文章挺多的,但是讲解ext4源码的文章很少。如果想要深入理解ext4,还是需要研究源码的。本文在上篇文章《ext4文件系统之文件查找ext4_lookup函数源码解析》基础上,讲解文件/目录创建函数ext4_create /ext4_mkdir源码。这个过程也会讲解ext4_add_entry、__ext4_new_inode、find_group_orlov、find_group_other、ext4_getblk、ext4_mark_inode_dirty函数源码。
本文内核源码版本3.10.96,详细内核详细源码注释见GitHub - dongzhiyan-stack/kernel-code-comment: 3.10.96 内核源代码注释。文中如有错误请指出。
1:ext4_create文件创建函数源码解析
先把ext4_create函数源码贴下:
- static int ext4_create(struct inode *dir, struct dentry *dentry, umode_t mode,
- bool excl)//dentry是待创建文件dentry
- {
- handle_t *handle;
- struct inode *inode;
- int err, credits, retries = 0;
- ..............
- retry:
- /*为新创建的文件分配一个inode结构,接着为该文件找一个有空闲inode和空闲block的块组group,然后在该块组的inode bitmap找一个空闲inode编号,最后把该inode编号赋值给inode->i_ino*/
- inode = ext4_new_inode_start_handle(dir, mode, &dentry->d_name, 0,
- NULL, EXT4_HT_DIR, credits);
- handle = ext4_journal_current_handle();
- err = PTR_ERR(inode);
- if (!IS_ERR(inode)) {//为文件分配inode成功
- //为inode个i_op和i_fop赋值
- inode->i_op = &ext4_file_inode_operations;
- inode->i_fop = &ext4_file_operations;
- ext4_set_aops(inode);
- //把dentry和inode对应的文件或目录添加到它父目录的ext4_dir_entry_2里
- err = ext4_add_nondir(handle, dentry, inode);
- if (!err && IS_DIRSYNC(dir))
- ext4_handle_sync(handle);
- }
- ..............
- return err;
- }
- #define ext4_new_inode_start_handle(dir, mode, qstr, goal, owner, \
- type, nblocks) \
- __ext4_new_inode(NULL, (dir), (mode), (qstr), (goal), (owner), \
- (type), __LINE__, (nblocks))
里边主要调用了ext4_new_inode_start_handle和ext4_add_nondir两个函数,而ext4_new_inode_start_handle主要是调用__ext4_new_inode,__ext4_new_inode是创建文件或目录的关键入口函数,这些函数我们依次讲解。
1.1__ext4_new_inode函数源码解析
__ext4_new_inode源码比较复杂,看下源码:
- //找到一个合适的块组,从这个块组分配一个空闲inode
- struct inode *__ext4_new_inode(handle_t *handle, struct inode *dir,
- umode_t mode, const struct qstr *qstr,//qstr是待创建的目录或文件名字
- __u32 goal, uid_t *owner, int handle_type,//创建目录和文件时goal都是0
- unsigned int line_no, int nblocks)
- {
- struct super_block *sb;
- struct buffer_head *inode_bitmap_bh = NULL;
- struct buffer_head *group_desc_bh;
- ext4_group_t ngroups, group = 0;
- unsigned long ino = 0;
- struct inode *inode;
- struct ext4_group_desc *gdp = NULL;
- struct ext4_inode_info *ei;
- struct ext4_sb_info *sbi;
- int ret2, err = 0;
- struct inode *ret;
- ext4_group_t i;
- ext4_group_t flex_group;
- /* Cannot create files in a deleted directory */
- if (!dir || !dir->i_nlink)
- return ERR_PTR(-EPERM);
- sb = dir->i_sb;
- //总块组个数
- ngroups = ext4_get_groups_count(sb);
- trace_ext4_request_inode(dir, mode);
- //分配ext4_inode_info结构并返回它的成员struct inode vfs_inode的地址
- inode = new_inode(sb);
- if (!inode)
- return ERR_PTR(-ENOMEM);
- //由inode得到ext4_inode_info
- ei = EXT4_I(inode);
- //由sb得到ext4_sb_info
- sbi = EXT4_SB(sb);
- ...............
- /*下边为新创建的文件或目录先找到一个有空闲inode和空闲block的块组,优先查找父目录所属块组。查找失败则遍历所有块组,看哪个有空闲inode和block。找到合适块组把块组号赋值给group,接着会在该块组分配一个空闲的inode编号*/
- if (S_ISDIR(mode))//创建的是目录inode
- ret2 = find_group_orlov(sb, dir, &group, mode, qstr);
- else//创建的是文件inode
- ret2 = find_group_other(sb, dir, &group, mode);
- got_group:
- //记录最近一次分配的inode所属的块组。到这里group就是本次要分配的inode所属的块组编号
- EXT4_I(dir)->i_last_alloc_group = group;
- err = -ENOSPC;
- if (ret2 == -1)
- goto out;
- for (i = 0; i < ngroups; i++, ino = 0) {//ngroups是ext4文件系统总的块组数
- err = -EIO;
- /*由块组编号group得到块组描述符结构ext4_group_desc,并且令group_desc_bh指向保存块组描述符数据的物理块映射的bh*/
- gdp = ext4_get_group_desc(sb, group, &group_desc_bh);
- if (!gdp)
- goto out;
- //块组要是空闲inode不够了,group加1指向下一个块组,如果group是最后一个块组则从
- //第一个块组开始
- if (ext4_free_inodes_count(sb, gdp) == 0) {
- if (++group == ngroups)
- group = 0;
- continue;
- }
- brelse(inode_bitmap_bh);
- /*先根据块组号group得到块组描述符ext4_group_desc,再由块组描述符得到保存inode bitmap数据的物理块号,最后读取该inode bitmap物理块的4K数据到inode_bitmap_bh并返回。注意,每个块组的inode bitmap应该只占一个物理块,最大数据量是4K*/
- inode_bitmap_bh = ext4_read_inode_bitmap(sb, group);
- if (!inode_bitmap_bh)
- goto out;
- repeat_in_this_group:
- /*一个块组内,inode bitmap占一个物理块,4K大小,总计有4k*8个bit。因此,理论上一个块组内最多可以容纳4k*8个inode,但实际上只有EXT4_INODES_PER_GROUP(sb)个,即8192个,这个应该是综合考虑的结果,一个块组实际容纳不了4k*8个inode。这里在inode bitmap对应的inode_bitmap_bh->b_data[]这4K数据中,找一个空闲的inode号。每在inode bitmap找一个空闲inode号,在对应的inode bitmap的bit位置1。比如inode bitmap的buf即inode_bitmap_bh->b_data[]的第1个字节的第1个bit是0,则给本次的inode分配的inode编号就是0,然后下边把这个bit位置1。下次分配新的inode,找到inode_bitmap_bh->b_data[]的第1个字节的第2个bit,则为该新inode分配的编号是1.inode_bitmap_bh->b_data[]某个bit位是1表示对应编号inode分配了,为0表示该bit位对应的inode空闲*/
- ino = ext4_find_next_zero_bit((unsigned long *)
- inode_bitmap_bh->b_data,
- EXT4_INODES_PER_GROUP(sb), ino);
- //新分配的inode编号大于最大值,则说明当前块组inode用完了,则去下一个块组分配inode
- if (ino >= EXT4_INODES_PER_GROUP(sb))
- goto next_group;
- ............................
- /*把inode bitmap的buf即inode_bitmap_bh->b_data[]数组的ino对应的bit位置1,表示该bit位对应的inode已经分配了,下次再分配inode就跳过该bit位,找一个新的是0的bit位。*/
- ret2 = ext4_test_and_set_bit(ino, inode_bitmap_bh->b_data);
- ext4_unlock_group(sb, group);
- //搞不清楚为什么这里要加1?我猜测应该是以1为最小inode编号,以1为base
- ino++;
- if (!ret2)//在group块组成功分配一个inode编号是ino的inode(空闲的inode),跳出循环
- goto got;
- /*执行到这里,说明没有找一个空闲的inode编号,则跳到repeat_in_this_group分支重新在inode bitmap的buf即inode_bitmap_bh->b_data[]数组重新找一个空闲的inode编号*/
- if (ino < EXT4_INODES_PER_GROUP(sb))
- goto repeat_in_this_group;
- next_group:
- //到这里说明已经遍历到最后一个块组,还是没找到有空闲inode的块组,那就从第一个块组中找一个空闲的inode
- if (++group == ngroups)
- group = 0;
- }
- err = -ENOSPC;
- goto out;
- got:
- /*inode bitmap的buf即inode_bitmap_bh->b_data[]数组的数据脏了,因为上边分配一个空闲的inode,把该buf里inode编号对应的bit位置1*/
- err = ext4_handle_dirty_metadata(handle, NULL, inode_bitmap_bh);
- if (err) {
- ext4_std_error(sb, err);
- goto out;
- }
- ............
- //令gdp对应的块组空闲的inode数减1
- ext4_free_inodes_set(sb, gdp, ext4_free_inodes_count(sb, gdp) - 1);
- if (S_ISDIR(mode)) {
- //设置块组的已分配的目录inode个数加1
- ext4_used_dirs_set(sb, gdp, ext4_used_dirs_count(sb, gdp) + 1);
- if (sbi->s_log_groups_per_flex) {
- ext4_group_t f = ext4_flex_group(sbi, group);
- atomic_inc(&sbi->s_flex_groups[f].used_dirs);
- }
- }
- .....................
- //前边修改了块组描述符的数据,比如块组空闲inode数,现在使块组描述符的buffer_head标记脏
- err = ext4_handle_dirty_metadata(handle, NULL, group_desc_bh);
- if (err) {
- ext4_std_error(sb, err);
- goto out;
- }
- //空闲inode数减1
- percpu_counter_dec(&sbi->s_freeinodes_counter);
- if (S_ISDIR(mode))
- percpu_counter_inc(&sbi->s_dirs_counter);//当前要创建的是目录时减1
- if (sbi->s_log_groups_per_flex) {//4
- flex_group = ext4_flex_group(sbi, group);
- atomic_dec(&sbi->s_flex_groups[flex_group].free_inodes);
- }
- //根据为inode分配的块组编号group和在块组内找到的一个空闲inode号,计算最终的inode编号
- inode->i_ino = ino + group * EXT4_INODES_PER_GROUP(sb);
- inode->i_blocks = 0;
- //计算当前inode的创建和修改时间
- inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime =
- ext4_current_time(inode);
- //对struct ext4_inode_info *ei赋值
- memset(ei->i_data, 0, sizeof(ei->i_data));
- ei->i_dir_start_lookup = 0;
- ei->i_disksize = 0;
- .....................
- return ERR_PTR(err);
- }
把__ext4_new_inode函数关键点总结下:
1:执行new_inode函数分配ext4_inode_info和inode结构
2:执行find_group_orlov或find_group_other为当前创建的文件查找一个有空闲inode和block的块组,有固定的规则。
3:接着是for循环里,找到该块组的块组描述符结构gdp,然后通过gdp找到该块组的inode bitmap,接着在inode bitmap为当前要创建的文件分配一个空闲的inode号,并令inode bitmap对应的bit位置1,表示这个inode已经分配走了。最后,再标记inode bitmap对应的物理块bh脏。
4:令当前块组空闲的inode数减1,ext4总的空闲inode数减1
5:为本次新创建inode赋值inode号、inode创建时间等等。
好的,__ext4_new_inode函数源码主体讲解过了,下边把里边涉及的主要函数源码讲解一下
1.1.1 find_group_orlov函数源码解析
find_group_orlov函数就是为当前创建的文件找一个有空闲inode的块组,先看下源码:
- static int find_group_other (struct super_block *sb, struct inode *parent,
- ext4_group_t *group, umode_t mode)
- {
- //父目录所在块组
- ext4_group_t parent_group = EXT4_I(parent)->i_block_group;
- ext4_group_t i, last, ngroups = ext4_get_groups_count(sb);
- struct ext4_group_desc *desc;
- int flex_size = ext4_flex_bg_size(EXT4_SB(sb));
- if (flex_size > 1)//使用了flex group块组
- {
- int retry = 0;
- try_again:
- //parent_group是父目录,这里计算的parent_group是父目录所属flex group块组里的第一个块组号
- parent_group &= ~(flex_size-1);
- //last是parent_group所属flex group块组里最后一个块组号
- last = parent_group + flex_size;
- if (last > ngroups)
- last = ngroups;
- //从flex group块组里第1个块组搜索到最后1个块组
- for (i = parent_group; i < last; i++) {
- //取出该块组的描述符
- desc = ext4_get_group_desc(sb, i, NULL);
- //该块组有空闲的inode,那它就是选中的块组
- if (desc && ext4_free_inodes_count(sb, desc)) {
- *group = i;
- return 0;
- }
- }
- //这里是取出父目录分配inode所属块组号i_last_alloc_group,再尝试一次
- if (!retry && EXT4_I(parent)->i_last_alloc_group != ~0) {
- retry = 1;
- parent_group = EXT4_I(parent)->i_last_alloc_group;
- goto try_again;
- }
- /*到这里说明在父目录所属flex group中的所16个块组,都没有空闲inode,于是下边执行find_group_orlov()再找一个合适的flex group块组*/
- *group = parent_group + flex_size;
- if (*group > ngroups)
- *group = 0;
- //找一个空闲inode数充裕、空闲block数充裕、已使用的目录很少的flex group块组,把找到的flex group块组号赋值给group
- return find_group_orlov(sb, parent, group, mode, NULL);
- }
- /*执行到这里,说明没有使用flex group*/
- //如果父目录所属块组有空闲inode和block,那这个块组就是本次选中的块组
- *group = parent_group;
- desc = ext4_get_group_desc(sb, *group, NULL);
- if (desc && ext4_free_inodes_count(sb, desc) &&
- ext4_free_group_clusters(sb, desc))
- return 0;
- /*执行到这里,说明没有使用flex group,并且父目录所属块组没有空闲的inode和block*/
- //这里计算后group应该是父目录所在块组的下一个块组号
- *group = (*group + parent->i_ino) % ngroups;
- //从group对应的块组一直向后搜索
- for (i = 1; i < ngroups; i <<= 1) {//搞不清楚i <<= 1 是什么意思???????
- //group块组号每次增加1、2、4、8、16.......
- *group += i;
- if (*group >= ngroups)
- *group -= ngroups;
- desc = ext4_get_group_desc(sb, *group, NULL);
- //新找到的块组group有空闲的inode和block,那它就是要找到的块组
- if (desc && ext4_free_inodes_count(sb, desc) &&
- ext4_free_group_clusters(sb, desc))
- return 0;
- }
- /*执行到这里,还没找到合适的块组,于是下边放宽查找块组的限制条件*/
- *group = parent_group;
- for (i = 0; i < ngroups; i++) {
- //group块组号每次只增加1
- if (++*group >= ngroups)
- *group = 0;
- desc = ext4_get_group_desc(sb, *group, NULL);
- //新找到的块组group有空闲的inode,那它就是要找到的块组,这里不再看是否有空闲block限制
- if (desc && ext4_free_inodes_count(sb, desc))
- return 0;
- }
- return -1;
- }
总结一个这个函数的工作流程:使用flex group时,先在父目录所属flex group的16个块组里找一个有空闲inode的块组,找不到就执行find_group_orlov()找一个有充裕空闲inode和block的flex group块组。不使用flex group时,先看父目录所属块组有没有空闲的inode和block,有就返回父目录的块组号。没有就遍历一个个块组,看哪个块组有空闲的inode。
1.1.2 find_group_orlov函数源码解析
find_group_orlov函数跟find_group_other比较像,但它是为当前创建的目录找一个空闲inode数充裕、空闲block数充裕、已使用的目录很少的块组,最后把找到的块组号赋值给group,流程更复杂点。这里先把源码贴下,然后慢慢讲解:
- static int find_group_orlov(struct super_block *sb, struct inode *parent,
- ext4_group_t *group, umode_t mode,
- const struct qstr *qstr)//qstr是创建的目录名字
- {
- //父inode所属的块组编号
- ext4_group_t parent_group = EXT4_I(parent)->i_block_group;
- struct ext4_sb_info *sbi = EXT4_SB(sb);
- //块组个数
- ext4_group_t real_ngroups = ext4_get_groups_count(sb);
- //每个块组的最多的inode数
- int inodes_per_group = EXT4_INODES_PER_GROUP(sb);
- unsigned int freei, avefreei, grp_free;
- ext4_fsblk_t freeb, avefreec;
- unsigned int ndirs;
- int max_dirs, min_inodes;
- ext4_grpblk_t min_clusters;
- ext4_group_t i, grp, g, ngroups;
- struct ext4_group_desc *desc;
- struct orlov_stats stats;
- //flex group包含的块组个数,16
- int flex_size = ext4_flex_bg_size(sbi);
- struct dx_hash_info hinfo;
- ngroups = real_ngroups;
- if (flex_size > 1) {
- ngroups = (real_ngroups + flex_size - 1) >>
- sbi->s_log_groups_per_flex;
- parent_group >>= sbi->s_log_groups_per_flex;
- }
- //空闲inode数
- freei = percpu_counter_read_positive(&sbi->s_freeinodes_counter);
- //平均每个块组的空闲inode数
- avefreei = freei / ngroups;
- //空闲block数
- freeb = EXT4_C2B(sbi,
- percpu_counter_read_positive(&sbi->s_freeclusters_counter));
- avefreec = freeb;
- //avefreec = avefreec/ngroups 平均每个块组空闲block数
- do_div(avefreec, ngroups);
- ndirs = percpu_counter_read_positive(&sbi->s_dirs_counter);
- /*这个if成立应该说明父目录是根目录或者顶层目录*/
- if (S_ISDIR(mode) &&
- ((parent == sb->s_root->d_inode) ||//父目录是根文件系统根目录
- (ext4_test_inode_flag(parent, EXT4_INODE_TOPDIR))))//顶层目录
- {
- //每个块组最多的inode数
- int best_ndir = inodes_per_group;
- int ret = -1;
- //下边这是根据各种规则计算一个初始块组号赋于grp
- if (qstr) {
- hinfo.hash_version = DX_HASH_HALF_MD4;
- hinfo.seed = sbi->s_hash_seed;
- ext4fs_dirhash(qstr->name, qstr->len, &hinfo);
- grp = hinfo.hash;
- } else
- get_random_bytes(&grp, sizeof(grp));
- //parent_group就是上边的初始块组号grp
- parent_group = (unsigned)grp % ngroups;
- /*从0号块组依次向后查找,看哪个块组有充裕的inode和block有个疑问,如果使用flex group块组的情况下,ngroups应该是flex group组个数,一个flex group组有16个真正的块组*/
- for (i = 0; i < ngroups; i++) {
- g = (parent_group + i) % ngroups;
- //得到块组或者flex group块组的空闲inode数、空闲block数、已使用的目录数
- get_orlov_stats(sb, g, flex_size, &stats);
- //当前块组(或者flex group块组),没有空闲inode,跳过
- if (!stats.free_inodes)
- continue;
- //这个成立说明当前块组(或者flex group块组)已使用的目录太多,高于每个块组最大inode数,跳过
- if (stats.used_dirs >= best_ndir)
- continue;
- //这个成立说明当前块组(或者flex group块组)空闲inode太少,低于平均值,跳过
- if (stats.free_inodes < avefreei)
- continue;
- //这个成立说明当前块组(或者flex group块组)空闲block太少,低于平均值,跳过
- if (stats.free_clusters < avefreec)
- continue;
- /*到这里很奇怪,明明已经找到inode、block等充足的块组g,但却没有跳转for循环,而是继续for循环查找下一个inode充足的块组,直到遍历到最后一个块组,搞不清楚*/
- grp = g;
- ret = 0;
- //这里更新best_ndir!!!!!!!!!
- best_ndir = stats.used_dirs;
- }
- /*如果上边for循环找到inode充足等的块组,则ret是0。否则没有找到inode、block充足等的块组,ret是-1,直接跳转到fallback分支*/
- if (ret)
- goto fallback;
- found_flex_bg:
- //没有使用flex group块组则直接使用grp作为本次找到的块组号
- if (flex_size == 1) {
- *group = grp;
- return 0;
- }
- /*到这里说明grp是flex group块组的编号令grp乘以flex_size(16),之后grp应该就是真正的块组号,一个flex group组有16个块组*/
- grp *= flex_size;
- /*这里应该是在选中的flex group组里的16个块组里,找一个空闲inode充足的块组,然后执行*group = grp+i就return,这就是最终选中的块组*/
- for (i = 0; i < flex_size; i++) {
- if (grp+i >= real_ngroups)//real_ngroups是最大块组树
- break;
- desc = ext4_get_group_desc(sb, grp+i, NULL);
- if (desc && ext4_free_inodes_count(sb, desc)) {
- *group = grp+i;
- return 0;
- }
- }
- goto fallback;
- }
- /*到这里,一般情况是,父目录不是顶层目录或者根目录*/
- //计算块组最大目录个数上限max_dirs
- max_dirs = ndirs / ngroups + inodes_per_group / 16;
- //减少avefreei,块组空闲inode数下限
- min_inodes = avefreei - inodes_per_group*flex_size / 4;
- if (min_inodes < 1)
- min_inodes = 1;
- //减少avefreec,块组空闲block数下限
- min_clusters = avefreec - EXT4_CLUSTERS_PER_GROUP(sb)*flex_size / 4;
- if (EXT4_I(parent)->i_last_alloc_group != ~0) {
- //取出父目录上一次分配inode所属的块组号给parent_group,作为本次查找块组的基准值
- parent_group = EXT4_I(parent)->i_last_alloc_group;
- if (flex_size > 1)
- parent_group >>= sbi->s_log_groups_per_flex;
- }
- //从0号块组向后遍历,找到一个inode充足的块组
- for (i = 0; i < ngroups; i++) {
- //其实就是父目录所在group加i
- grp = (parent_group + i) % ngroups;
- //得到块组或者flex group块组的空闲inode数、空闲block数、已使用的目录数
- get_orlov_stats(sb, grp, flex_size, &stats);
- //这个成立说明当前块组(或者flex group块组)已使用的目录太多,高于每个块组最大inode数,跳过
- if (stats.used_dirs >= max_dirs)
- continue;
- //这个成立说明当前块组(或者flex group块组)空闲inode太少,低于平均值,跳过
- if (stats.free_inodes < min_inodes)
- continue;
- //这个成立说明当前块组(或者flex group块组)空闲block太少,低于平均值,跳过
- if (stats.free_clusters < min_clusters)
- continue;
- /*到这里说明找到一个空闲inode充裕的块组(或者flex group块组),块组号是grp。则跳到found_flex_bg分支,如果是flex group组则从该flex group找一个空闲inode充裕的块组*/
- goto found_flex_bg;
- }
- fallback:
- ngroups = real_ngroups;
- //平均每个块组空闲inode数
- avefreei = freei / ngroups;
- /*到这里,说明上边按照 "块组或者flex group块组的空闲inode数、空闲block数、已使用的目录数" 的规则,找不到合适的块组。于是下边放松条件,重新找一个空闲inode充裕的块组*/
- fallback_retry:
- //父目录所属块组号
- parent_group = EXT4_I(parent)->i_block_group;
- //从0号块组或者flex group块组向后遍历,找到一个inode充足的块组或者flex group块组
- for (i = 0; i < ngroups; i++) {
- //其实就是父目录所在块组号或者flex group块组号加i得到快组号grp
- grp = (parent_group + i) % ngroups;
- desc = ext4_get_group_desc(sb, grp, NULL);
- if (desc) {
- //如果grp这个块组或者flex group块组空闲inode数大于avefreei(平均每个块组空闲inode数),那它就是本次选中的块组
- grp_free = ext4_free_inodes_count(sb, desc);
- if (grp_free && grp_free >= avefreei) {
- *group = grp;
- return 0;
- }
- }
- }
- ................
- return -1;
- }
这里说一下这个函数的工作细节:
1:如果父目录是顶层目录或者根目录,则以best_ndir、avefreei、avefreec 为阈值,从0号块组或者flex group块组向后一直查找,找到块组已使用目录数、块组空闲inode数、块组空闲block数符合阈值的块组。找到后,如果不是flex group块组,直接返回这个块组号。如果是flex group块组,则从该flex group块组找一个空闲inode充足的块组。
2:如果父目录不是顶层目录或者根目录,则以max_dirs、min_inodes、min_clusters为阈值,从0号块组或者flex group块组向后一直查找,找到块组已使用目录数、块组空闲inode数、块组空闲block数符合阈值的块组。找到后,如果不是flex group块组,如果是flex group块组,则从该flex group块组找一个空闲inode充足的块组。
3:如果按照步骤1和2找不到合适的块组,则从0号块组或者flex group块组向后一直查找,只要块组空闲inode数充裕,直接返回该块组号。
最后做个总结: parent_group 是个关键,它是搜索空闲块组(有空闲inode和block)的基准块组号
1:是顶层目录或者根目录时,则采用分散形式查找空闲块组,因为此时parent_group = (unsigned)grp % ngroups,parent_group是个随机值。
2:如果不是顶层目录或根目录,才在父目录所属块组附近查找空闲块组,因为此时parent_group = EXT4_I(parent)->i_block_group,就是父目录所属的块组编号
3:如果最后找不到合适的空闲块组,那就没那么多限制条件了,从0号块组开始遍历,谁有空闲inode就选中谁作为最终的块组
1.1.3 ext4_get_group_desc函数源码解析
ext4_get_group_desc函数主要作用是:由传入的块组编号block_group得到块组描述符结构ext4_group_desc,并且令group_desc_bh指向保存块组描述符数据的物理块映射的bh,下边把源码贴下:
- struct ext4_group_desc * ext4_get_group_desc(struct super_block *sb,
- ext4_group_t block_group,
- struct buffer_head **bh)
- {
- unsigned int group_desc;
- unsigned int offset;
- ext4_group_t ngroups = ext4_get_groups_count(sb);
- struct ext4_group_desc *desc;
- struct ext4_sb_info *sbi = EXT4_SB(sb);
- ...................
- group_desc = block_group >> EXT4_DESC_PER_BLOCK_BITS(sb);
- offset = block_group & (EXT4_DESC_PER_BLOCK(sb) - 1);
- ..............
- /*sbi->s_group_desc[group_desc]->b_data保存的数据是目标块组block_group这个块组所在物理块的4K数据,都是块组描述符结构。offset*EXT4_DESC_SIZE(sb)是目标块组block_group的64字节块组描述符数据在这个物理块4K数据中的的偏移。最终得到block_group这个块组的块组描述符结构*/
- desc = (struct ext4_group_desc *)(
- (__u8 *)sbi->s_group_desc[group_desc]->b_data +
- offset * EXT4_DESC_SIZE(sb));
- if (bh)//bh指向保存块组描述符数据的buffer_head
- *bh = sbi->s_group_desc[group_desc];
- return desc;
- }
主要说一下group_desc和offset的计算过程。ext4文件系统的组成是 超级块(1个block)+块组描述符(N个block)+预留块(N个block)+Data Block Bitmap(1个block)+ inode Bitmap(1个block)+inode table(N个block)+ data block(N个block)。ext4一个物理block大小4k,一个块组描述符ext4_group_desc结构64B,一个block可以容纳64个块组描述符。group_desc = block_group >> EXT4_DESC_PER_BLOCK_BITS(sb)就是group_desc=block_group/64,计算当前的块组号block_group落在第几个物理块(即第group_desc个物理块),offset = block_group & (EXT4_DESC_PER_BLOCK(sb) - 1)是计算当前的块组号block_group对应的块组描述符在第group_desc个物理块的偏移,准确说是group_desc物理块里的第offset个块组描述符。说着比较抽象,举个例子,一个2个物理块,2*64个块组,那块组号65的块组描述符在哪里?group_desc=65/64=1说明在第2个物理块,offset=65%64=1说明在第2个物理块的第2个块组描述符哪里。
ok,__ext4_new_inode函数讲解过了,下边回到ext4_create函数讲解ext4_add_nondir函数。
1.2 ext4_add_nondir、ext4_add_entry函数源码解析
这个函数简单说,就是把刚才为新文件分配的inode添加到它的父目录里,具体怎么操作呢?看下源码:
- static int ext4_add_nondir(handle_t *handle,
- struct dentry *dentry, struct inode *inode)
- {
- //把dentry和inode对应的文件或目录添加到它父目录
- int err = ext4_add_entry(handle, dentry, inode);
- if (!err) {
- //标记inode脏,重点是 根据inode编号得到它在 所属的块组的inode table的物理块号
- ext4_mark_inode_dirty(handle, inode);
- unlock_new_inode(inode);
- //建立dentry和inode联系
- d_instantiate(dentry, inode);
- return 0;
- }
- ..............
- return err;
- }
显然主要是执行ext4_add_entry把新创建的文件inode添加到父目录。并且,还执行ext4_mark_inode_dirty标记这个inode脏,ext4_mark_inode_dirty里还有一个重点操作是从inode table为该inode分配一席之地。
1.2.1 ext4_add_entry函数源码解析
这个函数就是把新创建的文件inode添加到父目录里。
- static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
- struct inode *inode)//dentry和inode都是待创建的目录或文件的
- {
- //父目录inode
- struct inode *dir = dentry->d_parent->d_inode;
- struct buffer_head *bh = NULL;
- struct ext4_dir_entry_2 *de;
- struct ext4_dir_entry_tail *t;
- struct super_block *sb;
- int retval;
- int dx_fallback=0;
- unsigned blocksize;
- ext4_lblk_t block, blocks;
- int csum_size = 0;
- if (EXT4_HAS_RO_COMPAT_FEATURE(inode->i_sb,
- EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
- csum_size = sizeof(struct ext4_dir_entry_tail);
- sb = dir->i_sb;
- blocksize = sb->s_blocksize;//ext4文件系统一个物理块4K大
- if (!dentry->d_name.len)
- return -EINVAL;
- ............
- //dir->i_size是父目录的数据量大小,blocks是父目录数据占的block个数
- blocks = dir->i_size >> sb->s_blocksize_bits;
- /*这个for循环是根据父目录逻辑块地址0~blocks,依次读取这些逻辑块映射的物理块的数据,然后在这些物理块数据中查找一个空闲的ext4_dir_entry_2结构,最后把本次添加的文件子文件或子目录的名字等信息赋值给ext4_dir_entry_2。这就相当于把该子目录或子文件添加到了父目录*/
- for (block = 0; block < blocks; block++) {
- /*根据父目录的逻辑地址block从ext4文件系统的data block区分配1个物理块,并与逻辑地址block构成映射,最后返回这物理块的bh。注意,bh->b_data就保存了该父目录的一个物理块数据,是一个个包含子目录或者子文件名字等信息的ext4_dir_entry_2结构*/
- bh = ext4_read_dirblock(dir, block, DIRENT);
- if (IS_ERR(bh))
- return PTR_ERR(bh);
- /*在父目录的block块数据中查找一个空闲的ext4_dir_entry_2结构并赋值给de,然后对de这个ext4_dir_entry_2结构赋值待添加的文件或目录名字、inode编号、文件长度等信息。这里就相当于把新的文件或目录添加到了父目录*/
- retval = add_dirent_to_buf(handle, dentry, inode, NULL, bh);
- if (retval != -ENOSPC)
- goto out;
- .................
- }
- //执行到这里,应该是说,dir父目录数据块被占满了,则需要增加一个物理块,并返回它的bh,最后把本次的子目录或子文件添加到这个父目录新的物理块
- bh = ext4_append(handle, dir, &block);
- if (IS_ERR(bh))
- return PTR_ERR(bh);
- de = (struct ext4_dir_entry_2 *) bh->b_data;
- de->inode = 0;
- de->rec_len = ext4_rec_len_to_disk(blocksize - csum_size, blocksize);
- if (csum_size) {
- t = EXT4_DIRENT_TAIL(bh->b_data, blocksize);
- initialize_dirent_tail(t, blocksize);
- }
- retval = add_dirent_to_buf(handle, dentry, inode, de, bh);
- out:
- brelse(bh);
- if (retval == 0)
- ext4_set_inode_state(inode, EXT4_STATE_NEWENTRY);
- return retval;
- }
首先执行ext4_read_dirblock函数从保存父目录数据的物理块中依次读取数据到这些物理块映射的bh。然后执行add_dirent_to_buf函数,在父目录的物理块数据里,为新创建的文件查找一个空闲的ext4_dir_entry_2结构,还不能有重名的子文件或目录,看下它的源码:
- static int add_dirent_to_buf(handle_t *handle, struct dentry *dentry,
- //dentry和inode都是待创建的目录或文件的
- struct inode *inode, struct ext4_dir_entry_2 *de,
- //bh是保存父目录的数据物理块映射的bh
- struct buffer_head *bh)
- {
- //父目录
- struct inode *dir = dentry->d_parent->d_inode;
- //本次创建的新文件或目录的名字
- const char *name = dentry->d_name.name;
- //本次创建的新文件或目录的名字长度
- int namelen = dentry->d_name.len;
- unsigned int blocksize = dir->i_sb->s_blocksize;
- int csum_size = 0;
- int err;
- if (EXT4_HAS_RO_COMPAT_FEATURE(inode->i_sb,
- EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
- csum_size = sizeof(struct ext4_dir_entry_tail);
- if (!de) {//一般de是NULL
- //在父目录的数据中查找一个空闲的ext4_dir_entry_2
- err = ext4_find_dest_de(dir, inode,
- bh, bh->b_data, blocksize - csum_size,
- name, namelen, &de);
- if (err)
- return err;
- }
- .................
- //对de这个ext4_dir_entry_2赋值待添加的文件或目录名字、inode编号、文件长度等等
- ext4_insert_dentry(inode, de, blocksize, name, namelen);
- //更新父目录修改时间
- dir->i_mtime = dir->i_ctime = ext4_current_time(dir);
- ext4_update_dx_flag(dir);
- dir->i_version++;
- ext4_mark_inode_dirty(handle, dir);
- BUFFER_TRACE(bh, "call ext4_handle_dirty_metadata");
- err = ext4_handle_dirty_dirent_node(handle, dir, bh);
- if (err)
- ext4_std_error(dir->i_sb, err);
- return 0;
- }
这个函数就是在父目录的数据中查找一个空闲并且不能重名的的ext4_dir_entry_2赋值给de,然后对de这个ext4_dir_entry_2赋值待添加的文件或目录名字、inode编号、文件长度等等。关键是调用ext4_find_dest_de函数。
- int ext4_find_dest_de(struct inode *dir, struct inode *inode,//inode都是新创建的目录或文件的
- struct buffer_head *bh,//bh是保存父目录的数据物理块映射的bh
- void *buf, int buf_size,//buf是bh->b_data,buf_size是bh->b_data这片buf大小,是4k
- const char *name, int namelen,//name和namelen是待创建文件或目录的名字和长度
- struct ext4_dir_entry_2 **dest_de)
- {
- struct ext4_dir_entry_2 *de;
- //reclen 比 namelen 稍大,容纳一些冗余信息吧
- unsigned short reclen = EXT4_DIR_REC_LEN(namelen);
- int nlen, rlen;
- unsigned int offset = 0;
- char *top;
- //buf是保存父目录的数据物理块映射的bh的buf,de指向这片内存首地址
- de = (struct ext4_dir_entry_2 *)buf;
- //top指向这片buf的顶端
- top = buf + buf_size - reclen;
- /*父目录的数据是一个个ext4_dir_entry_2结构,保存了子文件或者子目录的名字等关键信息。这个while循环是从保存父目录的数据的buf头开始,遍历一个个ext4_dir_entry_2结构*/
- while ((char *) de <= top) {
- if (ext4_check_dir_entry(dir, NULL, de, bh,
- buf, buf_size, offset))
- return -EIO;
- //如果父目录已经有了名字是name的文件或目录,返回EEXIST,不能重名
- if (ext4_match(namelen, name, de))
- return -EEXIST;
- //nlen比de->name_len大几个字节
- nlen = EXT4_DIR_REC_LEN(de->name_len);
- //rlen = de->rec_len
- rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
- /*如果当前的de没被使用,de->inode应该是0,此时只要rlen>=reclen,则当前的de就是选中的ext4_dir_entry_2。rlen是de的空间大小,reclen是本次创建的子目录或者子文件的名字的长度,rlen>=reclen 说明de可以容纳下本次创建的子目录或者子文件*/
- if ((de->inode ? rlen - nlen : rlen) >= reclen)
- break;
- //de指向下一个ext4_dir_entry_2结构
- de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
- offset += rlen;
- }
- //de超过保存父目录的数据物理块映射bh的buf尾部,说明空间不够了
- if ((char *) de > top)
- return -ENOSPC;
- //de就是为本次的子文件或子目录找到的ext4_dir_entry_2结构
- *dest_de = de;
- return 0;
- }
注释写的比较清晰,主要就是在父目录里找一个没有重名并且空闲的ext4_dir_entry_2给新创建的文件。下边重点讲解ext4_add_entry函数里执行的ext4_read_dirblock函数。
1.2.2 ext4_read_dirblock和__ext4_read_dirblock函数源码解析
该函数重点是读取父目录的一个物理块数据并返回这个物理块映射的bh,看下源码:
- #define ext4_read_dirblock(inode, block, type) \
- __ext4_read_dirblock((inode), (block), (type), __LINE__)
- static struct buffer_head *__ext4_read_dirblock(struct inode *inode,//inode是父目录的
- ext4_lblk_t block,
- dirblock_type_t type,
- unsigned int line)
- {
- struct buffer_head *bh;
- struct ext4_dir_entry *dirent;
- int err = 0, is_dx_block = 0;
- //根据传入的目录inode的逻辑地址block从ext4文件系统的data block区分配1个物理块,并与逻辑地址block构成映射,最后返回这物理块的bh
- bh = ext4_bread(NULL, inode, block, 0, &err);
- .............
- return bh;
- }
- struct buffer_head *ext4_bread(handle_t *handle, struct inode *inode,//inode是父目录的
- ext4_lblk_t block, int create, int *err)
- {
- struct buffer_head *bh;
- //根据传入的文件或目录inode的逻辑地址block从ext4文件系统的data block区分配1个物理块,并与逻辑地址block构成映射,最后返回这物理块的bh
- bh = ext4_getblk(handle, inode, block, create, err);
- if (!bh)
- return bh;
- if (buffer_uptodate(bh))
- return bh;
- //读取bh映射的物理块的数据到bh
- ll_rw_block(READ | REQ_META | REQ_PRIO, 1, &bh);
- //等待bh物理块的数据读取到bh
- wait_on_buffer(bh);
- if (buffer_uptodate(bh))
- return bh;
- put_bh(bh);
- *err = -EIO;
- return NULL;
- }
可以发现最终还是调用经典的ext4_getblk函数,这个函数根据传入的逻辑块地址从该文件inode所在块组的Data block区分配指定个数的连续物理块,然后完成传入的逻辑块地址与这些物理块的映射,最终执行ll_rw_block函数读取这个物理块的数据的其映射的bh。我们重点看看ext4_getblk函数
1.2.3 ext4_getblk函数源码解析
- struct buffer_head *ext4_getblk(handle_t *handle, struct inode *inode,
- ext4_lblk_t block, int create, int *errp)
- {
- struct ext4_map_blocks map;
- struct buffer_head *bh;
- int fatal = 0, err;
- map.m_lblk = block;
- map.m_len = 1;
- //根据传入的文件或目录inode的逻辑地址map->m_lblk从ext4文件系统的data block区分配1个物理块,并与逻辑地址map->m_lblk构成映射,并把映射关系保存到ext4 extent结构
- err = ext4_map_blocks(handle, inode, &map,
- create ? EXT4_GET_BLOCKS_CREATE : 0);
- ...........
- //map.m_pblk就是上边为文件inode分配的起始物理块,这里是找到它的bh
- bh = sb_getblk(inode->i_sb, map.m_pblk);
- return bh;
- }
ext4_getblk函数主要作用是:根据传入的文件或目录inode的逻辑地址block从ext4文件系统的data block区分配1个物理块,并与逻辑地址block构成映射,最后返回这物理块的bh。里边主要是调用ext4_map_blocks->ext4_ext_map_blocks函数。
1.2.4 ext4_ext_map_blocks函数源码解析
ext4_ext_map_blocks()函数根据传入的文件或目录inode的逻辑地址map->m_lblk从ext4文件系统的data block区分配map->m_len个物理块,并与逻辑地址map->m_lblk构成映射,并把映射关系保存到ext4 extent结构。里边牵涉到了ext4 extent,ext4 extent会在稍后发出的文章中详细介绍,这里只会简单介绍一下。
- int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
- struct ext4_map_blocks *map, int flags)
- {
- /*在ext4 extent B+树每一层索引节点(包含根节点)中找到起始逻辑块地址最接近传入的起始逻辑块地址map->m_lblk的ext4_extent_idx结构保存到path[ppos]->p_idx.然后找到最后一层的叶子节点中最接近传入的起始逻辑块地址map->m_lblk的ext4_extent结构,保存到path[ppos]->p_ext。这个ext4_extent才包含了逻辑块地址和物理块地址的映射关系。*/
- path = ext4_ext_find_extent(inode, map->m_lblk, NULL);
- ............
- /*注意,执行到这里说明没有从ext4 extent找到本次逻辑地址map->m_lblk映射的物理块,于是就要从ext4文件系统分配map->m_len个物理块,然后与逻辑地址map->m_lblk构成映射。ext4_ext_find_goal()是先找一个目标物理块号ar.goal,然后执行ext4_mb_new_blocks():以ar.goal为基准,搜索分配map->m_len个物理块。最后,再构成与逻辑地址map->m_lblk的映射*/
- ar.inode = inode;
- /*要为文件inode分配保存数据的物理块了,该函数是从inode所属块组先找一个理想的空闲物理块,后续从这个物理块开始搜索,最终查找本次要分配的物理块。简单说,找到map->m_lblk逻辑块地址映射的目标 起始物理块地址并返回给ar.goal*/
- ar.goal = ext4_ext_find_goal(inode, path, map->m_lblk);
- //ar.logical是逻辑块地址
- ar.logical = map->m_lblk;
- offset = EXT4_LBLK_COFF(sbi, map->m_lblk);//offset测试时0
- //分配的物理块个数
- ar.len = EXT4_NUM_B2C(sbi, offset+allocated);
- ............
- /*分配map->m_len个物理块,这就是map->m_lblk逻辑块地址映射的map->m_len个物理块,返回这map->m_len个物理块的起始物理块号newblock。测试结果 newblock 和 ar.goal有时相等,有时不相等。本次映射的起始逻辑块地址是map->m_lblk,映射物理块个数map->m_len,ext4_mb_new_blocks()除了要找到newblock这个起始逻辑块地址,还得保证找到newblock打头的连续map->m_len个物理块,必须是连续的,这才是更重要的。*/
- newblock = ext4_mb_new_blocks(handle, &ar, &err);
- .............
- map->m_flags |= EXT4_MAP_MAPPED;
- //本次起始逻辑块地址map->m_lblk映射的起始物理块号
- map->m_pblk = newblock;
- //本次逻辑块地址完成映射的物理块数,并不能保证allocated等于传入的map->m_len,还有可能小于
- map->m_len = allocated;
- return err ? err : allocated;
- }
ext4_ext_map_blocks里调用的ext4_ext_find_extent是从ext4 extent缓存中查找传入的逻辑块地址是否已经映射了物理块,没有的话那就需要分配新的物理块了。于是会先调用ext4_ext_find_goal函数查找目标物理块ar.goal,然后以它为基准执行ext4_mb_new_blocks函数最终分配物理块。下边看下源码:
1.2.5 ext4_ext_find_goal 和 ext4_inode_to_goal_block函数源码解析
ext4_ext_find_goal函数里主要调用了ext4_inode_to_goal_block,这里只看后者。
- ext4_fsblk_t ext4_inode_to_goal_block(struct inode *inode)
- {
- struct ext4_inode_info *ei = EXT4_I(inode);
- ext4_group_t block_group;
- ext4_grpblk_t colour;
- //实际测试flex 是16
- int flex_size = ext4_flex_bg_size(EXT4_SB(inode->i_sb));
- ext4_fsblk_t bg_start;
- ext4_fsblk_t last_block;
- //取出inode所属块组号 block_group
- block_group = ei->i_block_group;
- if (flex_size >= EXT4_FLEX_SIZE_DIR_ALLOC_SCHEME) {
- //这是令block_group除以16,得到flex group的编号,一个flex group有16个块组
- block_group &= ~(flex_size-1);
- if (S_ISREG(inode->i_mode))
- block_group++;
- }
- //bg_start:得到block_group这个块组第一个物理块号,就是该块组的起始物理块号
- bg_start = ext4_group_first_block_no(inode->i_sb, block_group);
- last_block = ext4_blocks_count(EXT4_SB(inode->i_sb)->s_es) - 1;
- if (test_opt(inode->i_sb, DELALLOC))
- return bg_start;
- //根据进程ID计算一个偏移值
- if (bg_start + EXT4_BLOCKS_PER_GROUP(inode->i_sb) <= last_block)
- colour = (current->pid % 16) *
- (EXT4_BLOCKS_PER_GROUP(inode->i_sb) / 16);
- else
- colour = (current->pid % 16) * ((last_block - bg_start) / 16);
- //bg_start+偏移值colour得到理想的要分配的物理块号
- return bg_start + colour;
- }
执行到该函数,是要为文件inode分配保存数据的物理块了。该函数是从inode所属块组先找一个理想的空闲物理块,后续从这个物理块开始搜索,最终查找本次要分配的物理块。下边接着看ext4_mb_new_blocks函数源码
1.2.6 ext4_mb_new_blocks函数源码解析
该函数主要是分配ar->len个连续的物理块并返回起始物理块号,直接看源码:
- ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
- struct ext4_allocation_request *ar, int *errp)
- {
- ..............
- //计算 ac->ac_b_ex.fe_logical、ac->ac_inode、ac->ac_o_ex.fe_logical、ac->ac_o_ex.fe_group、ac->ac_o_ex.fe_start、ac->ac_o_ex.fe_len 初值
- *errp = ext4_mb_initialize_context(ac, ar);
- ..............
- if (!ext4_mb_use_preallocated(ac)) {//先使用之前预分配的物理块
- ac->ac_op = EXT4_MB_HISTORY_ALLOC;
- /*主要是依照ar->goal这个 分配物理块时的基准物理块号,计算出本次要分配的物理块所在的ac->ac_f_ex.fe_group和计算它所在ac->ac_f_ex.fe_group块组的物理块号赋于ac->ac_f_ex.fe_start。注意,ac->ac_f_ex.fe_start就是本次在ac->ac_f_ex.fe_group块组找到的起始空闲物理块号,并且是在这个基础上连续分配了ac->ac_g_ex.fe_len个空闲物理块号*/
- ext4_mb_normalize_request(ac, ar);//分配失败则在这里正常分配物理块
- repeat:
- *errp = ext4_mb_regular_allocator(ac);
- ...............
- }
- if (likely(ac->ac_status == AC_STATUS_FOUND)) {
- /*执行到该函数,ac->ac_f_ex.fe_group是本次分配的物理块所在块组号,ac->ac_f_ex.fe_start是在ac->ac_f_ex.fe_group块组分配ac->ac_g_ex.fe_len个空闲物理块的起始物理块号。这里是令ext4总的空闲block个数和块组空闲的物理块个数减少ac->ac_b_ex.fe_len个,因为从ac->ac_b_ex.fe_group块组分配了ac->ac_b_ex.fe_len个物理块,则标记该块组的data block bitmap的对应bit位1,表示这些物理块已分配*/
- *errp = ext4_mb_mark_diskspace_used(ac, handle, reserv_clstrs);
- ..............
- } else {
- ..............
- }
- //返回起始物理块
- return block;
- }
大部分源码注释写的比较清楚,这里只再介绍下ext4_mb_mark_diskspace_used函数源码:
- static noinline_for_stack int
- ext4_mb_mark_diskspace_used(struct ext4_allocation_context *ac,
- handle_t *handle, unsigned int reserv_clstrs)
- {
- //读取ac->ac_b_ex.fe_group块组的data block bitmap,就是ext4文件系统块组的data block区的bitmap
- bitmap_bh = ext4_read_block_bitmap(sb, ac->ac_b_ex.fe_group);
- .............
- //得到ac->ac_b_ex.fe_group的块组描述符赋于gdp
- gdp = ext4_get_group_desc(sb, ac->ac_b_ex.fe_group, &gdp_bh);
- .............
- /*本次是在ac->ac_f_ex.fe_group块组分配ac->ac_g_ex.fe_len个空闲物理块的起始物理块号,ac->ac_f_ex.fe_start是个起始物理块号。因此这是在ext4文件系统块组的data block区的bitmap对应位置的bit位置1,表示这些data block区的物理块被分配了*/
- ext4_set_bits(bitmap_bh->b_data, ac->ac_b_ex.fe_start,ac->ac_b_ex.fe_len);
- .............
- len = ext4_free_group_clusters(sb, gdp) - ac->ac_b_ex.fe_len;
- //块组空闲的物理块个数减少ac->ac_b_ex.fe_len
- ext4_free_group_clusters_set(sb, gdp, len);
- .........
- //ext4总的空闲物理块个数减少ac->ac_b_ex.fe_len个
- percpu_counter_sub(&sbi->s_freeclusters_counter, ac->ac_b_ex.fe_len);
- .............
- }
简单总结下:执行到该函数,ac->ac_f_ex.fe_group是本次分配的物理块所在块组号,ac->ac_f_ex.fe_start是在ac->ac_f_ex.fe_group块组分配ac->ac_g_ex.fe_len个空闲物理块的起始物理块号。这里是令ext4总的空闲block个数和块组空闲的物理块个数减少ac->ac_b_ex.fe_len个。并且因为从ac->ac_b_ex.fe_group块组分配了ac->ac_b_ex.fe_len个物理块,则标记该块组的data block bitmap的对应bit位1,表示这些物理块已分配。
最后回到ext4_add_entry函数,在调用ext4_add_entry把新创建的文件inode添加到父目录后,然后执行ext4_mark_inode_dirty标记这个inode脏,这里边还有一个重点是从该inode所在块组的inode table分配inode结构,用来保存这个ext4 inode结构,看下源码。
1.2.7 ext4_mark_inode_dirty函数源码解析
- int ext4_mark_inode_dirty(handle_t *handle, struct inode *inode)
- {
- /*建立bh与jh的联系,二者一一对应,把jh添加到handle->h_transaction指向的transaction的BJ_Reserved链表。根据inode编号得到它在 所属的块组的inode table的物理块号,这个物理块保存的是该块组的inode table,这个inode table保存了这个inode结构最后得到inode元数据所在物理块的bh,存于iloc->bh*/
- err = ext4_reserve_inode_write(handle, inode, &iloc);
- }
- int ext4_reserve_inode_write(handle_t *handle, struct inode *inode,
- struct ext4_iloc *iloc)
- {
- int err;
- /*根据inode编号得到它在 所属的块组的inode table的物理块号,这个物理块保存的是该块组的inode table,这个inode table保存了这个inode结构最后得到inode元数据所在物理块的bh,存于iloc->bh*/
- err = ext4_get_inode_loc(inode, iloc);
- if (!err) {
- BUFFER_TRACE(iloc->bh, "get_write_access");
- //建立bh与jh的联系,二者一一对应,把jh添加到handle->h_transaction指向的transaction的BJ_Reserved链表
- err = ext4_journal_get_write_access(handle, iloc->bh);
- if (err) {
- brelse(iloc->bh);
- iloc->bh = NULL;
- }
- }
- ext4_std_error(inode->i_sb, err);
- return err;
- }
- int ext4_get_inode_loc(struct inode *inode, struct ext4_iloc *iloc)
- {
- return __ext4_get_inode_loc(inode, iloc,
- !ext4_test_inode_state(inode, EXT4_STATE_XATTR));
- }
这里边还有一些ext4 jbd2操作,这里先别理会。可以发现,最终调用的是__ext4_get_inode_loc函数,看下它的源码:
- static int __ext4_get_inode_loc(struct inode *inode,
- struct ext4_iloc *iloc, int in_mem)
- {
- struct ext4_group_desc *gdp;
- struct buffer_head *bh;
- struct super_block *sb = inode->i_sb;
- ext4_fsblk_t block;
- int inodes_per_block, inode_offset;
- iloc->bh = NULL;
- if (!ext4_valid_inum(sb, inode->i_ino))
- return -EIO;
- //inode的编号号除以每块组inode个数,计算出该inode在第几个块组,每个块组容纳的最大inode个数是一致的
- iloc->block_group = (inode->i_ino - 1) / EXT4_INODES_PER_GROUP(sb);
- //根据块组号得到该inode所属的块组描述符
- gdp = ext4_get_group_desc(sb, iloc->block_group, NULL);
- if (!gdp)
- return -EIO;
- //每个物理块容纳的inode个数
- inodes_per_block = EXT4_SB(sb)->s_inodes_per_block;
- //inode_offset是该inode编号在块组内的偏移
- inode_offset = ((inode->i_ino - 1) %
- EXT4_INODES_PER_GROUP(sb));
- /*ext4_inode_table(sb, gdp)是得到inode所属块组inode table所在的起始物理块号,(inode_offset / inodes_per_block)是根据inode号在块组内的偏移计算该inode在inode table的偏移,二者相加就是该inode在该块组的inode table的物理块号。ext4_inode_table(sb, gdp)是块组的inode table所在的起始物理块号,这里计算的是inode table的物理块,里边保存了该inode结构*/
- block = ext4_inode_table(sb, gdp) + (inode_offset / inodes_per_block);
- //该inode所在inode table的那个物理块里的偏移
- iloc->offset = (inode_offset % inodes_per_block) * EXT4_INODE_SIZE(sb);
- //这应该是得到inode元数据所在的物理块对应的bh吧,注意,这不是inode对应的文件的数据所在的物理块的bh
- bh = sb_getblk(sb, block);
- ..................
- //inode元数据所在的物理块的bh赋予iloc->bh
- iloc->bh = bh;
- return 0;
- }
简单说,该函数是根据inode编号得到它在所属的块组的inode table的物理块(这个物理块映射的bh是iloc->bh)和在该物理块里的偏移(iloc->offset)。这个物理块保存的是该块组的inode table,inode table保存了ext4 inode结构。可能比较抽象,我们举个例子就好点了。
举个例子,inode在块组5,块组5的inode table所在物理块号是1000,inode table占了6个物理块。一个ext4_inode大小256B,一个物理块容纳16个ext4_inode,一个块组容纳16*6=96个ext4_inode。假设inode编号是96*6+18,保存在inode table区的第2个物理块。则ext4_inode_table(sb, gdp) = 1000,inode_offset = (96*6+18 -1)%96=17,block=ext4_inode_table(sb, gdp)+(inode_offset/inodes_per_block)=1000 + 17/16=1001,就是说该inode保存在inode table区的第2个物理块。iloc->offset = (17 % 16)*256B,这就是说该inode在inode table 区的第2个物理块的第2个ext4_inode位置,乘以256B就是指向具体字节位置处。
我们再来看下怎么使用iloc->bh 和 iloc->offset得到该inode对应的ext4 inode结构的:
- static inline struct ext4_inode *ext4_raw_inode(struct ext4_iloc *iloc)
- {
- return (struct ext4_inode *) (iloc->bh->b_data + iloc->offset);
- }
iloc->bh是文件ext4 inode所在物理块对应的bh,iloc->bh->b_data是该物理块的数据保存到内存的首地址,iloc->offset是文件的ext4 inode结构在这个物理块的偏移。
2:ext4_mkdir目录创建函数源码解析
先把源码贴下:
- static int ext4_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
- {
- handle_t *handle;
- struct inode *inode;
- int err, credits, retries = 0;
- ..............
- retry:
- //为当前的目录分配一个inode
- inode = ext4_new_inode_start_handle(dir, S_IFDIR | mode,
- &dentry->d_name,
- 0, NULL, EXT4_HT_DIR, credits);
- handle = ext4_journal_current_handle();
- err = PTR_ERR(inode);
- if (IS_ERR(inode))
- goto out_stop;
- //inode->i_op和inode->i_fop赋值
- inode->i_op = &ext4_dir_inode_operations;
- inode->i_fop = &ext4_dir_operations;
- //初始化目录inode
- err = ext4_init_new_dir(handle, dir, inode);
- if (err)
- goto out_clear_inode;
- err = ext4_mark_inode_dirty(handle, inode);
- if (!err)
- //把dentry和inode对应的文件或目录添加到它父目录
- err = ext4_add_entry(handle, dentry, inode);
- ..............
- ext4_inc_count(handle, dir);
- ext4_update_dx_flag(dir);
- err = ext4_mark_inode_dirty(handle, dir);
- ..............
- return err;
- }
可以发现它跟文件创建函数ext4_create流程很接近,也是先分配一个inode,然后把该inode添加到父目录,关键函数前边都讲解过,这里不再赘述。