Linux的使用和用户空间程序的编程与文件系统密切相关。对于文件系统的概念,大家可能已经比较熟悉了,所以我不会过多地讲解。毕竟,只要能了解这些概念就可以了,对于想深入了解的人,可以随时通过百度等搜索引擎获取更多信息。现在我将重点介绍Linux的虚拟文件系统。
虚拟文件系统是Linux的一个重要特性之一,它支持多种不同的文件系统。文件系统的结构如下图所示:[图片见原文]
上图中VFS(虚拟文件系统)依赖数据结构来保存其对一个文件系统的一般表示,其中数据结构罗列如下:
超级块结构:存放已经安装的文件系统的相关信息;
索引结点结构:存放有关文件的 信息;
文件结构:存放被进程打开的文件的相关信息;
目录项结构:存放有关路径名和路径名所指向的文件的信息。
Linux内核使用全局变量来保存先前提到的指向结构体的指针,所有的结构都用双向链表保存,内核保存指向链表头的指针,并且把它作为链表的访问点,这些结构都用list_head类型的域,用它来指向链表中的前一个元素,下表是内核保存的全局变量以及这些变量指向的链表类型(与VFS相关的全局变量)
全局变量 | 结构类型 |
---|---|
super_blocks | super_block |
file_systems | file_systems_type |
dentry_unused | dentry |
vfsmntlist | vfsmount |
inode_in_use | inode |
inode_unused | inode |
super_block、file_system_type、dentry、vfsmoubt结构都保存在它们自己的链表中,索引结点能够在全局的inode_in_use上或者inode_unused上找到自己,或者它们对应的超级快的局部链表上都可以找到自己。
除了主要的VFS结构之外,还有几个其他的结构与VFS相互作用,fs_struct和files_struct,namespace,fd_set,下图讲诉了进程描述符是如何与文件相关的结构相关联的。
先来介绍fs_struct结构,fs_struct结构可以被多个进程描述符引用,下述代码在include/Linux/fs_struct.h中可以查到哦,代码解释不好的请大神指教
struct fs_struct{ atomic_t count; //保存引用特定fs_struct的进程描述符数目 rwlock_t lock; int umask; //保存一个掩码,表示将要在打开文件上设置的许可权 struct dentry * root, *pwd ,*altroot; //都是指针,,,, struct vfsmount * rootmnt, *pwdmnt, *altrootmnt; //指针, };
files_struct包含打开文件和其描述符的相关信息,它使用这些集合来对它的描述符进行分组。下面代码在include/linux/file.h上可以查看到
struct files_struct{ atomic_t count; //与fs_struct类似 spinlock_t file_lock; int max_fds; //表示进程能够打开的文件的最大数 int max_fdset; //表示描述符的最大数 int next_fd; //保存下一个将要分配的文件描述符的值 struct file ** fd; //fd数组指向打开的文件对象的数组 fd_set *close_on_exec; //是指向文件描述符集的一个指针,这些文件描述符在exec()时候就被标志位将要关闭,如果在exec()时候被标志位“打开”的文件描述符数超过close_on_exec_init域的大小,则改变close_on_exec域的值; fd_set *open_fds; //是一个指针,指向被标记为“打开”的文件描述符集合, fd_set close_on_exec_init; //保存一个位域,表示打开文件对应的文件描述符 fd_set open_fds_init; //这些都是fd_set类型的域,其实都不懂,,, struct file *fd_array[NR_OPEN_DEFAULT];//fd_array数组指针指向前32个打开的文件描述法 };
通过INIT_FILES宏初始化fs_struct结构:
#define INIT_FILES \ { .count = ATOMIC_INIT(1), .file_lock = SPIN_LOCK_UNLOCKED, .max_fds = NR_OPEN_DEFAULT, .max_fdset = __FD_SETSIZE, .next_fd = 0, .fd = &init_files.fd_array[0]; .close_on_exec = &init_files.close_on_exec_init, .open_fds = &init_files.open_fds_init, .close_on_exec_init = {{0, }}, .open_fda_init = {{0, }}, .fd_array = {NULL, } }
NR_OPEN_DEFAULT的全局定义被设置为BITS_PER_LONG,BITS_PER_LONG在32位系统中是32,在64位系统中是64.
下面来介绍一下页缓冲,我们现在看看它是如何工作和实现的。在Linux中,内存被分成区,每个拥有活跃页的链表和不活跃的链表,当页不活跃的时候,就会被写回磁盘,下图说明了上述关系:
image-20240202221039708
页缓冲的核心是address_space对象,其代码在include/linux/fs.h中可以查看(这段代码不是很懂,求大神指教):
struct address_space{ struct inode *host; struct radix_tree_root page_tree; spinlock_t tree_lock; unsigned long nrpages; pgoff_t writeback; struct address_space_operations *a_ops; struct prio_tree_root i_map; unsigned inr i_map_lock; struct list_head i_mmap_nonlinear; spinlock_t i_mmap_lock; atomic_t truncate_count; unsigned long flags; struct backing_dev_info *backing_dev_info; spinlock_t private_lock; struct list_head private_list; struct address_space *assoc_mapping; };
Linux内核还把块设备上的每个扇区表示buffer_head结构,buffer_head结构应用的物理区是设备b_dev的逻辑块b_blocknr,引用的物理内存是起始于块大小为b_size个字节的b_data内存数据块,这个内存块在物理页b_page中,其结构如下图:
最后来说说VFS系统调用和文件系统层,并且追踪它们的执行直到内核级别,我们得先了解四个函数:open()、close()、read()、write()。
open()函数:
open 函数用于打开和创建文件。以下是 open 函数的简单描述
#include int open(const char *pathname, int oflag, ... );
返回值:成功则返回文件描述符,否则返回 -1
对于 open 函数来说,第三个参数(…)仅当创建新文件时才使用,用于指定文件的访问权限位(access permission bits)。pathname 是待打开/创建文件的路径名(如 C:/cpp/a.cpp);oflag 用于指定文件的打开/创建模式,这个参数可由以下常量(定义于 fcntl.h)通过逻辑或构成。
O_RDONLY 只读模式
O_WRONLY 只写模式
O_RDWR 读写模式
打开/创建文件时,至少得使用上述三个常量中的一个。以下常量是选用的:
O_APPEND 每次写操作都写入文件的末尾
O_CREAT 如果指定文件不存在,则创建这个文件
O_EXCL 如果要创建的文件已存在,则返回 -1,并且修改 errno 的值
O_TRUNC 如果文件存在,并且以只写/读写方式打开,则清空文件全部内容
O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端。
O_NONBLOCK 如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode)
以下三个常量同样是选用的,它们用于同步输入输出
O_DSYNC 等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。
O_RSYNC read 等待所有写入同一区域的写操作完成后再进行
O_SYNC 等待物理 I/O 结束后再 write,包括更新文件属性的 I/O
open 返回的文件描述符一定是最小的未被使用的描述符。
如果 NAME_MAX(文件名最大长度,不包括’
以上是24小时学通Linux内核之有关Linux文件系统实现的问题的详细内容。更多信息请关注PHP中文网其他相关文章!