標籤:
其實你可以把顯卡想象成另外一台機器。那麼控制另外一台機器的辦法,就是往它的記憶體裡面寫指令和資料。往一塊記憶體裡面寫東西的辦法無非就幾種,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,越來越長了,不說了。
***************************************************************************************************************************************************
在回答這個問題之前,必須要有一些限定。因為顯卡是有很多種,顯卡所在平台也很多種,不能一概而論。我的回答都是基於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*, 從這裡看起吧少年。這類過於專業的知識,不建議在一般經驗交流的平台求助,很難得到準確的答案。你這類問題,需要的就是準確答案。不然會把本來就不容易理解的問題變得更複雜。
references:
http://www.zhihu.com/question/20722310
電腦底層是如何訪問顯卡的?