上一节我们提到了当open("file", O_CREAT)创建一个文件时,其对应的Ext2磁盘索引节点是如何建立的,又是如何与系统中的其他数据结构联系的。现在还是从一个普通文件的角度来分析,当我门在根目录下调用fd = open("file", O_RDONLY)打开一个已经存在文件时,同样也会启动do_sys_open系统调用,并根据路径“file”去触发do_filp_open函数返回一个file结构。
而这个时候,do_filp_open调用open_namei()函数就跟前面也差不多,即填充目标文件所在目录(也就是根目录)的dentry结构和所在文件系统的vfsmount结构,并将信息保存在nameidata结构中。在dentry结构中dentry->d_inode就指向目标文件的索引节点。
open_namei中,如果没有设置O_CREAT标志位,即所打开的文件是存在的,就会执行一步最重要的步骤——path_lookup_open函数。path_lookup_open()实现文件的查找功能;要打开的文件若不存在,也就是上一节的情况,则还需要有一个新建的过程,则调用 path_lookup_create(),后者和前者封装的是同一个实际的路径查找函数,只是参数不一样,使它们在处理细节上有所偏差。
当是以新建文件的方式打开文件时,即设置了O_CREAT标志位时需要创建一个新的索引节点,代表创建一个文件。上一节看到,open_namei会调用父索引节点的create方法分配一个新的磁盘索引节点,即在vfs_create()里的一句核心语句dir->i_op->create(dir, dentry, mode, nd)调用ext2文件系统所提供的创建索引节点方法ext2_create。
注意:这边的索引节点的概念,还只是位于内存之中,它和磁盘上的物理的索引节点的关系就像位于内存中和位于磁盘中的文件一样。此时新建的索引节点还不能完全标志一个物理文件的成功创建,只有当把索引节点回写到磁盘上才是一个物理文件的真正创建。想想我们以新建的方式打开一个文件,对其读写但最终没有保存而关闭,则位于内存中的索引节点会经历从新建到消失的过程,而磁盘却始终不知道有人曾经想过创建一个文件,这是因为索引节点没有回写的缘故。
我们这节只关注磁盘索引节点的查找,不管是path_lookup_open()还是path_lookup_create()最终都是调用__path_lookup_intent_open()来实现查找文件的功能。它调用do_path_lookup函数,如果路径名的第一个字符是“/”,那么查找操作必须从当前根目录开始:获取相应已安装文件对象(current->fs->rootmnt)和目录项对象(current->fs->root)的地址;增加引用计数器的值,并把它们的地址分别存放在nd->mnt和nd->dentry中。
否则,如果路径名的第一个字符不是“/”,则查找操作必须从当前工作目录开始:获得相应已安装文件系统对象(current->fs->mt)和目录项对象(current->fs->pwd)的地址;增加引用计数器的值,并把它们的地址分别存放在nd->mnt和nd->dentry中。
随后do_path_lookup函数调用link_path_walk()函数处理真正进行的查找操作:它接收的参数为要解析的路径名指针name和拥有目录项信息和安装文件系统信息的nameidata数据结构的地址nd,逐层地将各个路径组成部分解析成目录项对象,如果此目录项对象在目录项缓存中,则直接从缓存中获得。如果不在缓存中,则需从磁盘中读取该目录项所对应的索引节点;这将引发VFS和实际的文件系统的一次交互。
__link_path_walk函数完成上述过程主要是通过调用do_lookup ()函数进而触发__d_lookup()函数在目录项高速缓存中搜索分量。传递给它的参数是目录项对象参数parent,目的在于在指定的父目录中查找名字为name的目录项。比如我们要访问/usr/local/sbin/hello.c文件,那么就会搜索/、usr、local和sbin分量,分别作为usr、local、sbin和hello.c的父目录,如果usr、local、sbin或hello.c不在目录项高速缓存中,即目录项高速缓存中没有一个dentry的d_parent字段指向__d_lookup的参数parent,__d_lookup就会返回一个NULL的dentry结构。
如果该目录项在缓存中不存在,我们假设/、usr、local和sbin分量在目录项缓存中,而hello.c分量不在,则do_lookup函数调用__d_lookup()时会发现“hello.c”的dentry不在目录项高速缓存中,或者虽然在页目录项高速缓存中,但它dentry的d_parent字段并不是sbin对应的dentry结构,就会返回一个NULL的dentry结构。那么do_lookup ()函数就会触发real_lookup()。而传递给real_lookup()函数的参数是该被打开文件的父目录的目录项,即我们这里sbin分量对应的dentry结构;还有“hello.c”对应的qstr结构。
real_lookup()函数执行sbin分量对应的dentry结构对应的索引节点的lookup方法从磁盘读取目录,创建一个新的目录项对象并把它插入到目录项高速缓存中,然后创建一个新的索引节点对象并把它插入到索引节点高速缓存中(在少数情况下,函数real_lookup()可能发现所请求的索引节点已经在索引节点高速缓存中。路径名分量是最后一个路径名而且不是指向一个目录,与路径名相应的文件有几个硬健接,并且最近通过与这个路径名中被使用过的硬健接不同的硬链接访问过相应的文件)。
在这一步结束时,__link_path_walk函数中的next局部变量中的dentry和mnt字段将分别指向这次循环要解析的分量名hello.c的目录项对象和已安装文件系统对象,然后返回0。
所以,这里我们就从real_lookup函数开始,来探寻当要打开一个普通文件“hello.c”时,它的Ext2磁盘索引节点是如何被读到缓存中的。
446static struct dentry * real_lookup(struct dentry * parent, struct qstr * name, struct nameidata *nd)
447{
448 struct dentry * result;
449 struct inode *dir = parent->d_inode;
450
451 mutex_lock(&dir->i_mutex);
……
466 result = d_lookup(parent, name);
467 if (!result) {
468 struct dentry * dentry = d_alloc(parent, name);
469 result = ERR_PTR(-ENOMEM);
470 if (dentry) {
471 result = dir->i_op->lookup(dir, dentry, nd);
472 if (result)
473 dput(dentry);
474 else
475 result = dentry;
476 }
477 mutex_unlock(&dir->i_mutex);
478 return result;
479 }
480
……
493}
我们看到,471行real_lookup()执行索引节点的lookup方法,传递给他的参数是sbin分量对应的inode结构和通过d_alloc()函数给“hello.c”文件分配的一个dentry空壳。这个方法是什么呢?看到上一节的那个ext2_dir_inode_operations,对应的lookup方法是ext2_lookup,来自fs/ext2/namei.c:
55static struct dentry *ext2_lookup(struct inode * dir, struct dentry *dentry, struct nameidata *nd)
56{
57 struct inode * inode;
58 ino_t ino;
59
60 if (dentry->d_name.len > EXT2_NAME_LEN)
61 return ERR_PTR(-ENAMETOOLONG);
62
63 ino = ext2_inode_by_name(dir, dentry);
64 inode = NULL;
65 if (ino) {
66 inode = iget(dir->i_sb, ino);
67 if (!inode)
68 return ERR_PTR(-EACCES);
69 }
70 return d_splice_alias(inode, dentry);
71}
63行,通过ext2_inode_by_name得到sbin分量对应inode对应的索引节点号,该函数是通过调用ext2_find_entry函数通过“hello.c”文件目录项名和目录项名的长度与缓存在页高速缓存的其父目录,也就是sbin分量的磁盘数据(目录文件的磁盘数据内容跟普通文件不一样,其存放的就是整个目录内所有文件的ext2_dir_entry_2结构)进行比对,得到“hello.c”文件对应的磁盘目录项结构ext2_dir_entry_2:
struct ext2_dir_entry_2 {
__le32 inode; /* Inode number */
__le16 rec_len; /* Directory entry length */
__u8 name_len; /* Name length */
__u8 file_type;
char name[EXT2_NAME_LEN]; /* File name */
};
然后通过ext2_dir_entry_2 的inode字段获得“hello.c”文件对应的索引节点号。ext2_lookup函数的66行,调用iget从磁盘中获得ext2_inode,并更新inode结构。下面我们来重点关注iget函数,来自include/linux/fs.h:
1604static inline struct inode *iget(struct super_block *sb, unsigned long ino)
1605{
1606 struct inode *inode = iget_locked(sb, ino);
1607
1608 if (inode && (inode->i_state & I_NEW)) {
1609 sb->s_op->read_inode(inode);
1610 unlock_new_inode(inode);
1611 }
1612
1613 return inode;
1614}
iget函数1606行首先调用iget_locked,通过“hello.c”文件对应的索引节点号ino在索引节点缓存的inode_hashtable中获得其对应VFS的inode结构。当然,如果inode不在缓存里,iget_locked就会调用get_new_inode_fast去触发超级块的s_op字段的alloc_inode函数ext2_alloc_inode来为它分配一个嵌入了VFS的inode结构的ext2_inode_info。
于是,我们得到了一个VFS的inode“空壳”机器宿主结构ext2_inode_info“空壳”,随后,1609行,重要的来了,调用super_block的s_op字段的read_inode方法,将这个inode “空壳”作为参数传递进去。在“Ext2的超级块对象”一节中我们得知,ext2文件系统的超级块方法被赋值成了全局变量ext2_sops,它的read_inode方法是ext2_read_inode函数,来自fs/ext2/inode.c:
1058void ext2_read_inode (struct inode * inode)
1059{
1060 struct ext2_inode_info *ei = EXT2_I(inode);
1061 ino_t ino = inode->i_ino;
1062 struct buffer_head * bh;
1063 struct ext2_inode * raw_inode = ext2_get_inode(inode->i_sb, ino, &bh);
1064 int n;
1065
1066#ifdef CONFIG_EXT2_FS_POSIX_ACL
1067 ei->i_acl = EXT2_ACL_NOT_CACHED;
1068 ei->i_default_acl = EXT2_ACL_NOT_CACHED;
1069#endif
1070 if (IS_ERR(raw_inode))
1071 goto bad_inode;
1072
1073 inode->i_mode = le16_to_cpu(raw_inode->i_mode);
1074 inode->i_uid = (uid_t)le16_to_cpu(raw_inode->i_uid_low);
1075 inode->i_gid = (gid_t)le16_to_cpu(raw_inode->i_gid_low);
1076 if (!(test_opt (inode->i_sb, NO_UID32))) {
1077 inode->i_uid |= le16_to_cpu(raw_inode->i_uid_high) << 16;
1078 inode->i_gid |= le16_to_cpu(raw_inode->i_gid_high) << 16;
1079 }
1080 inode->i_nlink = le16_to_cpu(raw_inode->i_links_count);
1081 inode->i_size = le32_to_cpu(raw_inode->i_size);
1082 inode->i_atime.tv_sec = le32_to_cpu(raw_inode->i_atime);
1083 inode->i_ctime.tv_sec = le32_to_cpu(raw_inode->i_ctime);
1084 inode->i_mtime.tv_sec = le32_to_cpu(raw_inode->i_mtime);
1085 inode->i_atime.tv_nsec = inode->i_mtime.tv_nsec = inode->i_ctime.tv_nsec = 0;
1086 ei->i_dtime = le32_to_cpu(raw_inode->i_dtime);
1087 /* We now have enough fields to check if the inode was active or not.
1088 * This is needed because nfsd might try to access dead inodes
1089 * the test is that same one that e2fsck uses
1090 * NeilBrown 1999oct15
1091 */
1092 if (inode->i_nlink == 0 && (inode->i_mode == 0 || ei->i_dtime)) {
1093 /* this inode is deleted */
1094 brelse (bh);
1095 goto bad_inode;
1096 }
1097 inode->i_blksize = PAGE_SIZE; /* This is the optimal IO size (for stat), not the fs block size */
1098 inode->i_blocks = le32_to_cpu(raw_inode->i_blocks);
1099 ei->i_flags = le32_to_cpu(raw_inode->i_flags);
1100 ei->i_faddr = le32_to_cpu(raw_inode->i_faddr);
1101 ei->i_frag_no = raw_inode->i_frag;
1102 ei->i_frag_size = raw_inode->i_fsize;
1103 ei->i_file_acl = le32_to_cpu(raw_inode->i_file_acl);
1104 ei->i_dir_acl = 0;
1105 if (S_ISREG(inode->i_mode))
1106 inode->i_size |= ((__u64)le32_to_cpu(raw_inode->i_size_high)) << 32;
1107 else
1108 ei->i_dir_acl = le32_to_cpu(raw_inode->i_dir_acl);
1109 ei->i_dtime = 0;
1110 inode->i_generation = le32_to_cpu(raw_inode->i_generation);
1111 ei->i_state = 0;
1112 ei->i_next_alloc_block = 0;
1113 ei->i_next_alloc_goal = 0;
1114 ei->i_prealloc_count = 0;
1115 ei->i_block_group = (ino - 1) / EXT2_INODES_PER_GROUP(inode->i_sb);
1116 ei->i_dir_start_lookup = 0;
1117
1118 /*
1119 * NOTE! The in-memory inode i_data array is in little-endian order
1120 * even on big-endian machines: we do NOT byteswap the block numbers!
1121 */
1122 for (n = 0; n < EXT2_N_BLOCKS; n++)
1123 ei->i_data[n] = raw_inode->i_block[n];
1124
1125 if (S_ISREG(inode->i_mode)) {
1126 inode->i_op = &ext2_file_inode_operations;
1127 if (ext2_use_xip(inode->i_sb)) {
1128 inode->i_mapping->a_ops = &ext2_aops_xip;
1129 inode->i_fop = &ext2_xip_file_operations;
1130 } else if (test_opt(inode->i_sb, NOBH)) {
1131 inode->i_mapping->a_ops = &ext2_nobh_aops;
1132 inode->i_fop = &ext2_file_operations;
1133 } else {
1134 inode->i_mapping->a_ops = &ext2_aops;
1135 inode->i_fop = &ext2_file_operations;
1136 }
1137 } else if (S_ISDIR(inode->i_mode)) {
1138 inode->i_op = &ext2_dir_inode_operations;
1139 inode->i_fop = &ext2_dir_operations;
1140 if (test_opt(inode->i_sb, NOBH))
1141 inode->i_mapping->a_ops = &ext2_nobh_aops;
1142 else
1143 inode->i_mapping->a_ops = &ext2_aops;
1144 } else if (S_ISLNK(inode->i_mode)) {
1145 if (ext2_inode_is_fast_symlink(inode))
1146 inode->i_op = &ext2_fast_symlink_inode_operations;
1147 else {
1148 inode->i_op = &ext2_symlink_inode_operations;
1149 if (test_opt(inode->i_sb, NOBH))
1150 inode->i_mapping->a_ops = &ext2_nobh_aops;
1151 else
1152 inode->i_mapping->a_ops = &ext2_aops;
1153 }
1154 } else {
1155 inode->i_op = &ext2_special_inode_operations;
1156 if (raw_inode->i_block[0])
1157 init_special_inode(inode, inode->i_mode,
1158 old_decode_dev(le32_to_cpu(raw_inode->i_block[0])));
1159 else
1160 init_special_inode(inode, inode->i_mode,
1161 new_decode_dev(le32_to_cpu(raw_inode->i_block[1])));
1162 }
1163 brelse (bh);
1164 ext2_set_inode_flags(inode);
1165 return;
1166
1167bad_inode:
1168 make_bad_inode(inode);
1169 return;
1170}
“hello.c”文件对应的VFS的inode结构,作为参数传递进ext2_read_inode时,还只是个空壳,我们仅仅知道它的索引节点号,即inode->i_ino,所以1061行获得这个索引节点号。1060行,通过EXT2_I宏得到另一个作为其宿主的ext2_inode_info空壳。
1063行,调用ext2_get_inode函数,从一个页高速缓存中读入一个磁盘索引节点结构ext2_inode,传递进去的参数是super_block结构、索引节点号和一个还未被初始化的高速缓存头buffer_head结构。ext2_get_inode函数同样来自fs/ext2/inode.c:
static struct ext2_inode *ext2_get_inode(struct super_block *sb, ino_t ino,
struct buffer_head **p)
{
struct buffer_head * bh;
unsigned long block_group;
unsigned long block;
unsigned long offset;
struct ext2_group_desc * gdp;
*p = NULL;
……
block_group = (ino - 1) / EXT2_INODES_PER_GROUP(sb);
gdp = ext2_get_group_desc(sb, block_group, &bh);
……
offset = ((ino - 1) % EXT2_INODES_PER_GROUP(sb)) * EXT2_INODE_SIZE(sb);
block = le32_to_cpu(gdp->bg_inode_table) +
(offset >> EXT2_BLOCK_SIZE_BITS(sb));
if (!(bh = sb_bread(sb, block)))
goto Eio;
*p = bh;
offset &= (EXT2_BLOCK_SIZE(sb) - 1);
return (struct ext2_inode *) (bh->b_data + offset);
……
}
我们只简单地介绍一下ext2_get_inode的执行流程。函数首先获得索引节点号ino对应的磁盘索引节点ext2_inode所在块组的组号,存放到内部变量block_group中。然后通过前面介绍的ext2_get_group_desc函数获得该块组描述符结构ext2_group_desc,由内部指针变量gdp指向。接下来通过索引节点号得到该磁盘索引节点在该块组磁盘索引节点表的位置,这个值又保存在内部变量offset中。有了这个位置,就可以通过一个计算,得到引节点号ino对应的磁盘索引节点ext2_inode所在的块号block。那么我们通过sb_bread(sb, block)将这块缓存到sb对应的块设备页高速缓存中。那么此时,通过(struct ext2_inode *) (bh->b_data + offset)就能从内存中得到对应的磁盘索引节点了。
回到ext2_read_inode中,从内存中读到的ext2_inode的值保存在内部变量raw_inode中,随后1073~1162行代码通过raw_inode中从磁盘上读入的数据来初始化VFS的inode和其宿主ext2_inode_info结构。这里面最重要的位于磁盘索引节点中的“hello.c”的块索引i_block[n]拷贝到ext2_inode_info结构的i_data[n]中;然后把对应VFS的inode结构的i_op、i_mapping->a_ops和i_fop赋值成相应的操作函数表。
当VFS的inode及其宿主ext2_inode_info结构初始化完毕后,就可以把ext2_inode对应的高速缓存brelse掉以释放内存的空间,达到动态缓存的效果。至此,经过fd = open("file", O_RDONLY)打开一个已经存在文件,我们举的例子是/usr/local/sbin/hello.c文件,它对应的VFS的inode及其宿主ext2_inode_info结构就准备好了,下一步就可以通过read和write系统调用进行文件的读写了。