CPU到底是怎麼操作獨立顯卡的?
首先我最關心CPU和顯卡交互部分,顯卡內部原理並不是很在乎。
一、我大概知道早期DOS時代的VGA卡的原理:首先系統將高端的128K物理地址映射到VGA卡,然後CPU就可以直接操作VGA卡的顯存了。同時VGA卡根據顯存的內容以一定的頻率刷新顯示器。(如果有理解錯誤的地方也請指正,謝謝!)二、我對當代的PCI顯卡了解如下:在現在經典的南北橋+各種PCI匯流排架構下,首先系統將一部分物理地址空間(大概256M)映射到顯存,然後就不知道了,而且這僅有的一點理解也不一定準確。三、問題來了(主要針對當代經典的顯卡):1.請看下圖的三個範圍,我算了下分別是256M, 128K,256B,它們都是什麼?我的猜測是映射到物理地址的部分顯存,控制寄存器和埠。2.CPU是如何訪問顯存的(拿寫為例子),是通過物理地址直接訪問(如果上面256M是映射的顯存的話);還是通過控制寄存器間接訪問,即先把要寫的數據放到寄存器,再發出命令讓顯卡取走,最後顯卡把來自寄存器的數據寫入顯存。3.很多人說顯存的地址和內存的地址是統一編址的,那麼CPU就可以直接訪問顯存,而不是通過控制器介面間接訪問。我認為這是不對的,原因如下:我的顯卡是1G的,但是下圖顯示只有256M地址空間分配給了顯卡。4.我的顯卡是1G的為什麼只分配了256M的地址空間給它?5.要了解這方面的知識要看什麼書或哪方面知識,求推薦。。。
問題1:
設備管理器屬性里:
內存範圍,表示的是MMIO(內存映射)的範圍,這個範圍是通過PCI配置寄存器讀出來的,Windows里,在驅動里用HalGetBusData可以獲得,用戶態好像沒有直接訪問的方法。
關於PCI寄存器的描述,詳見:PCI configuration space
鑒於篇幅原因,只貼一個圖:
其中你能看到的屬性里的那些描述,都是Base Address Registers里的東西。I/O範圍,表示的是I/O埠號,如果你熟悉彙編,你就知道彙編里有IN/OUT指令,這個I/O範圍指的就是操作這個設備使用IN/OUT指令時的埠範圍,這個範圍跟內存就沒什麼關係了。有些I/O範圍也寫在PCI配置里,但是沒有嚴格的規定。
這段MMIO映射的內存大小代表什麼意思呢?如果準確點說,我也不知道。
因為顯卡廠商一般不太願意公開這方面的信息,從我看到的一些資料上看,各個廠商對MMIO映射的地址空間的描述也完全不同,沒有任何規範可言。
如果能拿到廠商的白皮書的話,也許還好辦,但顯卡廠商出於保密原因一般不給這方面的資料。
一般來說,這裡包含的內容有:
1) 操作顯卡的寄存器映射,用於發送控制命令,你的圖裡的那128K可能就是這個用途,這類地址一般都可以直接讀寫;
2) 映射顯卡的一部分顯存(注意不是全部),你的圖裡的那256M可能就是這個用途,這類地址讀寫的特性不確定。問題2:
Linux一般是向Frame Buffer(Framebuffer)里寫數據,然後顯卡會周期性的取走數據。
寫之前可能會通過I/O埠或者其它MMIO地址去操作顯卡寄存器發送一部分控制命令,具體是什麼就不清楚了,廠商一般都保密。
Windows應該採取的是類似的動作,Frame Buffer就是你截圖裡的那256M的一部分。
問題3:你截圖肯定不全,滾動條向下,你還能找到更多的MMIO內存地址範圍,但一般情況下,不會把全部顯存都映射過去。
原因是顯存可能很大,這麼做太浪費地址空間,對於32位系統來說,地址空間實在有限。
那麼CPU是如何通過256M地址空間訪問1G的顯存的?原因是256M只是一個窗口,它可以把顯存的某一段映射到內存中去,如果CPU要操作其它的位置,就發送控制命令改變映射的範圍即可。
這個技術有點類似於:Bank switching
問題4:
MMIO映射空間小於實際顯卡顯存,有些時候是為了刷新更多幀的需要,以題主你自己的顯卡為例,可以先映射1G空間里的第一個256M,把要顯示的圖像寫進去,再映射第二個256M,把後面要顯示的圖像寫進去……以此類推,這樣就能操作完整的顯存了,甚至這麼做可以提前把未來要繪製的圖像先寫到顯存里。
不同的顯卡每次能映射的最大顯存數是不同的,可能是顯卡本身DMA的限制,或者顯卡本身處理能力有限,畢竟一次傳輸完一個完整顯存大小的數據對於顯卡來說負擔還是太大了。
問題5:
你可以去了解一下PCI驅動的開發,但這方面真的沒有書可以推薦,因為顯卡技術涉及到的保密內容太多,其中很多東西甚至沒有專利,只有一些小廠商公開了一部分2D加速的技術資料,但具體如何操作寄存器,如何渲染圖像,資料實在太難找。
-----------------------------------
@Toe Z 的一些解釋我不是特別認同,說CPU不能直接訪問顯存這個問題,我覺得說法不太嚴謹。
一般來說,Linux里都是用Frame Buffer驅動來向顯卡寫數據的,Frame Buffer最終是把數據送到顯存里,這個過程是可以直接操作顯存的,只不過這個「操作」的概念很模糊,因為有些是用DMA把數據傳走的,如果說不能直接訪問,也不對,至少DMA控制器是看見這部分顯存了,只不過直接用CPU的指令MOV肯定會失敗。
通常情況下操作顯卡的動作是:
第一步:準備一個命令字,通過I/O埠或者MMIO映射的寄存器發送;
第二步:利用MMIO做DMA把內存里的東西送到顯存里;第三步:如果有返回命令,這裡可能要等待DMA中斷完成之類的動作;如果是頻繁的刷新圖像,可能會有一個專門的線程不停的向MMIO映射的那部分顯存里做DMA,目的就是不停的刷新顯存,讓顯卡那邊圖像更新。
統一編址的用途是給DMA控制器做DMA的時候用的,否則DMA操作起來會很麻煩。所以,嚴格的說,看具體什麼樣的顯卡,有些顯卡的顯存是可以被CPU直接訪問的,有些是不能的,但所有顯存都是可以被DMA的。
-----------------------------------
題外話:
顯卡的具體操作技術都是保密的,很多顯卡驅動也都是閉源的,包括在Linux上(有辦法可以規避GPL),顯卡的3D加速之類的具體如何操作硬體,基本上不會有廠商公布,所以對外表現出來的這些特性只能猜。
一些有合作的企業會公布一些代碼,但看代碼有時候很難理解它的行為,所以不要指望說看幾本書就會寫顯卡驅動,很不現實,要做顯卡驅動最好是到顯卡廠商那邊去。
-----------------------------------
睡不著,做了一個比較有意思的實驗:
在虛擬機里寫一個簡單的驅動,工作在內核態,然後找到顯卡的MMIO的第一段地址,在驅動里加一段代碼去讀這個地址,結果發現這段地址上的數據正好就是屏幕上顯示的像素信息。
VOID ReadGPUMem()
{
LARGE_INTEGER p;
int * np;
p.QuadPart = 0xE8000000;
np = MmMapIoSpace(p, PAGE_SIZE, MmNonCached);
if (np != NULL)
{
KdPrint(("%08x %08X %08X %08X %08X
", np, np[0], np[1], np[2], np[3]));
}
else
KdPrint(("Map fail
"));
return ;
}
因為是讀的前4個int,所以用截圖鍵(print screen)截圖比較了一下,像素點像素值完全等於我通過debug列印的數值,這說明0xE8000000這個地址就是我虛擬機上的顯存地址。
然後我又在列印的後面加了一句:
RtlZeroMemory(np, PAGE_SIZE);
屏幕顯示似乎沒有變化,但通過截圖鍵截圖發現,屏幕的上面有一條黑線,正好是我ZeroMemory的位置:
這說明至少虛擬機里顯卡映射的這部分是肯定可以讀寫的,甚至會影響系統截圖的效果。1. 內存和顯存統一編址是指UMA,多用於核顯。譬如Intel的核顯,它沒有自己獨立的內存,必須和CPU共享內存。BIOS可以為核顯在memory training後劃分獨立的空間,叫做stolen memory, 偷來給核顯用,大小可以通過BIOS設置。
2. 被劃分的核顯內存通過GTT來索引,GTT類似頁表。Frame Buffer也通過GTT來訪問。
3. Frame Buffer &< Stolen memory. 譬如GTT表本身就在stolen memory中,佔據一定空間,是由一個pci BAR指向的鏈表結構。還有些別的數據結構,譬如支持3D引擎的discriptor等等。還有雙frame buffer等等。
4. 核顯的PCI BAR一般不止一個,功能需要參考chip spec。
PCI的相關內容見:
深入PCI與PCIe之一:硬體篇 - 知乎專欄
深入PCI與PCIe之二:軟體篇 - 知乎專欄這個事情非常複雜,跟顯卡類型有關,跟系統的架構有關,也跟操作系統有關。
題主問的是windows系統,我不懂,不敢說。Linux略知一二,寫過幾年Linux的顯卡驅動。但顯卡驅動實在是太複雜了,這兒寫不下,只能簡單說一說,具體還是得查資料才能明白。好在設備驅動其實原理都差不多,相信能有一點幫助。
古代的顯卡就不去提它們了,只說現代的。一塊PCIe顯卡,首先是個PCIe設備,要理解CPU如何操作顯卡,首先得對PCIe設備的操作有些基本概念。需要的知識主要是:
1.地址空間。搞清楚內存空間(Memory Space)、I/O空間(I/O Space)和配置空間(Configuration Space)三者的區別和聯繫。可以先google和維基百科,詳情則可以去翻大部頭的PCI Express System Architecture,這裡有電子版https://www.mindshare.com/files/ebooks/PCI%20Express%20System%20Architecture.pdf
2.CPU配置設備的過程。一般來說,是到匯流排上掃一下(Enumerate)看看,找到設備之後,按照規定去檢測(probe)、初始化(init)。最開始的配置工作,是在配置空間做的,這個配置工作可以設置好I/O空間,以及別的一些基礎工作。之後,CPU就可以通過I/O來更加方便地操作設備了。這篇維基百科可以參考:PCI configuration space
然後回到題主的幾個問題:
***2.CPU是如何訪問顯存的(拿寫為例子),是通過物理地址直接訪問(如果上面256M是映射的顯存的話);還是通過控制寄存器間接訪問,即先把要寫的數據放到寄存器,再發出命令讓顯卡取走,最後顯卡把來自寄存器的數據寫入顯存。***
首先CPU不會通過物理地址直接訪問現存。物理地址是什麼?是相對於邏輯地址和虛擬地址來說的,只用來訪問系統內存。而顯卡的現存,是在設備之中的,CPU無法直接給出其物理地址。實際上現代的顯卡一般都自己帶一個MMU,做內存管理用的,CPU可以配置顯卡的MMU,但無法直接訪問它。
好吧其實也不絕對,因為不少移動設備的顯卡並不是獨立的,只是作為SoC的一個core(即GPU)存在,雖然也號稱有顯存,其顯存卻是由BIOS從系統內存中劃分出固定的一塊出來給它。於是乎,CPU是可以用物理地址訪問顯存的。這時候,CPU的MMU把一段虛地址空間映射到某一段物理內存上,GPU的MMU也把一段虛地址空間映射到同一段物理內存上,然後兩邊可以各自訪問,比如CPU寫了一段數據進去,然後讓GPU去渲染顯示。甚至CPU寫了一段代碼進去,請GPU去執行,即可以是3D渲染任務,也可以是利用GPU做一些加速運算。
所以說,一般CPU不直接訪問顯存。但在3D加速、GPGPU等場景中,有時是CPU和GPU共享一段物理內存,此時數據傳輸主要通過內存,而寄存器主要是傳遞控制命令用的。需要注意的是,3D加速、OpenCL、CUDA等場景下一般也是不共享內存的,而是從系統內存把數據和程序傳輸到顯卡中、運行之、最後將結果傳輸回系統內存。
***3.很多人說顯存的地址和內存的地址是統一編址的,那麼CPU就可以直接訪問顯存,而不是通過控制器介面間接訪問。我認為這是不對的,原因如下:我的顯卡是1G的,但是下圖顯示只有256M地址空間分配給了顯卡。***
顯存地址和內存地址統一編址,這個概念很含糊,一般可以算錯。然而還是有例外,具體請查Aperture Graphics Memory這個概念,但這東西比較古老,學習意義不大。
但是題主覺得顯存是1G,而顯示只有256M空間分配給了顯卡,所以顯存和內存地址一定不是統一編址的,這個邏輯也比較混亂。具體而言又不知從何說起,感覺題主對CPU如何訪問內存還不是很清楚,這個需要翻一翻計算機體系結構的教材。然後也參考一下設備驅動開發的入門資料吧,Linux有Linux Device Driver,Windows我就不清楚了。
問題1和4我不清楚,不懂Windows。
聲明:上述內容大多數是憑模糊的記憶寫的,相信有不少錯誤,歡迎指正。論文還沒寫完,暫時沒時間查證,也許以後會更新完善。謝謝閱讀!推薦題主一本書。windows內核原理與實現。潘愛民著。
或許可以嘗試另外一種方式來了解這個問題的本質,PC上的顯卡太複雜,也過於封閉。在下在工作中接觸了一款嵌入式"顯卡",或許"顯卡"不太適合這個名字,然而確實是一款很類似顯卡工作模式的IC,可以說這款IC的設計者起碼對PC的顯卡有著足夠的了解才去做設計的,思想完全是一致的。==========================================================================這款IC被稱作是Embedded Video Engine(EVE),官方的數據手冊和App Note都是開放的,可以去看看,晶元是FTDI的FT80x系列。==========================================================================一些概念:Display_List (DL)SwapREG OBJ DRAM==========================================================================
//DL顯示開始
HAL_CmdBufIn(CMD_DLSTART);
HAL_CmdBufIn(CLEAR_COLOR_RGB(0, 0, 0));
HAL_CmdBufIn(CLEAR(1, 1, 1));
//線條載入顯示
HAL_CmdBufIn(BEGIN(LINES));
HAL_CmdBufIn(CLEAR_COLOR_RGB(255, 255, 255));
HAL_CmdBufIn(LINE_WIDTH(1*16));
HAL_CmdBufIn(VERTEX2F(319*FT800_PIXEL_UNIT, 199*FT800_PIXEL_UNIT));
HAL_CmdBufIn(VERTEX2F(1*FT800_PIXEL_UNIT, 199*FT800_PIXEL_UNIT));
HAL_CmdBufIn(END());
//DL顯示結束
HAL_CmdBufIn(DISPLAY());
HAL_CmdBufIn(CMD_SWAP);
HAL_BufToReg(RAM_CMD,0);
以上是完整的過程,是否有一些體會呢,建議對著手冊看看一些細節的東西。
就我知道的來回答吧。背景是windows及directx。gpu架構分NUMA,NCC-UMA,CC-UMA。通常我們所說的獨顯是NUMA。在此架構下,內存分為系統內存和顯示內存,顯示內存不能被cpu直接訪問,必須通過gpu。然後gpu可以訪問系統內存,但不能隨意訪問。需要藉助圖形api來進行相關操作,如directx12下需要通過其提供相應api將資源從系統內存傳輸到系統內存中指定的direct3d資源的位置,才能夠被gpu訪問到。在dx12中有resource來代表gpu顯示內存中的資源,它裡面有相應的gpu vitual address。與resouce對應的有heap,分upload,read back,default三種類型,heap是用來創建resource的。它的類型限制了你能如何操作resouce,也就是相應的顯示內存。綜上如何訪問獨顯,在系統層面上有圖形api提供功能。
3.很多人說顯存的地址和內存的地址是統一編址的,那麼CPU就可以直接訪問顯存,而不是通過控制器介面間接訪問。我認為這是不對的,原因如下:我的顯卡是1G的,但是下圖顯示只有256M地址空間分配給了顯卡。
顯存的地址和物理內存的地址是統一編址的意思是,它們邏輯上是屬於一個地址空間,就是說對CPU來說,只要往匯流排上提供一個「數」,就可以明確向匯流排表達它到底想訪問的是哪個地址。假如不是統一編址的,那就必須再提供一個「名字空間的標記」同這個「數」一起確定一個唯一的地址,這樣帶來的是給寫軟體的人的複雜性,硬體發展的一個目標之一就是向軟體開發者屏蔽一切值得屏蔽的東西。至於通不通過獨立顯卡的控制器介面間接訪問,那當然是要通過。你CPU就算要訪問普通的DDR物理內存,也不是「直接」訪問啊,不也是要先把訪問請求送給PCI橋晶元來「間接」訪問的嗎。畢竟不管是物理內存還是獨立顯卡的顯存,都不是直接連在CPU的地址匯流排上的。所以肯定要通過其他控制器。
要了解這方面的知識要看什麼書或哪方面知識,求推薦。。。
個人覺得看書的幫助有限,不知道你要研究它們的目的是什麼?你開車時要求對引擎,變速箱的工作原理了解嗎?你如果是處女座不能接受你不理解的東西,那麼最高效獲取這些知識的方法就是去做相關東西的公司去上班,很快就可以了解了。
推薦閱讀:
※如何理解 Objective-C 中的 strong 和 weak ?
※為什麼 intrusive_ptr 沒有進入標準庫?
※現代c++內存管理的方式有哪些?
※Firefox和Chrome相比是否更適合小內存用戶?
※c++字元串拷貝和內存問題?