4.8 文件

文件是一种带存储格式的数据集合,比上述的概念更大,结构更灵活,一般包括文件头、数据、格式控制三部分。文件头中包括指示文件的存储方式、大小、起始和结束位置等信息,格式控制包括一些特殊的格式说明、格式符号等。

对文件的操作包括打开、定位、读、写、关闭等。

因为µCOS-Ⅱ没有很好的文件结构,所以下面以Linux为例来解释嵌入式操作系统中对文件如何实现和操作。

Linux系统的核心数据结构在文件操作中全部可以找到,所以弄懂Linux文件操作涉及的各种数据结构,就可以弄懂Linux。下面列举一些主要的数据结构。

1.inode和file_operations

inode用于描述目录节点,它描述了一个目录节点物理方面的属性,如大小、创建时间、修改时间、uid、gid等。file_operations是目录节点提供的操作“接口”。它包括open, read, wirte, ioctl, llseek, mmap等操作。一个inode通过成员i_fop对应一个file_operations。打开文件的过程就是寻找目录节点对应的inode的过程。文件被打开后,inode和file_operations都已经在内存中建立,file_operations的指针也已经指向了具体文件系统提供的函数,此后该文件的操作都由这些函数来完成。

例如,打开了一个普通文件 /root/file,其所在文件系统格式是ext2,那么,内存中结构如图4.7所示。

图4.7 inode和file_operations 关系图

2.目录节点入口dentry

本来,inode中应该包括目录节点的名称,但由于符号链接的存在,导致一个物理文件可能有多个文件名,因此把和目录节点名称相关的部分从inode中分出来,放在一个专门的dentry结构中。这样一个dentry通过成员d_inode对应到一个inode上,寻找inode的过程变成了寻找dentry的过程。因此,dentry变得更加关键,inode常常被dentry所遮掩。可以说,dentry是文件系统中最核心的数据结构,它的身影无处不在。由于符号链接的存在,导致多个dentry可能对应到同一个inode上。例如,有一个符号链接/tmp/abc指向一个普通文件/root/file,那么dentry与inode之间的关系大致如图4.8所示。

图4.8 dentry、inode和file_operations关系图

3.super_block和super_operations

一个存放在磁盘上的文件系统如EXT2等,在它的格式中通常包括一个“超级块”或者“控制块”的部分,用于从整体上描述文件系统,如文件系统的大小、是否可读可写等。

虚拟文件系统中也通过“超级块”这种概念来描述文件系统整体的信息,对应的结构是struct super_block。

super_block 除了要记录文件大小、访问权限等信息外,更重要的是提供一个操作“接口”

super_operations。

4.file结构

一个文件每被打开一次,就对应着一个file结构。每个文件对应着一个dentry和inode,每打开一个文件,只要找到对应的dentry和inode不就可以了吗?为什么还要引入这个file结构?这是因为一个文件可以被同时打开多次,每次打开的方式也可以不一样,而dentry和inode只能描述一个物理的文件,无法描述“打开”这个概念。因此有必要引入file结构来描述一个“被打开的文件”。每打开一个文件,就创建一个file结构。

file结构中包含以下信息:

  • 打开这个文件的进程的uid和pid;
  • 打开的方式;
  • 读/写的方式;
  • 当前在文件中的位置。

实际上,打开文件的过程正是建立file、dentry和inode之间的关联的过程,如图4.9所示。

图4.9 file结构和其他数据结构的关系

5.files_struct结构

一个进程可以打开多个文件,每打开一个文件,就创建一个file结构。所有的file结构的指针保存在一个数组中,而文件描述符正是这个数组的下标。读者在刚开始学习编程的时候,可能不容易理解这个“文件描述符”的概念。如果从内核的角度去看,就很容易明白“文件描述符”是怎么回事了。用户仅仅看到一个“整数”,实际底层对应着的是file、dentry、inode等复杂的数据结构。files_struct用于管理这个“打开文件”表。

struct files_struct {
   atomic_t count;
   rwlock_t file_lock; /* Protects all the below members. Nests inside
tsk->alloc_lock */
   int max_fds;
   int max_fdset;
   int next_fd;
   struct file ** fd; /* current fd array */
   fd_set *close_on_exec;
   fd_set *open_fds;
   fd_set close_on_exec_init;
   fd_set open_fds_init;
   struct file * fd_array[NR_OPEN_DEFAULT];
};

以上程序中的fd_array[]就是“打开文件”表。task_struct中通过成员files与files_struct关联起来。