背景

在Intel Atom芯片的环境中分析故障时,顺便看了下该环境的显卡驱动中的内存管理相关机制,显卡为Atom CPU的集成显卡,低端产品,gma500显卡。本文主要关注该显卡驱动中的内存管理相关的实现原理,其他显卡原理与之类似,有一定的参考意义。

CPU的内存管理

这个是个基础的话题,看似神秘,其实简单。简单描述如下:

CPU硬件中有个内存管理单元(MMU),内存管理依赖于页表,页表是一个逻辑概念,并不存在这样一个硬件单元,其本质就是一段物理内存(内核中分配的,地址不定),其中记录了虚拟地址物理地址的映射关系,将这段内存的起始地址告诉(其实就是将其写入指定的寄存器)CPU(MMU),然后CPU就可以利用这个页表来进行地址映射了。

页表中的虚拟地址物理地址的映射关系是动态创建的(除了内核部分),触发机制就是缺页异常(page fault),也就是说物理内存是动态的通过缺页异常分配的,当用户使用malloc或mmap之类的接口分配内存时,实际得到的虚拟地址(对用户来说,只能看见虚拟地址),实际的物理内存,是在实际访问该内存(写)时,硬件触发缺页异常,在缺页异常中分配的。

基本原理就介绍这些,关于Linux的内存管理机制足够写一本书,这里就不罗嗦了,资料也很多,以后有时间可以专门写一系列的文章来说明。

GPU的内存管理

GPU也需要管理内存?当然。而且更复杂一些。

通常来说,对于GPU,其需要管理两类内存:主存(即CPU使用的物理内存)和显存(GPU自带的内存)。

GPU还需要管理主存?

是的,其实这个说法不太准确,应该说:GPU需要使用主存(也称GTT内存,从GPU的角度),需要访问主存。实际的内存和显存的管理工作其实是内核做的,内核中有专门管理主存和显存的模块,通常为TTM或GEM(Intel),内容太多,不详细说了。

GPU管理内存的方式本质上跟CPU相似,它也有自己的MMU,也有相应的页表,这些跟CPU都是独立的。也使用页表进行地址映射,页表也使用多级方式。

不同的是,GPU需要能访问主存,也就是说GPU的页表需要能映射主存中的物理地址,基本原理其实也跟CPU处理IO内存的方式相似,也是通过一定的映射(GPU有个GART表)将主存中的物理内存和GPU自己的显存映射到统一的物理地址空间(从GPU的角度。这个地址空间对CPU是不可见的),也就是说,GPU看到的也就是自己的一个物理地址空间,GPU使用这些地址时,并不关心这些地址对应的是主存,还是显存。具体的映射工作是GPU硬件完成的,细节我们也不用关心,这里不描述。

有兴趣的可以自己搜一些资料看看。

如何使用内存?

前面说了基本的内存管理原理,这节从使用者的角度来继续说明。

大家平常都怎么使用内存?

内存使用有多种方式,通常有:

  1. malloc分配内存,这个应该是最常见的了,可能一些coder只用过这种方式~。如前面所说,其本质为分配了一段虚拟地址范围(虚拟内存),实际访问时(比如memset)通过缺页异常分配实际的物理内存。得到虚拟内存地址后,程序就可以通过这个地址来访问内存了,这个地址,在C语言中就对应指针。
  2. mmap匿名分配内存。当mmap参数的fd为空时,称为匿名映射,此时的本质操作跟malloc一样,就是分配虚拟内存。事实上,malloc实现中,也使用了mmap来分配内存,细节这里不关注了。
  3. mmap映射文件。当mmap中的fd参数不为空时,时间是将fd对应的设备空间映射到虚拟地址空间,这样,用户可以通过访问这段地址空间,而直接访问设备。当文件设备对应的是内存时(如共享内存、tmpfs),用户访问的实际为内存。

显卡如何使用内存?

回到显卡的场景,我们这里主要讨论显卡使用主存的场景,显卡为何需要使用主存呢?

通常情况下,显卡要实现图像显示,至少需要一段内存,对应于屏幕上显示的内容,这段内存就是我们平常熟知的Framebuffer,Framebuffer可以是主存或显存上分配的任意一段内存,而且还不要求物理上连续。有些显卡自带的显存很小,甚至没有,此时必须要使用主存。

不连续都可以?为什么?

是的,如之前所说,显卡(GPU)有自己的MMU和页表,会将不连续的物理内存映射到连续的虚拟地址空间中去。所以,Framebuffer对应的内存不需要物理上连续。

Framebuffer在主存或显存上都可以?为什么?

是的,如之前所说,显卡(GPU)可以范围主存中的内存,只需要将其映射到GPU的地址空间中即可,显卡可以访问的这部分主存,也叫GTT内存,具体的映射是显卡硬件完成的,我们不关心。

简单说,如果要使用主存中的内存做Framebuffer的话,只需要在主存中分配一段内存,然后告诉显卡就可以了,这个“告诉”动作,本质上就是写一些指定的寄存器,对驱动的coder来说,就是驱动提供的一些接口,通常是与硬件紧密相关的。

显卡使用内存,有两种的典型场景。

  1. 预先分配。即预先为Framebuffer分配好内存,然后直接使用。
  2. 延迟分配。不预先分配物理内存,仅在map时分配虚拟内存,并设置page fault处理接口。当实际使用这段内存时,触发page fault,在page fault处理接口分配内存,并设置为Framebuffer。

预先分配的使用流程,简单描述如下:

  1. 分配内存。使用drm提供的接口(通过ioctl方式调入内存,底层由具体的内核中的显卡驱动实现)分配内存,接口参数中提供用于选择内存分配类型的标志,用于选择分配显存或者GTT内存(也就是说选择权在用户手中)。内存分配通常依赖于内核中的TTM模块(或GEM)。
  2. 设置Framebuffer。设置分配后的内存(通常以bo方式)为Framebuffer(drm也提供了相应的接口,直接调用即可,底层也由显卡驱动实现)。
  3. map Framebuffer。步骤1中得到的内存是物理内存,还不能直接使用(使用虚拟内存的OS不能直接使用物理地址),需要将其映射到虚拟地址空间后(将物理地址转换为虚拟地址/线性地址)才能使用,此步骤即完成此操作。本质为分配相应的虚拟地址,并修改相应页表,完成映射。
  4. copy数据。将需要显示的数据拷贝到步骤上map后的虚拟地址中,该虚拟地址最终对应的就是Framebuffer,GPU会自动定期将Framebuffer中的内容显示到屏幕上。

延迟分配的使用流程,简单描述如下:

  1. 分配gtt相关管理结构(gtt_range)。gtt结构用于管理物理内存,此时并未实际分配物理内存。
  2. map内存。通常是对drm设备(/dev/dri/cardx)进行mmap操作,分配虚拟地址空间,此时也不分配物理内存,仅分配虚拟内存(vma),并设置相应的vm_ops操作,其中包括了关键的page fault处理接口。
  3. 拷贝数据,触发page fault。用户使用map后得到的虚拟地址,向其中拷贝数据,此时,由于此需要地址还没有对应的物理内存,所以,硬件会自动触发page fault,然后进入之前设置的page fault处理接口,该接口由显卡驱动实现,在其中实现物理内存的分配操作,并最终将其设置为Framebuffer。

其实过程都挺简单的,但是出于性能等因素的考虑,实际的操作可能要复杂很多,比如涉及3D加速之类的内容,这里就不详述了,后面有时间再写3D加速相关的内容。就本文而言,理解到这就可以了。

显卡驱动中的内存管理

前面说了基本的内存管理机制和使用方式,这节从驱动的角度,看看这些机制具体是如何实现的。

这里以Intel Atom集成显卡gma500为例说明。由于这个显卡的显存很小,只有8M左右,所以,很多情况下,显卡需要使用主存。本文关注的也是GPU使用主存的相关原理,对于使用显存的情况(相对简单),不做相关分析。

老版本的gma500显卡驱动中,对于Framebuffer小于8M的情况(分辨率低于1920*1080),直接在显存中分配内存并设置为Framebuffer。对于大于8M的情况,则使用vmalloc接口在主存中分配内存。其流程是典型的预先分配的场景。具体代码本文不做描述了,有兴趣的同学可以自己看看代码。

新版本的gma500显卡驱动中,对此进行改进,使用了延迟分配的方式,在后续章节中对相应代码进行分析。

## 分配Gtt Range

gtt_range可以理解为一段io mem,是显卡的pci配置空间中配置的一段IO mem资源,专门用来管理gtt内存的。

分配Gtt range的流程在显卡初始化的过程中,代码流程如下:

psbfb_createpsbfb_allocpsb_gtt_alloc_range/**    GTT resource allocator - allocate and manage GTT address space*//***    psb_gtt_alloc_range -   allocate GTT address space* @dev: Our DRM device*  @len: length (bytes) of address space required*    @name: resource name*  @backed: resource should be backed by stolen pages**   Ask the kernel core to find us a suitable range of addresses*   to use for a GTT mapping.** Returns a gtt_range structure describing the object, or NULL on*    error. On successful return the resource is both allocated and marked*  as in use.*/
struct gtt_range *psb_gtt_alloc_range(struct drm_device *dev, int len,const char *name, int backed, u32 align)
{struct drm_psb_private *dev_priv = dev->dev_private;struct gtt_range *gt;struct resource *r = dev_priv->gtt_mem;int ret;unsigned long start, end;if (backed) {/* The start of the GTT is the stolen pages */start = r->start;end = r->start + dev_priv->gtt.stolen_size - 1;} else {/* The rest we will use for GEM backed objects */start = r->start + dev_priv->gtt.stolen_size;end = r->end;}gt = kzalloc(sizeof(struct gtt_range), GFP_KERNEL);if (gt == NULL)return NULL;gt->resource.name = name;gt->stolen = backed;gt->in_gart = backed;gt->roll = 0;/* Ensure this is set for non GEM objects */gt->gem.dev = dev;ret = allocate_resource(dev_priv->gtt_mem, &gt->resource,len, start, end, align, NULL, NULL);if (ret == 0) {gt->offset = gt->resource.start - r->start;return gt;}kfree(gt);return NULL;
}

从代码看,关键就是调用了allocate_resource分配了一段空闲io mem资源,这些资源本质上是BIOS中协调和预留的。

mmap实现

用户要访问为显卡分配的内存,必须将其映射到CPU的虚拟地址空间中。通常,都是通过mmap方式。

这种情况下,显卡驱动就需要实现dri设备(/dev/dri/cardx)的mmap接口,看看dri设备的mmap接口是如何实现的:

static const struct file_operations psb_gem_fops = { .owner = THIS_MODULE, .open = drm_open, .release = drm_release, .unlocked_ioctl = psb_unlocked_ioctl, .mmap = drm_gem_mmap, .poll = drm_poll, .read = drm_read, };

可以看出,其mmap接口对应为drm_gem_mmap,继续看看该接口流程:

drm_gem_mmapdrm_gem_mmap_objint drm_gem_mmap_obj(struct drm_gem_object *obj, unsigned long obj_size,struct vm_area_struct *vma)
{struct drm_device *dev = obj->dev;lockdep_assert_held(&dev->struct_mutex);/* Check for valid size. */if (obj_size < vma->vm_end - vma->vm_start)return -EINVAL;if (!dev->driver->gem_vm_ops)return -EINVAL;vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP;//设置vma_ops,其中包括了page fault的处理接口vma->vm_ops = dev->driver->gem_vm_ops;vma->vm_private_data = obj;vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));/* Take a ref for this mapping of the object, so that the fault* handler can dereference the mmap offset's pointer to the object.* This reference is cleaned up by the corresponding vm_close* (which should happen whether the vma was created by this call, or* by a vm_open due to mremap or partial unmap or whatever).*/drm_gem_object_reference(obj);drm_vm_open_locked(dev, vma);return 0;

可以看出,这个流程中设置了vma_ops,其中包括了page fault的处理接口。

接下来的流程就跟硬件紧密相关了,gma500显卡驱动的实现如下:

static struct drm_driver driver = {.driver_features = DRIVER_HAVE_IRQ | DRIVER_IRQ_SHARED | \DRIVER_MODESET | DRIVER_GEM,.load = psb_driver_load,.unload = psb_driver_unload,.lastclose = psb_driver_lastclose,.preclose = psb_driver_preclose,.set_busid = drm_pci_set_busid,.num_ioctls = ARRAY_SIZE(psb_ioctls),.device_is_agp = psb_driver_device_is_agp,.irq_preinstall = psb_irq_preinstall,.irq_postinstall = psb_irq_postinstall,.irq_uninstall = psb_irq_uninstall,.irq_handler = psb_irq_handler,.enable_vblank = psb_enable_vblank,.disable_vblank = psb_disable_vblank,.get_vblank_counter = psb_get_vblank_counter,.gem_free_object = psb_gem_free_object,.gem_vm_ops = &psb_gem_vm_ops,

gem_vm_ops设置为了psb_gem_vm_ops,看看对应的实现:

static const struct vm_operations_struct psb_gem_vm_ops = {.fault = psb_gem_fault,.open = drm_gem_vm_open,.close = drm_gem_vm_close,
};

可以看到,page fault对应的处理接口为:psb_gem_fault

page fault处理接口实现

继续看看psb_gem_fault的具体实现。

int psb_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{struct drm_gem_object *obj;struct gtt_range *r;int ret;unsigned long pfn;pgoff_t page_offset;struct drm_device *dev;struct drm_psb_private *dev_priv;//获取drm_gem_object对象obj = vma->vm_private_data;   /* GEM object */dev = obj->dev;dev_priv = dev->dev_private;//获取gtt_range对象r = container_of(obj, struct gtt_range, gem);    /* Get the gtt range *//* Make sure we don't parallel update on a fault, nor move or removesomething from beneath our feet */mutex_lock(&dev->struct_mutex);/* For now the mmap pins the object and it stays pinned. As thingsstand that will do us no harm */if (r->mmapping == 0) {//为gtt_range中对应的page分配物理内存。ret = psb_gtt_pin(r);if (ret < 0) {dev_err(dev->dev, "gma500: pin failed: %d\n", ret);goto fail;}r->mmapping = 1;}/* Page relative to the VMA start - we must calculate this ourselvesbecause vmf->pgoff is the fake GEM offset */page_offset = ((unsigned long) vmf->virtual_address - vma->vm_start)>> PAGE_SHIFT;/* CPU view of the page, don't go via the GART for CPU writes */// 当使用显存时,pfn直接对应stolen_base中相应的page,这些page是从显存中映射过来的,通常由BIOS完成。if (r->stolen)pfn = (dev_priv->stolen_base + r->offset) >> PAGE_SHIFT;else// 当使用GTT内存时,从gtt_range->pages中取相应page的物理地址,这些page是在psb_gtt_pin()中分配的。pfn = page_to_pfn(r->pages[page_offset]);// 将取得的物理地址对应pfn,写入vma中,同时会修改相应页表,完成物理地址到虚拟地址的映射。ret = vm_insert_pfn(vma, (unsigned long)vmf->virtual_address, pfn);fail:mutex_unlock(&dev->struct_mutex);switch (ret) {case 0:case -ERESTARTSYS:case -EINTR:return VM_FAULT_NOPAGE;case -ENOMEM:return VM_FAULT_OOM;default:return VM_FAULT_SIGBUS;}
}

相关流程见注释,其中最关键的是psb_gtt_pin(),其中完成内存分配,实现如下:

/*** psb_gtt_pin     -   pin pages into the GTT* @gt: range to pin**    Pin a set of pages into the GTT. The pins are refcounted so that*   multiple pins need multiple unpins to undo.**   Non GEM backed objects treat this as a no-op as they are always GTT*    backed objects.*/
int psb_gtt_pin(struct gtt_range *gt)
{int ret = 0;struct drm_device *dev = gt->gem.dev;struct drm_psb_private *dev_priv = dev->dev_private;u32 gpu_base = dev_priv->gtt.gatt_start;mutex_lock(&dev_priv->gtt_mutex);// Stolen==0表示使用gtt内存,而不使用显存if (gt->in_gart == 0 && gt->stolen == 0) {// 分配物理内存,分配后的page放在了gtt_range->pages中ret = psb_gtt_attach_pages(gt);if (ret < 0)goto out;ret = psb_gtt_insert(dev, gt, 0);if (ret < 0) {psb_gtt_detach_pages(gt);goto out;}/* 从GPU的角度,将分配的物理内存对应的物理地址写入GPU的MMU中,* 即修改GPU的页表,将主存中的page(可以不连续)映射到GPU的虚拟* 地址空间中。这样GPU就可以访问这些内存了,作为Framebuffer使用。*/psb_mmu_insert_pages(psb_mmu_get_default_pd(dev_priv->mmu),gt->pages, (gpu_base + gt->offset),gt->npage, 0, 0, PSB_MMU_CACHED_MEMORY);}gt->in_gart++;
out:mutex_unlock(&dev_priv->gtt_mutex);return ret;
}

这里面有两个关键动作:

  1. psb_gtt_attach_pages完成内存分配。
  2. psb_mmu_insert_pages完成GPU中的地址映射,使分配的内存对GPU可见。

内存分配的后续流程为:

psb_gtt_attach_pagesdrm_gem_get_pagesshmem_read_mapping_pageshmem_getpage_gfpshmem_alloc_page  alloc_page_vmaalloc_pageshmem_add_to_page_cache ///由page cache中进行管理

至此,完成gma500显卡驱动中dri设备的mmap的实现分析,其中包括了显卡驱动中内存管理相关的关键操作,理解后,相信理解其他显卡驱动应该不会有大问题。

显卡内存管理机制及驱动实现(Intel gma500为例)相关推荐

  1. 全面介绍Windows内存管理机制及C++内存分配实例(一):进程空间

    本文背景: 在编程中,很多Windows或C++的内存函数不知道有什么区别,更别谈有效使用:根本的原因是,没有清楚的理解操作系统的内存管理机制,本文企图通过简单的总结描述,结合实例来阐明这个机制. 本 ...

  2. Windows的内存管理机制

    Windows下的内存是如何管理的? Windows内存的管理可以分为两个层面:物理内存和虚拟内存 其中物理内存由系统管理,不允许应用程序直接访问,应用程序可见的只有一个2G的地址空间,而内存分配是通 ...

  3. 【OS学习笔记】三十 保护模式九:段页式内存管理机制概述

    上几篇文章学习了任务切换相关知识,如下: [OS学习笔记]二十六 保护模式八:任务门-任务切换 [OS学习笔记]二十七 保护模式八:任务切换的方法之----jmp与call的区别以及任务的中断嵌套 今 ...

  4. MTK:内存管理机制简单分析

    MTK内存管理机制简单分析 1:内存: 内存,在手机里面,是个较为紧缺的资源,特别是在功能机上面.经常在功能机上面产生的内存不足,申请失败的地方比比皆是, 更是屡见不鲜,经常会为了节省内存,会进行代码 ...

  5. flink分析使用之八内存管理机制

    一.flink内存机制 在前面的槽机制上,提到了内存的共享,这篇文章就分析一下,在Flink中对内存的管理.在Flink中,内存被抽象出来,形成了一套自己的管理机制.Flink本身基本是以Java语言 ...

  6. 什么是 Python 的 「内存管理机制」?

    什么是内存管理器(what) Python作为一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言,与大多数编程语言不同,Python中的变量无需事先申明,变量无需指定类型,程序员无需关心内存 ...

  7. python中内存管理机制一共分为多少层_python 内存管理机制

    内存管理机制 ​python中万物皆对象,python的存储问题是对象的存储问题,并且对于每个对象,python会分配一块内存空间去存储它 ​Python的内存管理机制:引入计数.垃圾回收.内存池机制 ...

  8. JVM内存管理机制线上问题排查

    本文主要基于"深入java虚拟机"这本书总结JVM的内存管理机制,并总结了常见的线上问题分析思路.文章最后面是我对线上故障思考的ppt总结. Java内存区域 虚拟机运行时数据区如 ...

  9. 浅析java内存管理机制

    内存管理是计算机编程中的一个重要问题,一般来说,内存管理主要包括内存分配和内存回收两个部分.不同的编程语言有不同的内存管理机制,本文在对比C++和java语言内存管理机制的不同的基础上,浅析java中 ...

  10. 内存分段分页机制理解_深入理解虚拟机,JVM高级特性-自动内存管理机制

    什么是自动内存管理机制? 对于java程序员来说,有一点是要比C/C++程序员要方便的,那就是程序在运行时,java程序不需要为每一个对象其编写对应的释放内存的代码,JVM虚拟机将为你在合适的时间去释 ...

最新文章

  1. 【Qt】监视文件和目录的修改:QFileSystemWatcher
  2. hdu 1574(01背包)
  3. 解决逆向工程mapper映射文件不发布问题
  4. bigdecimal取小数部分_小数精度丢失问题分析和解决
  5. 回归分析之线性回归(N元线性回归)
  6. 在c语言中逗号运算符若不带括号,详解shell脚本括号区别--$()、$「 」、$「 」 、$(()) 、「 」 、「[ 」]...
  7. 开氏温度与摄氏度换算_【油品小知识】你不知道的“柴油密度”与“温度”的故事...
  8. ASP.NET 实践:使用用户控件实现 Web 部件的个性化
  9. BFS - 求最短路径
  10. 多模态简述(情感分析)
  11. 【代码段】动态生成页面heading
  12. ESP32笔记(7) OpenSSL下载安装
  13. 计算机画图更改,如何用电脑画图功能修改图片与加字
  14. Flume采集数据利器
  15. STM32L051 低功耗模式和踩坑随笔(自用)
  16. html怎么控制top值为0,关于vue滚动scrollTop 赋值一直为0问题
  17. Github标签管理
  18. 百度超级链(xuperchain),make时出现错误
  19. Opencv4(C++)实战案例1:将朦胧的图像变清晰(去雾)
  20. win10笔记本电脑找不到WLAN

热门文章

  1. kibana的安装(elasticsearch图形化界面)
  2. 感知颗粒度与高手之间的关系
  3. AndroidStudio自带的模拟器如何联网
  4. PostgreSQL 配置文件 postgresql.conf 及 postgresql.auto.conf
  5. redis.conf文件下载与配置
  6. Tell Me Where to Look: Guided Attention Inference Network论文翻译
  7. linux 配制aria2
  8. jbx添加加mysql驱动
  9. 原生JavaScript批量下载文件压缩包
  10. R语言结构方程模型(SEM)在生态学领域中的实践应用