1. Linux文件系统的特点与类别
1.1 特点
Linux系统中,文件组织在一个统一的树形目录结构中,整个文件系统有一个根“/”(文件夹),然后以每个目录(文件夹)作为分叉,叶子节点作为文件,如下图所示:
简单介绍一下:
/:所有文件的根目录
/bin:存放常用用户命令
/boot:存放内核系统启动所需文件
/dev:存放设备文件,如声卡文件,磁盘文件等
/etc:存放系统管理和配置文件目录
/home:用户主目录的基点目录,默认情况每个用户主目录都设在该目录下,如默认情况下用户user01 的主目录是/home/user01
/lib:存放标准程序设计库目录,又叫动态链接共享库目录,目录中文件类似windows里的后缀名为 dll的文件
/media:其他文件系统的挂载点
/proc:虚拟目录,是系统内存的映射,可直接访问这个目录来获取系统信息
/root:超级用户主目录
/sbin:系统管理程序
/sys:存放设备相关的系统信息
/usr:最庞大的目录,存放应用程序和文件目录
/tmp:临时文件
/var:存放系统产生的经常变化文件的目录,例如打印机、邮件等假脱机目录、日志文件、格式化后的手 册页以及一些应用程序的数据文件等
/usr/games:存放游戏
/usr/src:存放程序源代码
/usr/man:存放帮助文件
一般除了树形结构特点外,还具有如下特点:
- 文件是无结构的字符流式文件
- 文件可以动态增长或减少
- 可以设置权限
- 外部设备(磁盘,键盘,鼠标),都被看做文件,可以通过文件系统隐蔽掉设备特性
1.2文件类型
Linux文件可以分为6种类型:
- 普通文件:最常见的文件类型,包含文本文件、数据文件等,可以直接读取内容
- 目录文件:用于组织和管理其他文件和子目录,类似于Windows中的文件夹
- 字符设备文件:用于与字符设备(如键盘、鼠标)进行通信
- 块设备文件:用于与块设备(如硬盘、USB存储设备)进行数据交换
- 符号链接文件:类似于Windows中的快捷方式,指向另一个文件或目录
- 套接字文件:用于进程间通信,允许不同进程通过文件系统进行数据交换
2. Linux之虚拟文件系统
2.1 框架
Linux 提供了一种称为“虚拟文件系统”(Virtual File System, 简称 VFS)的软件抽象层,它允许 Linux 支持多种不同类型的文件系统。VFS 在用户空间和各种文件系统之间提供了一个抽象层,使得用户空间可以通过标准的系统调用来访问不同的文件系统,而不需要关心底层文件系统的具体实现细节。
2.1.1主要作用:
抽象层:VFS为底层具体的文件系统提供了一个抽象层,使得用户程序无需关心文件系统的具体实现。
统一接口:提供了一套统一的文件操作接口(如read、write、open等),使得应用程序可以使用相同的系统调用来访问不同类型的文件系统。
支持多种文件系统:VFS支持多种文件系统,包括本地文件系统(如ext4、XFS)、网络文件系统(如NFS)以及特殊文件系统(如procfs、sysfs)
2.1.2 VFS核心的四个数据结构
super_block :超级块,用于描述具体的文件系统信息
inode:索引节点,用以描述一个文件的元信息,如文件大小、权限、拥有者等,每个文件均对应一个inode
dentry:目录项结构,它的出现就是为了性能,一般在磁盘中是没有对应的结构的
file:文件结构,代表与进程交互过程中被打开的一个文件
2.1.2.1 超级块
一个具体的文件系统,如ext2、ext4等,都会对应一个超级块结构。内核也是通过扫描这个结构的信息来确定文件系统的大致信息,以下为其在内核源码中的部分定义(选自Linux 5.19,后续一样)
struct super_block {struct list_head s_list; // 指向超级块链表的指针dev_t s_dev; /* 块设备的具体标识号 */unsigned char s_blocksize_bits;unsigned long s_blocksize; // 文件系统中的数据块大小loff_t s_maxbytes; /* 允许的最大文件的大小 */struct file_system_type *s_type; // 具体的文件系统类型const struct super_operations *s_op; // 用于超级块操作的函数集合const struct quotactl_ops *s_qcop; // 限额操作的函数集合unsigned long s_flags; // 安装表示unsigned long s_magic; // 幻数 一般可以用于表示唯一的文件系统struct dentry *s_root; //根dentrystruct rw_semaphore s_umount; // 同步读写int s_count; // 超级块的使用计数atomic_t s_active;void *s_fs_info; /* 文件系统的隐私信息 *//* c/m/atime限制 */time64_t s_time_min;time64_t s_time_max;unsigned int s_max_links;fmode_t s_mode;// 默认的目录项操作集合const struct dentry_operations *s_d_op; /* default d_op for dentries */// 虽然链接数目为0 但仍然被引用atomic_long_t s_remove_count;// 没有被使用的dentry、inode会被加入这个struct list_lru s_dentry_lru;struct list_lru s_inode_lru;struct rcu_head rcu;/* s_inode_list_lock protects s_inodes */spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;struct list_head s_inodes; /* 所有的索引节点 由前面的锁进行保护 */spinlock_t s_inode_wblist_lock;struct list_head s_inodes_wb; /* writeback inodes */......
} __randomize_layout;
超级块的操作函数集合:
struct super_operations {//给超级块分配索引节点struct inode *(*alloc_inode)(struct super_block *sb); // 销毁索引节点void (*destroy_inode)(struct inode *);// 检查atime的更新情况void (*dirty_inode) (struct inode *, int flags);// 写入一个inode到磁盘中int (*write_inode) (struct inode *, struct writeback_control *wbc);// 删除一个inodeint (*drop_inode) (struct inode *);//在链接数目为0时会进行释放void (*evict_inode) (struct inode *);// 释放超级块所占用的内存void (*put_super) (struct super_block *);......
};
这些集合的函数会由具体的文件系统进行实现,没有实现的会被置为NULL
2.1.2.2 索引节点
Linux中是视一切为文件,一个文件就会有对应的inode,文件包含了常规文件、目录等。在需要时,在磁盘中的inode会被拷贝到内存中,修改完毕后会被写回到磁盘中。一个inode会被指向多个目录项索引(硬链接等)
以下为它在内核中的部分源码定义:
struct inode {umode_t i_mode; unsigned short i_opflags;kuid_t i_uid; // 文件所属的用户kgid_t i_gid; // 文件所属的组unsigned int i_flags;const struct inode_operations *i_op; // 索引节点操作函数集struct super_block *i_sb; // 文件所在文件系统的超级块struct address_space *i_mapping;/* Stat data, not accessed from path walking */unsigned long i_ino; // 索引号/** Filesystems may only read i_nlink directly. They shall use the* following functions for modification:** (set|clear|inc|drop)_nlink* inode_(inc|dec)_link_count*/union {const unsigned int i_nlink; // 链接数目unsigned int __i_nlink;};dev_t i_rdev; // 文件所在的设备号loff_t i_size; // 文件大小struct timespec64 i_atime;// 最后的访问时间struct timespec64 i_mtime; // 最后修改时间struct timespec64 i_ctime; // 最后改变时间spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */unsigned short i_bytes; // 使用的字节数/* Misc */unsigned long i_state;struct rw_semaphore i_rwsem; // 读写信号量struct hlist_node i_hash; // 哈希值 负责提高查找效率struct list_head i_io_list; /* backing dev IO list */struct list_head i_lru; /* inode LRU list 未使用的inode*/struct list_head i_sb_list; // 链接一个文件系统中的inode链表struct list_head i_wb_list; /* backing dev writeback list */union {struct hlist_head i_dentry; // 所属的目录项struct rcu_head i_rcu;};atomic64_t i_version; // 索引节点版本号atomic_t i_count; // 引用计数atomic_t i_writecount; // 写计数// 文件操作函数集合union {const struct file_operations *i_fop; /* former ->i_op->default_file_ops */void (*free_inode)(struct inode *);};struct address_space i_data;struct list_head i_devices;void *i_private; /* 文件与设备的私有指针 */......
} __randomize_layout;
索引节点的函数操作集合:
struct inode_operations {// 查找指定文件的dentrystruct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);//根据inode所描述的文件类型,如果是目录,则会创建一个inode,不是则不会调用int (*create) (struct user_namespace *, struct inode *,struct dentry *,umode_t, bool);//在指定目录下创建一个子目录int (*mkdir) (struct user_namespace *, struct inode *,struct dentry *,umode_t);//从inode所描述的目录中删除一个子目录时,会被调用int (*rmdir) (struct inode *,struct dentry *);......
} ____cacheline_aligned;
2.1.2.3 目录项结构
它的出现主要是为了查找性能,只存在于内存中,而不存在于磁盘中。这提供了一种非常快的查询机制来将一个路径名称(文件名称)转换为特定的目录项对象。
以下为它在内核中的部分源码定义:
struct dentry {/* RCU lookup touched fields */unsigned int d_flags; /* protected by d_lock */seqcount_spinlock_t d_seq; /* per dentry seqlock */struct hlist_bl_node d_hash; /* lookup hash list 哈希列表*/struct dentry *d_parent; /* parent directory 父目录 */struct qstr d_name; struct inode *d_inode; /*与目录关联的inode Where the name belongs to - NULL is* negative */unsigned char d_iname[DNAME_INLINE_LEN]; /* small names *//* Ref lookup also touches following */struct lockref d_lockref; /* per-dentry lock and refcount */const struct dentry_operations *d_op; // 目录项操作struct super_block *d_sb; /* 目录项所属文件系统的超级块 The root of the dentry tree */union {struct list_head d_lru; /* LRU list 未使用目录项以LRU算法链接的链表*/wait_queue_head_t *d_wait; /* in-lookup ones only */};struct list_head d_child; /* child of parent list 加入到父目录的d_subdirs */struct list_head d_subdirs; /* our children 子目录 *//** d_alias and d_rcu can share memory*/union {struct hlist_node d_alias; /* inode alias list */struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */struct rcu_head d_rcu;} d_u;
} __randomize_layout;
目录项的函数操作集合:
struct dentry_operations {// 检查当前目录项是否还有效int (*d_revalidate)(struct dentry *, unsigned int);// 较弱形式的校验int (*d_weak_revalidate)(struct dentry *, unsigned int);// int (*d_hash)(const struct dentry *, struct qstr *);// 引用计数为0时删除dentry(dput调用)int (*d_delete)(const struct dentry *);// 释放所占有的数据void (*d_release)(struct dentry *);//当dentry失去inode时调用,void (*d_iput)(struct dentry *, struct inode *);
......
} ____cacheline_aligned;
2.1.2.4 文件结构
当一个进程打开一个文件时,该文件就是用此文件结构进行描述的,如文件的读写模式、读写偏移量、所属inode等信息。这个文件结构会被进程的文件描述符表所存放。
以下为它在内核中的部分源码定义:
struct file {union {struct llist_node fu_llist; // 文件系统中被打开的文件对象(单个进程所打开的文件会被维护在另一个结构中 files_struct)struct rcu_head fu_rcuhead; } f_u;struct path f_path;struct inode *f_inode; /* cached value 被缓存的值 所属的inode吧*/const struct file_operations *f_op; // 文件操作指针/** Protects f_ep, f_flags.* Must not be taken from IRQ context.*/spinlock_t f_lock; // 自旋锁atomic_long_t f_count; // 引用计数器unsigned int f_flags; // 打开文件所引用的标志fmode_t f_mode; // 文件的访问模式(r模式等)struct mutex f_pos_lock;loff_t f_pos; // 读写偏移量struct fown_struct f_owner; // 所属者信息u64 f_version; // 版本号/* needed for tty driver, and maybe others */void *private_data; //隐私数据struct address_space *f_mapping;} __randomize_layout__attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
文件结构的函数操作集合:
struct file_operations {.......// 移动文件指针loff_t (*llseek) (struct file *, loff_t, int);// 从文件对象中读数据(系统调用中的读最终会被应用于此)ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);// 从文件对象中写数据ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);......
} __randomize_layout;
2.2 VFS系统调用
Linux VFS(虚拟文件系统)包括以下系统调用:
1. 挂装/卸载文件系统:
- mount():挂装文件系统
- umount():卸载文件系统
- 获取文件系统信息:
- sysfs():获取文件系统信息
- statfs():获取文件系统的状态信息
- fstatfs():获取已打开文件的文件系统状态信息
- ustat():获取统计信息
3. 更改根目录和当前目录:
- chroot():更改根目录
- chdir():改变当前工作目录
- fchdir():改变文件描述符的文件目录
- getcwd():获取当前工作目录的路径
4. 创建和删除目录:
- mkdir():创建目录
- rmdir():删除目录
5.读取文件状态:
- stat():读取文件状态信息
- fstat():读取已打开文件的文件状态信息
- lstat():读取符号链接的状态信息
- access():检查文件访问权限
6. 打开/关闭文件:
- open():打开文件
- close():关闭文件
- create():创建并打开文件
- umask():设置文件权限掩码
7. 对文件描述符进行操作:
- dup():复制文件描述符
- dup2():复制文件描述符并关闭原描述符
- fcntl():对文件描述符进行操作
8. 异步I/O通告:
- select():异步I/O通告
- poll():异步I/O通告(较老的接口)
9. 进行文件I/O操作:
- read():读取文件内容
- write():写入文件内容
- readv():读取多个数据块
- writev():写入多个数据块
- sendfile():高效地发送文件内容到套接字或管道中
10. 对软链接进行操作:
- readlink():读取符号链接的值
- symlink():创建符号链接
11. 更改文件属性:
- chown():更改文件所有者
- fchown():更改已打开文件的所有者
- lchown():更改符号链接的所有者
- chmod():更改文件权限
- fchmod():更改已打开文件的权限
- utime():更改文件的访问和修改时间戳
12. 进行通信操作:
- pipe():创建管道,用于进程间通信
3. 文件系统的注册和挂装
内核通过VFS使用一个具体的文件系统之前,必须对这个文件系统进行注册和挂装。
注册文件系统 -> 函数操作 -> 挂装到系统目录
4. 进程与文件系统的联系
5. ext2 文件系统
6. 块设备驱动
7. 字符设备驱动
8. 引用
Linux 文件系统之虚拟文件系统
Linux中的虚拟文件系统(virtual file system)