Linux中國

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

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

1、終端

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

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

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

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

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

新建文件名為 terminal.s,如下:

.section .data
.align 4
terminalStart:
.int terminalBuffer
terminalStop:
.int terminalBuffer
terminalView:
.int terminalBuffer
terminalColour:
.byte 0xf
.align 8
terminalBuffer:
.rept 128*128
.byte 0x7f
.byte 0x0
.endr
terminalScreen:
.rept 1024/8 core.md Dict.md lctt2014.md lctt2016.md lctt2018.md LICENSE published README.md scripts sources translated 768/16
.byte 0x7f
.byte 0x0
.endr

這是文件終端的配置數據文件。我們有兩個主要的存儲變數:terminalBufferterminalScreenterminalBuffer 保存所有顯示過的字元。它保存 128 行字元文本(1 行包含 128 個字元)。每個字元有一個 ASCII 字元和顏色單元組成,初始值為 0x7f(ASCII 的刪除字元)和 0(前景色和背景色為黑)。terminalScreen 保存當前屏幕顯示的字元。它保存 128x48 個字元,與 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 個背景色(這裡相當於有 16 2 = 256 種組合)。CGA終端的顏色定義如下:

表格 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。嘗試你自己實現。如果你感覺麻煩或者還沒有完成屏幕系列課程,我們的實現如下:

.section .text
TerminalColour:
teq r0,#6
ldreq r0,=0x02B5
beq SetForeColour

tst r0,#0b1000
ldrne r1,=0x52AA
moveq r1,#0
tst r0,#0b0100
addne r1,#0x15
tst r0,#0b0010
addne r1,#0x540
tst r0,#0b0001
addne r1,#0xA800
mov r0,r1
b SetForeColour

2、文本顯示

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

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

  1. 載入 terminalViewterminalStopterminalDisplay 的地址。
  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 的末尾位置。

.globl TerminalDisplay
TerminalDisplay:
push {r4,r5,r6,r7,r8,r9,r10,r11,lr}
x .req r4
y .req r5
char .req r6
col .req r7
screen .req r8
taddr .req r9
view .req r10
stop .req r11

ldr taddr,=terminalStart
ldr view,[taddr,#terminalView - terminalStart]
ldr stop,[taddr,#terminalStop - terminalStart]
add taddr,#terminalBuffer - terminalStart
add taddr,#128*128*2
mov screen,taddr

2、從 yLoop 開始運行。

mov y,#0
yLoop$:

2.1、

mov x,#0
xLoop$:

xLoop 開始運行。

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

teq view,stop
ldrneh char,[view]

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

moveq char,#0x7f

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

ldrh col,[screen]

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

teq col,char
beq xLoopContinue$

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

strh char,[screen]

2.1.6、我用比特偏移指令 lsrand 指令從切分 char 變數,將顏色放到 col 變數,字元放到 char 變數,然後再用比特偏移指令 lsr 獲取背景色後調用 TerminalColour

lsr col,char,#8
and char,#0x7f
lsr r0,col,#4
bl TerminalColour

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

mov r0,#0x7f
mov r1,x
mov r2,y
bl DrawCharacter

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

and r0,col,#0xf
bl TerminalColour

2.1.9、寫入我們需要的字元

mov r0,char
mov r1,x
mov r2,y
bl DrawCharacter

2.1.10、自增屏幕指針

xLoopContinue$:
add screen,#2

2.1.11、如果可能自增 view 指針

teq view,stop
addne view,#2

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

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

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

add x,#8
teq x,#1024
bne xLoop$

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

add y,#16
teq y,#768
bne yLoop$

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

pop {r4,r5,r6,r7,r8,r9,r10,r11,pc}
.unreq x
.unreq y
.unreq char
.unreq col
.unreq screen
.unreq taddr
.unreq view
.unreq stop

3、行列印

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

.globl TerminalClear
TerminalClear:
ldr r0,=terminalStart
add r1,r0,#terminalBuffer-terminalStart
str r1,[r0]
str r1,[r0,#terminalStop-terminalStart]
str r1,[r0,#terminalView-terminalStart]
mov pc,lr

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

  1. 檢查字元串的長度是否為 0,如果是就直接返回
  2. 載入 terminalStopterminalView
  3. 計算出 terminalStop 的 x 坐標
  4. 對每一個字元的操作:
    1. 檢查字元是否為新起一行
    2. 如果是的話,自增 bufferStop 到行末,同時寫入黑色刪除字元
    3. 否則拷貝當前 terminalColour 的字元
    4. 檢查是否在行末
    5. 如果是,檢查從 terminalViewterminalStop 之間的字元數是否大於一屏
    6. 如果是,terminalView 自增一行
    7. 檢查 terminalView 是否為緩衝區的末尾,如果是的話將其替換為緩衝區的起始位置
    8. 檢查 terminalStop 是否為緩衝區的末尾,如果是的話將其替換為緩衝區的起始位置
    9. 檢查 terminalStop 是否等於 terminalStart, 如果是的話 terminalStart 自增一行。
    10. 檢查 terminalStart 是否為緩衝區的末尾,如果是的話將其替換為緩衝區的起始位置
  5. 存取 terminalStopterminalView

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

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

.globl Print
Print:
teq r1,#0
moveq pc,lr

2、這裡我做了很多配置。 bufferStart 代表 terminalStartbufferStop 代表terminalStopview 代表 terminalViewtaddr 代表 terminalBuffer 的末尾地址。

push {r4,r5,r6,r7,r8,r9,r10,r11,lr}
bufferStart .req r4
taddr .req r5
x .req r6
string .req r7
length .req r8
char .req r9
bufferStop .req r10
view .req r11

mov string,r0
mov length,r1

ldr taddr,=terminalStart
ldr bufferStop,[taddr,#terminalStop-terminalStart]
ldr view,[taddr,#terminalView-terminalStart]
ldr bufferStart,[taddr]
add taddr,#terminalBuffer-terminalStart
add taddr,#128*128*2

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

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

4.1、我們需要檢查新行

charLoop$:
ldrb char,[string]
and char,#0x7f
teq char,#'n'
bne charNormal$

4.2、循環執行值到行末寫入 0x7f;黑色刪除字元

mov r0,#0x7f
clearLine$:
strh r0,[bufferStop]
add bufferStop,#2
add x,#1
teq x,#128 blt clearLine$

b charLoopContinue$

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

charNormal$:
strb char,[bufferStop]
ldr r0,=terminalColour
ldrb r0,[r0]
strb r0,[bufferStop,#1]
add bufferStop,#2
add x,#1

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

charLoopContinue$:
cmp x,#128
blt noScroll$

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

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

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

addge view,#128*2

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

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

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

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

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

teq bufferStop,bufferStart
addeq bufferStart,#128*2

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

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

循環執行知道字元串結束

subs length,#1
add string,#1
bgt charLoop$

5、保存變數然後返回

charLoopBreak$:
sub taddr,#128*128*2
sub taddr,#terminalBuffer-terminalStart
str bufferStop,[taddr,#terminalStop-terminalStart]
str view,[taddr,#terminalView-terminalStart]
str bufferStart,[taddr]

pop {r4,r5,r6,r7,r8,r9,r10,r11,pc}
.unreq bufferStart
.unreq taddr
.unreq x
.unreq string
.unreq length
.unreq char
.unreq bufferStop
.unreq view

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

4、標誌輸入

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

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

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

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

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

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

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

string .req r4
maxLength .req r5
input .req r6
taddr .req r7
length .req r8
view .req r9

push {r4,r5,r6,r7,r8,r9,lr}

mov string,r0
mov maxLength,r1
ldr taddr,=terminalStart
ldr input,[taddr,#terminalStop-terminalStart]
ldr view,[taddr,#terminalView-terminalStart]
mov length,#0

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

cmp maxLength,#128*64
movhi maxLength,#128*64

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

sub maxLength,#1

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

mov r0,#'_'
strb r0,[string,length]

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

readLoop$:
str input,[taddr,#terminalStop-terminalStart]
str view,[taddr,#terminalView-terminalStart]

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

mov r0,string
mov r1,length
add r1,#1
bl Print

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

bl TerminalDisplay

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

bl KeyboardUpdate

10、檢索鍵盤輸入鍵值

bl KeyboardGetChar

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

teq r0,#'n'
beq readLoopBreak$
teq r0,#0
beq cursor$
teq r0,#'b'
bne standard$

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

delete$:
cmp length,#0
subgt length,#1
b cursor$

13、寫回一個普通字元

standard$:
cmp length,maxLength
bge cursor$
strb r0,[string,length]
add length,#1

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

cursor$:
ldrb r0,[string,length]
teq r0,#'_'
moveq r0,#' '
movne r0,#'_'
strb r0,[string,length]

15、循環執行值到用戶輸入按下

b readLoop$
readLoopBreak$:

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

mov r0,#'n'
strb r0,[string,length]

17、重置 terminalViewterminalStop 然後調用 PrintTerminalDisplay 顯示最終的輸入

str input,[taddr,#terminalStop-terminalStart]
str view,[taddr,#terminalView-terminalStart]
mov r0,string
mov r1,length
add r1,#1
bl Print
bl TerminalDisplay

18、寫入一個結束符

mov r0,#0
strb r0,[string,length]

19、返回長度

mov r0,length
pop {r4,r5,r6,r7,r8,r9,pc}
.unreq string
.unreq maxLength
.unreq input
.unreq taddr
.unreq length
.unreq view

5、終端:機器進化

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

reset$:
  mov sp,#0x8000
  bl TerminalClear

  ldr r0,=welcome
  mov r1,#welcomeEnd-welcome
  bl Print

loop$:
  ldr r0,=prompt
  mov r1,#promptEnd-prompt
  bl Print

  ldr r0,=command
  mov r1,#commandEnd-command
  bl ReadLine

  teq r0,#0
  beq loopContinue$

  mov r4,r0

  ldr r5,=command
  ldr r6,=commandTable

  ldr r7,[r6,#0]
  ldr r9,[r6,#4]
  commandLoop$:
    ldr r8,[r6,#8]
    sub r1,r8,r7

    cmp r1,r4
    bgt commandLoopContinue$

    mov r0,#0
    commandName$:
      ldrb r2,[r5,r0]
      ldrb r3,[r7,r0]
      teq r2,r3
      bne commandLoopContinue$
      add r0,#1
      teq r0,r1
      bne commandName$

    ldrb r2,[r5,r0]
    teq r2,#0
    teqne r2,#' '
    bne commandLoopContinue$

    mov r0,r5
    mov r1,r4
    mov lr,pc
    mov pc,r9
    b loopContinue$

  commandLoopContinue$:
    add r6,#8
    mov r7,r8
    ldr r9,[r6,#4]
    teq r9,#0
    bne commandLoop$

  ldr r0,=commandUnknown
  mov r1,#commandUnknownEnd-commandUnknown
  ldr r2,=formatBuffer
  ldr r3,=command
  bl FormatString

  mov r1,r0
  ldr r0,=formatBuffer
  bl Print

loopContinue$:
  bl TerminalDisplay
  b loop$

echo:
  cmp r1,#5
  movle pc,lr

  add r0,#5
  sub r1,#5
  b Print

ok:
  teq r1,#5
  beq okOn$
  teq r1,#6
  beq okOff$
  mov pc,lr

  okOn$:
    ldrb r2,[r0,#3]
    teq r2,#'o'
    ldreqb r2,[r0,#4]
    teqeq r2,#'n'
    movne pc,lr
    mov r1,#0
    b okAct$

  okOff$:
    ldrb r2,[r0,#3]
    teq r2,#'o'
    ldreqb r2,[r0,#4]
    teqeq r2,#'f'
    ldreqb r2,[r0,#5]
    teqeq r2,#'f'
    movne pc,lr
    mov r1,#1

  okAct$:

    mov r0,#16
    b SetGpio

.section .data
.align 2
welcome: .ascii "Welcome to Alex's OS - Everyone's favourite OS"
welcomeEnd:
.align 2
prompt: .ascii "n> "
promptEnd:
.align 2
command:
  .rept 128
    .byte 0
  .endr
commandEnd:
.byte 0
.align 2
commandUnknown: .ascii "Command `%s' was not recognised.n"
commandUnknownEnd:
.align 2
formatBuffer:
  .rept 256
    .byte 0
  .endr
formatEnd:

.align 2
commandStringEcho: .ascii "echo"
commandStringReset: .ascii "reset"
commandStringOk: .ascii "ok"
commandStringCls: .ascii "cls"
commandStringEnd:

.align 2
commandTable:
.int commandStringEcho, echo
.int commandStringReset, reset$
.int commandStringOk, ok
.int commandStringCls, TerminalClear
.int commandStringEnd, 0

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

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

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

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

via: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/input02.html

作者:Alex Chadwick 選題:lujun9972 譯者:guevaraya 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國