Unix文件系统概念篇(file、inode、dentry和super_block)

本文深入探讨Unix文件系统,介绍VFS虚拟文件系统如何屏蔽不同文件系统的差异,以及超级块、索引节点(inode)、目录项(dentry)和文件这四大核心对象的作用和相互关系。inode存储文件元数据,目录作为一种特殊文件,通过dentry表示路径,文件对象则代表已打开的文件。

Unix文件系统概念

1. VFS虚拟文件系统

unix为了支持多种文件系统,包括DOS FAT、Windows NTFS、UNIX EXT2、EXT3等文件系统格式,采用了面向对象的实现了VFS虚拟文件系统,屏蔽了下层硬件和文件系统的差异,向上提供了统一的访问接口。
vfs架构
vfs使用一组struct数据结构作为对象,并定义了对应的opertions结构,其内部包括了操作对应数据结构的方法(函数指针)。其中最重要的对象有四个:super_block对象、inode对象、dentry对象和file对象。

2. 四大对象

在super_block、inode、dentry和file对象中,都有对应的super_operations、inode_operations、dentry_operations和file_operations结构体,这些struct中定义了函数指针来对对象进行操作。如file_operations中定义了read、write、open、flush等函数指针,对该file对象进行读、写、打开和写回的操作。
如果将磁盘分为多个分区,每个分区代表一个文件系统,则在文件系统在磁盘中的表示可以分为:自举块、超级块、inode表和数据块(包括目录和数据)。每个文件对应一个inode结构,有唯一的inode编号,并且unix将目录看作是一种特殊的文件,也用一个inode结构表示,可以嵌套包含普通文件和子目录。超级块用于储存特定文件系统的信息,对于非基于磁盘的文件系统,如sysfs,则在内存中现场创建此结构。
关系图
忽略超级块和自举块,文件系统的结构就是下图的样子。inode表中可以指向文件的数据块,可以指向目录块。而目录如前所述,是一种特殊的文件,里面包含一个及多个目录项,用inode号和文件名来表示。所以unix文件系统不是按照文件名存取文件的,而是借由inode来完成,它在当前文件系统的唯一表示一个文件的。一个大文件可能包括多个数据块,目录块也包含多个目录项。
详细的结构

2.1 超级块

struct super_block{
  struct list_head s_list;		/* 指向所有超级块的链表 */
  dev_t s_dev;					/* 设备标示标示符 */
  unsigned long s_blocksize;	/* 块大小 */
  unsigned char s_dirty;		/* 修改(脏)标志 */
  struct file_system_type s_type;/* 文件系统类型 */
  struct super_operation s_op;	/* 超级块操作方法 */
  unsigned long s_magic;		/* 文件系统幻数 */
  struct dentry *s_root;		/* 目录挂载点 */
  int s_count;					/* 超级块引用计数 */
  ...
};

以上,是超级块对象核心的结构。该对象通过alloc_super函数创建并初始化,在安装文件系统时,就会调用此函数,从磁盘中读出超级块,并将信息填充到此结构体中。

struct super_operations{
  /* 在inode表中创建并初始化一个新的inode对象 */
  struct inode* (*alloc_inode)(struct super_block *sb);
  /* 释放给定的inode节点 */
  void (*destory_inode)(struct inode *);
  /* inode节点脏时,VFS调用此函数 */
  void (*dirty_inode)(struct inode *);
  /* 将给定inode写入磁盘,wait指定是否同步 */
  int (*write_inode)(struct inode *, int wait);
  /* 当最后一个inode节点的引用被释放时,VFS调用此函数 */
  void (*drop_inode)(struct inode *);
  /* 从磁盘删除给定inode节点 */
  void (*delete_inode)(struct inode *);
  /* 释放超级块,VFS卸载文件系统时调用 */
  void (*put_super)(struct super_block *sb);
  /* 更新磁盘上的超级块 */
  void (*write_super)(struct super_block *sb);
  /* 同步内存与磁盘中文件系统的数据元 */
  int (*sync_fs)(struct super_block *sb, int wait);
  /* 获取文件系统状态 */
  int (*statfs)(struct dentry *, struct kstatfs *);
  /* 重新挂在文件系统时,VFS调用此函数 */
  int (*remount_fs)(struct super_block *, int *, char *);
  /* 释放inode,并清除inode对应的数据的所有页面 */
  void (*clear_inode)(struct inode *);
  ...
};

super_operations结构提供了创建、释放、写入和删除inode的方法,以及更新、释放和锁定超级块的方法。

2.2 索引节点

inode包含文件的元数据,如文件权限、字节数、所属组id号、拥有者id号、时间戳和引用数等,但就是不会包括文件名。文件名在目录的目录项中存在,与inode有对应关系。

struct inode {
  struct hlist_node       i_hash;   /* 哈希表 */
  struct list_head        i_list;   /* 索引节点链表 */
  struct list_head        i_dentry; /* 目录项链表 */
  unsigned long           i_ino;    /* 节点号 */
  atomic_t                i_count;  /* 引用记数 */
  umode_t                 i_mode;   /* 访问权限控制 */
  unsigned int            i_nlink;  /* 硬链接数 */
  uid_t                   i_uid;    /* 使用者id */
  gid_t                   i_gid;    /* 使用者id组 */
  kdev_t                  i_rdev;   /* 实设备标识符 */
  loff_t                  i_size;   /* 文件大小(字节) */
  struct timespec         i_atime;  /* 最后访问时间 */
  struct timespec         i_mtime;  /* 最后修改时间 */
  struct timespec         i_ctime;  /* 最后改变时间 */
  unsigned long           i_blksize;/* 以字节为单位的块大小 */
  unsigned long           i_blocks; /* 文件的块数 */
  unsigned short          i_bytes;  /* 使用的字节数 */
  spinlock_t              i_lock;   /* 自旋锁 */
  struct super_block      *i_sb;    /* 相关的超级块 */
  struct file_lock         *i_flock;  /* 文件锁链表 */
  struct pipe_inode_info  *i_pipe;  /* 管道信息 */
  struct block_device     *i_bdev;  /* 块设备驱动 */
  unsigned long           i_state;  /* 状态标志 */
  unsigned int            i_flags;   /* 文件系统标志 */
  unsigned char           i_sock;   /* 可能是个套接字吧 */
  atomic_t                i_writecount;/* 写者记数 */
  ...
};

inode_operations主要是对给定inode完成create、link、symlink、unlink、truncate等操作。

struct inode_operations{
  /* 为dentry对象创建一个新的索引节点,mode指定初始模式 */
  int (*create)(struct inode *dir, struct dentry *, int mode);
  /* 在特定的目录中寻找索引节点,该索引节点对应dentry中文件名 */
  struct dentry * (*lookup)(struct inode *dir, 
                            struct dentry *);
  /* 创建硬链接 */
  int (*link)(struct dentry *old_dentry, 
              struct inode *dir, struct dentry *);
  /* 从dir目录中删除dentry指定的索引节点对象 */
  int (*unlink)(struct inode *dir, struct dentry *);
  /* 创建符号链接,名称由symname指定 */
  int (*symlink)(struct inode *dir, struct dentry *, const char *symname);
  /* 创建新目录 */
  int (*mkdir)(struct inode *dir, struct dentry *, int mode);
  /* 删除目录 */
  int (*rmdir)(struct inode *dir, struct dentry *);
  /* 创建特殊文件,如设备文件、管道文件或套接字 */
  int (*mknod)(struct inode *, struct dentry *, int, dev_t);
  /* 移动文件,源文件在old_dir指定的目录文件中*/
  /* 源文件由old_dentry目录项指定,拷贝到new_dir中 */
  int (*rename)(struct inode *old_dir, struct dentry *old_dentry, 
                struct inode *new_dir, struct dentry *new_dentry);
  /* 读符号链接文件内容 */
  int (*readlink)(struct dentry *, char __user *buffer, int len);
  /* 截断,修改文件大小 */
  void (*truncate)(struct inode *);
  /* 检查给定inode所代表文件是否具有特定的访问模式 */
  int (*permission)(struct inode *, int mask);
  ...
};

如前所述,目录也是文件,在自己的文件系统有唯一的inode结构对应。而dentry就是一个临时的目录项结构,用于标示路径。如/usr/bin/ls,其中“/” ,“usr”,“bin”,“ls”都用dentry来标示。
所以,也不难理解inode_operaions中各种定义的函数。下面以创建软链接和硬链接来举例说明inode和dentry之间联系。

软链接与硬链接
首先,硬链接是多个文件引用同一个inode,这些文件存在目录文件的目录项中,这些引用都会使得inode引用计数加1,当所有目录项都不再引用该inode时,就会释放该inode以及inode管理的的数据块。所以,删除一个硬链接时,会触发unlink系统调用,如果不是最后一个删除者,那么此文件仍然存在于文件系统中。另外,inode是在当前文件系统中唯一表示一个文件的,在不同的文件系统,那么inode就不能保证是唯一的,而硬链接依靠共享同一个inode实现,所以不能跨越文件系统创建硬链接。
软链接是一个符号链接,软链接是一个特殊的文件,所以它也有自己的inode。软链接文件内容是一个文件路径,就是它链接的路径。在作查找时,是跟随软链接的,所以open一个软链接,是打开它指向的那个路径,真正需要获取软链接内容,需要readlink系统调用来实现。软链接有自己独立的inode,所以它是可以跨越文件系统而独立存在的。
软链接与硬链接

2.3 目录项

对于路径中每一部分都看作是dentry对象,如/bin/ls,bin属于目录文件,ls是普通文件,他们都用inode对象表示。VFS为了方便查找操作,将“/”,“bin”,“ls”都看作是目录项对象。

struct dentry{
  atomic_t d_count;  /* 使用计数 */
  struct inode *d_inode;  /* 相关联的索引节点 */
  struct hlist_node d_hash; /* 散列表 */
  struct qstr d_name;  /* 目录项名称 */
  struct dentry *d_parent;  /* 父目录的目录项对象 */
  struct list_head d_subdirs;  /* 子目录链表 */
  struct dentry_operations *d_op;  /* 目录项操作节点 */
  struct super_block *d_sb;  /* 文件超级块 */
  unsigned char d_iname[DNAE_INLINE_LEN_MIN];  /* 短文件名 */
  ...
};

目录项不存在磁盘中,VFS根据字符串形式路径在内存动态创建。

struct dentry_operations{
  /* 验证目录项对象是否有效 */
  int (*d_revalidate)(struct dentry *, struct nameidata *);
  /* 生成目录项的哈希值 */
  int (*d_hash)(struct dentry *, struct qstr *);
  /* 比较文件名,可能按字典序,可能不区分大小写 */
  int (*d_compare)(struct dentry *, struct qstr *name1, struct qstr *name2);
  /* d_count为0时被调用,删除目录项,需要加锁 */
  int (*d_delete)(struct dentry *);
  /* 目录项将被释放时调用 */
  void (*d_release)(struct dentry *);
  /* 磁盘索引节点被删除时被调用 */
  void (*d_input)(struct dentry *, struct inode *);
};

目录项的存在,是便于VFS作路径查找。为了加速查找过程,VFS还是用了目录项缓存dcache,并使用hashtable缓存最近解析过的路径的目录项。方便在下次需要解析路径时,使用d_hash函数计算哈希值,然后快速在hashtable中找到匹配项。因为文件访问空间和时间的局部性,对目录项的缓存命中率概率较高。

2.4 文件

文件对象表示进程中已打开的文件,是已打开文件在内存中的表示。在使用open时被创建,在调用close时撤销。同时,多个进程可能同时打开同一个文件,所以同一个文件可能包含多个文件对象,但他们对应的dentry和inode对象是唯一的。

struct file{
  struct path f_path;         /* 包含目录项 */
  struct file_operations *f_op;/* 文件操作方法 */
  spinlock_t f_lock;          /* 文件结构锁 */
  atomic_t f_count;           /* 使用计数 */
  unsigned int f_flags;        /* 打开文件指定的标志 */
  mode_t f_mode;              /* 访问模式 */
  loff_t f_pos;               /* 文件偏移量 */
  ...
};

文件对象和dentry对象一项,是在内存中创建的,没有对应的磁盘数据,所以不需要脏标志来判读是否需要写回磁盘。脏标志存在于inode中,可以通过f_path找到dentry,然后在找到对应inode结构。

struct file_operations{
  struct module *owner;
  /* 更新偏移量指针 */
  loff_t (*llseek)(struct file *, loff_t offset, int); 
  /* 读操作 */
  ssize_t (*read)(struct file *, char *buf, size_t count, loff_t offset);
  /* 写操作 */
  ssize_t (*write)(struct file, const char *, size_t, loff_t *);
  /* 异步读 */
  ssize_t (*aio_read)(struct kiocb *iocb, char *buf, size_t, loff_t);
  /* 异步写 */
  ssize_t (*aio_write)(struct kiocb *, const char *, size_t, loff_t);
  /* 获取目录列表中的一项 */
  int (*readdir)(struct file *, void *dirent, filldir_t filldir);
  /* io异步,监控多个文件描述符事件 */
  unsigned int (*poll)(struct file *, struct poll_table_struct *);
  /* 用于向设备发送命令和传递参数 */
  int (*ioctl)(struct inode *, struct file *, 
               unsigned int cmd, unsigned long arg);
  /* 将文件映射到指定的地址空间 */
  int (*map)(struct file *, struct vm_area_struct *vma);
  /* 打开文件 */
  int (*open)(struct inode *, struct file *);
  /* 将给定文件数据写回磁盘 */
  int (*fsync)(struct file *, struct dentry *, int datasync);
  /* 给指定文件上锁 */
  int (*lock)(struct file *, int cmd, struct file_lock *lock);
  /* scatter read and write */
  ssize_t (*readv)(struct file *, const struct invective *, 
                   unsigned long count, loff_t *);
  ssize_t (*writev)(struct file *, const struct invective *, 
                   unsigned long count, loff_t *);
  /* 拷贝一个文件到另一个文件,在内核态完成,减少拷贝 */
  ssize_t (*sendfile)(struct file *, loff_t *, size_t size, 
                     read_actor_t, void *target);
  ssize_t (*sendpage)(struct file *, struct page *, 
                      int offset, size_t size, loff_t *, int more);
  ...
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值