9.2.6 索引节点表及实例分析
在两个位图块后面,就是索引节点表了,每个块组中的索引节点都存储在各自的索引节点表中,并且按索引节点号依次存储。索引节点表通常占好几个数据块,索引节点表所占的块使用时也像普通的数据块一样被调入块高速缓存。
有了以上几个概念和数据结构后,我们分析一个具体的例子,来看看这些数据结构是如何配合工作的。
在fs/ext2/inode.c中,有一个ext2_read_inode(),用来读取指定的索引节点信息。其代码如下:
void ext2_read_inode (struct inode * inode)
{
struct buffer_head * bh;
struct ext2_inode * raw_inode;
unsigned long block_group;
unsigned long desc;
unsigned long block;
unsigned long offset;
struct ext2_group_desc * gdp;
if ((inode->i_ino != EXT2_ROOT_INO && inode->i_ino != EXT2_ACL_IDX_INO && inode->i_ino != EXT2_ACL_DATA_INO && inode->i_ino < EXT2_FIRST_INO(inode->i_sb)) || inode->i_ino > le32_to_cpu(inode->i_sb->u.ext2_sb.s_es->s_inodes_count))
{
ext2_error (inode->i_sb, "ext2_read_inode",
"bad inode number: %lu", inode->i_ino);
}
block_group = (inode->i_ino - 1) / EXT2_INODES_PER_GROUP(inode->i_sb);
if (block_group >= inode->i_sb->u.ext2_sb.s_groups_count) {
ext2_error (inode->i_sb, "ext2_read_inode",
goto bad_inode;
}
group_desc = block_group >> EXT2_DESC_PER_BLOCK_BITS(inode->i_sb);
desc = block_group & (EXT2_DESC_PER_BLOCK(inode->i_sb) - 1);
bh = inode->i_sb->u.ext2_sb.s_group_desc[group_desc];
if (!bh) {
ext2_error (inode->i_sb, "ext2_read_inode",
"Descriptor not loaded");
goto bad_inode;
}
gdp = (struct ext2_group_desc *) bh->b_data;
/*
* Figure out the offset within the block group inode table
*/
offset = ((inode->i_ino - 1) % EXT2_INODES_PER_GROUP(inode->i_sb)) *
EXT2_INODE_SIZE(inode->i_sb);
block = le32_to_cpu(gdp[desc].bg_inode_table) +
(offset >> EXT2_BLOCK_SIZE_BITS(inode->i_sb));
if (!(bh = sb_bread(inode->i_sb, block))) {
ext2_error (inode->i_sb, "ext2_read_inode",
"unable to read inode block - "
"inode=%lu, block=%lu", inode->i_ino, block);
goto bad_inode;
}
offset &= (EXT2_BLOCK_SIZE(inode->i_sb) - 1);
raw_inode = (struct ext2_inode *) (bh->b_data + offset);
inode->i_uid = (uid_t)le16_to_cpu(raw_inode->i_uid_low);
inode->i_gid = (gid_t)le16_to_cpu(raw_inode->i_gid_low);
if(!(test_opt (inode->i_sb, NO_UID32))) {
inode->i_uid |= le16_to_cpu(raw_inode->i_uid_high) << 16;
inode->i_gid |= le16_to_cpu(raw_inode->i_gid_high) << 16;
}
inode->i_nlink = le16_to_cpu(raw_inode->i_links_count);
inode->i_size = le32_to_cpu(raw_inode->i_size);
inode->i_atime = le32_to_cpu(raw_inode->i_atime);
inode->i_ctime = le32_to_cpu(raw_inode->i_ctime);
inode->i_mtime = le32_to_cpu(raw_inode->i_mtime);
inode->u.ext2_i.i_dtime = le32_to_cpu(raw_inode->i_dtime);
* This is needed because nfsd might try to access dead inodes
* the test is that same one that e2fsck uses
* NeilBrown 1999oct15
*/
if (inode->i_nlink == 0 && (inode->i_mode == 0 || inode->u.ext2_i.i_dtime)) {
/* this inode is deleted */
brelse (bh);
goto bad_inode;
}
inode->i_blksize = PAGE_SIZE; /* This is the optimal IO size (for stat), not the fs block size */
inode->i_blocks = le32_to_cpu(raw_inode->i_blocks);
inode->i_version = ++event;
inode->u.ext2_i.i_flags = le32_to_cpu(raw_inode->i_flags);
inode->u.ext2_i.i_frag_no = raw_inode->i_frag;
inode->u.ext2_i.i_frag_size = raw_inode->i_fsize;
inode->u.ext2_i.i_file_acl = le32_to_cpu(raw_inode->i_file_acl);
if (S_ISREG(inode->i_mode))
inode->i_size |= ((__u64)le32_to_cpu(raw_inode->i_size_high)) << 32;
else
inode->u.ext2_i.i_dir_acl = le32_to_cpu(raw_inode->i_dir_acl);
inode->i_generation = le32_to_cpu(raw_inode->i_generation);
inode->u.ext2_i.i_prealloc_count = 0;
inode->u.ext2_i.i_block_group = block_group;
/*
* NOTE! The in-memory inode i_data array is in little-endian order
* even on big-endian machines: we do NOT byteswap the block numbers!
*/
inode->u.ext2_i.i_data[block] = raw_inode->i_block[block];
if (inode->i_ino == EXT2_ACL_IDX_INO ||
inode->i_ino == EXT2_ACL_DATA_INO)
/* Nothing to do */ ;
else if (S_ISREG(inode->i_mode)) {
inode->i_op = &ext2_file_inode_operations;
inode->i_fop = &ext2_file_operations;
inode->i_mapping->a_ops = &ext2_aops;
} else if (S_ISDIR(inode->i_mode)) {
inode->i_op = &ext2_dir_inode_operations;
inode->i_fop = &ext2_dir_operations;
inode->i_mapping->a_ops = &ext2_aops;
} else if (S_ISLNK(inode->i_mode)) {
if (!inode->i_blocks)
inode->i_op = &ext2_fast_symlink_inode_operations;
else {
inode->i_op = &page_symlink_inode_operations;
inode->i_mapping->a_ops = &ext2_aops;
} else
init_special_inode(inode, inode->i_mode,
brelse (bh);
inode->i_attr_flags = 0;
if (inode->u.ext2_i.i_flags & EXT2_SYNC_FL) {
inode->i_attr_flags |= ATTR_FLAG_SYNCRONOUS;
inode->i_flags |= S_SYNC;
}
if (inode->u.ext2_i.i_flags & EXT2_APPEND_FL) {
inode->i_attr_flags |= ATTR_FLAG_APPEND;
inode->i_flags |= S_APPEND;
}
if (inode->u.ext2_i.i_flags & EXT2_IMMUTABLE_FL) {
inode->i_attr_flags |= ATTR_FLAG_IMMUTABLE;
inode->i_flags |= S_IMMUTABLE;
}
if (inode->u.ext2_i.i_flags & EXT2_NOATIME_FL) {
inode->i_attr_flags |= ATTR_FLAG_NOATIME;
inode->i_flags |= S_NOATIME;
}
return;
bad_inode:
make_bad_inode(inode);
return;
}
这个函数的代码有200多行,为了突出重点,下面是对该函数主要内容的描述:
· 如果指定的索引节点号是一个特殊的节点号(EXT2_ROOT_INO、EXT2_ACL_IDX_INO及EXT2_ACL_DATA_INO),或者小于第一个非特殊用途的节点号,即EXT2_FIRST_INO (为11),或者大于该文件系统中索引节点总数,则输出错误信息,并返回;
· 用索引节点号整除每组中索引节点数(我的理解是每组中的索引节点数是一样的),计算出该索引节点所在的块组号;
即block_group = (inode->i_ino - 1) / Ext2_INODES_PER_GROUP(inode->i_sb) //Ext2_INODES_PER_GROUP(inode->i_sb)是每块的节点数
· 找到该组的组描述符在组描述符表中的位置。因为组描述符表可能占多个数据块,所以需要确定组描述符在组描述符表的哪一块以及是该块中第几个组描述符。
即:(组描述符所在的块,这个块是相对于组描述符表起始块的偏移量)group_desc = block_group >> Ext2_DESC_PER_BLOCK_BITS(inode->i_sb)表示块组号整除每块中组描述符数即32,我们知道,每个组描述符是32字节大小,在一个1K大小的块中可存储32个组描述符。一个数向右移一位,相当于整除一下2,在这里要处以32(即2的5次方),就相当于要想右移5位,Ext2_DESC_PER_BLOCK_BITS(inode->i_sb)对应的是一块所含的描述符数转化为2进制需要的位数-1
· 块组号与每块中组的描述符数-1(是个常量)进行“与”运算,得到这个组描述符具体是该块(已经找到组描述符所在的快)中第几个描述符。即(组描述符在指定块中的第几个描述符)desc = block_group & (Ext2_DESC_PER_BLOCK(inode->i_sb) - 1)。相当于32-1=011111
· 有了group_desc和desc,接下来在高速缓存中找这个组描述符就比较容易了:
即:bh = inode->i_sb->u.ext2_sb.s_group_desc[group_desc](s_group_desc是一个buffer_head的指针数组,这些缓冲区专门用来存放组描述符表的)首先通过s_group_desc[]数组找到这个组描述符所在块在高速缓存中的缓冲区首部;然后通过缓冲区首部找到数据区,即gdp= (struct ext2_group_desc *) bh->b_data。(ext2_group_desc是组描述符结构体)
· 找到组描述符后,就可以通过组描述符结构中的bg_inode_tabl找到索引节点表首块在高速缓存中的地址:
offset = ((inode->i_ino - 1) % Ext2_INODES_PER_GROUP(inode->i_sb)) * Ext2_INODE_SIZE(inode->i_sb), 计算该索引节点相对首快的偏移位置,以字节为单位;
((inode->i_ino - 1) % Ext2_INODES_PER_GROUP(inode->i_sb))//表示在快组中的第几个索引节点
。。。
block(gdp[desc].bg_inode_table) +(offset >> Ext2_BLOCK_SIZE_BITS(inode->i_sb)),计算索引节点所在块的块号;
gdp[desc]才是真正的这一个描述符项在缓冲区中的地址
· 代码中le32_to_cpu()、le16_to_cpu()按具体CPU的要求进行数据的排列,在i386处理器上访问Ext2文件系统时这些函数不做任何事情。因为不同的处理器在存取数据时在字节的排列次序上有所谓“big ending”和“little ending”之分。例如,i386就是“little ending”处理器,它在存储一个16位数据0x1234时,实际存储的却是0x3412,对32位数据也是如此。这里索引节点号与块的长度都作为32位或16位无符号整数存储在磁盘上,而同一磁盘既可以安装在采用“little ending”方式的CPU机器上,也可能安装在采用“big ending”方式的CPU机器上,所以要选择一种形式作为标准。事实上,Ext2采用的标准为“little ending”,所以,le32_to_cpu()、le16_to_cpu()函数不作任何转换。
· 计算出索引节点所在块的地址后,就可以调用sb_bread()通过设备驱动程序读入该块。从磁盘读入的索引节点为ext2_Inode数据结构,前面我们已经看到它的定义。磁盘上索引节点中的信息是原始的、未经加工的,所以代码中称之为raw_ inode,即
bh = sb_bread(inode->i_sb, block)//从磁盘读入指定的块,返回这个块对应的缓冲区头
offset
raw_inode = (struct ext2_inode *) (bh->b_data + offset) / / bh->b_data对应的是缓冲区的地址
· 与磁盘索引节点ext2_ inode相对照,内存中VFS的inode结构中的信息则分为两部分,一部分是属于VFS层的,适用于所有的文件系统;另一部份则属于具体的文件系统,这就是inode中的那个union,因具体文件系统的不同而赋予不同的解释。对Ext2来说,这部分数据就是前面介绍的ext2_inode_info结构。至于代表着符号链接的节点,则并没有文件内容(数据),所以正好用这块空间来存储链接目标的路径名。ext2_inode_info结构的大小为60个字节。虽然节点名最长可达255个字节,但一般都不会太长,因此将符号链接目标的路径名限制在60个字节不至于引起问题。代码中inode->u.*设置的就是Ext2文件系统的特定信息。
· 接着,根据索引节点所提供的信息设置inode结构中的inode_operations结构指针和file_operations结构指针,完成具体文件系统与虚拟文件系统VFS之间的连接。
· 目前2.4版内核并不支持存取控制表ACL,因此,代码中只是为之留下了位置,而暂时没做任何处理。
· 另外,通过检查inode结构中的mode域来确定该索引节点是常规文件(S_ISREG)、目录(S_ISDIR)、符号链接(S_ISLNK)还是其他特殊文件而作不同的设置或处理。例如,对Ext2文件系统的目录节点,就将i_op和i_fop分配设置为ext2_dir_inode_operations和ext2_dir_operations。而对于Ext2常规文件,则除i_op和i_fop以外,还设置了另一个指针a_ops,它指向一个address_apace_operation结构,用于文件到内存空间的映射或缓冲。对特殊文件,则通过init_special_inode()函数加以检查和处理。
从这个读索引节点的过程可以看出,首先要寻找指定的索引节点,要找索引节点,必须先找组描述符,然后通过组描述符找到索引节点表,最后才是在这个索引节点表中找索引节点。当从磁盘找到索引节点以后,就要把其读入内存,并存放在VFS索引节点相关的域中。从这个实例的分析,读者可以仔细体会前面所介绍的各种数据结构的具体应用。