歡迎光臨
每天分享高質量文章

計算機實驗室之樹莓派:課程 11 輸入02 | Linux 中國

課程輸入 02 是以課程輸入 01 為基礎講解的,通過一個簡單的命令列實現用戶的命令輸入和計算機的處理和顯示。

— Alex Chadwick

 

課程輸入 02 是以課程輸入 01 為基礎講解的,通過一個簡單的命令列實現用戶的命令輸入和計算機的處理和顯示。本文假設你已經具備 課程11:輸入01[1] 的操作系統代碼基礎。

1、終端

幾乎所有的操作系統都是以字符終端顯示啟動的。經典的黑底白字,通過鍵盤輸入計算機要執行的命令,然後會提示你拼寫錯誤,或者恰好得到你想要的執行結果。這種方法有兩個主要優點:鍵盤和顯示器可以提供簡易、健壯的計算機交互機制,幾乎所有的計算機系統都採用這個機制,這個也廣泛被系統管理員應用。

早期的計算一般是在一棟樓里的一個巨型計算機系統,它有很多可以輸命令的’終端’。計算機依次執行不同來源的命令。

讓我們分析下真正想要哪些信息:

1. 計算機打開後,顯示歡迎信息
2. 計算機啟動後可以接受輸入標誌
3. 用戶從鍵盤輸入帶引數的命令
4. 用戶輸入回車鍵或提交按鈕
5. 計算機解析命令後執行可用的命令
6. 計算機顯示命令的執行結果,過程信息
7. 迴圈跳轉到步驟 2

這樣的終端被定義為標準的輸入輸出設備。用於(顯示)輸入的屏幕和打印輸出內容的屏幕是同一個(LCTT 譯註:最早期的輸出打印真是“打印”到印表機/電傳機的,而用於輸入的終端只是鍵盤,除非做了回顯,否則輸出終端是不會顯示輸入的字符的)。也就是說終端是對字符顯示的一個抽象。字符顯示中,單個字符是最小的單元,而不是像素。屏幕被劃分成固定數量不同顏色的字符。我們可以在現有的屏幕代碼基礎上,先儲存字符和對應的顏色,然後再用方法 DrawCharacter 把其推送到屏幕上。一旦我們需要字符顯示,就只需要在屏幕上畫出一行字串。

新建檔案名為 terminal.s,如下:

  1. .section .data
  2. .align 4
  3. terminalStart:
  4. .int terminalBuffer
  5. terminalStop:
  6. .int terminalBuffer
  7. terminalView:
  8. .int terminalBuffer
  9. terminalColour:
  10. .byte 0xf
  11. .align 8
  12. terminalBuffer:
  13. .rept 128*128
  14. .byte 0x7f
  15. .byte 0x0
  16. .endr
  17. terminalScreen:
  18. .rept 1024/8 core.md Dict.md lctt2014.md lctt2016.md lctt2018.md LICENSE published README.md scripts sources translated 768/16
  19. .byte 0x7f
  20. .byte 0x0
  21. .endr

這是檔案終端的配置資料檔案。我們有兩個主要的儲存變數:terminalBuffer 和 terminalScreenterminalBuffer 儲存所有顯示過的字符。它儲存 128 行字符文本(1 行包含 128 個字符)。每個字符有一個 ASCII 字符和顏色單元組成,初始值為 0x7f(ASCII 的刪除字符)和 0(前景色和背景色為黑)。terminalScreen 儲存當前屏幕顯示的字符。它儲存 128×48 個字符,與 terminalBuffer 初始化值一樣。你可能會覺得我僅需要 terminalScreen 就夠了,為什麼還要terminalBuffer,其實有兩個好處:

1. 我們可以很容易看到字串的變化,只需畫出有變化的字符。
2. 我們可以回滾終端顯示的歷史字符,也就是緩衝的字符(有限制)

這種獨特的技巧在低功耗系統里很常見。畫屏是很耗時的操作,因此我們僅在不得已的時候才去執行這個操作。在這個系統里,我們可以任意改變 terminalBuffer,然後呼叫一個僅拷貝屏幕上位元組變化的方法。也就是說我們不需要持續畫出每個字符,這樣可以節省一大段跨行文本的操作時間。

你總是需要嘗試去設計一個高效的系統,如果在很少變化的情況下這個系統會運行的更快。

其他在 .data 段的值得含義如下:

◈ terminalStart 寫入到 terminalBuffer 的第一個字符
◈ terminalStop 寫入到 terminalBuffer 的最後一個字符
◈ terminalView 表示當前屏幕的第一個字符,這樣我們可以控制滾動屏幕
◈ temrinalColour 即將被描畫的字符顏色

terminalStart 需要儲存起來的原因是 termainlBuffer 是一個環狀緩衝區。意思是當緩衝區變滿時,末尾地方會回滾改寫開始位置,這樣最後一個字符變成了第一個字符。因此我們需要將 terminalStart 往前推進,這樣我們知道我們已經占滿它了。如何實現緩衝區檢測:如果索引越界到緩衝區的末尾,就將索引指向緩衝區的開始位置。環狀緩衝區是一個比較常見的儲存大量資料的高明方法,往往這些資料的最近部分比較重要。它允許無限制的寫入,只保證最近一些特定資料有效。這個常常用於信號處理和資料壓縮演算法。這樣的情況,可以允許我們儲存 128 行終端記錄,超過128行也不會有問題。如果不是這樣,當超過第 128 行時,我們需要把 127 行分別向前拷貝一次,這樣很浪費時間。

顯示 Hellow world 插入到大小為5的迴圈緩衝區的示意圖。

環狀緩衝區是資料結構一個例子。這是一個組織資料的思路,有時我們通過軟體實現這種思路。

之前已經提到過 terminalColour 幾次了。你可以根據你的想法實現終端顏色,但這個文本終端有 16 個前景色和 16 個背景色(這裡相當於有 162 = 256 種組合)。CGA[2]終端的顏色定義如下:

表格 1.1 – CGA 顏色編碼

< 如顯示不全,請左右滑動 >
序號 顏色 (R, G, B)
0 黑 (0, 0, 0)
1 藍 (0, 0, ⅔)
2 綠 (0, ⅔, 0)
3 青色 (0, ⅔, ⅔)
4 紅色 (⅔, 0, 0)
5 品紅 (⅔, 0, ⅔)
6 棕色 (⅔, ⅓, 0)
7 淺灰色 (⅔, ⅔, ⅔)
8 灰色 (⅓, ⅓, ⅓)
9 淡藍色 (⅓, ⅓, 1)
10 淡綠色 (⅓, 1, ⅓)
11 淡青色 (⅓, 1, 1)
12 淡紅色 (1, ⅓, ⅓)
13 淺品紅 (1, ⅓, 1)
14 黃色 (1, 1, ⅓)
15 白色 (1, 1, 1)

我們將前景色儲存到顏色的低位元組,背景色儲存到顏色高位元組。除了棕色,其他這些顏色遵循一種樣式如二進制的高位比特代表增加 ⅓ 到每個組件,其他比特代表增加 ⅔ 到各自組件。這樣很容易進行 RGB 顏色轉換。

棕色作為替代色(黑黃色)既不吸引人也沒有什麼用處。

我們需要一個方法從 TerminalColour 讀取顏色編碼的四個比特,然後用 16 比特等效引數呼叫 SetForeColour。嘗試你自己實現。如果你感覺麻煩或者還沒有完成屏幕系列課程,我們的實現如下:

  1. .section .text
  2. TerminalColour:
  3. teq r0,#6
  4. ldreq r0,=0x02B5
  5. beq SetForeColour
  6. tst r0,#0b1000
  7. ldrne r1,=0x52AA
  8. moveq r1,#0
  9. tst r0,#0b0100
  10. addne r1,#0x15
  11. tst r0,#0b0010
  12. addne r1,#0x540
  13. tst r0,#0b0001
  14. addne r1,#0xA800
  15. mov r0,r1
  16. b SetForeColour

2、文本顯示

我們的終端第一個真正需要的方法是 TerminalDisplay,它用來把當前的資料從 terminalBuffer拷貝到 terminalScreen 和實際的屏幕。如上所述,這個方法必須是最小開銷的操作,因為我們需要頻繁呼叫它。它主要比較 terminalBuffer 與 terminalDisplay 的文本,然後只拷貝有差異的位元組。請記住 terminalBuffer 是以環狀緩衝區運行的,這種情況,就是從 terminalView 到 terminalStop,或者 128*48 個字符,要看哪個來的最快。如果我們遇到 terminalStop,我們將會假定在這之後的所有字符是 7f16 (ASCII 刪除字符),顏色為 0(黑色前景色和背景色)。

讓我們看看必須要做的事情:

1. 加載 terminalViewterminalStop 和 terminalDisplay 的地址。
2. 對於每一行:

1. 對於每一列:

1. 如果 terminalView 不等於 terminalStop,根據 terminalView加載當前字符和顏色
2. 否則加載 0x7f 和顏色 0
3. 從 terminalDisplay 加載當前的字符
4. 如果字符和顏色相同,直接跳轉到第 10 步
5. 儲存字符和顏色到 terminalDisplay
6. 用 r0 作為背景色引數呼叫 TerminalColour
7. 用 r0 = 0x7f(ASCII 刪除字符,一個塊)、 r1 = xr2 = y 呼叫 DrawCharacter
8. 用 r0 作為前景色引數呼叫 TerminalColour
9. 用 r0 = 字符r1 = xr2 = y 呼叫 DrawCharacter
10. 對位置引數 terminalDisplay 累加 2
11. 如果 terminalView 不等於 terminalStopterminalView 位置引數累加 2
12. 如果 terminalView 位置已經是檔案緩衝器的末尾,將它設置為緩衝區的開始位置
13. x 坐標增加 8
2. y 坐標增加 16

嘗試去自己實現吧。如果你遇到問題,我們的方案下麵給出來了:

1、我這裡的變數有點亂。為了方便起見,我用 taddr 儲存 textBuffer 的末尾位置。

  1. .globl TerminalDisplay
  2. TerminalDisplay:
  3. push {r4,r5,r6,r7,r8,r9,r10,r11,lr}
  4. x .req r4
  5. y .req r5
  6. char .req r6
  7. col .req r7
  8. screen .req r8
  9. taddr .req r9
  10. view .req r10
  11. stop .req r11
  12. ldr taddr,=terminalStart
  13. ldr view,[taddr,#terminalView - terminalStart]
  14. ldr stop,[taddr,#terminalStop - terminalStart]
  15. add taddr,#terminalBuffer - terminalStart
  16. add taddr,#128*128*2
  17. mov screen,taddr

2、從 yLoop 開始運行。

  1. mov y,#0
  2. yLoop$:

2.1、

  1. mov x,#0
  2. xLoop$:

從 xLoop 開始運行。

2.1.1、為了方便起見,我把字符和顏色同時加載到 char 變數了

  1. teq view,stop
  2. ldrneh char,[view]

2.1.2、這行是對上面一行的補充說明:讀取黑色的刪除字符

  1. moveq char,#0x7f

2.1.3、為了簡便我把字符和顏色同時加載到 col 里。

  1. ldrh col,[screen]

2.1.4、 現在我用 teq 指令檢查是否有資料變化

  1. teq col,char
  2. beq xLoopContinue$

2.1.5、我可以容易的儲存當前值

  1. strh char,[screen]

2.1.6、我用比特偏移指令 lsr 和 and 指令從切分 char 變數,將顏色放到 col 變數,字符放到 char 變數,然後再用比特偏移指令 lsr 獲取背景色後呼叫 TerminalColour 。

  1. lsr col,char,#8
  2. and char,#0x7f
  3. lsr r0,col,#4
  4. bl TerminalColour

2.1.7、寫入一個彩色的刪除字符

  1. mov r0,#0x7f
  2. mov r1,x
  3. mov r2,y
  4. bl DrawCharacter

2.1.8、用 and 指令獲取 col 變數的低半位元組,然後呼叫 TerminalColour

  1. and r0,col,#0xf
  2. bl TerminalColour

2.1.9、寫入我們需要的字符

  1. mov r0,char
  2. mov r1,x
  3. mov r2,y
  4. bl DrawCharacter

2.1.10、自增屏幕指標

  1. xLoopContinue$:
  2. add screen,#2

2.1.11、如果可能自增 view 指標

  1. teq view,stop
  2. addne view,#2

2.1.12、很容易檢測 view 指標是否越界到緩衝區的末尾,因為緩衝區的地址儲存在 taddr變數里

  1. teq view,taddr
  2. subeq view,#128*128*2

2.1.13、 如果還有字符需要顯示,我們就需要自增 x 變數然後到 xLoop 迴圈執行

  1. add x,#8
  2. teq x,#1024
  3. bne xLoop$

2.2、 如果還有更多的字符顯示我們就需要自增 y 變數,然後到 yLoop 迴圈執行

  1. add y,#16
  2. teq y,#768
  3. bne yLoop$

3、不要忘記最後清除變數

  1. pop {r4,r5,r6,r7,r8,r9,r10,r11,pc}
  2. .unreq x
  3. .unreq y
  4. .unreq char
  5. .unreq col
  6. .unreq screen
  7. .unreq taddr
  8. .unreq view
  9. .unreq stop

3、行打印

現在我有了自己 TerminalDisplay 方法,它可以自動顯示 terminalBuffer 內容到 terminalScreen,因此理論上我們可以畫出文本。但是實際上我們沒有任何基於字符顯示的例程。 首先快速容易上手的方法便是 TerminalClear, 它可以徹底清除終端。這個方法不用迴圈也很容易實現。可以嘗試分析下麵的方法應該不難:

  1. .globl TerminalClear
  2. TerminalClear:
  3. ldr r0,=terminalStart
  4. add r1,r0,#terminalBuffer-terminalStart
  5. str r1,[r0]
  6. str r1,[r0,#terminalStop-terminalStart]
  7. str r1,[r0,#terminalView-terminalStart]
  8. mov pc,lr

現在我們需要構造一個字符顯示的基礎方法:Print 函式。它將儲存在 r0 的字串和儲存在 r1 的字串長度簡單的寫到屏幕上。有一些特定字符需要特別的註意,這些特定的操作是確保 terminalView 是最新的。我們來分析一下需要做什麼:

1. 檢查字串的長度是否為 0,如果是就直接傳回
2. 加載 terminalStop 和 terminalView
3. 計算出 terminalStop 的 x 坐標
4. 對每一個字符的操作:

1. 檢查字符是否為新起一行
2. 如果是的話,自增 bufferStop 到行末,同時寫入黑色刪除字符
3. 否則拷貝當前 terminalColour 的字符
4. 檢查是否在行末
5. 如果是,檢查從 terminalView 到 terminalStop 之間的字符數是否大於一屏
6. 如果是,terminalView 自增一行
7. 檢查 terminalView 是否為緩衝區的末尾,如果是的話將其替換為緩衝區的起始位置
8. 檢查 terminalStop 是否為緩衝區的末尾,如果是的話將其替換為緩衝區的起始位置
9. 檢查 terminalStop 是否等於 terminalStart, 如果是的話 terminalStart 自增一行。
10. 檢查 terminalStart 是否為緩衝區的末尾,如果是的話將其替換為緩衝區的起始位置
5. 存取 terminalStop 和 terminalView

試一下自己去實現。我們的方案提供如下:

1、這個是 Print 函式開始快速檢查字串為0的代碼

  1. .globl Print
  2. Print:
  3. teq r1,#0
  4. moveq pc,lr

2、這裡我做了很多配置。 bufferStart 代表 terminalStart, bufferStop 代表terminalStop, view 代表 terminalViewtaddr 代表 terminalBuffer 的末尾地址。

  1. push {r4,r5,r6,r7,r8,r9,r10,r11,lr}
  2. bufferStart .req r4
  3. taddr .req r5
  4. x .req r6
  5. string .req r7
  6. length .req r8
  7. char .req r9
  8. bufferStop .req r10
  9. view .req r11
  10. mov string,r0
  11. mov length,r1
  12. ldr taddr,=terminalStart
  13. ldr bufferStop,[taddr,#terminalStop-terminalStart]
  14. ldr view,[taddr,#terminalView-terminalStart]
  15. ldr bufferStart,[taddr]
  16. add taddr,#terminalBuffer-terminalStart
  17. add taddr,#128*128*2

3、和通常一樣,巧妙的對齊技巧讓許多事情更容易。由於需要對齊 terminalBuffer,每個字符的 x 坐標需要 8 位要除以 2。

  1. and x,bufferStop,#0xfe
  2. lsr x,#1

4.1、我們需要檢查新行

  1. charLoop$:
  2. ldrb char,[string]
  3. and char,#0x7f
  4. teq char,#'\n'
  5. bne charNormal$

4.2、迴圈執行值到行末寫入 0x7f;黑色刪除字符

  1. mov r0,#0x7f
  2. clearLine$:
  3. strh r0,[bufferStop]
  4. add bufferStop,#2
  5. add x,#1
  6. teq x,#128 blt clearLine$
  7. b charLoopContinue$

4.3、儲存字串的當前字符和 terminalBuffer 末尾的 terminalColour 然後將它和 x 變數自增

  1. charNormal$:
  2. strb char,[bufferStop]
  3. ldr r0,=terminalColour
  4. ldrb r0,[r0]
  5. strb r0,[bufferStop,#1]
  6. add bufferStop,#2
  7. add x,#1

4.4、檢查 x 是否為行末;128

  1. charLoopContinue$:
  2. cmp x,#128
  3. blt noScroll$

4.5、設置 x 為 0 然後檢查我們是否已經顯示超過 1 屏。請記住,我們是用的迴圈緩衝區,因此如果 bufferStop 和 view 之前的差是負值,我們實際上是環繞了緩衝區。

  1. mov x,#0
  2. subs r0,bufferStop,view
  3. addlt r0,#128*128*2
  4. cmp r0,#128*(768/16)*2

4.6、增加一行位元組到 view 的地址

  1. addge view,#128*2

4.7、 如果 view 地址是緩衝區的末尾,我們就從它上面減去緩衝區的長度,讓其指向開始位置。我會在開始的時候設置 taddr 為緩衝區的末尾地址。

  1. teq view,taddr
  2. subeq view,taddr,#128*128*2

4.8、如果 stop 的地址在緩衝區末尾,我們就從它上面減去緩衝區的長度,讓其指向開始位置。我會在開始的時候設置 taddr 為緩衝區的末尾地址。

  1. noScroll$:
  2. teq bufferStop,taddr
  3. subeq bufferStop,taddr,#128*128*2

4.9、檢查 bufferStop 是否等於 bufferStart。 如果等於增加一行到 bufferStart

  1. teq bufferStop,bufferStart
  2. addeq bufferStart,#128*2

4.10、如果 start 的地址在緩衝區的末尾,我們就從它上面減去緩衝區的長度,讓其指向開始位置。我會在開始的時候設置 taddr 為緩衝區的末尾地址。

  1. teq bufferStart,taddr
  2. subeq bufferStart,taddr,#128*128*2

迴圈執行知道字串結束

  1. subs length,#1
  2. add string,#1
  3. bgt charLoop$

5、儲存變數然後傳回

  1. charLoopBreak$:
  2. sub taddr,#128*128*2
  3. sub taddr,#terminalBuffer-terminalStart
  4. str bufferStop,[taddr,#terminalStop-terminalStart]
  5. str view,[taddr,#terminalView-terminalStart]
  6. str bufferStart,[taddr]
  7. pop {r4,r5,r6,r7,r8,r9,r10,r11,pc}
  8. .unreq bufferStart
  9. .unreq taddr
  10. .unreq x
  11. .unreq string
  12. .unreq length
  13. .unreq char
  14. .unreq bufferStop
  15. .unreq view

這個方法允許我們打印任意字符到屏幕。然而我們用了顏色變數,但實際上沒有設置它。一般終端用特性的組合字符去行修改顏色。如 ASCII 轉義(1b16)後面跟著一個 0 – f 的 16 進制的數,就可以設置前景色為 CGA 顏色號。如果你自己想嘗試實現;在下載頁面有一個我的詳細的例子。

4、標誌輸入

現在我們有一個可以打印和顯示文本的輸出終端。這僅僅是說對了一半,我們需要輸入。我們想實現一個方法:ReadLine,可以儲存檔案的一行文本,文本位置由 r0 給出,最大的長度由 r1 給出,傳回 r0 裡面的字串長度。棘手的是用戶輸出字符的時候要回顯功能,同時想要退格鍵的刪除功能和命令回車執行功能。它們還需要一個閃爍的下劃線代表計算機需要輸入。這些完全合理的要求讓構造這個方法更具有挑戰性。有一個方法完成這些需求就是儲存用戶輸入的文本和檔案大小到記憶體的某個地方。然後當呼叫 ReadLine 的時候,移動 terminalStop 的地址到它開始的地方然後呼叫 Print。也就是說我們只需要確保在記憶體維護一個字串,然後構造一個我們自己的打印函式。

按照慣例,許多編程語言中,任意程式可以訪問 stdin 和 stdin,它們可以連接到終端的輸入和輸出流。在圖形程式其實也可以進行同樣操作,但實際幾乎不用。

讓我們看看 ReadLine 做了哪些事情:

1. 如果字串可儲存的最大長度為 0,直接傳回
2. 檢索 terminalStop 和 terminalStop 的當前值
3. 如果字串的最大長度大約緩衝區的一半,就設置大小為緩衝區的一半
4. 從最大長度裡面減去 1 來確保輸入的閃爍字符或結束符
5. 向字串寫入一個下劃線
6. 寫入一個 terminalView 和 terminalStop 的地址到記憶體
7. 呼叫 Print 打印當前字串
8. 呼叫 TerminalDisplay
9. 呼叫 KeyboardUpdate
10. 呼叫 KeyboardGetChar
11. 如果是一個新行直接跳轉到第 16 步
12. 如果是一個退格鍵,將字串長度減 1(如果其大於 0)
13. 如果是一個普通字符,將它寫入字串(字串大小確保小於最大值)
14. 如果字串是以下劃線結束,寫入一個空格,否則寫入下劃線
15. 跳轉到第 6 步
16. 字串的末尾寫入一個新行字符
17. 呼叫 Print 和 TerminalDisplay
18. 用結束符替換新行
19. 傳回字串的長度

為了方便讀者理解,然後然後自己去實現,我們的實現提供如下:

1. 快速處理長度為 0 的情況
  1. .globl ReadLine
  2. ReadLine:
  3. teq r1,#0
  4. moveq r0,#0
  5. moveq pc,lr

2、考慮到常見的場景,我們初期做了很多初始化動作。input 代表 terminalStop 的值,view 代表 terminalViewLength 預設為 0

  1. string .req r4
  2. maxLength .req r5
  3. input .req r6
  4. taddr .req r7
  5. length .req r8
  6. view .req r9
  7. push {r4,r5,r6,r7,r8,r9,lr}
  8. mov string,r0
  9. mov maxLength,r1
  10. ldr taddr,=terminalStart
  11. ldr input,[taddr,#terminalStop-terminalStart]
  12. ldr view,[taddr,#terminalView-terminalStart]
  13. mov length,#0

3、我們必須檢查異常大的讀操作,我們不能處理超過 terminalBuffer 大小的輸入(理論上可行,但是 terminalStart 移動越過儲存的 terminalStop`,會有很多問題)。

  1. cmp maxLength,#128*64
  2. movhi maxLength,#128*64

4、由於用戶需要一個閃爍的光標,我們需要一個備用字符在理想狀況在這個字串後面放一個結束符。

  1. sub maxLength,#1

5、寫入一個下劃線讓用戶知道我們可以輸入了。

  1. mov r0,#'_'
  2. strb r0,[string,length]

6、儲存 terminalStop 和 terminalView。這個對重置一個終端很重要,它會修改這些變數。嚴格講也可以修改 terminalStart,但是不可逆。

  1. readLoop$:
  2. str input,[taddr,#terminalStop-terminalStart]
  3. str view,[taddr,#terminalView-terminalStart]

7、寫入當前的輸入。由於下劃線因此字串長度加 1

  1. mov r0,string
  2. mov r1,length
  3. add r1,#1
  4. bl Print

8、拷貝下一個文本到屏幕

  1. bl TerminalDisplay

9、獲取最近一次鍵盤輸入

  1. bl KeyboardUpdate

10、檢索鍵盤輸入鍵值

  1. bl KeyboardGetChar

11、如果我們有一個回車鍵,迴圈中斷。如果有結束符和一個退格鍵也會同樣跳出迴圈。

  1. teq r0,#'\n'
  2. beq readLoopBreak$
  3. teq r0,#0
  4. beq cursor$
  5. teq r0,#'\b'
  6. bne standard$

12、從 length 裡面刪除一個字符

  1. delete$:
  2. cmp length,#0
  3. subgt length,#1
  4. b cursor$

13、寫回一個普通字符

  1. standard$:
  2. cmp length,maxLength
  3. bge cursor$
  4. strb r0,[string,length]
  5. add length,#1

14、加載最近的一個字符,如果不是下劃線則修改為下換線,如果是則修改為空格

  1. cursor$:
  2. ldrb r0,[string,length]
  3. teq r0,#'_'
  4. moveq r0,#' '
  5. movne r0,#'_'
  6. strb r0,[string,length]

15、迴圈執行值到用戶輸入按下

  1. b readLoop$
  2. readLoopBreak$:

16、在字串的結尾處存入一個新行字符

  1. mov r0,#'\n'
  2. strb r0,[string,length]

17、重置 terminalView 和 terminalStop 然後呼叫 Print 和 TerminalDisplay 顯示最終的輸入

  1. str input,[taddr,#terminalStop-terminalStart]
  2. str view,[taddr,#terminalView-terminalStart]
  3. mov r0,string
  4. mov r1,length
  5. add r1,#1
  6. bl Print
  7. bl TerminalDisplay

18、寫入一個結束符

  1. mov r0,#0
  2. strb r0,[string,length]

19、傳回長度

  1. mov r0,length
  2. pop {r4,r5,r6,r7,r8,r9,pc}
  3. .unreq string
  4. .unreq maxLength
  5. .unreq input
  6. .unreq taddr
  7. .unreq length
  8. .unreq view

5、終端:機器進化

現在我們理論用終端和用戶可以交互了。最顯而易見的事情就是拿去測試了!刪除 main.s 里 bl UsbInitialise 後面的代碼後如下:

  1. reset$:
  2. mov sp,#0x8000
  3. bl TerminalClear
  4. ldr r0,=welcome
  5. mov r1,#welcomeEnd-welcome
  6. bl Print
  7. loop$:
  8. ldr r0,=prompt
  9. mov r1,#promptEnd-prompt
  10. bl Print
  11. ldr r0,=command
  12. mov r1,#commandEnd-command
  13. bl ReadLine
  14. teq r0,#0
  15. beq loopContinue$
  16. mov r4,r0
  17. ldr r5,=command
  18. ldr r6,=commandTable
  19. ldr r7,[r6,#0]
  20. ldr r9,[r6,#4]
  21. commandLoop$:
  22. ldr r8,[r6,#8]
  23. sub r1,r8,r7
  24. cmp r1,r4
  25. bgt commandLoopContinue$
  26. mov r0,#0
  27. commandName$:
  28. ldrb r2,[r5,r0]
  29. ldrb r3,[r7,r0]
  30. teq r2,r3
  31. bne commandLoopContinue$
  32. add r0,#1
  33. teq r0,r1
  34. bne commandName$
  35. ldrb r2,[r5,r0]
  36. teq r2,#0
  37. teqne r2,#' '
  38. bne commandLoopContinue$
  39. mov r0,r5
  40. mov r1,r4
  41. mov lr,pc
  42. mov pc,r9
  43. b loopContinue$
  44. commandLoopContinue$:
  45. add r6,#8
  46. mov r7,r8
  47. ldr r9,[r6,#4]
  48. teq r9,#0
  49. bne commandLoop$
  50. ldr r0,=commandUnknown
  51. mov r1,#commandUnknownEnd-commandUnknown
  52. ldr r2,=formatBuffer
  53. ldr r3,=command
  54. bl FormatString
  55. mov r1,r0
  56. ldr r0,=formatBuffer
  57. bl Print
  58. loopContinue$:
  59. bl TerminalDisplay
  60. b loop$
  61. echo:
  62. cmp r1,#5
  63. movle pc,lr
  64. add r0,#5
  65. sub r1,#5
  66. b Print
  67. ok:
  68. teq r1,#5
  69. beq okOn$
  70. teq r1,#6
  71. beq okOff$
  72. mov pc,lr
  73. okOn$:
  74. ldrb r2,[r0,#3]
  75. teq r2,#'o'
  76. ldreqb r2,[r0,#4]
  77. teqeq r2,#'n'
  78. movne pc,lr
  79. mov r1,#0
  80. b okAct$
  81. okOff$:
  82. ldrb r2,[r0,#3]
  83. teq r2,#'o'
  84. ldreqb r2,[r0,#4]
  85. teqeq r2,#'f'
  86. ldreqb r2,[r0,#5]
  87. teqeq r2,#'f'
  88. movne pc,lr
  89. mov r1,#1
  90. okAct$:
  91. mov r0,#16
  92. b SetGpio
  93. .section .data
  94. .align 2
  95. welcome: .ascii "Welcome to Alex's OS - Everyone's favourite OS"
  96. welcomeEnd:
  97. .align 2
  98. prompt: .ascii "\n> "
  99. promptEnd:
  100. .align 2
  101. command:
  102. .rept 128
  103. .byte 0
  104. .endr
  105. commandEnd:
  106. .byte 0
  107. .align 2
  108. commandUnknown: .ascii "Command `%s' was not recognised.\n"
  109. commandUnknownEnd:
  110. .align 2
  111. formatBuffer:
  112. .rept 256
  113. .byte 0
  114. .endr
  115. formatEnd:
  116. .align 2
  117. commandStringEcho: .ascii "echo"
  118. commandStringReset: .ascii "reset"
  119. commandStringOk: .ascii "ok"
  120. commandStringCls: .ascii "cls"
  121. commandStringEnd:
  122. .align 2
  123. commandTable:
  124. .int commandStringEcho, echo
  125. .int commandStringReset, reset$
  126. .int commandStringOk, ok
  127. .int commandStringCls, TerminalClear
  128. .int commandStringEnd, 0

這塊代碼集成了一個簡易的命令列操作系統。支持命令:echoresetok 和 clsecho 拷貝任意文本到終端,reset 命令會在系統出現問題的是複位操作系統,ok 有兩個功能:設置 OK 燈亮滅,最後 cls 呼叫 TerminalClear 清空終端。

試試樹莓派的代碼吧。如果遇到問題,請參照問題集錦頁面吧。

如果運行正常,祝賀你完成了一個操作系統基本終端和輸入系列的課程。很遺憾這個教程先講到這裡,但是我希望將來能製作更多教程。有問題請反饋至 [email protected][3]

你已經在建立了一個簡易的終端操作系統。我們的代碼在 commandTable 構造了一個可用的命令表格。每個表格的入口是一個整型數字,用來表示字串的地址,和一個整型數字表格代碼的執行入口。 最後一個入口是 為 0 的 commandStringEnd。嘗試實現你自己的命令,可以參照已有的函式,建立一個新的。函式的引數 r0 是用戶輸入的命令地址,r1 是其長度。你可以用這個傳遞你輸入值到你的命令。也許你有一個計算器程式,或許是一個繪圖程式或國際象棋。不管你的什麼點子,讓它跑起來!

已同步到看一看
赞(0)

分享創造快樂