以Linux 0.11为实例,个人总结,不保证正确性。。。
[文件系统]
磁盘上的数据以块为单位进行读写,每一个块称为一个逻辑块。在理解磁盘的逻辑视图时,以逻辑块为单位来理解。
磁盘上数据按照使用情况分,可以分成以下几个部分:引导块、超级块、i节点位图区、逻辑块节点位图区、i节点区、数据区
引导块中是整个系统的启动代码,只有用于启动的文件系统才有数据,其它的文件系统这个块没有数据(可以这样理解)
超级块中存放的是关于整个文件系统的布局描述的数据
i节点位图区中存放的是i节点区中i节点的使用情况
逻辑块节点位图区中存放的是磁盘中逻辑块的使用情况
i节点区中存放的是对应文件的i节点数据
数据区存放文件数据的部分
示意图如下
![](https://img-my.csdn.net/uploads/201301/05/1357364863_8075.jpg)
[超级块]
超级块中含有描述整个文件系统分布的数据。相应的数据结构如下(fs.h)
struct
d_super_block {
unsigned
short
s_ninodes; //i节点个数
unsigned
short
s_nzones; //磁盘上全部的逻辑块的个数
unsigned
short
s_imap_blocks; //i节点位图区所使用的逻辑块的个数
unsigned
short
s_zmap_blocks; //逻辑块位图区所使用的逻辑块的个数
unsigned
short
s_firstdatazone; //第一个数据块的逻辑块号
unsigned
short
s_log_zone_size;
unsigned
long
s_max_size; // 最大文件长度
unsigned
short
s_magic; //文件系统魔数
};
struct d_super_block是对磁盘上的超级块的数据描述。
在内存中的超级块的数据结构,除了要存放磁盘中相应的数据外,还要存放一些额外的信息。如读写标志、加锁标志、该文件系统安装的根节点、i节点位图区的高速缓存、设备号等。内存中超级块的数据结构描述如下
struct
super_block {
unsigned
short
s_ninodes;
unsigned
short
s_nzones;
unsigned
short
s_imap_blocks;
unsigned
short
s_zmap_blocks;
unsigned
short
s_firstdatazone;
unsigned
short
s_log_zone_size;
unsigned
long
s_max_size;
unsigned
short
s_magic;
/* These are only in memory */
struct
buffer_head * s_imap[8]; //i节点位图区的高速缓存数组,8个逻辑块的大小
struct
buffer_head * s_zmap[8]; //逻辑块位图区的高速缓存数组,8个逻辑块的大小
unsigned
short
s_dev; //设备号
struct
m_inode * s_isup; //被安装文件系统根目录i节点
struct
m_inode * s_imount; //该文件系统被安装的i节点
unsigned
long
s_time; //修改时间
struct
task_struct * s_wait; //等待在该超级块上的进程
unsigned
char
s_lock; //加锁标志
unsigned
char
s_rd_only; //只读标志
unsigned
char
s_dirt; //内容是否已修改标志
};
索引节点(也称i节点)分两种:一个是磁盘上的索引节点,它描述了文件的全部信息;一个是内存中的索引节点信息,它除了包好磁盘中的索引节点的数据外,还包括一些额外的和内存、进程相关的字段。
索引节点描述了一个文件的布局,
索引节点以静态形式存于磁盘上,内核把它们读到内存索引节点表以便操作它们。磁盘上的索引节点由如下字段组成:文件所有者标识号、所有者在的用户组标识号、文件类型、文件存取权限、文件存取时间、指向文件的链接数、文件数据的磁盘地址明细表、文件大小。也就是说,文件的索引节点描述了文件的一切信息。
内存中的索引节点额外包括的字段有:内存索引节点的状态、含有该文件的文件系统的逻辑设备号、索引节点号、指向其它内存索引节点的指针、应用数。
索引节点与文件数据有区别的,当对文件进行读写时,改变的是文件数据,但文件的索引节点信息并没有改变。但当改变文件的所有者,改变文件的访问权限时,会使得文件的索引节点信息发生变化,但没有改变文件数据。
[磁盘上的索引节点和内存中的索引节点]
磁盘上的索引节点和内存中的索引节点在0.11中的数据结构分别如下,从代码上可以很明显看到它们之间的差别
/*磁盘上的i节点的数据结构描述*/
struct
d_inode {
unsigned
short
i_mode;
unsigned
short
i_uid;
unsigned
long
i_size;
unsigned
long
i_time;
unsigned
char
i_gid;
unsigned
char
i_nlinks;
unsigned
short
i_zone[9];
};
/*
内存中的i节点的数据结构描述
*/
struct
m_inode {
unsigned
short
i_mode;
unsigned
short
i_uid;
unsigned
long
i_size;
unsigned
long
i_mtime;
unsigned
char
i_gid;
unsigned
char
i_nlinks;
unsigned
short
i_zone[9];
/* these are in memory also */
struct
task_struct * i_wait;
unsigned
long
i_atime;
unsigned
long
i_ctime;
unsigned
short
i_dev;
unsigned
short
i_num;
unsigned
short
i_count;
unsigned
char
i_lock;
unsigned
char
i_dirt;
unsigned
char
i_pipe;
unsigned
char
i_mount;
unsigned
char
i_seek;
unsigned
char
i_update;
};
i_mode表示文件的访问权限,i_uid表示文件所有者的用户id,i_size表示文件的大小,i_mtime文件的修改时间,i_gid文件所有者的组id,i_nlinks链接数,i_zone逻辑块号数组,i_wait等待在此i节点上的进程,i_atime最后访问时间,i_ctime为i节点创建时间,i_dev为i节点所在的设备号,i_num为i节点号,i_count为i节点被引用次数,i_lock是否加锁标志,i_dirti节点数据是否修改标志,i_pipe管道标志,i_mount安装标志,i_seek搜寻标志,i_update更新标志
[文件与i节点的关系]
文件中的数据是放在磁盘上的数据区的,由一个个的数据块组成。对应文件此文件的i节点中会以某种方式存放保存有文件数据的这些逻辑块号。文件名与i节点对应。这样文件名就通过i节点与实际的文件的数据对应起来。在i节点中存放磁盘逻辑块号的字段是逻辑块号数组,也就是unsigned short i_zone[9]。i_zone[0]~i_zone[6]中直接存放对应的逻辑块号。当文件较大时,会需要使用i_zone[7]存放一次间接逻辑块号;而当文件再大一点,使用一次间接逻辑块号不足以保存时,就是用i_zone[8]存放二次间接逻辑块号。 有一个专门的函数(inode.c/_bmap)来处理文件数据块与磁盘逻辑块之间的映射。
i_zone[0]~i_zone[6]共能存储7个逻辑块号,也就是7K的文件大小。当文件小于7K时,只需要使用直接存储即可。
当文件大于7K时,使用i_zone[7],不过i_zone[7]对应的逻辑块中存放的不是文件数据,而是存储有文件数据的逻辑块号。也就是说,通过i_zone[7]找到逻辑块,然后通过这个逻辑块中的数据去得到真正存储有文件数据的逻辑块号。一个逻辑块大小是1K。也就是1024字节,一个逻辑块号用unsigned short表示,也就是需要2个直接。因此i_zone[7]对应的逻辑块中,存储放有512个逻辑块号,即i_zone[7]表示的文件大小是512K。因此,当文件大小在7k到7k+512K之间时,就需要用到i_zone[7]。
当文件再大一些时,就需要用到i_zone[8]。i_zone[8]中对应的逻辑块中存放有512个逻辑块号,这512个逻辑块中又分别存放有512个逻辑块号。通过二次间接,最后才得到真正存放文件数据的逻辑块号码。因此,i_zone[8]表示的文件大小是512×512K。所以,当文件大小在7K+512K~7K+512K+512*512K时,就需要用到i_zone[8]。当文件再大一点时,linux 0.11中就没法表示了。
[数据区逻辑块映射函数_bmap]
/*
_bmap实现i节点表示的文件,和存储文件数据的逻辑块之间的映射关系
inode为表示文件的i节点
block为文件数据块编号
create为标志位,指明当对应的逻辑块不存在时,是否需要新建逻辑块
*/
static
int
_bmap(
struct
m_inode * inode,
int
block,
int
create)
{
struct
buffer_head * bh;
int
i;
if
(block<0)
panic(
"_bmap: block<0"
);
if
(block >= 7+512+512*512)
panic(
"_bmap: block>big"
);
if
(block<7) {
//block<7,只需要使用i_zone[0]~i_zone[6]直接存储逻辑块号即可
/*
当create标识为1,并且对应的逻辑块号不存在时,就需要新建逻辑块
*/
if
(create && !inode->i_zone[block])
if
(inode->i_zone[block]=new_block(inode->i_dev)) {
inode->i_ctime=CURRENT_TIME;
inode->i_dirt=1;
//i节点的内容被修改
}
return
inode->i_zone[block]; //返回对应的逻辑块号
}
block -= 7;
if
(block<512) { //需要使用到i_zone[7]来作为间接存储
if
(create && !inode->i_zone[7]) //需要新建逻辑块
if
(inode->i_zone[7]=new_block(inode->i_dev)) {
inode->i_dirt=1;
inode->i_ctime=CURRENT_TIME;
}
if
(!inode->i_zone[7])
return
0;
/*
读取i_zone[7]对应的逻辑块数据,要对这个逻辑块的数据进行修改,所以将其读取到高速缓存中
*/
if
(!(bh = bread(inode->i_dev,inode->i_zone[7])))
return
0;
/*
将整个i_zone[7]表示的逻辑块中的数据看成是逻辑块号数组,数组的最大长度是512(BLOCK_SIZE/sizeof(unsigned short))
*/
i = ((
unsigned
short
*) (bh->b_data))[block];
/*
new_block申请新的磁盘块时,会把这个块的数据清零。所以,如果i为0,表示之前对应的逻辑块不存在。需要新建时,就申请逻辑
块用来存放文件数据,同时把这个申请的逻辑块的编号写入i_zone[7]表示的逻辑块中去,作为间接存储。
*/
if
(create && !i)
if
(i=new_block(inode->i_dev)) {
((
unsigned
short
*) (bh->b_data))[block]=i;
bh->b_dirt=1;
}
brelse(bh);
return
i;
}
//block编号大于7+512,需要使用i_zone[8]来进行二次间接存储。
block -= 512;
if
(create && !inode->i_zone[8])
if
(inode->i_zone[8]=new_block(inode->i_dev)) {
inode->i_dirt=1;
inode->i_ctime=CURRENT_TIME;
}
if
(!inode->i_zone[8])
//
return
0;
if
(!(bh=bread(inode->i_dev,inode->i_zone[8])))
return
0;
/*
通过i_zone[8]对应的逻辑块,找到一个逻辑块,然后在这个逻辑块中找到真正的存储数据的逻辑块的编号。使用两层间接索引模式,在创建
新的逻辑块时,就需要两步才能实现。
首先,i_zone[8]表示的逻辑块中的数据,然后是下一层的逻辑块。在i_zone[8]表示的逻辑块中的逻辑块编号数组中,每个相邻元素之间相差
512。因此,在计算索引时,使用block/512,也就是block>>9。第二层的索引是在0~511之间,所以使用block&511。
也可以看成,把block的高8位表示在第一层的数组的索引,把block的低8位看成是在第二层的数组的索引
*/
i = ((
unsigned
short
*)bh->b_data)[block>>9];
if
(create && !i)
if
(i=new_block(inode->i_dev)) {
((
unsigned
short
*) (bh->b_data))[block>>9]=i;
//设置i_zone[8]对应的逻辑块中的数据,也就是第一层的数组数据
bh->b_dirt=1;
}
brelse(bh);
if
(!i)
return
0;
if
(!(bh=bread(inode->i_dev,i)))
return
0;
i = ((
unsigned
short
*)bh->b_data)[block&511];
//第二层的数组
if
(create && !i)
if
(i=new_block(inode->i_dev)) {
((
unsigned
short
*) (bh->b_data))[block&511]=i;
//第二层数组的写入
bh->b_dirt=1;
//数据写回磁盘
}
brelse(bh);
return
i;
}