1. 显卡驱动是怎么控制显卡的, 就是说, 使用那些指令控制显卡, 通过端口么?
2. DirectX 或 OpenGL 或 CUDA 或 OpenCL 怎么找到显卡驱动, 显卡驱动是不是要为他们提供接口的实现, 如果是, 那么DirectX和OpenGL和CUDA和OpenCL需要显卡驱动提供的接口都是什么, 这个文档在哪能下载到? 如果不是, 那么DirectX, OpenGL, CL, CUDA是怎么控制显卡的?
3. 显卡中的流处理器具体是做什么的, 是执行某些特殊运算么, 还是按某些顺序执行一组运算, 具体是什么, 光栅单元呢, 纹理单元呢?
4. 显卡 ( 或其他设备 ) 可以访问内存么? 内存地址映射的原理是什么, 为什么 B8000H 到 C7FFFH 是显存的地址, 向这个地址空间写入数据后, 是直接通过总线写入显存了么, 还是依然写在内存中, 显卡到内存中读取, 如果直接写到显存了, 会出现延时和等待么?
5. 以上这些知识从哪些书籍上可以获得?
Jet Chen
针对赞同最多的Belleve的答案,我必须要说几句。他的答案内容有一些是明显的错误。我是知乎新用户,不懂规矩,但是不希望这样的答案误导其他人。为什么我这么肯定我的答案是对的?因为我们就是这么干的!我在全球知名计算机芯片公司为主流平台的主流操作系统提供graphics驱动的支持工作。
我在这里回答一下第一个,第二个,第四个和第五个问题。
在回答这个问题之前,必须要有一些限定。因为显卡是有很多种,显卡所在平台也很多种,不能一概而论。我的回答都是基于Intel x86平台下的Intel自家的GEN显示核心单元(也就是市面上的HD 4000什么的)。操作系统大多数以Linux为例。
>>> Q1. 显卡驱动是怎么控制显卡的, 就是说, 使用那些指令控制显卡, 通过端口么?
目前的显卡驱动,不是单纯的一个独立的驱动模块,而是几个驱动模块的集合。用户态和内核态驱动都有。以Linux桌面系统为例,按照模块划分,内核驱动有drm/i915模块, 用户驱动包括libdrm, Xorg的DDX和DIX,3D的LibGL, Video的Libva等等,各个用户态驱动可能相互依赖,相互协作,作用各不相同。限于篇幅无法一一介绍。如果按照功能划分的话,大概分成5大类,display, 2D, 3D, video, 以及General Purpose Computing 通用计算。Display是关于如何显示内容,比如分辨率啊,刷新率啊,多屏显示啊。2D现在用的很少了,基本就是画点画线加速,快速内存拷贝(也就是一种DMA)。3D就复杂了,基本现在2D的事儿也用3D干。3D设计很多计算机图形学的知识,我的短板,我就不多说了。Video是指硬件加速的视频编解码。通用计算就是对于OpenCL,OpenCV,CUDA这些框架的支持。
回到问题,驱动如何控制显卡。
首先,操作硬件的动作是敏感动作,一般只有内核才有权限。个别情况会由用户态操作,但是也是通过内核建立寄存器映射才行。
理解驱动程序最重要的一句话是,寄存器是软件控制硬件的唯一途径。所以你问如何控制显卡,答案就是靠读写显卡提供的寄存器。
通过什么读写呢?据我所知的目前的显卡驱动,基本没有用低效的端口IO的方式读写。现在都是通过MMIO把寄存器映射的内核地址空间,然后用内存访问指令(也就是一般的C语言赋值语句)来访问。具体可以参考”内核内存映射,MMIO“的相关资料。
>>>Q2.2. DirectX 或 OpenGL 或 CUDA 或 OpenCL 怎么找到显卡驱动, 显卡驱动是不是要为他们提供接口的实现, 如果是, 那么DirectX和OpenGL和CUDA和OpenCL需要显卡驱动提供的接口都是什么, 这个文档在哪能下载到? 如果不是, 那么DirectX, OpenGL, CL, CUDA是怎么控制显卡的?
这个问题我仅仅针对OpenGL和OpenCL在Linux上的实现尝试回答一下。
a.关于如何找到驱动?首先这里我们要明确一下驱动程序是什么,对于OpenGL来说,有个用户态的库叫做LibGL.so,这个就是OpenGL的用户态驱动(也可以称之为库,但是一定会另外再依赖一个硬件相关的动态库,这个就是更狭义的驱动),直接对应用程序提供API。同样,OpenCL,也有一个LibCL.so.。这些so文件都依赖下层更底层的用户态驱动作为支持(在Linux下,显卡相关的驱动,一般是一个通用层驱动.so文件提供API,然后下面接一个平台相关的.so文件提供对应的硬件支持。比如LibVA.so提供视频加速的API,i965_video_drv.so是他的后端,提供Intel平台对应libva的硬件加速的实现)。 下面给你一张大图:如图可见,最上层的用户态驱动向下依赖很多设备相关的驱动,最后回到Libdrm这层,这一层是内核和用户态的临界。一般在这里,想用显卡的程序会open一个/dev/dri/card0的设备节点,这个节点是由显卡内核驱动创建的。当然这个open的动作不是由应用程序直接进行的,通常会使用一些富足函数,比如drmOpenByName, drmOpenByBusID. 在此之前还会有一些查询的操作,查询板卡的名称或者Bus ID。然后调用对应的辅助函数打开设备节点。打开之后,他就可以根据DRI的规范来使用显卡的功能。我说的这一切都是有规范的,在Linux里叫DRI(Direct Rendering Infrastructure)。
所有这些图片文档都可以Direct Rendering Infrastructure和 freedesktop上的页面DRI wiki找到DRI Wiki
显卡驱动的结构很复杂,这里有设计原因也有历史原因。
b.关于接口的定义,源代码都可以在我上面提供的链接里找到。这一套是规范,有协议的。
c.OpenGL, OpenCL或者LibVA之类的需要显卡提供点阵运算,通用计算,或者编解码服务的驱动程序,一般都是通过两种途径操作显卡。第一个是使用DRM提供的ioctl机制,就是系统调用。这类操作一般包括申请释放显存对象,映射显存对象,执行GPU指令等等。另一种是用户态驱动把用户的API语意翻译成为一组GPU指令,然后在内核驱动的帮助下(就是第一种的执行GPU指令的ioctl)把指令下达给GPU做运算。具体细节就不多说了,这些可以通过阅读源代码获得。
>>>Q4. 显卡 ( 或其他设备 ) 可以访问内存么? 内存地址映射的原理是什么, 为什么 B8000H 到 C7FFFH 是显存的地址, 向这个地址空间写入数据后, 是直接通过总线写入显存了么, 还是依然写在内存中, 显卡到内存中读取, 如果直接写到显存了, 会出现延时和等待么?
a..可以访问内存。如果访问不了,那显示的东西是从哪儿来的呢?你在硬盘的一部A片,总不能自己放到显卡里解码渲染吧?
b.显卡访问内存,3种主要方式。
第一种,就是framebuffer。CPU搞一块内存名叫Framebuffer,里面放上要显示的东西,显卡有个部件叫DIsplay Controller会扫描那块内存,然后把内容显示到屏幕上。至于具体如何配置成功的,Long story, 这里不细说了。
第二种,DMA。DMA懂吧?就是硬件设备直接从内存取数据,当然需要软件先配置,这就是graphics driver的活儿。在显卡驱动里,DMA还有个专用的名字叫Blit。
第三种,内存共享。Intel的平台,显存和内存本质都是主存。区别是CPU用的需要MMU映射,GPU用的需要GPU的MMU叫做GTT映射。所以共享内存的方法很简单,把同一个物理页既填到MMU页表里,也填到GTT页表里。具体细节和原理,依照每个人的基础不同,需要看的文档不同。。。
c.为什么是那个固定地址?这个地址学名叫做Aperture空间,就是为了吧显存映射到一个段连续的物理空间。为什么要映射,就是为了显卡可以连续访问一段地址。因为内存是分页的,但是硬件经常需要连续的页。其实还有一个更重要的原因是为了解决叫做tiling的关于图形内存存储形势和不同内存不一致的问题(这个太专业了对于一般人来说)。
这地址的起始地址是平台相关,PC平台一般由固件(BIOS之流)统筹规划总线地址空间之后为显卡特别划分一块。地址区间的大小一般也可以在固件里指定或者配置。
另外,还有一类地址也是高位固定划分的称为stolen memory,这个是x86平台都有的,就是窃取一块物理内存专门为最基本的图形输出使用,比如终端字符显示,framebuffer。起始地址也是固件决定,大小有默认值也可以配置。
d. 刚才说了,Intel的显存内存一回事儿。至于独立显卡有独立显存的平台来回答你这个问题是这样的:任何访存都是通过总线的,直接写也是通过总线写,拷贝也是通过总线拷贝;有时候需要先写入临时内存再拷贝一遍到目标区域,原因很多种;写操作都是通过PCI总线都有延迟,写谁都有。总线就是各个设备共享的资源,需要仲裁之类的机制,肯定有时候要等一下。
>>>Q5. 以上这些知识从哪些书籍上可以获得?
Intel Graphics for Linux*, 从这里看起吧少年。这类过于专业的知识,不建议在一般经验交流的平台求助,很难得到准确的答案。你这类问题,需要的就是准确答案。不然会把本来就不容易理解的问题变得更复杂。
谷俊
其实你可以把显卡想象成另外一台机器。那么控制另外一台机器的办法,就是往它的内存里面写指令和数据。往一块内存里面写东西的办法无非就几种,1, 用CPU去做,那么就是用MMIO(Memory Mapped IO)把'显存' map到CPU寻址空间,然后去读写,2, 用DMA控制器去做,这里面有系统自带的DMA控制器或者显卡带的,不管哪种你可以把DMA控制器再一次看作另外一台机器,那么其实就是向DMA控制器写指令让它帮你传一些东西到显存去,传的这些东西就是显卡要执行的命令和数据。显卡上的内存控制器,原来AGP的时候叫GART,现在不知道叫啥名了,另外SoC里面也有类似的概念,不过大多数SoC只有一个内存控制器,所以不分显存和内存。
把显卡想象成另外一台机器。它要工作,无非也是“程序存储”原理,上电之后,从特定的内存(显存)地址去取指,然后执行指令。显卡的工作逻辑比CPU简单多了,它一般就从一个环形buffer不断的取指令,然后执行,CPU就不断的去往环形buffer填指令。
很多时候同一个动作既可以用MMIO,也可以用DMA,比如flip framebuffer。只要把flip framebuffer的指令正确传到环形buffer就好了。但是MMIO需要CPU参与,传大数据的时候,打乱CPU GPU并行性,划不来。
驱动程序其实也是围绕着这件事情来做的,Vista以前,显卡的驱动全都是kernel mode执行的,因为只有kernel mode才能访问的物理地址,但是kernel mode的坏处是一旦有问题,系统就崩溃,而且kernel mode有很多局限性,比如没有C库支持,浮点运算很难,代价很大等等。所以Vista之后,显卡驱动都分两部分,kmd负责需要访问物理地址的动作,其他事情都放到umd去做,包括API支持等等。所以一个3D程序执行的过程是这样的,app generate command, call D3D runtime,D3D runtime call driver umd, driver umd system call driver kmd, kmd send command to ring buffer, graphic card exeute.
至于显卡驱动要完成什么部分,这个就是所谓HAL(hardware abstraction layer)层,也就是说HAL以下由厂商提供,以上就是操作系统自带,在HAL层以上,所有的操作都是统一的,比如画一个点,画一条线,驱动来对应具体的某一款芯片生成真正的命令,比如画点,需要0x9指令,把绝对坐标放到地址0x12345678(举例)。微软管的比较宽,umd, kmd都有HAL层,意思是即使kmd你也不能乱写,能统一的尽量统一,比如CPU GPU external fence读写同步机制就是微软统一做的。
流处理器就是说,那些处理器可以执行很多的指令,而不是就几个固定的功能,比如原来我把几个矩阵的乘法固定成一个操作(比如T&L单元),现在我把这个操作拆了,改成更基本的指令,比如,取矩阵元素,加乘,这样更灵活。不过你就得多费心思去组合这些指令了,组合这些指令有个高大上的名字,shader。至于为什么叫shader,越来越长了,不说了。
空明流转,给有故事的人点赞。
关于 Jet Chen 对Belleve的点评,我多说几句。
1. B5在他的回答中,其实是提到了“地址线”的。
2. D3D 的 Driver 模型和Linux上以DRI为主的驱动模型不太一样。但是我只做过User Mode部分的开发所以也说不多了。
3. 逻辑视图上,对非UMA的架构来说,Graphics RAM的部分和System RAM的部分对于用户视图来说,是Isolated的。但是在物理视图上,Jet Chen 和 谷俊 都提到了,GPU和CPU可能使用相同或不同的内存控制器,访问相同的物理内存页。GPU所访问到的部分,可能是映射,也可能是拷贝。
------------------------------------
1. 是。
2. Windows上提供了DDK,你可以根据DDK上规定的API和约束来开发显卡驱动。(具体来说是WDDM)然后OS就可以调用到你的驱动了。Linux上也有DRI之类的驱动程序接口。
3. 流处理器主要作用是计算,和CPU的ALU和FPU的作用是相同的。光栅化以及光栅化之前的三角形生成(Triangle Setup)和后面的插值、以及更后面的深度/Alpha/Stencil测试都是很特殊的操作,所以有专门的硬件来加速这一过程。
4. 现有硬件上不可以。未来的硬件可以。内存映射是总线的仲裁器(Arbitrator)和CPU的寻址单元协同提供的功能。在硬件的某个地方保存了一张表,你访问一个地址,硬件会查询一下这个地址是GPU还是CPU的,如果是属于GPU的,那就把数据或者请求发送过去。
5. 一般的体系结构知识可以参考《计算机组成原理》。尽管没有专门说GPU,但是GPU也只是PCI-E总线上的一种普通设备而已。
Belleve,炼金术士
1. 显卡驱动是怎么控制显卡的, 就是说, 使用那些指令控制显卡, 通过端口么?
IN/OUT 指令或者地址映射,把地址线直接塞进显卡。关于地址线怎么连到显卡请翻你们的大学课本。
2. DirectX 或 OpenGL 或 CUDA 或 OpenCL 怎么找到显卡驱动, 显卡驱动是不是要为他们提供接口的实现, 如果是, 那么DirectX和OpenGL和CUDA和OpenCL需要显卡驱动提供的接口都是什么, 这个文档在哪能下载到? 如果不是, 那么DirectX, OpenGL, CL, CUDA是怎么控制显卡的?
显卡驱动实现了 API 所要求的接口,不同接口的要求是不同的。DX 是驱动实现一半 M$ 自己实现一半,OpenGL 则是驱动全权搞定。
另外现代的显卡驱动实际上是个 JIT 编译器,它会将 API 所用的指令编译成 GPU 指令,同时负责在两者之间倒腾图像。
3. 显卡中的流处理器具体是做什么的, 是执行某些特殊运算么, 还是按某些顺序执行一组运算, 具体是什么, 光栅单元呢, 纹理单元呢?
这一部分是值得大书特书的,让我慢慢说。
在最早期显卡干的事情就是把显存里的数据给倒腾成显示器信号,因为 PC 的显卡一直是All points addressable的,所以在早期,画图就是写显存。
但是这么做有个问题就是性能:图像的计算全部堆在 CPU,如果要绘制复杂的图像(比如 3D),CPU 的算力就会不足。此时 PC 显卡的厂商看中了游戏机的处理方法。
游戏机的显卡和 PC 很不同,很多早期的游戏机并不 All points addressable,他们的图形是按照图块存储,然后由显卡现场拼接现场输出。此时 CPU 只需要告诉显卡要拼哪些图块,每个图块的位置大小是多少,效率大大提高。更重要的是,绘图的过程是可以高度并发的,所以显卡很容易通过增加规模来提高效率。PC 厂商很快就效仿游戏机推出了加速卡,它们将一些常用的绘图操作固化在芯片里,用并发来提高效率。
这时的 GPU 还是固定管线时代,所谓「固定管线」说的是,绘图的功能完全是由硬件电路实现,用户最多组合它们,不能增加新功能。GPU 界第二次大革新是引入了可编程 Shader,将原本固化的图形功能、特效等改用程序表示,GPU 就不需要再安放特化的电路,只需要堆通用电路就行了。于是现在的 GPU 就剩下三种电路:流处理器 SP、光栅单元 ROP 和纹理单元 TMU。
SP、ROP 和 TMU 分别实现了 Shader 计算、三角形点阵化和纹理访问(texture2D 之类的函数),SP 是可编程的,ROP 和 TMU 则是彻头彻尾的 ASIC。目前 DX 和 OpenGL 的渲染管线可以说是一个模子刻出来的,流程是这样:这个过程里凡是有 Shader 参与的,都是流处理器进行计算,TMU 负责访问内存读纹理或者 buffer。Rasterizer 则是 ROP 的工作,将三角形变成平面图。
4. 显卡 ( 或其他设备 ) 可以访问内存么? 内存地址映射的原理是什么, 为什么 B8000H 到 C7FFFH 是显存的地址, 向这个地址空间写入数据后, 是直接通过总线写入显存了么, 还是依然写在内存中, 显卡到内存中读取, 如果直接写到显存了, 会出现延时和等待么?
PC 的显卡目前不能访问内存,不过近期倒是有厂商打算打通这些隔阂。内存地址映射,在最早期是通过主板的地址线走向配合片选电路实现的,之后在 Intel 弄出内存分页之后,就可以动态映射(线性地址和物理地址由页表映射),实现「动态改变地址线连线」的效果。向这些地址写数据就是通过外设总线发给外设,任何这种动作都有延迟,写内存也是有延迟的。
崔进,你们头像那么美,怎么舍得取消关注?
任何一个外挂设备你可以看成一个有两个usb口的u盘,一个口负责数据控制,一个口负责数据交换