显存映射
化学显存合称为寻址,动态随机访问显存(DRAM)。只有内核才可以直接访问数学显存。
Linux内核给每位进程都提供了一个独立的虚拟地址空间,但是这个地址空间是连续的。这样,进程就可以很便捷地访问显存,更准确地说是访问虚拟显存。虚拟地址空间的内部又被分为内核空间和用户空间两部份。
进程在用户态时,只能访问用户空间显存;只有步入内核态后,才可以访问内核空间显存。其实每位进程的地址空间都包含了内核空间,但这种内核空间,虽然关联的都是相同的化学显存,也就是共享动态链接库、共享显存等。当进程切换到内核态后,就可以很便捷地访问内核空间显存。
并不是所有的虚拟显存就会分配化学显存,只有这些实际使用的虚拟显存才分配化学显存,但是分配后的化学显存,是通过显存映射来管理的。显存映射,虽然就是将虚拟显存地址映射到化学显存地址。为了完成显存映射,内核为每位进程都维护了一张页表,记录虚拟地址与化学地址的映射关系。
页表实际上储存在CPU的显存管理单元MMU中,这样,正常情况下,处理器就可以直接通过硬件,找出要访问的显存。而当进程访问的虚拟地址在页表中查不到时,系统会形成一个缺页异常,步入内核空间分配化学显存、更新进程页表,最后再返回用户空间,恢复进程的运行。
CPU上下文切换中的TLB(TranslationLookasideBuffer,转译后备缓冲器)是MMU中页表的高速缓存。因为进程的虚拟地址空间是独立的linux是什么系统,而TLB的访问速率又比MMU快得多,所以,通过降低进程的上下文切换,降低TLB的刷新次数,就可以提升TLB缓存的使用率,从而提升CPU的显存访问性能。
MMU规定了一个显存映射的最小单位,也就是页,一般是4KB大小。这样,每一次显存映射,都须要关联4KB或则4KB整数倍的显存空间。
4KB大小的页,会造成整个页表会显得十分大,例如32位系统4GB/4KB=100多万个页表项。为了解决页表项过多的问题,Linux提供了两种机制,也就是多级页表和大页(HugePage)。
多级页表就是把显存分成区块来管理,将原先的映射关系改成区块索引和区块内的偏斜。因为虚拟显存空间一般只用了极少一部份,这么,多级页表就只保存那些使用中的区块,这样就可以大大地减低页表的项数。Linux用四级页表来管理显存页,虚拟地址被分为5个部份,前4个表项用于选择页,而最后一个索引表示页内偏斜。
大页,就是比普通页更大的显存块,常见的大小有2MB和1GB。大页一般用在使用大量显存的进程上,例如Oracle、DPDK等。
通过这种机制,在页表的映射下,进程就可以通过虚拟地址来访问数学显存了。
虚拟显存空间分布
最上方的是内核空间,下方的是用户空间显存,用户空间又被分成多个不同的段
用户空间显存,从低到高分别是5种不同的显存段
1、只读段,包括代码和常量等
2、数据段,包括全景变量等
3、堆,包括动态分配的显存,从低地址开始向下下降
4、文件映射段,包括动态库,共享显存等,从高地址开始向上下降
5、栈,包括局部变量和函数调用的上下文等,栈的大小是固定的,通常是8M
这5个显存段中,堆和文件映射的显存是动态分配的,例如使用C标准库的malloc或mmap(),就可以分别在堆和文件映射段动态分配显存。64位系统的显存分布也是类似的,只是显存空间要大的多
显存分配与回收
malloc()是C标准库提供的显存分配函数,对应到系统调用上,有两种实现方法,即brk()和mmap()。
对小块显存(大于128K),C标准库使用brk()来分配,也就是通过联通堆顶的位置来分配显存。这种显存释放后并不会立即归还系统,而是被缓存上去,这样就可以重复使用。
对大块显存(小于128K),则直接使用显存映射mmap()来分配,也就是在文件映射段找一块空闲显存分配出去。
这两种方法的异同点:
brk()方法的缓存,可以降低缺页异常的发生,提升显存访问效率。不过,因为这种显存没有归还系统,在显存工作忙碌时,频繁的显存分配和释放会导致显存碎片。
mmap()方法分配的显存,会在释放时直接归还系统,所以每次mmap就会发生缺页异常。在显存工作忙碌时,频繁的显存分配会造成大量的缺页异常,使内核的管理负担减小。这也是malloc只对大块显存使用mmap的诱因。
须要注意的是:当这两种调用发生后,虽然并没有真正分配显存。这种显存,都只在首次访问时才分配,也就是通过缺页异常步入内核中,再由内核来分配显存。
整体来说,Linux使用伙伴系统来管理显存分配。上面我们谈到过,这种显存在MMU中以页为单位进行管理,伙伴系统也一样linux漏洞扫描,以页为单位来管理显存,而且会通过相邻页的合并,降低显存碎片化(例如brk方法导致的显存碎片)。
但在实际系统运行中,会有大量比页还小的对象,如不到1K,倘若为它们也分配单独的页,会浪费大量的显存,那该如何分配显存呢?
在用户空间linux 用户分配空间,malloc通过brk()分配的显存,在释放时并不立刻归还系统,而是缓存上去重复借助。
在内核空间,Linux则通过slab分配器来管理小显存。你可以把slab看成建立在伙伴系统上的一个缓存,主要作用就是分配并释放内核中的小对象。
显存回收:对显存来说,假如只分配而不释放,还会导致显存泄露,甚至会用尽系统显存。所以,在应用程序用完显存后,还须要调用free()或unmap(),来释放那些不用的显存。其实,系统也不会任由某个进程用完所有显存。在发觉显存紧张时,系统还会通过一系列机制来回收显存,例如下边这三种形式:
(1)回收缓存,例如使用LRU(LeastRecentlyUsed)算法,回收近来使用最少的显存页面。
(2)回收不常访问的显存,把不常用的显存通过交换分区(Swap)直接讲到c盘中。Swap虽然就是把一块c盘空间当作显存来用。它可以把进程暂时不用的数据储存到c盘中(这个过程称为换出),当进程访问那些显存时,再从c盘读取这种数据到显存中(这个过程称为换入)。Swap把系统的可用显存变大了,但一般只在显存不足时,就会发生Swap交换,而且因为c盘读写的速率远比显存慢,Swap会造成严重的显存性能问题。
(3)杀害进程,显存紧张时系统就会通过OOM(OutofMemory,内核的一种保护机制),直接杀掉占用大量显存的进程.。OOM监控进程的显存使用情况,但是使用oom_score为每位进程的显存使用情况进行评分:
一个进程消耗的显存越大,oom_score就越大;
一个进程运行占用的CPU越多,oom_score就越小。
这样,进程的oom_score越大,代表消耗的显存越多,也就越容易被OOM杀害,进而可以更好保护系统。
其实,为了实际工作的须要,管理员可以通过/proc文件系统,自动设置进程的oom_adj,因而调整进程的oom_score。oom_adj的范围是[-17,15],数值越大,表示进程越容易被OOM杀害;数值越小,表示进程越不容易被OOM杀害,其中-17表示严禁OOM。如用下边的命令,你就可以把sshd进程的oom_adj调小为-16,这样,sshd进程就不容易被OOM杀害。
echo-16>/proc/$(pidofsshd)/oom_adj
buffer和cache
free命令中buffer和cache都表示缓存,但用途不一样
1、Buffer,是内核缓冲区用到的显存,对应的是/proc/meminfo中的Buffer值
2、Cache,是内核页缓存和Slab用到的显存,对应的是/proc/meminfo中的Cache和SReclaimable之和
简单来说,Buffer是对c盘数据的缓存,而Cache是文件数据的缓存,它们既会用在读恳求中,也会用在写恳求中。
cache(缓存)从CPU角度考虑,是为了提升cpu和显存之间的数据交换速率而设计的,比如平时看到的一级缓存、二级缓存、三级缓存。cpu在执行程序所用的指令和读数据都是针对显存的,也就是从显存中取得的。因为显存读写速率慢,为了提升cpu和显存之间数据交换的速率,在cpu和显存之间降低了cache,它的速率比显存快linux 用户分配空间,而且造价高,又因为在cpu内不能集成太多集成电路,所以通常cache比较小,之后intel等公司为了进一步提升速率,又降低了二级cache,甚至五级cache,它是按照程序的局部性原理而设计的,就是cpu执行的指令和访问的数据常常在集中的某一块,所以把这块内容装入cache后,cpu就不用在访问显存了,这就提升了访问速率。其实若cache中没有cpu所须要的内容,还是要访问显存的。
从显存读取与c盘读取角度考虑,cache可以理解为操作系统为了更高的读取效率,更多的使用显存来缓存可能被再度访问的数据。
缓冲(buffers)是为了提升显存和硬碟(或其他I/O设备)之间的数据交换的速率而设计的。把分散的写操作集中进行,降低c盘碎片和硬碟的反复寻道,因而提升系统性能。linux有一个守护进程定期清空缓冲内容(即写入c盘),也可以通过sync命令自动清空缓冲。
简单来说,buffer是即即将被写入c盘的,而cache是被从c盘中读下来的。buffer是由各类进程分配的,被用在如输入队列等方面。一个简单的反例如某个进程要求有多个数组读入,在所有数组被读入完整之前,进程把原本读入的数组置于buffer中保存。
cache常常被用在c盘的I/O恳求上,假如有多个进程都要访问某个文件,于是该文件便被弄成cache以便捷上次被访问,这样可提升系统性能。
以上是深入理解 Linux 内核:虚拟地址空间与物理内存的映射关系的详细内容。更多信息请关注PHP中文网其他相关文章!