网上讲解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添加到父目录,关键函数前边都讲解过,这里不再赘述。