一步步编写操作系统 71 直接操作显卡,编写自己的打印函数71-74
一直以來,我們在往屏幕上輸出文本時,要么利用bios中斷,要么利用系統調用,這些都是依賴別人的方法。咱們還用過一個稍微有點獨立的方法,就是直接寫顯存,但這貌似又沒什么含量。如今我們要寫一個打印函數了,似乎,我們馬上就要站起來了
之前我們講述了有關顯卡的知識,但當時怕影響兄弟們的學習積極性,我們并沒有說把有關顯卡的寄存器羅列出來。話說,出來混早晚要還的,躲得過初一躲不過十五。如今我們需要通過端口來控制顯卡的行為,有些問題還是要面對的。
之前咱們對顯卡的操作和對普通內存操作是一樣的,打印字符時,就是往顯存中mov一些字符的ascii碼和屬性,那還是我們在顯存默認的文本模式下。您想,我們都愛看視頻、電影,話說十年前第一次看到DVD版本的電影時我都被震撼到了,當時看的是《星河戰隊》,清晰到毛發可見的程度,何況大家現在都偏愛藍光高清版本,咳咳,說遠了,總之能夠讓我們看到如此炫麗的畫面,這都是顯卡的功勞,這說明顯卡還可以工作在彩色圖形模式。對于顯卡的操作可不是咱們之前的mov來mov去就行了。不過我們也并不需要那么復雜的功能,咱們還是在80*25的文本模式下轉悠,而且還只是簡單的操作。
之前我們已經對硬盤有過端口操作了,無非就是用in和out指令加不同的端口號,對顯卡也是如此。顯卡中的寄存器很多,不,是非常多,這里按照它們在圖形管線(位于cpu和video之間)中的位置的順序給大家介紹下,見下表
?
如您所見,表中列出的寄存器的數量似乎沒我說的那么恐怖,不要高興的太早,馬上就要讓大伙兒難過了,其實這些只是寄存器的目錄而已,這有沒有讓大家想起了周星馳主演的電影《鹿鼎記》中,天地會總舵主陳近南讓韋小寶練武功時的場景,拿出了一本不算太厚的“武功秘密”,起初小寶還很高興,但陳近南告訴他這只是個目錄,而且是練了之后才九死一生,否則就十死無生^_^。
好了,下節到解釋,本節到此,現來玩哦。
接上文,請見“一步步編寫操作系統 71 直接操作顯卡,編寫自己的打印函數1” 下面解釋下顯卡寄存器的內容。
以上所說的目錄其實就是寄存器分組,在這些寄存器中也不全是分組。前四組寄存器屬于分組,它們有一個特征,就是被分成了兩類寄存器,即Address Register和Data Register。這兩個寄存器是干嗎的呢?這得先從寄存器為什么要分成組開始說。
端口實際上就是IO接口電路上的寄存器,為了能訪問到這些cpu外部的寄存器,計算機系統為這些寄存器統一編址,一個寄存器被賦予一個地址,這些地址可不是我們所說的內存地址,內存地址是用來訪問內存用的,其范圍取決于地址總線的寬度,而寄存器的地址范圍是0~65535(Intel系統)。這些地址就是我們所說的端口號,用專門的IO指令in和out來讀寫這些寄存器。至于計算機內部訪問端口怎么實現的,這是硬件工程師的事,咱們暫且奉行拿來主義,認同這個事實就夠了。
IO接口電路上的寄存器數量有多有少,這要看具體的外設了,我這么說您就明白了,這里給寄存器分組的原因是,顯卡(顯示器的IO接口電路)上的寄存器太多了,如果一個寄存器就要占用一個系統端口的話,這得多浪費硬件資源,萬一別的硬件也這么干,這63336個地址可就捉襟見肘了。所以計算機系統說了,我不管你們內部有多少寄存器,給你們的端口地址是有數的,你們自己內部協調吧。
計算機工程師是非常聰明的,把數據結構中數組的知識用到了硬件中。他們把每一個寄存器分組視為一個寄存器數組,提供個寄存器用于指定數組下標,再提供個寄存器用于對索引所指向的數組元素(也就是寄存器)進行輸入輸出操作。這樣用這兩個寄存器就能夠定位寄存器數組中的任何寄存器啦。
這兩個寄存器就是各組中的Address Register和Data Register。Address Register做為數組的索引(下標),Data Register做為寄存器數組中該索引對應的寄存器,它相當于所對應的寄存器的窗口,往此窗口讀寫的數據都作用在索引所對應的寄存器上。
所以,對這類分組的寄存器操作方法是,先在Address Register中指定寄存器的索引值,用來確定所操作的寄存器是哪個,然后在Data Register寄存器中對所索引的寄存器進行讀寫操作。
上面CRT Controller Registers寄存器組中的Address Register和Data Register的端口地址有些特殊,它的端口地址并不固定,具體值取決于Miscellaneous Output Register寄存器中的Input/Output Address Select字段,現在咱們看一下這個寄存器。
?
和大家坦白一點,顯卡參數還需要專業人士來解釋,由于咱們用不到這么高深的設置,加之我對顯卡沒有深入學習,所以這里面有好多參數術語,我不敢隨意翻譯成中文,擔心誤導大家,所以我直接把此寄存器各字段的英文描述搬過來了,至于中文的意思,大家仁者見仁智者見智吧,請您見諒。
?
好了,簡直了,就這樣吧,晚安。
?
?
萬事開頭難,我們先從簡單的打印字符開始。這個功能類似c語言中的putchar,每次只打印一個字符,由于此函數咱們是在內核中實現的,暫且將其命名為put_char。
在這之前,為了開發方便,我們定義一些數據類型。主要是參考了linux的/usr/include/stdint.h文件,有環境的同學可以自行看下,沒環境的同學,請看圖
?
該文件在我目前的linux版本上是320行,這里只是冰山一角,里面各種宏顯得好高大上啊,不過請放心,把這個圖貼出來就是為了“嚇唬”大家的^_^,咱們不會寫這么復雜,不信請看代碼:
1 #ifndef __LIB_STDINT_H2 #define __LIB_STDINT_H3 typedef signed char int8_t;4 typedef signed short int int16_t;5 typedef signed int int32_t;6 typedef signed long long int int64_t;7 typedef unsigned char uint8_t;8 typedef unsigned short int uint16_t;9 typedef unsigned int uint32_t; 10 typedef unsigned long long int uint64_t; 11 #endif怎么樣,確實是很簡單吧。以后我們采用的任何數據類型就要用這些定義好的啦。估計大家也注意到啦,咱們定義的stdint.h文件位于lib目錄下,也就是說我新建了個lib目錄做來專門存放各種庫文件。不僅如此,在lib目錄下還建立了user和kernel兩個子目錄,以后供內核使用的庫文件就放在lib/kernel/下,lib/user/中是用戶進程使用的庫文件。
我們要實現的字符打印函數叫put_char,它是用匯編語言寫的。因為要和顯卡打交道啦,里面涉及到端口的讀寫操作,目前還是用純匯編文件較方便,以后慢慢發展起來后,咱們會采取內聯匯編的方式。
直接上代碼啦,我們的打印函數統統在print.S文件中完成,該文件是各種打印函數的核心,重中之重,這里先給大家介紹下它的處理流程:
該文件相對來說又有點長,故需要將其拆分成3部分,先給大伙兒呈上其第一部分,代碼:
1 TI_GDT equ 02 RPL0 equ 03 SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL045 [bits 32]6 section .text7 ;------------------------ put_char -----------------------------8 ;功能描述:把棧中的1個字符寫入光標所在處9 ;-------------------------------------------------------------------10 global put_char11 put_char:12 pushad ;備份32位寄存器環境13 ;需要保證gs中為正確的視頻段選擇子,;為保險起見,每次打印時都為gs賦值14 mov ax, SELECTOR_VIDEO ; 不能直接把立即數送入段寄存器 15 mov gs, ax1617 ;;;;;;;;; 獲取當前光標位置 ;;;;;;;;;18 ;先獲得高8位19 mov dx, 0x03d4 ;索引寄存器20 mov al, 0x0e ;用于提供光標位置的高8位21 out dx, al22 mov dx, 0x03d5 ;通過讀寫數據端口0x3d5來獲得或設置光標位置23 in al, dx ;得到了光標位置的高8位24 mov ah, al2526 ;再獲取低8位27 mov dx, 0x03d428 mov al, 0x0f29 out dx, al30 mov dx, 0x03d531 in al, dx3233 ;將光標存入bx34 mov bx, ax35 ;下面這行是在棧中獲取待打印的字符36 mov ecx, [esp + 36] ;pushad壓入4×8=32字節,;加上主調函數4字節的返回地址,故esp+36字節37 cmp cl, 0xd ;CR是0x0d,LF是0x0a38 jz .is_carriage_return39 cmp cl, 0xa40 jz .is_line_feed4142 cmp cl, 0x8 ;BS(backspace)的asc碼是843 jz .is_backspace44 jmp .put_other45 ;;;;;;;;;;;;;;;;;;下節我們再解釋代碼吧,再來玩哦。
?
?
接前文,下面把代碼解釋一下。
1 TI_GDT equ 02 RPL0 equ 03 SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL045 [bits 32]6 section .text7 ;------------------------ put_char -----------------------------8 ;功能描述:把棧中的1個字符寫入光標所在處9 ;-------------------------------------------------------------------10 global put_char11 put_char:12 pushad ;備份32位寄存器環境13 ;需要保證gs中為正確的視頻段選擇子,;為保險起見,每次打印時都為gs賦值14 mov ax, SELECTOR_VIDEO ; 不能直接把立即數送入段寄存器 15 mov gs, ax1617 ;;;;;;;;; 獲取當前光標位置 ;;;;;;;;;18 ;先獲得高8位19 mov dx, 0x03d4 ;索引寄存器20 mov al, 0x0e ;用于提供光標位置的高8位21 out dx, al22 mov dx, 0x03d5 ;通過讀寫數據端口0x3d5來獲得或設置光標位置23 in al, dx ;得到了光標位置的高8位24 mov ah, al2526 ;再獲取低8位27 mov dx, 0x03d428 mov al, 0x0f29 out dx, al30 mov dx, 0x03d531 in al, dx3233 ;將光標存入bx34 mov bx, ax35 ;下面這行是在棧中獲取待打印的字符36 mov ecx, [esp + 36] ;pushad壓入4×8=32字節,;加上主調函數4字節的返回地址,故esp+36字節37 cmp cl, 0xd ;CR是0x0d,LF是0x0a38 jz .is_carriage_return39 cmp cl, 0xa40 jz .is_line_feed4142 cmp cl, 0x8 ;BS(backspace)的asc碼是843 jz .is_backspace44 jmp .put_other45 ;;;;;;;;;;;;;;;;;;put_char函數中以后我們任何一個打印功能的核心,所以光它的實現就要112行,這似乎是我們目前寫過的最長的一個函數了,我保證以后也沒有這么長的啦。好啦,長歸長,不過也沒什么難度,下面咱們開講啦。
put_char的打印原理是直接寫顯存,在32位保護模式下對內存的操作是“[段基址(選擇子):段內偏移量]”,所以這就涉及到視頻段選擇子啦。一直以來我們都是用段寄存器gs來存儲視頻段選擇子,以后也是,所以得保證在寫顯存之前,gs中的值是正確的選擇子。第14~15行是我們為GS寄存器賦值的代碼,別小看這兩行,大有來頭,可不亞于攤上大事呢,吼吼,待咱們把put_char函數說完再跟大家好好說道說道吧,大家要做好心理準備。咱們先說別的。
第1~3行是定義了視頻段的選擇子,由于只需要這三行,專門定義個配置文件有點不值當的,所以直接在這定義了,好的習慣是放在配置文件中,大家在實踐中不要學我。
第10行是通過關鍵字global把函數put_char導出為全局符號,這樣對外部文件便可見了,外部文件通過聲明便可以調用。
第11行開始定義函數put_char。
第12行是用pushad指令備份32位寄存器的環境,按理說用到哪些寄存器就要備份哪些,我這里是偷懶行為,將8個32位全部備份了。PUSHAD是push all double,該指令壓入所有雙字長的寄存器,這里的“所有”一共是8個,它們的入棧先后順序是: EAX->ECX->EDX->EBX->ESP->EBP->ESI->EDI,EAX是最先入棧。
第14~15行是為gs安裝正確的選擇子,原因如前所述完事再說。
我們在打印字符時,通常都不用指定字符顯示的坐標位置,大家也沒覺得有什么奇怪,原因是字符是在當前光標的位置處顯示的,而且光標的位置會一直更新順延,我們的字符一直跟著光標走,似乎光標就是字符的導航一樣,而我們已經習慣了跟隨光標。我想大伙兒已經清楚了光標和字符的關系了,對,它們的關系就是沒有任何關系^_^。“光標在哪字符就在哪”,這是我們人為有意設置的,我們是在光標處打印字符。也就是說,我們也可以不在光標處打印字符,讓光標和字符的位置分開。這一點在理論上就能證明,我們知道打印字符本質上就是把字符寫入在顯存中的某個地址處。在文本模式80*25下的顯存可以顯示80*25=2000個字符,每個字符占2字節,低字節是字符的ascii碼,高字節是前景色和背景色屬性,所以在4000字節的顯存空間中,只要起始地址為偶數的任意2字節我們都可以寫入字符,您看,這哪里是光標能限制的。光標只是個亮點,用來吸引用戶眼球的,它能夠幫助咱們快速找到屏幕上的活躍位置,它本身與字符顯示的位置沒有關系。
有點長,下節再說吧。
?
?
總結
以上是生活随笔為你收集整理的一步步编写操作系统 71 直接操作显卡,编写自己的打印函数71-74的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 信用卡还了钱为什么也会逾期
- 下一篇: 一步步编写操作系统 47 elf格式文件