成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

按下鍵盤后為什么屏幕上就會有輸出

網絡 通信技術
內存中有這樣一部分區域,是和顯存映射的。啥意思,就是你往上圖的這些內存區域中寫數據,相當于寫在了顯存中。而往顯存中寫數據,就相當于在屏幕上輸出文本了。

書接上回,上回書咱們說到,繼內存管理結構 mem_map 和中斷描述符表 idt 建立好之后,我們又在內存中倒騰出一個新的數據結構 request。

并且把它們都放在了一個數組中。

這是塊設備驅動程序與內存緩沖區的橋梁,通過它可以完整地表示一個塊設備讀寫操作要做的事。

我們繼續往下看,tty_init。

  1. void main(void) { 
  2.     ... 
  3.     mem_init(main_memory_start,memory_end); 
  4.     trap_init(); 
  5.     blk_dev_init(); 
  6.     chr_dev_init(); 
  7.     tty_init(); 
  8.     time_init(); 
  9.     sched_init(); 
  10.     buffer_init(buffer_memory_end); 
  11.     hd_init(); 
  12.     floppy_init(); 
  13.      
  14.     sti(); 
  15.     move_to_user_mode(); 
  16.     if (!fork()) {init();} 
  17.     for(;;) pause(); 

這個方法執行完成之后,我們將會具備鍵盤輸入到顯示器輸出字符這個最常用的功能。

打開這個函數后我有點慌。

  1. void tty_init(void) 
  2.     rs_init(); 
  3.     con_init(); 

看來這個方法已經多到需要拆成兩個子方法了。

打開第一個方法,還好。

  1. void rs_init(void) 
  2.     set_intr_gate(0x24,rs1_interrupt); 
  3.     set_intr_gate(0x23,rs2_interrupt); 
  4.     init(tty_table[1].read_q.data); 
  5.     init(tty_table[2].read_q.data); 
  6.     outb(inb_p(0x21)&0xE7,0x21); 

這個方法是串口中斷的開啟,以及設置對應的中斷處理程序,串口在我們現在的 PC 機上已經很少用到了,所以這個直接忽略,要講我也不懂。

看第二個方法,這是重點。代碼非常長,有點嚇人,我先把大體框架寫出。

  1. void con_init(void) { 
  2.     ... 
  3.     if (ORIG_VIDEO_MODE == 7) { 
  4.         ... 
  5.         if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10) {...} 
  6.         else {...} 
  7.     } else { 
  8.         ... 
  9.         if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10) {...} 
  10.         else {...} 
  11.     } 
  12.     ... 

可以看出,非常多的 if else。

這是為了應對不同的顯示模式,來分配不同的變量值,那如果我們僅僅找出一個顯示模式,這些分支就可以只看一個了。 啥是顯示模式呢?那我們得簡單說說顯示,一個字符是如何顯示在屏幕上的呢?換句話說,如果你可以隨意操作內存和 CPU 等設備,你如何操作才能使得你的顯示器上,顯示一個字符‘a’呢?

我們先看一張圖。

內存中有這樣一部分區域,是和顯存映射的。啥意思,就是你往上圖的這些內存區域中寫數據,相當于寫在了顯存中。而往顯存中寫數據,就相當于在屏幕上輸出文本了。

沒錯,就是這么簡單。 如果我們寫這一行匯編語句。

  1. mov [0xB8000],'h' 

后面那個 h 相當于匯編編輯器幫我們轉換成 ASCII 碼的二進制數值,當然我們也可以直接寫。

  1. mov [0xB8000],0x68 

其實就是往內存中 0xB8000 這個位置寫了一個值,只要一寫,屏幕上就會是這樣。

簡單吧,具體說來,這片內存是每兩個字節表示一個顯示在屏幕上的字符,第一個是字符的編碼,第二個是字符的顏色,那我們先不管顏色,如果多寫幾個字符就像這樣。

  1. mov [0xB8000],'h' 
  2. mov [0xB8002],'e' 
  3. mov [0xB8004],'l' 
  4. mov [0xB8006],'l' 
  5. mov [0xB8008],'o' 

此時屏幕上就會是這樣。

是不是賊簡單?那我們回過頭看剛剛的代碼,我們就假設顯示模式是我們現在的這種文本模式,那條件分支就可以去掉好多。 代碼可以簡化成這個樣子。

  1. #define ORIG_X          (*(unsigned char *)0x90000) 
  2. #define ORIG_Y          (*(unsigned char *)0x90001) 
  3. void con_init(void) { 
  4.     register unsigned char a; 
  5.     // 第一部分 獲取顯示模式相關信息 
  6.     video_num_columns = (((*(unsigned short *)0x90006) & 0xff00) >> 8); 
  7.     video_size_row = video_num_columns * 2; 
  8.     video_num_lines = 25; 
  9.     video_page = (*(unsigned short *)0x90004); 
  10.     video_erase_char = 0x0720; 
  11.     // 第二部分 顯存映射的內存區域  
  12.     video_mem_start = 0xb8000; 
  13.     video_port_reg  = 0x3d4; 
  14.     video_port_val  = 0x3d5; 
  15.     video_mem_end = 0xba000; 
  16.     // 第三部分 滾動屏幕操作時的信息 
  17.     origin  = video_mem_start; 
  18.     scr_end = video_mem_start + video_num_lines * video_size_row; 
  19.     top = 0; 
  20.     bottom  = video_num_lines; 
  21.     // 第四部分 定位光標并開啟鍵盤中斷 
  22.     gotoxy(ORIG_X, ORIG_Y); 
  23.     set_trap_gate(0x21,&keyboard_interrupt); 
  24.     outb_p(inb_p(0x21)&0xfd,0x21); 
  25.     a=inb_p(0x61); 
  26.     outb_p(a|0x80,0x61); 
  27.     outb(a,0x61); 

別看這么多,一點都不難。

首先還記不記得之前匯編語言的時候做的工作,存了好多以后要用的數據在內存中。

內存地址 長度(字節) 名稱
0x90000 2 光標位置
0x90002 2
擴展內存數
0x90004 2 顯示頁面
0x90006 1
顯示模式
0x90007 1 字符列數
0x90008 2 未知
0x9000A 1
顯示內存
0x9000B 1
顯示狀態
0x9000C 2 顯卡特性參數
0x9000E 1
屏幕行數
0x9000F 1 屏幕列數
0x90080 16
硬盤1參數表
0x90090 16 硬盤2參數表
0x901FC 2
根設備號

所以,第一部分獲取 0x90006 地址處的數據,就是獲取顯示模式等相關信息。

第二部分就是顯存映射的內存地址范圍,我們現在假設是 CGA 類型的文本模式,所以映射的內存是從 0xB8000 到 0xBA000。

第三部分是設置一些滾動屏幕時需要的參數,定義頂行和底行是哪里,這里頂行就是第一行,底行就是最后一行,很合理。

第四部分是把光標定位到之前保存的光標位置處(取內存地址 0x90000 處的數據),然后設置并開啟鍵盤中斷。

開啟鍵盤中斷后,鍵盤上敲擊一個按鍵后就會觸發中斷,中斷程序就會讀鍵盤碼轉換成 ASCII 碼,然后寫到光標處的內存地址,也就相當于往顯存寫,于是這個鍵盤敲擊的字符就顯示在了屏幕上。

這一切具體是怎么做到的呢?我們先看看我們干了什么。

1. 我們現在根據已有信息已經可以實現往屏幕上的任意位置寫字符了,而且還能指定顏色。

2. 并且,我們也能接受鍵盤中斷,根據鍵盤碼中斷處理程序就可以得知哪個鍵按下了。

有了這倆功能,那我們想干嘛還不是為所欲為?

好,接下來我們看看代碼是怎么處理的,很簡單。一切的起點,就是第四步的 gotoxy 函數,定位當前光標。

  1. #define ORIG_X          (*(unsigned char *)0x90000) 
  2. #define ORIG_Y          (*(unsigned char *)0x90001) 
  3. void con_init(void) { 
  4.     ... 
  5.     // 第四部分 定位光標并開啟鍵盤中斷 
  6.     gotoxy(ORIG_X, ORIG_Y); 
  7.     ... 

這里面干嘛了呢?

  1. static inline void gotoxy(unsigned int new_x,unsigned int new_y) { 
  2.    ... 
  3.    x = new_x; 
  4.    y = new_y; 
  5.    pos = origin + y*video_size_row + (x<<1); 

就是給 x y pos 這三個參數附上了值。

其中 x 表示光標在哪一列,y 表示光標在哪一行,pos 表示根據列號和行號計算出來的內存指針,也就是往這個 pos 指向的地址處寫數據,就相當于往控制臺的 x 列 y 行處寫入字符了,簡單吧?

然后,當你按下鍵盤后,觸發鍵盤中斷,之后的程序調用鏈是這樣的。

  1. _keyboard_interrupt: 
  2.     ... 
  3.     call _do_tty_interrupt 
  4.     ... 
  5.      
  6. void do_tty_interrupt(int tty) { 
  7.    copy_to_cooked(tty_table+tty); 
  8.  
  9. void copy_to_cooked(struct tty_struct * tty) { 
  10.     ... 
  11.     tty->write(tty); 
  12.     ... 
  13.  
  14. // 控制臺時 tty 的 write 為 con_write 函數 
  15. void con_write(struct tty_struct * tty) { 
  16.     ... 
  17.     __asm__("movb _attr,%%ah\n\t" 
  18.       "movw %%ax,%1\n\t" 
  19.       ::"a" (c),"m" (*(short *)pos) 
  20.       :"ax"); 
  21.      pos += 2; 
  22.      x++; 
  23.     ... 

前面的過程不用管,我們看最后一個函數 con_write 中的關鍵代碼。

__asm__ 內聯匯編,就是把鍵盤輸入的字符 c 寫入pos 指針指向的內存,相當于往屏幕輸出了。

之后兩行 pos+=2 和 x++,就是調整所謂的光標。

你看,寫入一個字符,最底層,其實就是往內存的某處寫個數據,然后順便調整一下光標。

由此我們也可以看出,光標的本質,其實就是這里的 x y pos 這仨變量而已。

我們還可以做換行效果,當發現光標位置處于某一行的結尾時(這個應該很好算吧,我們都知道屏幕上一共有幾行幾列了),就把光標計算出一個新值,讓其處于下一行的開頭。

就一個小計算公式即可搞定,仍然在 con_write 源碼處有體現,就是判斷列號 x 是否大于了總列數。

  1. void con_write(struct tty_struct * tty) { 
  2.     ... 
  3.     if (x>=video_num_columns) { 
  4.         x -= video_num_columns; 
  5.         pos -= video_size_row; 
  6.         lf(); 
  7.   } 
  8.   ... 
  9.  
  10. static void lf(void) { 
  11.    if (y+1<bottom) { 
  12.       y++; 
  13.       pos += video_size_row; 
  14.       return
  15.    } 
  16.  ... 

相似的,我們還可以實現滾屏的效果,無非就是當檢測到光標已經出現在最后一行最后一列了,那就把每一行的字符,都復制到它上一行,其實就是算好哪些內存地址上的值,拷貝到哪些內存地址,就好了。

這里大家自己看源碼尋找。 所以,有了這個初始化工作,我們就可以利用這些信息,弄幾個小算法,實現各種我們常見控制臺的操作。

或者換句話說,我們見慣不怪的控制臺,回車、換行、刪除、滾屏、清屏等操作,其實底層都要實現相應的代碼的。 所以 console.c 中的其他方法就是做這個事的,我們就不展開每一個功能的方法體了,簡單看看有哪些方法。

  1. // 定位光標的 
  2. static inline void gotoxy(unsigned int new_x, unsigned int new_y){} 
  3. // 滾屏,即內容向上滾動一行 
  4. static void scrup(void){} 
  5. // 光標同列位置下移一行 
  6. static void lf(int currcons){} 
  7. // 光標回到第一列 
  8. static void cr(void){} 
  9. ... 
  10. // 刪除一行 
  11. static void delete_line(void){} 

內容繁多,但沒什么難度,只要理解了基本原理即可了。

OK,整個 console.c 就講完了,要知道這個文件可是整個內核中代碼量最大的文件,可是功能特別單一,也都很簡單,主要是處理鍵盤各種不同的按鍵,需要寫好多 switch case 等語句,十分麻煩,我們這里就完全沒必要去展開了,就是個苦力活。 到這里,我們就正式講完了 tty_init 的作用。

在此之后,內核代碼就可以用它來方便地在控制臺輸出字符啦!這在之后內核想要在啟動過程中告訴用戶一些信息,以及后面內核完全建立起來之后,由用戶用 shell 進行操作時手動輸入命令,都是可以用到這里的代碼的! 讓我們繼續向前進發,看下一個被初始化的倒霉鬼是什么東東。 欲知后事如何,且聽下回分解。

本文轉載自微信公眾號「低并發編程」,可以通過以下二維碼關注。轉載本文請聯系低并發編程公眾號。本網站已獲得低并發編程的授權。

 

責任編輯:武曉燕 來源: 低并發編程
相關推薦

2021-05-28 08:01:00

JS原型概念

2020-08-02 22:54:04

Python編程語言開發

2017-03-09 11:15:18

LinuxRoot賬戶

2022-07-26 23:43:29

編程語言開發Java

2017-12-21 19:38:50

潤乾中間表

2021-12-20 14:42:39

程序員職業技術

2021-07-29 10:26:34

數據分析上云CIO

2016-03-01 15:38:37

微軟鍵盤App

2019-12-02 14:22:01

浪費云計算支出

2013-01-15 09:41:45

編程語言

2022-08-02 18:37:24

BI系統快照表

2019-12-02 15:48:13

SSD容量閃存

2013-01-08 09:40:16

大數據軟硬件技術

2020-10-15 13:19:24

為什么會存在亂碼

2013-01-24 09:44:44

數據庫

2021-04-15 21:55:38

電腦磁盤微軟

2015-05-18 15:08:08

多種程序設計語言程序設計語言

2020-05-28 07:50:18

重排序happens-befCPU

2020-06-02 14:17:55

QWER排列鍵盤打印機

2013-08-01 12:17:21

SAP
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩毛片免费看 | 精品久久久久久久久久久久久久 | 天天爽天天操 | 在线成人精品视频 | 日韩一区在线观看视频 | 一区二区三区小视频 | 91在线色视频 | 免费在线日韩 | 日韩福利一区 | 国产乱码精品一品二品 | 国产精品一区二 | 亚洲福利网 | 国产精品一卡 | 亚洲 中文 欧美 日韩 在线观看 | 国产一区二区精品在线观看 | 亚洲精品成人 | 一区二区三区不卡视频 | 色欧美片视频在线观看 | 龙珠z在线观看 | 亚洲一二三区精品 | 亚洲少妇综合网 | 亚洲精品中文字幕 | 日日淫 | 久久久精品一区二区三区 | 国产成人精品久久二区二区 | 久久久人成影片免费观看 | 久久精品中文 | 激情av免费看 | 天天操天天摸天天爽 | 日韩av在线免费 | 国产乱码久久久久久一区二区 | 国内精品成人 | 黑色丝袜三级在线播放 | 亚洲看片网站 | 日韩一区二区在线观看 | 色综合99 | 国产精品一二区 | 成人性生交大片 | 欧美v日韩v| 欧美日韩在线电影 | 亚洲一区高清 |