专家
公告
财富商城
电子网
旗下网站
首页
问题库
专栏
标签库
话题
专家
NEW
门户
发布
提问题
发文章
Linux内核——第十五章:页高速缓存
2019-04-14 21:48
发布
生成海报
站内文章
/
模拟电子
15799
0
1712
文章中,红 {MOD}为不理解的问题,紫 {MOD}为名词和问题标注。
有问题的地方欢迎在评论中提出,以便及时改正~
基本知识:
计算机:
CPU
(运算器、控制器、寄存器、髙速缓存、总线)
内存(也叫随机存储器
RAM
)
----
体积小、速度快、有电可存、无电清空
硬盘(属于外存)
Linux
内核
,即
Linux
操作系统所使用的内核
?
"内核"
指的是一个提供硬件抽象层,磁盘及文件系统控制,多任务等功能的系统软件。一个内核不是一个完整的操作系统。
一套基于
Linux
内核的完整操作系统叫做
Linux
操作系统
高速缓存:
介于
CPU
和内存之间,是高速小容量存储器
磁盘高速缓存:
介于内存和硬盘之间?是一种软件机制,允许系统把通常放在磁盘上的一些数据保。留在
RAM
中,以便对数据的进一步访问不用再访问磁盘而尽快得到满足。
(二者重点区别)?
其它高速缓存:目录项高速缓存(存放描述文件系统路径名的目录项对象)
索引节点高速缓存(存放描述磁盘索引节点的索引节点对象)
页高速缓存
:一种对完整的数据页进行操作的
磁盘高速缓存
。是
Linux
内核所使用的主要磁盘高速缓存。在多数情况下,内核在读写磁盘时都引用页高速缓存。新页被追加到页高速缓存以满足用户进程的读请求。如果页不在高速缓存中,新页就被加到高速缓存中,然后用磁盘读出的数据填充它。如果内存有足够的空闲空间,就让该页在高速缓存中长期保留,使其他进程再使用该页时不再访问磁盘。
实现页高速缓存的目的:
1.
快速定位含有给定所有者相关数据的特定页。为了尽可能充分发挥页高速缓存的优势,对它应该釆用高速的捜索操作。
2.
记录在读或写页中的数据时应当如何处理高速缓存中的每个页。例如,从普通文件、块设备文件或交换区读一个数据页必须用不同的实现方式,因此内核必须根据页的所有者选择适当的操作。
页高速缓存中的信息单位是一个完整的数据页。通过页的所有者和所有者数据中的索引来识别页高速缓存中的页。
页高速缓存中的核心数据是
address-space
对象,是一个嵌入在页所有者的索引节点对象中的数据结构。
Mapping
和
index
是两个把页链接到页高速缓存的两个字段
,每个页描述符都包括这两个字段。
Mapping
字段
指向拥有页的索引结点的
address-space
对象
Index
字段
表示在所有者的地址空间中以页为单位的偏移量,即在所有者的磁盘映象中页中数据的位置。
如果页属于一个文件,那么
页的所有者
就是文件的索引节点
,而相应的
address-space
对象存放在
VFS(?)
索引节点对象的
i-data
字段中。索引节点的
I-mapping
字段指向同一个结点的
i-data
字段,而
address-space
对象的
host
字段也指向这个索引节点。
基数
Radix tree
Linux
访问大文件时,用大量的捜索树实现页高速缓存的高效查找,每一个
address-space
对象对应一棵捜索树。
脏页
:应当被刷新到磁盘的页。
页索引:
表示页在所有者磁盘映象中的位置
Page-tree
字段(在
address-space
对象中
)------
基树的根
含有指针
-----
指向页所有者的页描述符
当查找所需要的页时,内核把页索引转换为基树中的路径
(
基树中的路径是什么
?)
并快速找到页描述符所在的位置。
1.
找到:获取页描述符,确定该页是否是脏页,确定其
数据
IO??
传送
是否在进行。
2.
没找到怎么办
??
基树的每个节点可以有多到
64
个指针指向其他节点或页描述符。
每个节点由
radix_tree_node
数据结构表示
Struct radix_tree_node
{
*ElemType
slots[64];
//
包含
64
个指针的数组
int
count;
//
记录节点中非空指针数量的计数器
ElemType
tags[ ][ ];
//
二维标志数组
}
Struct radix_tree_root
{
Int
height;
//
表述树的当前深度(不包括叶子节点的层数)
ElemType
gfp_mask;
//
指定为新节点请求内存时所用的标志
ElemType
rnode;
//
指向与树中第一层节点相应的数据结构
radix_tree_node
}
如果树中的索引没有大于
63
,那么树的深度就等于
1
,因为可能存在的
64
个叶子都存放在第一层的节点中。
如果与索引
131
相应的新页的描述符肯定存放页高速缓存中(叶子什么时候存放在第一层节点?什么时候存放在页高速缓存?)
,那么树的高度就增加为
2
,这样基树就可以查找多达
4095
个索引(
64*64=4096
,即
0~4095
)
基树的最大深度是
6
,但系统中的页告诉缓存不大可能使用那么大的基树。因为页索引存放在
32
位变量中,当树的深度为
6
时,最高层的叶子节点最多可以有
4
个孩子节点。
分页系统是如何利用页表实现线性地址到物理地址的转换的?如何实现页查找?
线性地址最高
20
位分成两个
10
位的字段,第一个字段是页目录中的偏移量,而第二个字段是某个页目录项所指的页表中的偏移量。
基树中,页索引相当于线性地址。但页索引中要考虑的字段的数量依赖于基树的深度。如果基树的深度为
1
,就只能表示
0~63
范围的索引,因此页索引的低
6
位被解释为
slots
数组的下标,每个下标对应第一层的一个节点。如果基树的深度为
2
,就可以表示
0~4095
范围的索引,页索引的低
12
位分成
2
个
6
位的字段,高位的字段用于表示第一层节点数组的下标,
而地位的字段用于表示第二层节点数组的下标。以此类推,如果深度等于
6
,页索引的最高两位表示第一层节点数组的下标,接下来
6
位表示第二层节点数组的下标,这样一直到最低
6
位,它们表示第六层节点数组的下标。
页高速缓存的处理函数
1.
查找页
find_get_page(
指向
address_space
对象的指针,指向
address_space
对象的偏移量
)
{
获取地址空间的
自旋锁?
;
调用
radix_tree_lookup( )
函数;
//
根据偏移量值中的位依次从树根开始向下搜索,搜索拥有指定偏移量的基树的叶子节点
如果遇到空指针
返回
NULL
;
Else
返回叶子节点的地址;
//
即所需要的页描述符指针
如果根据也描述符找到了所需要的页
{
Find_get_page
函数就增加该页的使用计数器;
释放自旋锁;
返回该页的地址;
}
Else
{
释放自旋锁;
返回
NULL
;
}
}
2.
增加页
add_to_page_cache(
页描述符的地址
page
,
address_space
对象的地址
mapping
,表示地址空间内的页索引的值
offset
,为基树分配新节点时所使用的内存分配标志
gfp_mask)
{
调用
radix_tree_preload( )
函数;
/
/
禁用内核抢占?
,并把一些空的
radix_tree_node
结构赋给每个
CPU
变量
radix_tree_preloads
获取
mapping->tree_lock
自旋锁;
调用
radix_tree_insert( )
在树中插入新节点;
增加页描述符的使用计数器
page->count
设置页框的
PG_locked
标志;
//
新页的内容无效,设置这个标志来阻止其它的内核路径并发访问该页
用
mapping
和
offset
参数
初始化
page->mapping
和
page->index;
递增在地址空间所缓存页的计数器
释放地址空间的自旋锁;
调用
radix_tree_preload_end( )
;
//
重新启用内核抢占?
return 0
;
}
3.
删除页
remove_from_page_cache( )
{
获取自旋锁
page->mapping->tree_lock
并
关掉中断?
;
调用
radix_tree_delete( )
函数从树中删除节点;
把
page->mapping
字段置为
NULL
;
把所缓存页中的
page->mapping->nrpages
计数器的值减
1
;
释放自旋锁
page->mapping->tree_lock
,
打开中断,函数终止?
}
4.
更新页(确保高速缓存中包括最新版本的指定页)
read_get_page(
指向
address_space
对象的地址
mapping
,表示所请求页的偏移量的值
index
,指向从磁盘度页数据的函数的指针
filler
,传递给
filler
函数的指针
data)
{
调用函数
find_get_page( )
;
//
检查页是否已经在高速缓存中
如果页不在高速缓存中
{
调用
alloc_pages
分配一个新页框;
调用
add_to_page_cache( )
;
//
在页高速缓存中插入相应的页描述符
调用
lru_cache_add( )
;
//
把页插入该管理区的非活动
LRU
链表中
}
此时,页已经在高速缓冲中了
调用
mark_page_accessed( )
;
//
记录页已经被访问过的事实
如果页不是新的(即
PG_locked
标志为
0
)
调用
filler
函数;
//
从磁盘读该页
返回页描述符的地址;
}
把块存放在页高速缓存中
——讨论如何使用该页高速缓存快速检索单独的数据块(例如超级块和索引节点),是提高虚拟文件系统和磁盘文件系统的关键特征。
“块”:
一种组织磁盘数据的逻辑单位。
稳定版本的
LINUX
内核不再单独分配块缓冲区,相反,把它们都存放在叫做“
缓冲区页
”的专门页中,而缓冲区页存在页高速缓存中。
“缓冲区首部”
是附加描述符。该描述符包含内核必须了解的、有关如何处理块的所有信息。
缓冲区页在形式上就是与称作“缓冲区首部”的附加描述符相关的数据页,其主要目的是快速确定叶中的一个块在磁盘中的地址。
如下图:
(在对块的操作之前要检查缓冲区首部)
分配块设备缓冲区页
当内核发现指定块的缓冲区页不在页告诉缓存中时,就分配一个新的块设备缓冲区页。
内核调用
grow_buffers( )
把块设备缓冲区添加到页告诉缓存中
grow_buffers(block_device
描述符的地址
bdev,
块在设备中的位置
block
,块大小
size)
{
计算数据页在所请求块的块设备中的偏移量
index
;
如果需要,调用
grow_dev_page( )
;
//
创建新的块设备缓冲区页
为页解锁;
//
函数
find_or_create_page( )
曾为页加了锁
递减页的使用计数器
;
return 1;
}
释放块设备缓冲区页
当内核驶入获得更多的空闲内存时,就释放块设备缓冲区页(不可能释放有脏的缓冲区的页
或上锁的缓冲区页)
内核调用
try_to_release_page( )
释放缓冲区页
try_to_release_page(
页描述符的地址
page)
{
如果设置了页的
PG_writeback
标志
//
因为正在把页写回磁盘,不可能释放该页
Return 0
;
如果已经定义了块设备
address_space
对象的
releasepage
方法
就调用该方法(这个方法通常没有)
调用
try_to_free_buffers( )
并返回它的错误代码;
}
try_to_free_buffers( )
——依次扫描链接到缓冲区页的缓冲区首部
{
检查页中所有缓冲区的缓冲区首部标志;
如果有些缓冲区首部的
BH_Dirty
或
BH_Locked
标志被置位
//
说明函数不可能释放这些缓冲区
函数终止,
return 0
;
如果缓冲区首部在间接缓冲区的链表中
从链表中删除它;
清除页描述符的
PG_private
标记
把
private
字段设置为
NULL
递减页的使用计数器
清除页的
PG_dirty
标记
反复调用
free_buffer_head()
//
释放页的所有缓冲区首部
return 1
;
}
在页高速缓存中搜索块
当内核需要读或写一个单独的物理设备快时(例如一个
超级块??
),必须检查所请求的块缓冲区是否已经在页高速缓存中。
在页高速缓存中搜索指定的块缓冲区:
1.
获取一个指针,让他指向包含指定块的块设备的
address_space
对象
bdev->bd_inode->i_mapping)
2.
获得设备的块大小,并计算包含指定块的页索引
3.
在块设备的基树中搜索缓冲区页,获得页描述符之后,内核访问缓冲区首部,它描述了页中块缓冲区的状态。
Find_get_block(block_device
描述符地址
bdev
,块号
block
,块大小
size )
//
函数返回页高速缓存中的块缓存中的块缓冲区对应的缓冲区首部的地址;如果不存在指定的块,就返回
NULL
。
{
检查执行
CPU
的
LRU
块高速缓存数组中是否有一个缓冲区首部,其
b_bdev
,
b_blocknr
和
b_size
字段分别等于
bdev
、
block
和
size
。
如果缓冲区首部在
LRU
块高速缓存
中
刷新数组中的元素
如果缓冲区首部不在
LRU
块高速缓存中
Index=block>>(PAGE_SHIFT-bdev->bd->i_blkbits)
调用
find_get_page( );
//
确定存有所请求的块缓冲区的缓冲区页的描述符在页高速缓存中的位置。
【函数已经得到了缓冲区页描述符的地址】
扫描链接到缓冲区页的缓冲区首部链表,查找逻辑块号等于
block
的块
递减页描述符
count
字段
把
LRU
块高速缓存中的所有元素向下移动一个位置,并把指向所请求块的缓冲区首部的指针插入到第一个位置
如果一个缓冲区首部已经不再
LRU
块高速缓存中,就递减它的引用计数器
b_count
。
如果需要,调用
mark_page_accessed();
//
把缓冲区页移至适当的
LRU
链表中
返回缓冲区首部指针。
}
Getblk(block_device
描述符的地址
bdev
,块号
block
,块大小
size )
——分配块设备缓冲区页并返回将要描述块的缓冲区首部的指针。
{
调用
find_get_block();
//
检查块是否已经在页高速缓存中
如果找到块
返回块的缓冲区首部的地址;
Else
调用
grow_buffers();
//
为请求的页分配一个新的缓冲区页
如果
grow_buffers()
分配页时失败
调用函数
free_more_memory();
//
回收一部分内存
Ta的文章
更多
>>
Linux内核——第十五章:页高速缓存
0 个评论
热门文章
×
关闭
举报内容
检举类型
检举内容
检举用户
检举原因
广告推广
恶意灌水
回答内容与提问无关
抄袭答案
其他
检举说明(必填)
提交
关闭
×
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮