大家好,又见面了,我是全栈君。
为了提高效率,略微复杂一些的操作系统对文件的读写都是带缓冲的,Linux当然也不例外。所谓缓冲,就是操作系统为近期刚读写的文件内容在内核保留一份副本,以便当再次须要已经缓冲存储在副本中的内容时就不必再暂时从设备上读入,而须要写的时候则能够先写到副本中,待系统较为空暇的时候再从副本写入设备。在多进程的系统中,因为同一个文件可能为多个进程所共享,缓冲的作用就更为显著。
然而,怎么样实现缓冲,在哪一个层次上实现缓冲,确实一个值得考虑的问题。
在文件层有三种基本的数据结构:file、dentry、inode。
先看file结构:前面讲过。一个file结构代表着目标文件的一个上下文,不但不同的进程能够在同一个文件上建立不同的上下文(每一个进程都有自己的file结构体),就是同一个进程也能够通过打开同一个文件多次而建立起多个上下文。假设在file结构中设置一个缓冲区队列,那么缓冲区中的内容尽管贴近这个特定上下文的使用者,却不便于为多个进程共享,甚至不便于同一个进程打开的不同上下文共享,这显然是不合适的。
那么dentry结构怎么样呢?这个数据结构并不属于某一个上下文,也不属于一个进程,能够为全部进程和上下文共享。但是dentry结构与目标文件并非一一相应的关系,通过文件链接,我们能够为已经存在的文件建立别名。一个dentry结构知识唯一的代表这文件系统中的一个节点,也就是一个路径名,但是多个节点能够同一时候代表同一个文件,所以还应该再抽象一次。
显然,在inode数据结构设置一个缓冲队列是最合适只是了,首先。inode结构与文件是一一相应的关系。即使一个文件有多个路径名。最后也归为同一个inode上。再说,一个文件里的内容是不能由其它文件共享的,在同一时间里,设备上的每个记录都仅仅能属于至多一个文件,将载有同一个文件内容的缓冲区都放在其所属文件的inode结构中是非常自然的事。因此在inode数据结构中设置了一个指针i_mapping,它就指向一个address_space数据结构。缓冲区队列就在这个数据结构中。
只是。挂在缓冲区队列中的并非记录块而是内存页面。也就说,文件的内容并非以记录块为单位。而是以页面为单位进行缓冲的。为什么这个搞?这是为了将文件内容的缓冲与文件的内存映射结合在一起。进程能够通过系统调用mmap()将一个文件映射到它的用户空间。建立了这种映射以后。就能够像訪问内存一样訪问这个文件。假设将文件的内容以页面为单位缓冲,放在附属于该文件的inode结构的缓冲队列中,那么仅仅要对应的设置进程的内存映射表。就能够非常自然地将这些缓冲页面映射到用户空间中。这样。在按常规方式文件操作訪问一个文件时,就能够通过read()和write()系统调用訪问目标文件的inode结构訪问这些缓冲页面;而通过内存映射机制訪问这个文件时。就能够经由页面映射直接訪问这些缓冲着的页面。当目标页面不在内存中时,常规的文件操作通过系统调用read()、write()的底层将其从设备读入。而通过内存映射机制訪问这个文件时,则由缺页异常的服务程序将目标页面从设备上读入。明确了这个背景,就明确上述的指针为什么叫i_mapping,它指向的数据结构为什么叫address_space就不会奇怪了。
但是,虽然以页面为单位的缓冲对于文件层确实是非常好的选择,对于设备层则不那么合适了。对设备层而言。最自然的当然是以记录块为单位的缓冲,由于设备的读写都是以记录块为单位的。
只是,从磁盘上读写基本的时间都花在准备工作上,一旦准备好了以后读一个记录块与接连读几个记录块相差并不大,并且每次仅仅读写一个记录块反而是不经济的。所以每次读写若干连续的记录块、以页面为单位缓冲并非问题。
还有一方面,假设以页面为单位缓冲。而一个页面相当于若干连续记录块,那么不管是对于缓冲页面还是对于记录块缓冲区,其控制信息显然应该游离于该页面之外,这些信息不应该映射到进程的用户空间。
这个问题不难解决。
在设备层中要保持一些buffer_head结构。让它们的b_data指针分别指向缓冲区页面中的对应位置就能够了。
以一个缓冲页面为例,在文件层它通过一个page数据结构挂入所属inode结构的缓冲页面队列。并且同一时候又能够通过各个进程的页面映射表映射到这些进程的内存空间。而在设备层又通过若干buffer_head结构挂入其所在设备的缓冲区队列。
在这样一个结构框架中,一旦所欲訪问的内容已经在缓冲页面队列中,读文件的效率就非常高了。仅仅要找到文件的inode结构就找到了缓冲页面的队列,从队列中找到对应的页面就能够读出了。
上面的设备缓冲区队列我们还没介绍是什么东西,以下来介绍
当一个块调入内存时,它要存储在一个缓冲区中。每一个缓冲区与一个块相应。它相当于是磁盘块在内存中的表示,磁盘块包括一个或者多个扇区,可是不能超过一个页面。所以一个物理页能够容纳一个或者多个内存中的块。因为内核在处理数据时须要知道一些相关信息(比方块属于哪一个块设备。块相应于哪个缓冲区等),所以每一个缓冲区都有一个相应的描写叙述符。该描写叙述符用buffer_head结构体表示。成为缓冲区头,在文件<linux/buffer_head.h>中定义,它包括了内核操作缓冲区的所有信息。
事实上这个buffer_head存在于linux2.4版本号中。在linux中使用bio结构体取代。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/116504.html原文链接:https://javaforall.cn