計算機實驗室之樹莓派:課程 11 輸入02
課程輸入 02 是以課程輸入 01 為基礎講解的,通過一個簡單的命令行實現用戶的命令輸入和計算機的處理和顯示。本文假設你已經具備 課程11:輸入01 的操作系統代碼基礎。
1、終端
幾乎所有的操作系統都是以字元終端顯示啟動的。經典的黑底白字,通過鍵盤輸入計算機要執行的命令,然後會提示你拼寫錯誤,或者恰好得到你想要的執行結果。這種方法有兩個主要優點:鍵盤和顯示器可以提供簡易、健壯的計算機交互機制,幾乎所有的計算機系統都採用這個機制,這個也廣泛被系統管理員應用。
早期的計算一般是在一棟樓里的一個巨型計算機系統,它有很多可以輸命令的'終端'。計算機依次執行不同來源的命令。
讓我們分析下真正想要哪些信息:
- 計算機打開後,顯示歡迎信息
- 計算機啟動後可以接受輸入標誌
- 用戶從鍵盤輸入帶參數的命令
- 用戶輸入回車鍵或提交按鈕
- 計算機解析命令後執行可用的命令
- 計算機顯示命令的執行結果,過程信息
- 循環跳轉到步驟 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
這是文件終端的配置數據文件。我們有兩個主要的存儲變數:terminalBuffer
和 terminalScreen
。terminalBuffer
保存所有顯示過的字元。它保存 128 行字元文本(1 行包含 128 個字元)。每個字元有一個 ASCII 字元和顏色單元組成,初始值為 0x7f(ASCII 的刪除字元)和 0(前景色和背景色為黑)。terminalScreen
保存當前屏幕顯示的字元。它保存 128x48 個字元,與 terminalBuffer
初始化值一樣。你可能會覺得我僅需要 terminalScreen
就夠了,為什麼還要terminalBuffer
,其實有兩個好處:
- 我們可以很容易看到字元串的變化,只需畫出有變化的字元。
- 我們可以回滾終端顯示的歷史字元,也就是緩衝的字元(有限制)
這種獨特的技巧在低功耗系統里很常見。畫屏是很耗時的操作,因此我們僅在不得已的時候才去執行這個操作。在這個系統里,我們可以任意改變 terminalBuffer
,然後調用一個僅拷貝屏幕上位元組變化的方法。也就是說我們不需要持續畫出每個字元,這樣可以節省一大段跨行文本的操作時間。
你總是需要嘗試去設計一個高效的系統,如果在很少變化的情況下這個系統會運行的更快。
其他在 .data
段的值得含義如下:
terminalStart
寫入到terminalBuffer
的第一個字元terminalStop
寫入到terminalBuffer
的最後一個字元terminalView
表示當前屏幕的第一個字元,這樣我們可以控制滾動屏幕temrinalColour
即將被描畫的字元顏色
terminalStart
需要保存起來的原因是 termainlBuffer
是一個環狀緩衝區。意思是當緩衝區變滿時,末尾地方會回滾覆蓋開始位置,這樣最後一個字元變成了第一個字元。因此我們需要將 terminalStart
往前推進,這樣我們知道我們已經佔滿它了。如何實現緩衝區檢測:如果索引越界到緩衝區的末尾,就將索引指向緩衝區的開始位置。環狀緩衝區是一個比較常見的存儲大量數據的高明方法,往往這些數據的最近部分比較重要。它允許無限制的寫入,只保證最近一些特定數據有效。這個常常用於信號處理和數據壓縮演算法。這樣的情況,可以允許我們存儲 128 行終端記錄,超過128行也不會有問題。如果不是這樣,當超過第 128 行時,我們需要把 127 行分別向前拷貝一次,這樣很浪費時間。
環狀緩衝區是數據結構一個例子。這是一個組織數據的思路,有時我們通過軟體實現這種思路。
之前已經提到過 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
和實際的屏幕。如上所述,這個方法必須是最小開銷的操作,因為我們需要頻繁調用它。它主要比較 terminalBuffer
與 terminalDisplay
的文本,然後只拷貝有差異的位元組。請記住 terminalBuffer
是以環狀緩衝區運行的,這種情況,就是從 terminalView
到 terminalStop
,或者 128*48 個字元,要看哪個來的最快。如果我們遇到 terminalStop
,我們將會假定在這之後的所有字元是 7f 16 (ASCII 刪除字元),顏色為 0(黑色前景色和背景色)。
讓我們看看必須要做的事情:
- 載入
terminalView
、terminalStop
和terminalDisplay
的地址。 - 對於每一行:
- 對於每一列:
- 如果
terminalView
不等於terminalStop
,根據terminalView
載入當前字元和顏色 - 否則載入 0x7f 和顏色 0
- 從
terminalDisplay
載入當前的字元 - 如果字元和顏色相同,直接跳轉到第 10 步
- 存儲字元和顏色到
terminalDisplay
- 用
r0
作為背景色參數調用TerminalColour
- 用
r0 = 0x7f
(ASCII 刪除字元,一個塊)、r1 = x
、r2 = y
調用DrawCharacter
- 用
r0
作為前景色參數調用TerminalColour
- 用
r0 = 字元
、r1 = x
、r2 = y
調用DrawCharacter
- 對位置參數
terminalDisplay
累加 2 - 如果
terminalView
不等於terminalStop
,terminalView
位置參數累加 2 - 如果
terminalView
位置已經是文件緩衝器的末尾,將它設置為緩衝區的開始位置 - x 坐標增加 8
- 如果
- 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、我用比特偏移指令 lsr
和 and
指令從切分 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
是最新的。我們來分析一下需要做什麼:
- 檢查字元串的長度是否為 0,如果是就直接返回
- 載入
terminalStop
和terminalView
- 計算出
terminalStop
的 x 坐標 - 對每一個字元的操作:
- 檢查字元是否為新起一行
- 如果是的話,自增
bufferStop
到行末,同時寫入黑色刪除字元 - 否則拷貝當前
terminalColour
的字元 - 檢查是否在行末
- 如果是,檢查從
terminalView
到terminalStop
之間的字元數是否大於一屏 - 如果是,
terminalView
自增一行 - 檢查
terminalView
是否為緩衝區的末尾,如果是的話將其替換為緩衝區的起始位置 - 檢查
terminalStop
是否為緩衝區的末尾,如果是的話將其替換為緩衝區的起始位置 - 檢查
terminalStop
是否等於terminalStart
, 如果是的話terminalStart
自增一行。 - 檢查
terminalStart
是否為緩衝區的末尾,如果是的話將其替換為緩衝區的起始位置
- 存取
terminalStop
和terminalView
試一下自己去實現。我們的方案提供如下:
1、這個是 Print
函數開始快速檢查字元串為0的代碼
.globl Print
Print:
teq r1,#0
moveq pc,lr
2、這裡我做了很多配置。 bufferStart
代表 terminalStart
, bufferStop
代表terminalStop
, view
代表 terminalView
,taddr
代表 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 屏。請記住,我們是用的循環緩衝區,因此如果 bufferStop
和 view
之前的差是負值,我們實際上是環繞了緩衝區。
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
做了哪些事情:
- 如果字元串可保存的最大長度為 0,直接返回
- 檢索
terminalStop
和terminalStop
的當前值 - 如果字元串的最大長度大約緩衝區的一半,就設置大小為緩衝區的一半
- 從最大長度裡面減去 1 來確保輸入的閃爍字元或結束符
- 向字元串寫入一個下劃線
- 寫入一個
terminalView
和terminalStop
的地址到內存 - 調用
Print
列印當前字元串 - 調用
TerminalDisplay
- 調用
KeyboardUpdate
- 調用
KeyboardGetChar
- 如果是一個新行直接跳轉到第 16 步
- 如果是一個退格鍵,將字元串長度減 1(如果其大於 0)
- 如果是一個普通字元,將它寫入字元串(字元串大小確保小於最大值)
- 如果字元串是以下劃線結束,寫入一個空格,否則寫入下劃線
- 跳轉到第 6 步
- 字元串的末尾寫入一個新行字元
- 調用
Print
和TerminalDisplay
- 用結束符替換新行
- 返回字元串的長度
為了方便讀者理解,然後然後自己去實現,我們的實現提供如下:
- 快速處理長度為 0 的情況
.globl ReadLine
ReadLine:
teq r1,#0
moveq r0,#0
moveq pc,lr
2、考慮到常見的場景,我們初期做了很多初始化動作。input
代表 terminalStop
的值,view
代表 terminalView
。Length
默認為 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、保存 terminalStop
和 terminalView
。這個對重置一個終端很重要,它會修改這些變數。嚴格講也可以修改 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、重置 terminalView
和 terminalStop
然後調用 Print
和 TerminalDisplay
顯示最終的輸入
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.s
里 bl 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
這塊代碼集成了一個簡易的命令行操作系統。支持命令:echo
、reset
、ok
和 cls
。echo
拷貝任意文本到終端,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
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive