Ext2索引节点对象的读取

    技术2024-07-25  17

    1.2.4 Ext2索引节点对象的读取

    上一节我们提到了当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->mntnd->dentry中。

     

    否则,如果路径名的第一个字符不是“/”,则查找操作必须从当前工作目录开始:获得相应已安装文件系统对象(current->fs->mt)和目录项对象(current->fs->pwd)的地址;增加引用计数器的值,并把它们的地址分别存放在nd->mntnd->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文件,那么就会搜索/usrlocalsbin分量,分别作为usrlocalsbinhello.c的父目录,如果usrlocalsbinhello.c不在目录项高速缓存中,即目录项高速缓存中没有一个dentryd_parent字段指向__d_lookup的参数parent__d_lookup就会返回一个NULLdentry结构。

     

    如果该目录项在缓存中不存在,我们假设/usrlocalsbin分量在目录项缓存中,而hello.c分量不在,则do_lookup函数调用__d_lookup()时会发现“hello.c”的dentry不在目录项高速缓存中,或者虽然在页目录项高速缓存中,但它dentryd_parent字段并不是sbin对应的dentry结构,就会返回一个NULLdentry结构。那么do_lookup ()函数就会触发real_lookup()。而传递给real_lookup()函数的参数是该被打开文件的父目录的目录项,即我们这里sbin分量对应的dentry结构;还有“hello.c”对应的qstr结构。

     

    real_lookup()函数执行sbin分量对应的dentry结构对应的索引节点的lookup方法从磁盘读取目录,创建一个新的目录项对象并把它插入到目录项高速缓存中,然后创建一个新的索引节点对象并把它插入到索引节点高速缓存中(在少数情况下,函数real_lookup()可能发现所请求的索引节点已经在索引节点高速缓存中。路径名分量是最后一个路径名而且不是指向一个目录,与路径名相应的文件有几个硬健接,并且最近通过与这个路径名中被使用过的硬健接不同的硬链接访问过相应的文件)。

     

    在这一步结束时,__link_path_walk函数中的next局部变量中的dentrymnt字段将分别指向这次循环要解析的分量名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}

     

    我们看到,471real_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中获得其对应VFSinode结构。当然,如果inode不在缓存里,iget_locked就会调用get_new_inode_fast去触发超级块的s_op字段的alloc_inode函数ext2_alloc_inode来为它分配一个嵌入了VFSinode结构的ext2_inode_info

     

    于是,我们得到了一个VFSinode“空壳”机器宿主结构ext2_inode_info“空壳”,随后,1609行,重要的来了,调用super_blocks_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”文件对应的VFSinode结构,作为参数传递进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中从磁盘上读入的数据来初始化VFSinode和其宿主ext2_inode_info结构。这里面最重要的位于磁盘索引节点中的“hello.c”的块索引i_block[n]拷贝到ext2_inode_info结构的i_data[n]中;然后把对应VFSinode结构的i_opi_mapping->a_opsi_fop赋值成相应的操作函数表。

     

    VFSinode及其宿主ext2_inode_info结构初始化完毕后,就可以把ext2_inode对应的高速缓存brelse掉以释放内存的空间,达到动态缓存的效果。至此,经过fd = open("file", O_RDONLY)打开一个已经存在文件,我们举的例子是/usr/local/sbin/hello.c文件,它对应的VFSinode及其宿主ext2_inode_info结构就准备好了,下一步就可以通过readwrite系统调用进行文件的读写了。

     

    最新回复(0)