文章目录
首先提出几个简单的问题:
- 为什么
read()函数
在所有Linux
机器下都能正常运行,不管你是什么系统,在什么类型的文件系统下以及是什么类型的硬盘?这是如何做到的?
1.那么什么是文件系统呐?
文件系统是对一个存储设备上的数据和元数据进行组织的机制
。Linux系统中每个分区都是一个文件系统,都有自己的目录层次结构 。(df && cat /proc/filesystems
)
2.Linux 文件系统的体系结构
下图所示的体系结构显示了用户空间和内核中与文件系统相关的主要组件之间的关系
用户空间包含一些应用程序(例如,文件系统的使用者)和 GNU C 库(glibc),它们为文件系统调用(打开、读取、写和关闭)提供用户接口。系统调用接口的作用就像是交换器,它将系统调用从用户空间发送到内核空间中的适当端点。
VFS 是底层文件系统的主要接口。这个组件导出一组接口,然后将它们抽象到各个文件系统,各个文件系统的行为可能差异很大。
有两个针对文件系统对象的缓存(inode 和 dentry)。它们缓存最近使用过的文件系统对象。
每个文件系统实现(比如 ext2、fat 等等)导出一组通用接口,供 VFS 使用。缓冲区缓存会缓存文件系统和相关块设备之间的请求。
例如,对底层设备驱动程序的读写请求会通过缓冲区缓存来传递。这就允许在其中缓存请求,减少访问物理设备的次数,加快访问速度。以最近使用(LRU)列表的形式管理缓冲区缓存。注意,可以使用 sync 命令将缓冲区缓存中的请求发送到存储媒体(迫使所有未写的数据发送到设备驱动程序,进而发送到存储设备)。
最后来一个实例说明:
这就是 VFS 和文件系统组件的高层情况。现在,主要讨论VFS
系统的主要结构。
3. VFS 虚拟文件系统
(1)虚拟文件系统中的四个通用对象
Linux 以一组通用对象的角度看待所有文件系统。
这些对象是超级块(superblock)、inode、dentry 和文件.
下面分别来具体说明:
超级块(superblock): 对应一个已安装的文件系统的信息
超级块在每个文件系统的根上,用来描述整个文件系统的信息。 每个具体的文件系统都有各自的超级块.
它包含管理文件系统所需的信息,包括文件系统名称(比如 ext2)、文件系统的大小和状态、块设备的引用和元数据信息(比如空闲列表等等)。超级块通常存储在存储媒体上
值得关注的几个结构是:
struct file_system_type *s_type ; //文件系统类型
struct super_operations *s_op; // 超级块操作
这个结构定义一组用来管理这个文件系统中的 inode 的函数
。例如,可以用 alloc_inode 分配 inode,用 destroy_inode 删除 inode。可以用 read_inode 和 write_inode 读写 inode,用 sync_fs 执行文件系统同步。每个文件系统提供自己的可用的 inode 方法,这些方法实现操作并向 VFS 层提供通用的抽象。
索引节点(inode): 对应一个实际物理文件的信息
存放文件系统处理文件所需要的所有信息
同一个文件系统中,每个索引节点号都是唯一的
同上,i-node
也定义了inode_operations && file_operations
,分别表示在 inode 上可以执行的操作以及 与文件和目录相关的方法(标准系统调用),见下图:
目录项(dentry): 表示具体文件的位置
文件系统中大都采用的树状结构,dentry
反映了这种树状关系。
dentry
和 inode
共同描述了一个文件
反应了在文件系统中的位置和信息
文件(file) : 对应打开文件与进程之间进行交互的有关信息
文件对象是已打开的文件在内存中的表示,主要用于建立进程和磁盘上的文件的对应关系
。它由sys_open() 现场创建,由sys_close()销毁。进程打开一个文件,内核就动态的创建一个文件对象,同一个文件在不同的进程中有不同的文件对象。
进程通过文件描述符来访问文件,C 语言封装为指针
文件对象和物理文件的关系有点像进程和程序的关系一样。
这里特别需要注意的地方是:内核中还有一个fd结构,它与int fd 不同,他包含了file结构
另外,和文件系统相关的还有:file_system_type && vfsmount
file_system_type结构用于描述具体的文件系统的类型信息。被Linux支持的文件系统,不管它有零个或多个实例被安装到系统中,都有且仅有一 个file_system_type结构.
而与此对应的是每当一个文件系统被实际安装,就有一个vfsmount结构体被创建,这个结构体对应一个安装点。
我们在编写程序的时候,肯定会时不时的用到关于文件的操作,那么进程与文件系统之间关系到底是怎么样的呐?
4.与进程相关的一个对象、两个结构
- 文件对象(见上文)
- 用户打开文件表 files_struct 结构
- 建立进程与文件系统的关系 fs_struct 结构
(1) 文件对象
(2) 用户打开文件对象集 files_struct 结构
文件描述符是用来描述打开的文件的。每个进程用一个files_struct结构来记录文件描述符的使用情况,这个files_struct结构称为用户打开文件表,它是进程的私有数据。
struct files_struct {//打开的文件集
atomic_t count; /*结构的使用计数*/
……
int max_fds; /*文件对象数的上限*/
int max_fdset; /*文件描述符的上限*/
int next_fd; /*下一个文件描述符*/
struct file ** fd; /*全部文件对象数组*/
……
};
(3) 建立进程与文件系统的关系 fs_struct 结构
fs_struct 结构描述进程与文件系统的关系
struct fs_struct {//建立进程与文件系统的关系 多对一的关系
atomic_t count; /*结构的使用计数*/
rwlock_t lock; /*保护该结构体的锁*/
int umask; /*默认的文件访问权限*/
struct dentry * root; /*根目录的目录项对象*/
struct dentry * pwd; /*当前工作目录的目录项对象*/
struct dentry * altroot; /*可供选择的根目录的目录项对象*/
struct vfsmount * rootmnt; /*根目录的安装点对象*/
struct vfsmount * pwdmnt; /*pwd的安装点对象*/
struct vfsmount * altrootmnt;/*可供选择的根目录的安装点对象*/
};
5.四个对象与进程是如何协同工作的
被Linux支持的文件系统,不管它有零个或多个实例被安装到系统中,都有且仅有一 个file_system_type
结构。每安装一个文件系统,就对应有一个超级块和安装点。超级块通过它的一个域s_type
指向其对应的具体的文件系统类型。具体的 文件系统通过file_system_type
中的一个域fs_supers
链接具有同一种文 件类型的超级块。同一种文件系统类型的超级块通过域s_instances
链 接。
超级块、安装点和具体的文件系统的关系
进程通过task_struct
中的一个域files_struct files
来了解它当前所打开的文件对象;而我们通常所说的文件 描述符其实是进程打开的文件对象数组的索引值。文件对象通过域f_dentry
找到它对应的dentry对象
,再由dentry对象
的域d_inode
找 到它对应的索引结点,这样就建立了文件对象与实际的物理文件的关联。最后,还有一点很重要的是, 文件对象所对应的文件操作函数 列表是通过索引结点的域i_fop
得到的。图6对第三部分源码的理解起到很大的作用。
进程与超级块、文件、索引结点、目录项的关系
好了,基本的也就这么多了,下面以read()
函数为例来总结一下总的调用过程
6.read() 函数
read函数在用户空间是由read系统调用实现的,由编译器编译成软中断 int 0x80 来进入内核空间,然后在中断门上进入函数sys_read,从而进入内核空间执行read操作。
sys_read函数定义在fs/read_write.c文件
进程打开一个文件时,会在内存组装一个文件对象,希望对该文件执行的操作方法已在文件对象设置好。所以对文件进行读操作时,VFS在做了一些简单的转换后(由文件描述符得到其对应的文件对象;其核心思想是返回current->files->fd[fd]所指向的文件对象,在函数 fget_light() 中进行的),就可以通过语句 file->f_op->read(file, buf, count, pos) 轻松调用实际文件系统的相应方法对文件进行读操作了。
具体代码调用如下:
1.进入陷阱门调用到函数 ksys_read()
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
return ksys_read(fd, buf, count); //最新的内核中的名字就是这个
}
2. 以下的都是旧版内核代码,但是意思真的一样,哈哈哈
ssize_t sys_read(unsigned int fd, char __user * buf, size_t count)
{
struct file *file;/*文件指针*/
ssize_t ret = -EBADF;
int fput_needed;
/*轻量级的由文件描述符得到文件指针函数*/
file = fget_light(fd, &fput_needed);
if (file) {
/*file结构体里的指示文件读写位置的int变量读取*/
loff_t pos = file_pos_read(file);
/*vfs虚拟文件系统实现read操作的地方*/
ret = vfs_read(file, buf, count, &pos);
/*file结构体里的指示文件读写位置的int变量写入*/
file_pos_write(file, pos);
/*释放file结构体指针*/
fput_light(file, fput_needed);
}
return ret;
}
3. vfs_read()函数
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;
/*首先检查文件是否可以读取,否则返回坏的文件描述符标记*/
if (!(file->f_mode & FMODE_READ))
return -EBADF;
/*如果没有对应的文件操作函数集合,也返回错误*/
if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
return -EINVAL;
/*检查有没有权限*/
if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
return -EFAULT;
/*检查当前写入的地方有没有被上锁,是否可读写*/
ret = rw_verify_area(READ, file, pos, count);
if (ret >= 0) {
count = ret;
/*安全操作*/
ret = security_file_permission (file, MAY_READ);
if (!ret) {
/*如果file结构体里有read函数,就调用*/
if (file->f_op->read)
ret = file->f_op->read(file, buf, count, pos);
else
/*否则就调用异步读取的*/
ret = do_sync_read(file, buf, count, pos);
if (ret > 0) {
/*成功读取以后,通知父目录已经读取,并在当前进程结构体上记录*/
fsnotify_access(file->f_path.dentry);
add_rchar(current, ret);
}
inc_syscr(current);
}
}
return ret;
}
附录:fget_light()函数
struct file fastcall *fget_light(unsigned int fd, int *fput_needed)
{
struct file *file;
/*得到当前进程的task_struct的打开的files指针*/
struct files_struct *files = current->files;
*fput_needed = 0;
/*如果只有一个进程使用这个结构体,就不必考虑锁,否则要先得到锁才可以读取*/
if (likely((atomic_read(&files->count) == 1))) {
/*从files结构体的fd数组上得到file结构体*/
file = fcheck_files(files, fd);
} else {
/*先上锁,在得到对应结构体*/
rcu_read_lock();
file = fcheck_files(files, fd);
if (file) {
if (atomic_inc_not_zero(&file->f_count))
*fput_needed = 1;
else
/* Didn't get the reference, someone's freed */
file = NULL;
}
rcu_read_unlock();
}
return file;
}