文件的写与读

来源:互联网 发布:淘宝发布q币教程 编辑:程序博客网 时间:2024/04/29 18:28

文件的写与读

《linux内核源代码情景分析》P579,之前看过一次,当时不是很清楚讲,现在回头看了下,有些明白,于是想记录一下心得,甚是开心。
本篇主要叙述页缓存,为什么缓存在页层,如何做页缓存。
有没有想过进程是如何读写文件的呢?
读写文件的进程,目标文件由一个“打开文件号”代表。
为了效率,一些操作系统对文件的读写都是带缓冲的,linux操作系统也不例外。linux文件系统的特色机制:VFS和缓冲。缓冲,指系统最近刚读/写过的文件内容在内核中保留一份副本,当再次读/写文件直接从副本读取,不需要再从设备上读入。在多进程的系统中,由于同一文件可能为多个进程所共享,缓冲就更能提高读写效率。
《原书》都用缓冲,好想改成缓存。

2. 缓冲在哪一层实现

从用户空间执行read等系统调用——>系统调用层——>VFS层——>具体的文件系统层——>设备层,这是一个层次关系。
《源码情景分析》讲的很清楚,在用户空间做缓冲可以,那就需要用户进程介入,不能做到对用户透明,且缓存的内容不能被其他进程共享,排除;再往下是,VFS层、具体的文件系统层、设备层,都是在内核中,所以这些层次上实现缓存都能达到对用户透明的效果。总之,说了一堆,最后只有在VFS层实现缓冲。

3. 缓冲如何实现

那如何在VFS层实现呢?
VFS层有3种主要的数据结构,file结构、dentry结构、inode结构。

3.1 file、dentry、inode

先看file结构。一个file结构代表着目标文件的一个上下文,不但不同的进程可以在同一个文件上建立不同的上下文,就是同一个进程也可以通过打开一个文件多次而建立起多个上下文。如果在file结构中设置一个缓冲区队列,缓冲区内容不便于多个进程共享,甚至不便于同一进程打开的不同上下文共享,所以file结构不行。理想的是,将缓冲区放在一个公共区域,大家都能使用。
接着看dentry结构。dentry结构,可以为所有的进程和上下文共享,但dentry与目标文件不是一对一的关系,通过连接,可以对一个已存在的文件建立别名。一个dentry只能对应一个路径名,有时可能多个路径名对应同一个文件,不能达到共享的目标。
最后,在inode结构中设置缓冲区队列,inode与文件是一对一的关系,即使一个文件由多个路径名,最后也归结到同一个inode结构中。另外,一个文件中的内容是不能由其他文件共享的,同一时间里,设备上的每一个磁盘块只能属于至多一个文件,所以将有同一个文件内容的缓冲区都放在其所属文件的inode结构也是很自然的事情。请看inode结构:

struct inode {    ......    struct address_space    *i_mapping;              struct address_space    i_data;                  ......};

指针i_mapping它指向一个address_space数据结构(通常这个数据结构就是inode结构中的i_data),缓冲区队列就在这个数据结构中。
为什么还要有个i_mapping指针,多此一举?

3.2 缓冲区队列实现

在没有完全搞清楚,没有完全把握的情况下,不要任意修改原版的内容。
挂在缓冲区队列中的并不是磁盘块,而是内存页面。也就是说,文件的内容并不是以磁盘块为单位,而是以页面为单位进行缓冲的。如果磁盘块大小是1K,那么一个页面就相当于4个磁盘块,为什么这样做呢?为了将文件内容的缓冲和文件的内存映射结合在一起。

文件的内存映射
参考[1]讲的很好的了,务必看下,内存映射只是将硬盘上文件的位置与进程 逻辑地址空间中一块大小相同的区域之间的一一对应,但并没有实际的数据拷贝,文件没有被载入内存,只是逻辑上被放入了内存。

那进程又怎么能最终直接通过内存操作访问到硬盘上的文件呢?那就要看内存映射之后的几个相关的过程了。
看图
void* mmap ( void * addr , size_t len , int prot , int flags ,int fd , off_t offset );
通过mmap()系统调用,将一个已打开文件的内容映射到进程的虚拟内存空间。mmap()会返回一个指针ptr,它指向进程逻辑地址空间中的一个地址,这样以后,进程无需再调用read或write对文件进行读写,而只需要通过ptr就能够操作文件。但是ptr所指向的是一个逻辑地址,要操作其中的数据,必须通过MMU将逻辑地址转换成物理地址。

将文件在磁盘上的内容和文件的内存映射结合起来后,进程就可以像访问内存一样访问文件。如果将文件的内容以页面为单位缓冲,放在附属于该文件的inode结构的缓冲队列中,那么只要相应地设置进程的内存映射表,就可以很自然地将这些缓冲页面映射到进程的虚拟内存空间。这样,在按常规的文件操作访问一个文件时,可以通过read()和write()系统调用目标文件的inode结构访问这些缓冲页面;而通过内存映射机制访问这个文件时,就可以经由页面映射表直接读写这些缓冲着的页面。当目标页面不在内存时,常规的文件操作通过系统调用read()、write()的底层将其从设备上读入,而通过内存映射机制访问这个文件时则由“缺页异常”的服务程序将目标页面从设备上读入。
不是很明白,先写到这里,回来补充。

参考:
[1] http://blog.csdn.net/mg0832058/article/details/5890688
[2] 《linux内核源代码情景分析》毛德操

0 0
原创粉丝点击