Linux 内核源代码情景分析(三)(上)

简介: Linux 内核源代码情景分析(三)

第5章 文件系统

1、概述

   若要问构成一个“操作系统”的最重要的部件是什么,那就莫过于进程管理和文件系统了。事实上,有些操作系统(如一些“嵌入式”系统)可能有进程管理而没有文件系统;而另一些操作系统(如 MSDOS)则有文件系统而没有进程管理。可是,要是二者都没有,那就称不上“操作系统” 了。在本书的前几章中已经讲述了与 Linux 的进程管理有关的内容,从现在开始,让我们把注意力转向它的文件系统。


   “文件系统”这个词的含义比较模糊。首先,其中“文件”的含义就自狭义与广义之分。狭义地说,“文件”是指“磁盘文件”,进而可以是有组织有次序地存储于任何介质(包括内存)中的一组信 息。广义地说,则 Unix 从一开始时就把外部设备都当成“文件”。从这个意义上讲,凡是可以产生或消耗信息的都是文件。以在网络环境中用来收发报文的“插口”机制来说,它就并不代表存储着的信息,但是插口的发送端“消耗”信息,而接收端则“产出”信息,所以把插口看成文件是合乎逻辑的。 可是,即使抛开“文件”这个词的模糊性不说,“文件系统”这个词又进一步有几种不同的含义,要根据上下文才能加以区分:


指一种特定的文件格式。例如,我们说 Linux 的文件系统是 Ext2, MSDOS 的文件系统是 FAT16,而 Windows NT 的文件系统是 NTFS 或 FAT32 ,就是指这个意思。

指按特定格式进行了 “格式化” 的一块存储介质。当我们说“安装”或“拆卸” 一个文件系统时,指的就是这个意思。

指操作系统中(通常在内核中)用来管理文件系统以及对文件进行操作的机制及其实现,这就是本章的主要话题。

   Linux 最初采用的是 minix 的文件系统,但是 minix 只是一种实验性(用于教学)的操作系统,其文件系统的大小仅限于 64M 字节,文件名长度限于 14 个字符。所以,经过一段时间的改进和发展, 特别是吸取了多年来对传统 Unix 文件系统的各种改进所累积起的经验,最后形成了现在的 Ext2 文件系统。这个文件系统可以说就是 “Linux文件系统” 。


   除 Linux 本身的文件系统 Ext2 外,设计人员很早就注意到了如何使 Linux 支持其他各种不同文件系统的问题。要实现这个目的,就要将对各种不同文件系统的操作和管理纳入到一个统一的框架中。


   让内核中的文件系统界面成为一条文件系统“总线”,使得用户程序可以通过同一个文件系统操作界面, 也就是同一组系统调用,对各种不同的文件系统(以及文件)进行操作。这样,就可以对用户程序隐去各种不同文件系统的实现细节,为用户程序提供一个统一的、抽象的、虚拟的文件系统界面,这就是所谓“虚拟文件系统” VFS (Virtual File System)。这个抽象的界面主要由一组标准的、抽象的 文件操作构成,以系统调用的形式提供于用户程序,如 read()、write()、lseek() 等等。这样,用户程序就可以把所有的文件都看作一致的、抽象的 “VFS文件” ,通过这些系统调用对文件进行操作,而无需关心具体的文件属于什么文件系统以及具体文件系统的设计和实现。例如,在 Linux 操作系统中, 可以将 DOS 格式的磁盘或分区(即文件系统)“安装”到系统中,然后用户程序就可以按完全相同的方式访问这些文件,就好像它们也是 Ext2 格式的文件一样。


   如果把内核比拟为 PC 机中的 “母板” ,把 VFS 比拟为 “母板” 上的一个 “插槽” ,那么每个具体的文件系统就好像一块“接口卡”。不同的接口卡上有不同的电子线路,但是它们与插槽的连接有几条线、每条线干什么用则是有明确定义的。同样,不同的文件系统通过不同的程序来实现其各种功能, 但是与 VFS 之间的界面则是有明确定义的。这个界面的主体就是一个 file_operations 数据结构,其定义在 include/linux/fs.h 中。

(1)file_operations

// include/linux/fs.h
struct file_operations {
  struct module *owner;
  loff_t (*llseek) (struct file *, loff_t, int);
  ssize_t (*read) (struct file *, char *, size_t, loff_t *);
  ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
  int (*readdir) (struct file *, void *, filldir_t);
  unsigned int (*poll) (struct file *, struct poll_table_struct *);
  int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
  int (*mmap) (struct file *, struct vm_area_struct *);
  int (*open) (struct inode *, struct file *);
  int (*flush) (struct file *);
  int (*release) (struct inode *, struct file *);
  int (*fsync) (struct file *, struct dentry *, int datasync);
  int (*fasync) (int, struct file *, int);
  int (*lock) (struct file *, int, struct file_lock *);
  ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
  ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
};

   每种文件系统都为自己的 file_operations 数据结构,结构中的成分几乎全是函数指针,所以实际上是个函数跳转表,例如 read 就指向具体文件系统用来实现读文件操作的入口函数。如果具体的文件系统不支持某种操作,其 file_operations 结构中的相应函数指针就是 NULL。我们不在这里介绍这个结构中各种指针的用途,以后读者将会看到其中主要的一些操作的典型代码。


   每个进程通过 “打开文件” ( open() )与具体的文件建立起连接,或者说建立起一个读写的 “上下文” 。这种连接以一个 file 数据结构作为表示,结构中有个 file_operations 结构指针 f_op。将 file 结构中的指针 f_op 设置成指向某个具体的 file_operations 结构,就指定了这个文件所属的文件系统,并且与具体文件系统所提供的一组函数挂上了钩,就好像把具体的 “接口插入到了 “插槽” 中。读者以后会看到有关的详情。


   Linux 内核中对 VFS 与具体文件系统的关系划分可以用图5.1表示:


  进程与文件的连接,即 “已打开文件” ,是进程的一项 “财产” ,归具体的进程所有。代表着这种连接的 file 结构必然与代表着进程的 task_struct 数据结构存在着联系。在 “进程与进程调度” 一章中我们看过这个数据结构的定义,但是那时候忽略了与文件系统有关的内容。现在把 task_struct 结构中与此有关的几行再列出如下 (include/linux/sched.h)。

// include/linux/sched.h
struct task_struct {
  // ...
/* filesystem information */
  struct fs_struct *fs;
/* open file information */
  struct files_struct *files; 
  // ...  
}

   这里有两个指针 fs 和 files ,一个指向 fs_struct 数据结构,是关于文件系统的信息;另一个指向 files_struct 数据结构,是关于已打开文件的信息。先看 fs_struct 结构,它的定义在 include/linux/fs_struct.h 中:

(2)fs_struct

// include/linux/fs_struct.h
struct fs_struct {
  atomic_t count;
  rwlock_t lock;
  int umask;
  struct dentry * root, * pwd, * altroot;
  struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
};

   结构中有六个指针。前三个是 dentry 结构指针,就是 root、pwd 以及应 altroot 。这些指针各自指向代表着一个 “目录项” 的 dentry 数据结构,里面记录着文件的各项属性,如文件名、访问权限等等。其中 pwd 则指向进程当前所在的目录;而 root 所指向的 dentry 结构代表着本进程的 “根目录” ,那就是当用户登录进入系统时所 “看到 “的根目录;至于 altroot 则为用户设置的 “替换根目录” ,我们以后还会讲到。实际运行时这二个目录不一定都在同一个文件系统中,例如进程的根目录通常是安装于节点上的 Ext2 文件系统中,而当前工作目录则可能安装于 /dosc 的一个 DOS 文件系统中。在文件系统的操作中这些安装点起着重要的作用而常常要用到,所以后三个指针就各自指向代表着这些 “安装” 的 vfsmount 数据结构。注意,fs_struct 结构中的信息都是与文件系统和进程有关的,带有全局性的(对具体的进程而言),而与具体的已打开文件没有什么关系。例如进程的根目录在 Ext2 文件系统中,当前工作目录在 DOS 文件系统中,而一个具体的已打开文件却可能是设备文件。


   与具体已打开文件有关的信息在 file 结构中,而 files_struct 结构的主体就是一个 file 结构数组。每打开一个文件以后,进程就通过一个 “打开文件号” fid 来访问这个文件,而 fid 实际上就是相应 file 结构在数组中的下标。如前所述,每个 file 结构中有个指针 f_op ,指向该文件所属文件系统的 file_operations 数据结构。同时,file 结构中还有个指针 f_dentry,指向该文件的 dentry 数据结构。那么,为什么不干脆把文件的 dentry 结构放在 file 结构里面,而只是让 file 结构通过指针来指向它呢?这是因为一个文件只有一个 dentry 数据结构,而可能有多个进程打开它,甚至同一个进程也可能多次打开它而建立起多个读写上下文。同理,每种文件系统只有一个 file_operations 数据结构,它既不专属于某个特定的文件, 更不专属于某个特定的上下文。


   每个文件除有一个 “目录项” 即 dentry 数据结构以外,还有一个 “索用节点” 即 inode 数据结构, 里面记录着文件在存储介质上的位置与分布等信息。同时,dentry 结构中有个 inode 结构指针 d_inode 指向相应的 inode 结构。读者也许要问,既然一个文件的 dentry 结构和 inode 结构都在从不同的角度描述这个文件各方面的属性,那为什么不 “合二为一” ,而要 “一分为二” 呢?其实,dentry 结构与 inode 结构所描述的目标是不同的,因为一个文件可能有好几个文件名,而通过不同的文件名访问同一个文件时的权限也可能不同。所以,dentry 结构所代表的是逻辑意义上的文件,记录的是其逻辑上的属性。而 inode 结构所代表的是物理意义上的文件,记录的是其物理上的属性;它们之间的关系是多对一的关系。


   前面我们说虚拟文件系统 VFS 与具体的文件系统之间的界面的 “主体” 是 file_operations 数据结 构,是因为除此之外还有一些其他的数据结构。其中主要的还有与目录项相联系的 dentry_operations 数据结构和与索引节点相联系的 inode_operations 数据结构。这两个数据结构中的内容也都是一些函数指针,但是这些函数大多只是在打开文件的过程中使用,或者仅在文件操作的 “底层” 使用(如分配空间),所以不像 file_operations 结构中那些函数那么常用,或者不那么有 “知名度” 。以 dentry_operations 为例,其定义在 include/linux/dcache.h 中。

(3)dentry_operations

// include/linux/dcache.h
struct dentry_operations {
  int (*d_revalidate)(struct dentry *, int);
  int (*d_hash) (struct dentry *, struct qstr *);
  int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
  int (*d_delete)(struct dentry *);
  void (*d_release)(struct dentry *);
  void (*d_iput)(struct dentry *, struct inode *);
};

   这里 d_delete 是指向具体文件系统的 “删除文件” 操作的入口函数,d_release 则用于 “关闭文件” 操作。还有个函数指针 d_compare 很有意思,它用于文件名的比对。读者可能感到奇怪,文件名的比对就是字符串的比对,这难道也因文件系统而异?其实不足为奇,想一想:有的文件系统中文件名的长度限于 8 个字符,有的则可以有 255 个字符;有的文件系统容许在文件名里有空格,有的则不容许; 有的支持汉字,有的则不支持。所以,文件名的比对确实因文件系统而异。


   总之,具体文件系统与虚拟文件系统 VFS 间的界面是一组数据结构,包括 file_operations 、dentry_operations 、inode_operations,还有其他(此处暂从略)。原则上每种文件系统都必须在内核中提供这些数据结构,后面我们还要深入讨论。


   虽然我们尚未深入地讲述文件系统的内部结构和操作,读者可以从图 5.2 看到文件系统内部结构的基本情况。我们姑且称之为 “文件系统逻辑结构图” ,以后读者将需要反复地回过来看这幅结构示意图。 至于这幅图中各个数据结构的分配与设置,也就是这幅图的构筑,则会在 “打开文件” 一节中叙述。

   那么,Linux 到底支持哪一些具体的文件系统呢?数据结构 inode 中有一个成分 u,是一个 union。 根据具体文件系统的不同,可以将这个 union 解释成不同的数据结构。例如,当 inode 所代表的文件是个插口 (socket)时,u 就用作 socket 数据结构;当 inode 所代表的文件属于 Ext2 文件系统时,u 就用作 Ext2 文件系统的详细描述结构 ext2_inode_info。所以,看一下对这个 union 的定义就可以看出 Linux 目前支持多少种文件系统,这个定义是 inode 数据结构定义的部分,在文件 include/linux/fs.h 中:

// include/linux/fs.h
struct inode {
  struct list_head  i_hash;
  struct list_head  i_list;
  struct list_head  i_dentry;
  
  struct list_head  i_dirty_buffers;

  unsigned long   i_ino;
  atomic_t    i_count;
  kdev_t      i_dev;
  umode_t     i_mode;
  nlink_t     i_nlink;
  uid_t     i_uid;
  gid_t     i_gid;
  kdev_t      i_rdev;
  loff_t      i_size;
  time_t      i_atime;
  time_t      i_mtime;
  time_t      i_ctime;
  unsigned long   i_blksize;
  unsigned long   i_blocks;
  unsigned long   i_version;
  struct semaphore  i_sem;
  struct semaphore  i_zombie;
  struct inode_operations *i_op;
  struct file_operations  *i_fop; /* former ->i_op->default_file_ops */
  struct super_block  *i_sb;
  wait_queue_head_t i_wait;
  struct file_lock  *i_flock;
  struct address_space  *i_mapping;
  struct address_space  i_data; 
  struct dquot    *i_dquot[MAXQUOTAS];
  struct pipe_inode_info  *i_pipe;
  struct block_device *i_bdev;

  unsigned long   i_dnotify_mask; /* Directory notify events */
  struct dnotify_struct *i_dnotify; /* for directory notifications */

  unsigned long   i_state;

  unsigned int    i_flags;
  unsigned char   i_sock;

  atomic_t    i_writecount;
  unsigned int    i_attr_flags;
  __u32     i_generation;
  union {
    struct minix_inode_info   minix_i;
    struct ext2_inode_info    ext2_i;
    struct hpfs_inode_info    hpfs_i;
    struct ntfs_inode_info    ntfs_i;
    struct msdos_inode_info   msdos_i;
    struct umsdos_inode_info  umsdos_i;
    struct iso_inode_info   isofs_i;
    struct nfs_inode_info   nfs_i;
    struct sysv_inode_info    sysv_i;
    struct affs_inode_info    affs_i;
    struct ufs_inode_info   ufs_i;
    struct efs_inode_info   efs_i;
    struct romfs_inode_info   romfs_i;
    struct shmem_inode_info   shmem_i;
    struct coda_inode_info    coda_i;
    struct smb_inode_info   smbfs_i;
    struct hfs_inode_info   hfs_i;
    struct adfs_inode_info    adfs_i;
    struct qnx4_inode_info    qnx4_i;
    struct bfs_inode_info   bfs_i;
    struct udf_inode_info   udf_i;
    struct ncp_inode_info   ncpfs_i;
    struct proc_inode_info    proc_i;
    struct socket     socket_i;
    struct usbdev_inode_info        usbdev_i;
    void        *generic_ip;
  } u;
};

   其中有些成分是不言自明的,如 ext2、msdos 等,但是多数都需要一点简短的说明:


hpfs —— IBM为PC开发的OS/2操作系统所采用的文件系统。这种格式只用于硬盘,而OS/2 所用的软盘则与msdos相同。

ntfs 一一Windows NT 的文件系统。

umsdos 一一 一种特殊的 “文件系统",用msdos文件系统来模拟Ext2文件系统。其好处是可以在磁盘上的DOS分区中直接运行Linux,而不需要先重新划区并格式化。坏处当然也不少,首先是降低了运行的速度,而且这样一来就对DOS文件系统的病毒失去了 “免疫力” 。

isofs —用于 CDROM (光盘)。

nfs —— “网络文件系统” NFS。

sysv —— Unix系统 V 的文件系统 S5FS。

affs —— BSD对S5FS作了很大的改进,改进后的文件系统称为 “快速文件系统” FFS。由于当时Amiga公司在其操作系统AmigaOS中采用了这种文件系统,所以称为affs。

ufs —— 这是FFS的另一种实现,广泛适用于BSD的各种版本以及各种Unix变种(如SunOS、 Solaris、FreeBSD、NetBSD、OpenBSD 以及 Nextstep 等等)版本,因而实际上成为了 “Unix File System”所以称为 ufs。

efs —— Silicon Graphics 的 IRIX 文件系统。

romfs —— “只读” 文件系统。顾名思义,这种文件系统可以建立在只读介质上,如EPROM、PROM 等。还有一个特点是这种文件系统的实现在内核中所占的 “地盘” 很小。例如,msdos 文件系统在内核中约占30K字节,nfs文件系统约占57K字节,而romfs只占一个页面。 所以,romfs常常比较适合于一些 “嵌入” 式系统。

coda — 也是一种网络文件系统,是对nfs的一种改进。

smbfs 一即samba,使Win 95、Win NT等系统可以通过网络访问Linux文件系统。

hfs 一一 Apple Macintosh 的文件系统。

adfs 一一 Acorn 公司开发了 一种基于 ARM 处理器的RISC PC,其操作系统称为RISCOS,而文

件系统即为 “Acorn Disk Filing System"。

qnx4 —— QNX是一个操作系统,常用于 “嵌入式” 系统,其文件系统为QNXFS。

bfs —— 用于 SCO Unix Ware V 的一种文件系统

udf —— "Universal Disk File” 最新的 “通用文件系统” ,既用于DVD和可写光盘,也可用于硬盘。

ncpfs 一 Novell NetWare 文件系统。

proc ——目录/proc下的特殊文件,这些文件对系统管理和程序调试都很有用处。

usbdev —— “通过串行总线” USB的驱动程序。

   这个 union 的定义只是大致地反映了 Linux 内核目前所支持的各种文件系统,因为这是以 inode 结构中这一部分空间的不同用法和解释为基础的。如果两种文件系统对这个 union 的解释相同,那就不能从这个定义中反映出来了。


   举例来说,早期 Linux 曾开发和使用了另一个文件系统 ext (还有一个文件系统叫Xiafs),后来才发展到 Ext2(表示 ext 第2版),现在的 Linux 内核也还支持这个文件系统(读者可以用命令 “man mount” 或 “man fs” 察看),但是由于它在 inode 结构中对 u 的解释与 Ext2 相同,这里就看不出来了。另一方 面,虽然原则下每个文件系统都有其自身的函数跳转表,即 file_operations 数据结构,但是反过来说, 每个 file_operations 结构都代表着一个不同的文件系统就不确切了。读者以后在第6章的 “管道” 一节中可以看到,就在 Ext2 文件系统的框架中,光是用作管道的文件就根据读、写权限的不同而有一个不同的 file_operations 结构。


   还有,在 Linux 系统中外部设备是视同文件的,所以从概念上讲每种不同的外部设备就相当于一种不同的文件系统。可是,在这个 union 的定义中却只列出了 usbdev 作为一种独立的文件系统,那么 “块设备” 又怎样? “字符设备” 又怎样? “网络设备” 又怎样?为什么这里都没有呢?原因就在于这些设备都不要求将 inode 结构中的这个 union 作不同的解释。同样的道理,对 “特殊文件” ,这里只列出了 proc 与 socket,但是用来实现 “命名管道” 的另一种特殊文件 FIFO 就没有在这里单独地列出。


   所以,inode 结构中的这个 union 反映了各种文件系统在部分数据结构上的不同,而 file_operations 结构则反应了它们在算法(操作)上的不同。


   当一个文件系统代表着磁盘(或其他介质)上按特定格式组织的文件时,对每个文件的操作最终都要转化为对某一部分磁盘介质的操作,所以从层次的观点来看,在 “文件系统” 以下还有一层 “设备驱动” 。可是,既然设备(实际上是设备驱动)也是视作文件的,那么作为与文件 “平级” 的磁盘设备 “文件” 与作为文件系统 “底层” 的磁盘 “设备” 又有什么区别呢?这实际上反映了对磁盘介质上的数据的两种不同观点,一种是把它看成有结构、有组织的数据,而另一种则把它看成线性空间的数据。下列示意图(图5.3)表不了不同文件的这种层次结构。

(4)磁盘文件

  磁盘文件也许应该称为 “存储文件” ,这就是狭义的、本来意义上的 “文件” ,通常以磁盘为存储介质,但也可能采用其他介质。例如,romfs 就是采用 EPROM 之类的介质,而 RAMDISK 则在内存中模拟磁盘介质。所谓 “文件” ,就是按一定的组织形式存储在介质上的信息,所以一个 “文件” 其实包含了两方面的信息,一是存储的数据本身,还有一部分就是有关该文件的组织和管理的信息。对于磁盘文件来说,这两种信息必定全都存储在 “文件系统” 中,也就是磁盘上。其中与组织和管理有关的信息主要存储在文件的 “索引节点” 和 “目录项” 中。磁盘上的索引节点与前述(存在于内存中)的 inode 数据结构相似,但有所不同,可以看成是 inode 结构的简化了的版本。不过,磁盘上的索引节点不像 inode 结构那样会因断电而 “挥发” ,也不占用内存空间。每个文件都有一个并且只有一个索引节点。即使文件中暂时没有数据,其索引节点总是存在的。这类文件是本章的重点,不过我们在本书中将只关心 Ext2 文件系统。磁盘上的目录项也比内存中的 dentry 结构简单,存在于所谓 “目录节点” 中。 目录节点实际上是一种特殊形式和用途的文件。

(5)设备文件

   设备文件同样包含有用于组织和管理的信息,同样有存储介质上的索引节点和目录项,但是却不一定有存储着的数据。根据设备类型和性质的不同,它可以是用于存储/读出的(如磁盘),也可以是用于接收/发送的(如网络卡),还可以是供采集/控制的(如一些机电设备),甚至可以是数种类型的结合。实际上,不管什么设备,在操作的过程中总要伴随着一定程度的数据采集和控制,通常都通过设备接口上的一个 “控制/状态寄存器” 进行,具体可参看本书下册 “设备驱动” 一章。

(6)特殊文件

   特殊文件在内存中也有 inode 数据结构和 dentry 数据结构,但是不一定在存储介质上有索引节点和目录项。与前两种文件主要的不同是:特殊文件一般都与外部设备无关,所涉及的介质通常就是内存以及 CPU 本身。当从一特殊文件 “读” 时,所读出的数据都是由系统内部按一定的规则临时生成出来的,或者从内存中收集、加工出来的,反之亦然。从这个意义上说,文件 “dev/null” 就是一个特殊文件,凡是写入这个文件的数据全部都被丢弃了,根本就与外部设备无关。所以这个文件虽然在 “/dev" 目录下,实质上却是一个特殊文件。读者在 “进程间通信” 一章中将会看到用来实现 “管道” 的文件, 特别是 “命名管道” 的 FIFO 文件,还有 Unix 域的 socket,也都属于特殊文件。在本章中我们还要介绍另一种重要的特殊文件,那就是在 “/proc” 目录下的一系列文件。


   三种不同类型的文件有一个共同点,那就是它们都有一些关于组织和管理的信息。因此,每个文件都有一个 inode。所谓 inode,也就是 “索引节点” (或称 “i 节点” )的意思。要 “访问” 一个文件时, 一定要通过它的索引才能知道这个文件是什么类型的文件(例如,是否设备文件)、是怎样组织的、文件中存储着多少数据、这些数据在什么地方以及其下层的驱动程序在哪儿等必要的信息。数据结构 inode 的定义在文件 include/linux/fs.h 中给出,我们把它列在这里,但是现在还不是有系统地解释结构中各个成分的时候,读者以后还得反复回过来看它的定义。最后,读者自己就能作出这种解释了。此处我们只对结构中的某些成分作一些介绍。先看 inode 的定义(include/linux/fs.h)。

// include/linux/fs.h
struct inode {
  struct list_head  i_hash;
  struct list_head  i_list;
  struct list_head  i_dentry;
  
  struct list_head  i_dirty_buffers;

  unsigned long   i_ino;
  atomic_t    i_count;
  kdev_t      i_dev;
  umode_t     i_mode;
  nlink_t     i_nlink;
  uid_t     i_uid;
  gid_t     i_gid;
  kdev_t      i_rdev;
  loff_t      i_size;
  time_t      i_atime;
  time_t      i_mtime;
  time_t      i_ctime;
  unsigned long   i_blksize;
  unsigned long   i_blocks;
  unsigned long   i_version;
  struct semaphore  i_sem;
  struct semaphore  i_zombie;
  struct inode_operations *i_op;
  struct file_operations  *i_fop; /* former ->i_op->default_file_ops */
  struct super_block  *i_sb;
  wait_queue_head_t i_wait;
  struct file_lock  *i_flock;
  struct address_space  *i_mapping;
  struct address_space  i_data; 
  struct dquot    *i_dquot[MAXQUOTAS];
  struct pipe_inode_info  *i_pipe;
  struct block_device *i_bdev;

  unsigned long   i_dnotify_mask; /* Directory notify events */
  struct dnotify_struct *i_dnotify; /* for directory notifications */

  unsigned long   i_state;

  unsigned int    i_flags;
  unsigned char   i_sock;

  atomic_t    i_writecount;
  unsigned int    i_attr_flags;
  __u32     i_generation;
  union {
    struct minix_inode_info   minix_i;
    struct ext2_inode_info    ext2_i;
    struct hpfs_inode_info    hpfs_i;
    struct ntfs_inode_info    ntfs_i;
    struct msdos_inode_info   msdos_i;
    struct umsdos_inode_info  umsdos_i;
    struct iso_inode_info   isofs_i;
    struct nfs_inode_info   nfs_i;
    struct sysv_inode_info    sysv_i;
    struct affs_inode_info    affs_i;
    struct ufs_inode_info   ufs_i;
    struct efs_inode_info   efs_i;
    struct romfs_inode_info   romfs_i;
    struct shmem_inode_info   shmem_i;
    struct coda_inode_info    coda_i;
    struct smb_inode_info   smbfs_i;
    struct hfs_inode_info   hfs_i;
    struct adfs_inode_info    adfs_i;
    struct qnx4_inode_info    qnx4_i;
    struct bfs_inode_info   bfs_i;
    struct udf_inode_info   udf_i;
    struct ncp_inode_info   ncpfs_i;
    struct proc_inode_info    proc_i;
    struct socket     socket_i;
    struct usbdev_inode_info        usbdev_i;
    void        *generic_ip;
  } u;
};

   每个 inode 都有一个 “i节点号” i_ino,在同一文件系统中每个 i 节点号都是惟一的,内核中有时候会根据 i 节点号的杂凑值寻找其 inode 结构。同时,每个文件都有个 “文件主” ,显初是创建了这个文件的用户,但是可以改变。系统的每个用户都有一个用户号,即 uid,并且都属于某一个用户 “组” , 所以又有个组号 gid。因此,在 inode 结构中就相应地有 i_uid 和 i_gid 两个成分,以指明文件主的身分。


   值得注意的是,inode 结构中有两个设备号,即 i_dev 和 i_rdev 。首先,除特殊文件外,一个索引节点总得存储在某个设备上,这就是 i_dev。其次,如果索引节点所代表的并不是常规文件,而是某个设备,那就还要有个设备号,那就是 i_rdev 。设备号实际上由两部分构成,即 “主设备号” 与 “次设备号” 。主设备号表示设备的种类,例如磁盘就分成软盘、IDE硬盘、SCSI硬盘等等。次设备号则表示系统内配备的同一种设备中的某个具体设备。


   每当一个文件受到访问时,系统都要在这个文件的 inode 中留下时间印记,inode 结构中的 i_atime、i_mtime 和 i_ctime 分别为最后一次访问该文件的时间、修改该文件的时间以及最初创建该文件的时间。


   对于具有数据部分的文件(磁盘文件或 “普通文件” )来说,i_size 就是其数据部分当前的大小。 至于数据所在的位置,则根据文件系统的不同而记录在 inode 中的 union 里面。


   就像人可以有别名一样,文件也可以有多个文件名,也就是说可以将一个己经创建的文件 “连接” (link)到另一个文件名。这个 “别名” 与原来的文件名可以在同一个目录中,也可以在不同的目录中, 但是这些不同的目录项都指向同一个 inode 。与此相应,在 inode 结构中有个计数器 i_link,用来记住这个文件有多少个这样的连接。同时,还有个队列头 i_dentry,用来构成一个 dentry 结构的队列,沿着这个队列就可以找到与这个文件相联系的所有 dentry 结构。


   除了相对静态的信息以外,inode 结构中还有些成分用于表示一些动态的信息。例如, i_count 就是 inode 结构的共享计数,这个数值在系统运行的过程中是常常在变化的。又如, inode 结构可以通过它的几个 list_head 结构动态地链入到内存中的若干队列中,这种关系显然也是在动态地变化的。


   另外,inode 结构中 union 里面的信息也有很多是动态的。显然,inode 结构中相对静态的一些信息是需要保存在 “不挥发性” 介质如磁盘上的。这一点对具有数据部分的磁盘文件固不待言,就是对于不具有数据部分的设备文件和特殊文件也是必需的(只有少数特殊文件例外,如无名管道文件)。所以,在前面的文件系统层次图(图5.3)中,实际上从 VFS 和磁盘介质之间还应该加上一条连线,表示 VFS 层为管理的目的在磁盘上保存和恢复 inode 结构(以及其他一些数据结构,如 dentry )所需的一些信息。 以后读者还会看到,磁盘的格式化也考虑到了这个问题。以 Ext2 格式为例,磁盘上的记录块(扇区) 主要分成两部分,一部分用于索引节点,一部分用于文件的数据。给定一个索引节点号,就可以通过磁盘的设备驱动程序将其所在的记录块读入内存中。


   那么,既然只要把 inode 结构中的部分信息保存在磁盘上的 “索引节点” 中,这些节点又是什么样的呢?这要看具体的文件系统而定。就 Linux 本身的文件系统 Ext2 而言,那就是 ext2_inode 数据结构, 这是在 include/linux/ext2_fs.h 中定义的,读者不妨先大致上比较下它与 inode 结构的异同。

Linux 内核源代码情景分析(三)(中):https://developer.aliyun.com/article/1597990

目录
相关文章
|
6天前
|
存储 安全 Linux
探索Linux操作系统的心脏:内核
在这篇文章中,我们将深入探讨Linux操作系统的核心—内核。通过简单易懂的语言和比喻,我们会发现内核是如何像心脏一样为系统提供动力,处理数据,并保持一切顺畅运行。从文件系统的管理到进程调度,再到设备驱动,我们将一探究竟,看看内核是怎样支撑起整个操作系统的大厦。无论你是计算机新手还是资深用户,这篇文章都将带你领略Linux内核的魅力,让你对这台复杂机器的内部运作有一个清晰的认识。
19 3
|
15天前
|
缓存 安全 Unix
Linux 内核黑客不可靠指南【ChatGPT】
Linux 内核黑客不可靠指南【ChatGPT】
|
15天前
|
Linux 开发者
Linux内核贡献成熟度模型 【ChatGPT】
Linux内核贡献成熟度模型 【ChatGPT】
|
15天前
|
网络协议 Ubuntu Linux
用Qemu模拟vexpress-a9 (三)--- 实现用u-boot引导Linux内核
用Qemu模拟vexpress-a9 (三)--- 实现用u-boot引导Linux内核
|
15天前
|
Linux
用clang编译Linux内核
用clang编译Linux内核
|
15天前
|
Linux API C语言
Linux 内核补丁提交的清单 【ChatGPT】
Linux 内核补丁提交的清单 【ChatGPT】
|
15天前
|
安全 Linux 开发者
Linux内核管理风格 【ChatGPT】
Linux内核管理风格 【ChatGPT】
|
16天前
|
Linux 程序员 编译器
Linux内核驱动程序接口 【ChatGPT】
Linux内核驱动程序接口 【ChatGPT】
|
16天前
|
存储 Linux 开发工具
如何进行Linux内核开发【ChatGPT】
如何进行Linux内核开发【ChatGPT】
|
15天前
|
Linux API 调度
关于在Linux内核中使用不同延迟/休眠机制 【ChatGPT】
关于在Linux内核中使用不同延迟/休眠机制 【ChatGPT】