9.2.7  Ext2的目录项及文件的定位

文件系统一个很重要的问题就是文件的定位,如何通过一个路径来找到一个文件的具体位置,这就要依靠ext2_dir_entry这个结构。

1Ext2目录项结构

Ext2中,目录是一种特殊的文件,它是由ext2_dir_entry这个结构组成的列表。这个结构是变长的,这样可以减少磁盘空间的浪费,但是,它还是有一定的长度方面的限制,一是文件名最长只能为255个字符。二是尽管文件名长度可以不限(在255个字符之内),但系统自动将之变成4的整数,不足的地方用null字符(\0)填充。目录中有文件和子目录,每一项对应一个ext2_dir_entry。该结构在include/Linux/ext2_fs.h中定义如下:

 /*

  * Structure of a directory entry

  */

  #define EXT2_NAME_LEN 255

struct ext2_dir_entry {

         __u32   inode;                  /* Inode number */

         __u16   rec_len;                /* Directory entry length */

         __u16   name_len;               /* Name length */

         char    name[EXT2_NAME_LEN];    /* File name */

};

 

这是老版本的定义方式,在ext2_fs.h中还有一种新的定义方式:

 /*

  * The new version of the directory entry.  Since EXT2 structures are

  * stored in intel byte order, and the name_len field could never be

  * bigger than 255 chars, it's safe to reclaim the extra byte for the

  * file_type field.

  */

struct ext2_dir_entry_2 {

         __u32   inode;                  /* Inode number */

         __u16   rec_len;                /* Directory entry length */

         __u8    name_len;               /* Name length */

        __u8    file_type;

         char    name[EXT2_NAME_LEN];    /* File name */

};

  其二者的差异在于,一是新版中结构名改为ext2_dir_entry_2;二是老版本中ext2_dir_entry中的name_len为无符号短整数,而新版中则改为8位的无符号字符,腾出一半用作文件类型。目前已定义的文件类型为:

  *

  * Ext2 directory file types.  Only the low 3 bits are used.  The

  * other bits are reserved for now.

  */

enum {

         EXT2_FT_UNKNOWN,      /*未知*/

         EXT2_FT_REG_FILE,     /*常规文件*/

         EXT2_FT_DIR,     /*目录文件*/

         EXT2_FT_CHRDEV,       /*字符设备文件*/

         EXT2_FT_BLKDEV,       /*块设备文件*/

         EXT2_FT_FIFO,    /*命名管道文件*/

         EXT2_FT_SOCK,    /*套接字文件*/

         EXT2_FT_SYMLINK, /*符号连文件*/

         EXT2_FT_MAX      /*文件类型的最大个数*/

};

2.各种文件类型如何使用数据块

  我们说,不管哪种类型的文件,每个文件都对应一个inode结构,在inode结构中有一个指向数据块的指针i_blaock,用来标识分配给文件的数据块。但是Ext2所定义的文件类型以不同的方式使用数据块。有些类型的文件不存放数据,因此,根本不需要数据块,下面对不同文件类型如何使用数据块给予说明。

   1)常规文件

      常规文件时最常用的文件。常规文件在刚创建时是空的,并不需要数据块,只有在开始有数据时才需要数据块;可以用系统调用truncate()清空一个常规文件。

   2)目录文件

     Ext2以一种特殊的方式实现了目录,这种文件的数据块中存放的就是ext2_dir_entry_2结构。如前所述,这个结构的最后一个域是可变长度数组,因此该结构的长度是可变的。

   ext2_dir_entry_2结构中,因为rec_len域是目录项的长度,把它与目录项的起始地址相加就得到下一个目录项的起始地址,因此说,rec_len可以被解释为指向下一个有效目录项的指针。为了删除一个目录项,把ext2_dir_entry_2inode域置为0并适当增加前一个有效目录项rec_len域的值就可以了。

  3)符号连

   如果符号连的路径名小于60个字符,就把它存放在索引节点的i_blaock域,该域是由154字节整数组成的数组,因此无需数据块。但是,如果路径名大于60个字符,就需要一个单独的数据块。

  4)设备文件、管道和套接字

  这些类型的文件不需要数据块。所有必要的信息都存放在索引节点中。

3.文件的定位

文件的定位是一个复杂的过程,我们先看一个具体的例子,然后再结合上面的数据结构具体介绍一下如何找到一个目录项的过程。

如果要找的文件为普通文件,则可通过文件所对应的索引节点找到文件的具体位置,如果是一个目录文件,则也可通过相应的索引节点找到目录文件具体所在,然后再从这个目录文件中进行下一步查找,来看一个具体的例子。

假设路径为/home/user1/file1homeuser1是目录名,而file1为文件名。为了找到这个文件,有两种途径,一是从根目录开始查找,二是从当前目录开始查找。假设我们从根目录查找,则必须先找到根目录的节点,这个节点的位置在VFS中的超级块中已经给出,然后可找到根目录文件,其中必有home所对应的目录项,由此可先找到home节点,从而home的目录文件,然后依次是user1的节点和目录文件,最后,在该目录文件中的file1目录项中找到file1的节点,至此,已经可以找到file1文件的具体所在了。

目录中还有两个特殊的子目录:“.    . .”,分别代表当前目录和父目录。它们是无法被删除的,其作用就是用来进行相对路径的查找。

现在,我们来分析一下fs/ext2/dir.c中的函数ext2_find_entry(),该函数从磁盘上找到并读入当前节点的目录项,其代码及解释如下:

 /*

  *      ext2_find_entry()

  *

  * finds an entry in the specified directory with the wanted name. It

  * returns the page in which the entry was found, and the entry itself

  * (as a parameter - res_dir). Page is returned mapped and unlocked.

  * Entry is guaranteed to be valid.

  */

typedef struct ext2_dir_entry_2 ext2_dirent

struct ext2_dir_entry_2 * ext2_find_entry (struct inode * dir,

                         struct dentry *dentry, struct page ** res_page)

{

        const char *name = dentry->d_name.name; *目录项名*/

        int namelen = dentry->d_name.len;    *目录项名的长度*/

        unsigned reclen = EXT2_DIR_REC_LEN(namelen); *目录项的长度*/

        unsigned long start, n;

        unsigned long npages = dir_pages(dir);   *把以字节为单位的文件大小

转换为物理页面数*/

        struct page *page = NULL;

         ext2_dirent * de;               *de为要返回的Ext2目录项结构*/

 

         /* OFFSET_CACHE */

         *res_page = NULL;

 

        start = dir->u.ext2_i.i_dir_start_lookup; *目录项在内存的起始位置*/

         if (start >= npages)

                 start = 0;

         n = start;

        do {

                 char *kaddr;

                 page = ext2_get_page(dir, n); *从页面高速缓存中获得目录

项所在的页面*

                 if (!IS_ERR(page)) {

                       kaddr = page_address(page);*获得page所对应的内核

虚拟地址*

                         de = (ext2_dirent *) kaddr;*获得该目录项结构

起始地址*/

                           kaddr += PAGE_CACHE_SIZE - reclen; * PAGE_CACHE_SIZE

的大小为1个页面的大小,假定所有的目录项结构都存放在一个页面内*/

                         while ((char *) de <= kaddr) {  *循环查找,直到

  找到匹配的目录项*/

                                 if (ext2_match (namelen, name, de))

                                        goto found;

                                de = ext2_next_entry(de);

                         }

                        ext2_put_page(page); /*释放目录项所在的页面*

                }

                if (++n >= npages)

                         n = 0;

        } while (n != start);

         return NULL;

 

found:

         *res_page = page;

         dir->u.ext2_i.i_dir_start_lookup = n;

         return de;

}

通过代码的注释,读者应当很容易明理解其含义.