8.2 显存
Intel的GPU集成到芯片组中,一般没有专用显存,通常是由BIOS从系统物理内存中分配一块空间给GPU作专用显存。一般而言,BIOS会有个默认的分配规则,有的BIOS也会为用户留有接口,用户可以通过BIOS设置显存的大小。如对于具有1GB物理内存的系统来说,可以划分256MB内存给GPU用作显存。
但是这种静态的分配方式带来的问题之一就是如何平衡系统与显示占用的内存,究竟分配多少内存给GPU才能在系统常规使用和运行图形计算密集的应用(如3D应用)之间达到最优。如果分配给GPU的显存少了,那么在进行图形处理时性能必然会降低。而单纯提高分配给GPU的显存,也可能会造成系统的整体性能降低。而且,过多的分配内存给显存,那么当不运行3D应用时,就是一种内存浪费。毕竟,用户的使用模式不会是一成不变的,比如对于一个程序员来说,在编程之余也可能会玩一些游戏。但是我们显然不能期望用户根据具体运行应用的情况,每次都进入BIOS修改内存分配给显存的大小。
为了最优利用内存,一种方式就是不再从内存中为GPU分配固定的显存,而是当GPU需要时,直接从系统内存中分配,不使用时就归还给系统使用。但是CPU和GPU毕竟是两个完全独立的处理器,虽然现在CPU和GPU正在走融合之路,但是它们依然有自己的地址空间。显然,我们不能允许CPU和GPU彼此独立地去使用物理内存,这样必然会导致冲突,也正是因为这个原因,才有了我们前面提到的BIOS会从物理内存中划分一块区域给GPU,这样CPU和GPU才能井水不犯河水,分别使用属于自己的存储区域。
8.2.1 动态显存技术
为了解决这个矛盾,Intel的开发者们开发了动态显存技术(Dynamic Video Memory Technology),相比于以前在内存中为GPU开辟专用显存,使用动态显存技术后,显存和系统可以按需动态共享整个主存。
动态显存中关键的是GART(graphics address remapping table),也被称为GTT(graphics translation table),它是GPU直接访问系统内存的关键。事实上,这是CPU和GPU的融合过程中的一个产物,最终,CPU和GPU有可能完全实现统一的寻址。
GTT就是一个表格,或者说就是一个数组,表格中的每一个表项占用4字节,或者指向物理内存中的一个页面,或者设置为无效。整个GTT所能寻址的范围就代表了GPU的逻辑寻址空间,如512KB大小的GTT可以寻址512MB的显存空间(512K/4*4KB=512MB),如图8-2所示。
图 8-2 显存映射
这是一种动态按需从内存中分配显存的方式,GTT中的所有表项不必全部都映射到实际的物理内存,完全可以按需映射。而且当GTT中的某个表项指向的内存不再被GPU使用时,可以收回为系统所用。通过这种动态按需分配的方式,达到系统和GPU最优分享内存。内核中的DRM模块设计了特殊的互斥机制,保证CPU和GPU独立寻址物理内存时不会发生冲突。
我们注意到,GPU是通过GTT访问内存的(内存中用作显存的部分),所以GPU首先要访问GTT,但是,GTT也是在内存中。显然,这又是一个先有鸡还是先有蛋的问题。因此,需要另外一个协调人出现,这个协调人就是BIOS。在BIOS中,仍然需要在物理内存中划分出一块对操作系统不可见、专用于显存的存储区域,这个区域通常也称为Graphics Stolen Memory。但是相比于以前动辄分配几百兆的专用显存给GPU,这个区域要小多了,一般几MB就足矣,如我们前面讨论的寻址512MB的显存,只需要一个512KB的GTT表。
BIOS负责在Graphics Stolen Memory中建立GTT表格,初始化GTT表格的表项,更重要的是,BIOS负责将GTT的相关信息,如GTT的基址,写入到GPU的PCI的配置寄存器(PCI Configuration Registers),这样,GPU可以直接找到GTT了。BIOS中初始化GTT的代码大致如下:
在上面代码中,变量gfxMemAddr代表Graphics Stolen Memory的起始地址,gttMemStart代表GTT的起始地址,指针pGttEntry指向GTT的表项。代码第8~10行初始化了这几个变量,在初始化时,pGttEntry指向GTT表的开始,为后面填充GTT表作准备。
BIOS从物理内存的最顶端分配一块区域作为Graphics Stolen Memory,然后在这块区域中分配一块区域用作GTT,并将GTT所在的地址写入GPU的PCI的配置寄存器,见代码第5~6行。操作系统启动后将从这个寄存器中读取GTT表的地址,其中PCI_REG_GTT表示GPU中用作保存GTT地址的PCI配置寄存器。
在初始化时,GTT只需要映射Graphics Stolen Memory区域即可,当然GTT占用的空间就无需映射了。代码第12~16行就是映射GSM中除GTT以外的显存的,即gfxMemAddr到gttMemStart之间的部分。
显存的其余部分需要时动态按需分配,所以代码第18~21行的while循环就是将GTT中的表项设置为无效,亦即尚未分配显存。
在操作系统启动后,显存的分配和回收就由操作系统负责了,因此操作系统需要访问GTT。但是,GTT存储在操作系统不可见的Graphics Stolen Memory中,那么操作系统如何找到GTT呢?这就是BIOS将GTT的地址设置到GPU的PCI配置寄存器中的原因。在操作系统启动后,将从GPU的PCI配置寄存器中获取如GTT的基址等信息,代码如下:
在内核中,对于Intel不同系列的GPU,都有相应的GTT驱动,比如分别有针对i8xx、i915、sandybridge等型号的GTT驱动模块。
以i915系列的GPU为例,我们在其GTT驱动的函数i9xx_setup中可见,其使用pci_read_config_dword从GPU的PCI的配置寄存器I915_PTEADDR中读取GTT的基址等相关信息,然后将i9xx_setup读取的GTT的地址保存到gtt_bus_addr,这个变量就是用来保存GPU的GTT的地址的,后面我们在讨论显存绑定时会再次见到这个变量。
当然在GTT的驱动模块中,也包含操作GTT表的函数,如更新GTT表项的函数write_entry,后面在讨论Buffer Object绑定到GTT时,我们会看到这个函数。