页高速缓存是一种软件机制
页高速缓存是一种软件机制,它允许系统把通常存放在磁盘上的一些数据保存在RAM中,以便对那些数据的进一步访问不用再访问磁盘而能尽快的到满足。
页缓存和硬件cache的原理基本相同,将容量大而低速设备中的部分数据存放到容量小而快速的设备中,这样速度快的设备将作为低速设备的缓存,当访问低速设备中的数据时,可以直接从缓存中获取数据而不需再访问低速设备,从而节省了整体的访问时间。
页缓存以页为大小进行数据缓存,它将磁盘中最常用和最重要的数据存放到部分物理内存中,使得系统访问块设备时可以直接从主存中获取块设备数据,而不需从磁盘中获取数据。
因为对同一磁盘数据的反复访问频繁发生,所以磁盘高速缓存对系统性能至关重要。
页高速缓存:一种对完整的数据页进行操作的磁盘高速缓存
页高速缓存(page cache),是linux内核所使用的主要磁盘高速缓存。在绝大多数情况下,内核在读写磁盘时都引用页高速缓存。新页被追加到页高速缓存以满足用户态进程的读请求
如果页不在高速缓存中,新页就被追加到高速缓存中,然后用从磁盘读出的数据填充它。如果内存有足够的空闲空间,就让该页在高速缓存中长期保留,使其他进程再使用该页时不再访问磁盘。
同样在把一页数据写到块设备之前,内核首先检查对应的页是否已经在高速缓存中;不在就还是添加新项,并用要写到磁盘的数据填充该项,IO数据的传送并不是马上开始,而是要延迟几秒才对磁盘更新,从而使进程有机会对要写入的数据做进一步修改(换句话说就是内核执行延时的写操作)。
写操作与读操作时类似,直接在页缓存中修改数据,但是页缓存中修改的数据(该页此时被称为Dirty Page)并不是马上就被写入磁盘,而是延迟几秒钟,以防止进程对该页缓存中的数据再次修改。
几乎所有的文件读和写都依赖于页高速缓存,只有在O_DIRECT标志被置位而进程打开的情况下才会出现例外;此时,IO数据的传送绕过了页高速缓存而使用了进程用户态地址空间的缓冲区(直接IO传送);少数数据库应用软件为了能采用自己的磁盘高速缓存算法而使用了O_DIRECT。
直接IO传送:
考虑高性能数据库服务器;他们大多实现了自己的高速缓存机制,以充分挖掘对数据库独特的查询机制。对于这些类型的程序,内核页高速缓存毫无帮助,相反由于下面原因它是有害的
- 很多页框浪费在复制已在RAM中的磁盘数据上(在用户级磁盘高速缓存中)
- 处理页高速缓存和预读多于指令降低了read()和write()的系统调用执行效率,也降低了与文件内存映射相关的分页操作。
- read() write()的系统调用不是在磁盘和用户存储器之间直接传输数据。而是分两次传送:磁盘和内核缓冲区之间和内核缓冲区与用户存储器之间。
因此必须通过中断和DMA处理快硬件设备,而且这只能在内核态完成,因此最终需要某种内核支持来实现自缓存的应用程序。
linux提供了绕过页高速缓存的简单方法:直接IO传送,在每次直接IO传送中,内核对磁盘控制器进行编程,以便在自缓存的应用程序的用户态地址空间中的页与磁盘之间直接传输数据。(还有很多具体细节后面写)
内核缓冲区:除了在进程中设计缓冲区,内核也有自己的缓冲区。
当一个用户进程要从磁盘读取数据时,内核一般不直接读磁盘,而是将内核缓冲区中的数据复制到进程缓冲区中。(这里是在RAM上的东西)
但若是内核缓冲区中没有数据,内核会把对数据块的请求,加入到请求队列,然后把进程挂起,为其它进程提供服务。
等到数据已经读取到内核缓冲区时,把内核缓冲区中的数据读取到用户进程中,才会通知进程,当然不同的io模型,在调度和使用内核缓冲区的方式上有所不同。
你可以认为,read是把数据从内核缓冲区复制到进程缓冲区。write是把进程缓冲区复制到内核缓冲区。
当然,write并不一定导致内核的写动作,比如os可能会把内核缓冲区的数据积累到一定量后,再一次写入。这也就是为什么断电有时会导致数据丢失。
所以说内核缓冲区,是为了在OS级别,提高磁盘IO效率,优化磁盘写操作。
用户进程缓冲区
前面提到,用户进程通过系统调用访问系统资源的时候,需要切换到内核态,而这对应一些特殊的堆栈和内存环境,必须在系统调用前建立好。而在系统调用结束后,cpu会从核心模式切回到用户模式,而堆栈又必须恢复成用户进程的上下文。而这种切换就会有大量的耗时。
你看一些程序在读取文件时,会先申请一块内存数组,称为buffer,然后每次调用read,读取设定字节长度的数据,写入buffer。(用较小的次数填满buffer)。之后的程序都是从buffer中获取数据,当buffer使用完后,在进行下一次调用,填充buffer。
所以说:用户缓冲区的目的是为了减少系统调用次数,从而降低操作系统在用户态与核心态切换所耗费的时间。
CPU缓存(Cache Memory)
是位于CPU与内存之间的临时存储器,因为cpu的计算速度要比内存的读写速度快很多,而把这些可能会被重复访问到的数据存储于cpu缓存中,就会提高读取速度。可以说缓存是cpu和内存之间的临时存储器。
缓存的应为是cache,缓冲区的英文是buffer
回过头来继续页高速缓存:
页高速缓存的信息单位显然不是一个完整的数据页,一个页包含的磁盘块在物理上不一定是相邻的,所以不能用设备 和块 来识别它,取而代之的是所有者和所有者数据中的索引(通常是一个索引节点在相应文件中的偏移量)来识别页高速缓存中的页。
//这里基础不能忘掉:通常将虚拟地址空间以512Byte ~ 8K,作为一个单位,称为页(win为页4k),并从0开始依次对每一个页编 。这个大小通常被称为页面 ,记住虚拟的叫页物理的叫框,操作系统通过维护一张表,这张表上记录了每一对页和框的映射关系叫页表。
页缓存的设计需求
内核通过抽象出address_space数据结构来满足上述两种设计需求。
address_space对象
页高速缓存的核心数据结构是adress_space对象,它是一个嵌入在页所有者的索引节点对象中的数据结构。高速缓存中的许多页可能属于同一所有者,从而可能被链接到同一个address_space对象,(这里区别硬链接是多个文件相同索引指令ln,它只是一个索引对应多个页的数据),该对象还在所有者的页和对这些页的操作之间建立起链接关系。
每个页描述符都包括把页链接到页高速缓存的两个字段mapping和index。mapping字段指向拥有页的索引节点的address_space对象,index字段表示在所有者的地址空间中以页的大小为单位的偏移量,也就是在所有者的磁盘影像中页的数据的位置,在页高速缓存查找页是使用这两个字段。
页高速缓存可包含同一磁盘数据的多个副本。
因此,两个不同 address_space 对象所引用的两个不同的页中出现了相同的磁盘数据。
address_space 的一些字段:
- host:如果页高速缓存中页的所有者是一个文件,address_space 对象就嵌入在 VFS 索引节点对象的 i_data 字段中。
索引节点的 i_mapping 字段总是指向索引节点的数据页所拥有的 address_space 对象。
address_space 对象的 host 字段指向其所有者的索引节点对象。 - backing_dev_info:指向 backing_dev_info 描述符,是对所有者的数据所在块设备进行描述的数据结构。
backing_dev_info 结构通常嵌入在块设备的请求队列描述符中。 - private_list:普通链表的首部,文件系统在实现其特定功能时可随意使用。
如,Ext2 文件系统利用该链表收集与索引节点相关的“间接”块的脏缓冲区。
当刷新操作把索引节点强行写入磁盘时,内核页同时刷新该链表中的所有缓冲区。 - a_ops:指向一个类型为 address_space_operations 的表,表中定义了对所有者的页进行处理的各种方法。
最重要的方法是 readpage、writepage、prepare_write 和 commit_write。
address_space结构是页高速缓存机制中的核心数据结构,该结构并不是对某一个页高速缓存进行描述,而是以页高速缓存的所有者(owner)为单位,对其所拥有的缓存进行抽象描述。页高速缓存中每个页包含的数据肯定属于某个文件,该文件对应的inode对象就称为页高速缓存的所有者。
页缓存与文件系统和内存管理都有联系。每个inode结构中都嵌套一个address_space结构,即inode字段中的i_data;同时inode中还有i_maping字段指向所嵌套address_spaces结构。而address_space结构通过host字段反指向页高速缓存的所有者。页缓存的本质就是一个物理页框,因此每个页描述符中通过mmaping和index两个字段与高速缓存进行关联。mmaping指向页缓存所有者中的address_space对象。index表示以页大小为单位的偏移量,该偏移量表示页框内数据在磁盘文件中的偏移量。
address_space结构中的i_mmap字段指向一个radix优先搜索树。该树将一个文件所有者中的所有页缓存组织在一起,这样可以快速搜索到指定的页缓存。内核中关于radix树有一套标准的使用方法,它不与特定的数据联系(与内核双联表类似),这样使得使用范围更加灵活。具体操作如下:
radix_tree_lookup():在radix树中对指定节点进行查找;
radix_tree_insert():在radix树中插入新节点;
radix_tree_delete():在radix树中删除指定节点;
通过上面的描述,可以看到address_space结构中的优先搜索树和钩子函数集解决了页高速缓存的两个主要设计需求。
内核对页缓存的操作函数
内核对页缓存的基本操作包含了在一个页缓存所形成的radix树中查找,增加和删除一个页缓存。基于radix的基本操作函数,页高速缓存的处理函数如下:
page_cache_alloc():分配一个新的页缓存;
find_get_page():在页高速缓存中查找指定页;
add_to_page_cache():把一个新页添加到页高速缓存;
remove_from_page_cache():将指定页从页高速缓存中移除;
read_cache_page():确保指定页在页高速缓存中包含最新的数据;
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!