显存管理的主要工作就是对化学显存进行组织,之后对化学显存的分配和回收。并且Linux引入了虚拟地址的概念。
虚拟地址的作用
假如用户进程直接操作数学地址会有以下的益处:
1、用户进程可以直接操作内核对应的显存,破坏内核运行。
2、用户进程也会破坏其他进程的运行
CPU中寄存器中储存的是逻辑地址,须要进行映射能够转化为对应的数学地址,之后获取对应的显存。
通过引入逻辑地址,每位进程都拥有单独的逻辑地址范围。
当进程申请显存的时侯,会为其分配逻辑地址和化学地址,并将逻辑地址和化学地址做一个映射。
所以linux服务器代维,Linux显存管理涉及到了以下三个部份:
1、物理显存化学显存的组织
Linux中显存分为3个级别,从下到上依次为:
1、Page:一个page的大小为4k,Page是显存的一个最基本的单位。
2、Zone:Zone中提供了多个队列来管理page。
Zone分为3种
2.1、ZONE_DMA:拿来储存DMA读取IO设备的数据,内核专用
2.2、ZONE_NORMAL:拿来储存内核的相关数据,内核专用
2.3、ZONE_HIGHMEM:高档显存,拿来储存用户进程数据
3、Node节点,一个CPU对应着一个Node,一个Node包括一个Zone_DMA、ZONE_NORMAL、ZONE_HIGHMEM。
同时当一个CPU对应的显存用光后,可以申请其他CPU对应的显存。
化学显存的分配
Linux将显存分配分为两种:
1、大显存
大显存借助伙伴系统分配。
伙伴系统的做法是将ZONE中的Page分组,之后组装为多个数组。数组中储存的是页块的集合。页块对应着有不同的大小,分别为1、2、4、8…1024个页。
当恳求(2i-1,2i]大小的page的时侯,会直接恳求2i个页,假如对应的数组中有对应的页块,就直接分配。假如对应的数组没有,就往上找2i+1,假若2i+1存在,就将其分为2个2i页块,将其中1个2i加入到对应的数组中,将另外一个分配出去。
比如,要恳求一个128个页的页块时,先检测128个页的页块数组是否有空闲块。假如没有,则查256个
的页块数组;假如有空闲块的话,则将256个页的页块分成两份,一份使用,一份插入128个页的页块数组中。假如还是没有,就查512个页的页块数组;假如有的话,就分裂为128、128、256三个页块,一个128的使用,剩余两个插入对应页块数组。
2、小显存分配
小显存分配借助slub分配,例如对象等数据slub就是将几个页单独拎下来作为缓存,上面维护了数组。每次直接从数组中获取对应的显存,用完以后也不用清空,就直接挂到数组上,之后等待上次借助。
2、如何组织虚拟地址
虚拟地址对应的是虚拟空间,虚拟空间只不过是一个虚拟地址的集合,拿来映射数学显存。
虚拟空间分为用户态和内核态。
32位系统中将虚拟空间根据1:3的比列分配给内核态和用户态。
64位系统中分别给内核态和用户态分配了128T。
用户态结构
每位进程就会对应一个用户态虚拟空间,上面储存了Text(代码)的显存虚拟地址范围、Data(数据)的显存虚拟地址范围、BSS(全局变量)的显存虚拟地址范围、堆的虚拟地址范围、栈的虚拟地址范围,以及mmap显存映射区。
其中mmap用于申请动态显存的时侯的映射,堆和栈都是动态变化的。
一个进程对应的用户态中的各个方面的虚拟地址信息都通过一个struct来储存在显存中,当创建进程的时侯会为其分配显存储存对应的虚拟地址信息。
内核态结构
Linux的内核程序共用一个内核态虚拟空间。其中分为了以下几部份:
1、直接映射区
896M,内核空间直接映射到对应的ZONE_DMA和ZONE_NORMAL中。为何称作直接映射呢?逻辑地址直接乘以对应的差值就可以得到对应的数学地址。固定死了。
2、动态映射
为何要引入动态映射呢?由于所有数学显存的分配都须要内核程序进行申请,用户进程没有这个权限。所以内核空间一定要能映射到所有的数学显存地址。
这么假如都采用直接映射的话,1G大小逻辑地址的内核空间只能映射1G大小的数学显存。
所以引入了动态映射,动态映射就是内核空间的逻辑地址可以映射到化学显存中的ZONE_HIGHMEM(高档显存)中的任何一个地址,但是在对应的数学显存使用完以后,可以再映射其他化学显存地址。
动态映射分为三种:
1、动态显存映射:使用完对应的数学显存后,就可以映射其他化学显存了。
2、永久显存映射:一个虚拟地址只能映射一个数学地址。假如须要映射其他化学地址,须要解绑。
3、固定显存映射:只能被个别特定的函数来调用引用数学地址。
动态显存映射和直接映射的区别
动态映射和直接映射的区别就是逻辑地址到化学地址的转化规则。
直接映射
直接映射的规则是死的,一个逻辑地址对应的数学地址是固定的。通过逻辑地址加或则乘以一个数,就可以得到对应的数学地址。
动态映射
动态映射是动态的绑定,每位逻辑地址对应的数学地址是动态的,通过页表进行查询。
用户空间映射:
用户空间采用动态映射,每位虚拟地址可以被映射到一个数学地址,映射到ZONE_HIGHMEM。
为何用户空间不采用直接映射呢?
由于数学显存是多个进程所有的,每位进程都有一个用户空间。倘若采用直接映射的话,对应的数学地址是会冲突的。其用户空间的逻辑地址大小都为3G,所以存在逻辑地址相同linux内核启动流程图,并且对应的数学地址不同。须要通过页表来转化,一个进程会对应一个页表。
3、如何将虚拟地址映射到化学显存
虚拟地址通过页表将虚拟地址转化为化学地址,每位进程都对应着一个页表,内核只有一个页表。
虚拟空间和化学显存都根据4k来分页,一个虚拟空间中的页和化学显存中页是一一对应的。
页表映射
如上图所示,将虚拟地址中的页号通过页表转化为对应的数学页号,之后通过页内偏斜量就可以得到对应的数学地址了。
然而1个进程就须要一个页表,一个4G的显存条,就须要1M个页表记录来描述,如果1个页表记录须要4个字节,这么就须要4MB。并且页表记录是通过下标来对应的,通过虚拟页号来除以对应的页表项大小来估算得到对应的地址的。
所以Linux将4M分为1K个4K,一个4K对应着一个page,拿来储存对应的真正的页表记录。将1K个page分开储存,就不要求连续的4M了。
假如将4M分成1K个离散的page的话,如何虚拟地址对应的页表号呢?
借助表针,储存1K个地址,分别指向这1K个page,地址的大小为4个字节,也就是32位,完全可以表示整个显存的地址范围。
1K*4个字节,刚好是一个page4k,所以也就是借助1个page来储存对应的页表记录索引。
所以我们的虚拟地址找寻过程如下:
1、找到对应的页表记录索引位置,由于有1K个索引,所以用10位就可以表示了
2、通过索引可以找到对应的真正的页表地址,对应的有1K个页表记录,所以用10位就可以表示了
3、1个页有4K,通过12位就可以表示其页内偏斜量了。
所以虚拟地址被分为了三部份:
1、10位表示索引偏斜
2、10位表示页表记录偏斜
3、12位表示页内偏斜
尽管这些方法降低了索引项,进一步降低了显存,并且降低了连续显存的使用,通过离散的显存就可以储存页表。
这是对于32位系统ubuntu linux,而64位系统采用了5级页表。
映射流程图
用户态申请显存时,只会申请对应的虚拟地址,不会直接为其分配化学显存,而是等到真正访问显存的时侯,形成缺页中断,之后内核才能为其分配,之后为其完善映射,也就是构建对应的页表项。
TLB
TLB就是一个缓存,置于CPU中。拿来将虚拟地址和对应的数学地址进行缓存。当查询对应的数学地址的时侯,首先查询TLB,假如TLB中存在对应的记录,就直接返回。若果不存在,就再去查询页表。
虚拟显存
虚拟显存指的是将硬碟中划出一段swap分区当成虚拟的显存,拿来储存显存中暂时用不到的显存页,等到须要的时侯再从swap分区中将对应的显存页调入到显存中。硬碟此时相当于一个虚拟的显存。
从逻辑上才能运行更大显存的程序,由于程序运行的时侯并不须要把所有数据都加载到显存中,只须要将当前运行必要的相关程序和数据加载到显存中就可以了,当须要其他数据和程序的时侯,再将其调入。
相较于真正的显存加载,虚拟显存须要将数据在显存和c盘中不断切换linux内核启动流程图,这是一个历时的操作,所以速率比不上真正的显存加载。
总结
虚拟空间和化学显存都分为内核空间和用户空间。
虚拟地址须要通过页表转化为化学地址,之后才会访问。
用户虚拟空间只能映射数学显存中的用户显存,难以映射到化学显存中的内核显存,也就是说,用户进程只能操作用户显存。
内核空间只能被内核申请使用,用户进程只能操作用户空间的数学显存和虚拟空间。
当用户进程调用系统调用的时侯,会将其对应的代码和数据运行在内核空间中。
所以当调用内核空间读取文件或则网路数据的时侯,首先会将数据拷贝到显存空间,之后在将数据从内核空间拷贝到用户空间。由于用户进程不能访问内核空间。
原文: