課程輸入 02 是以課程輸入 01 為基礎講解的,通過一個簡單的命令列實現使用者的命令輸入和計算機的處理和顯示。本文假設你已經具備 的作業系統程式碼基礎。
幾乎所有的作業系統都是以字元終端顯示啟動的。經典的黑底白字,通過鍵盤輸入計算機要執行的命令,然後會提示你拼寫錯誤,或者恰好得到你想要的執行結果。這種方法有兩個主要優點:鍵盤和顯示器可以提供簡易、健壯的計算機互動機制,幾乎所有的計算機系統都採用這個機制,這個也廣泛被系統管理員應用。
早期的計算一般是在一棟樓裡的一個巨型計算機系統,它有很多可以輸命令的'終端'。計算機依次執行不同來源的命令。
讓我們分析下真正想要哪些資訊:
這樣的終端被定義為標準的輸入輸出裝置。用於(顯示)輸入的螢幕和列印輸出內容的螢幕是同一個(LCTT 譯註:最早期的輸出列印真是“列印”到印表機/電傳機的,而用於輸入的終端只是鍵盤,除非做了回顯,否則輸出終端是不會顯示輸入的字元的)。也就是說終端是對字元顯示的一個抽象。字元顯示中,單個字元是最小的單元,而不是畫素。螢幕被劃分成固定數量不同顏色的字元。我們可以在現有的螢幕程式碼基礎上,先儲存字元和對應的顏色,然後再用方法 DrawCharacter
把其推播到螢幕上。一旦我們需要字元顯示,就只需要在螢幕上畫出一行字串。
新建檔名為 terminal.s
,如下:
.section .data.align 4terminalStart:.int terminalBufferterminalStop:.int terminalBufferterminalView:.int terminalBufferterminalColour:.byte 0xf.align 8terminalBuffer:.rept 128*128.byte 0x7f.byte 0x0.endrterminalScreen:.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 個背景色(這裡相當於有 162 = 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 .textTerminalColour:teq r0,#6ldreq r0,=0x02B5beq SetForeColourtst r0,#0b1000ldrne r1,=0x52AAmoveq r1,#0tst r0,#0b0100addne r1,#0x15tst r0,#0b0010addne r1,#0x540tst r0,#0b0001addne r1,#0xA800mov r0,r1b SetForeColour
我們的終端第一個真正需要的方法是 TerminalDisplay
,它用來把當前的資料從 terminalBuffer
拷貝到 terminalScreen
和實際的螢幕。如上所述,這個方法必須是最小開銷的操作,因為我們需要頻繁呼叫它。它主要比較 terminalBuffer
與 terminalDisplay
的文字,然後只拷貝有差異的位元組。請記住 terminalBuffer
是以環狀緩衝區執行的,這種情況,就是從 terminalView
到 terminalStop
,或者 128*48 個字元,要看哪個來的最快。如果我們遇到 terminalStop
,我們將會假定在這之後的所有字元是 7f16 (ASCII 刪除字元),顏色為 0(黑色前景色和背景色)。
讓我們看看必須要做的事情:
terminalView
、terminalStop
和 terminalDisplay
的地址。terminalView
不等於 terminalStop
,根據 terminalView
載入當前字元和顏色terminalDisplay
載入當前的字元terminalDisplay
r0
作為背景色引數呼叫 TerminalColour
r0 = 0x7f
(ASCII 刪除字元,一個塊)、 r1 = x
、r2 = y
呼叫 DrawCharacter
r0
作為前景色引數呼叫 TerminalColour
r0 = 字元
、r1 = x
、r2 = y
呼叫 DrawCharacter
terminalDisplay
累加 2terminalView
不等於 terminalStop
,terminalView
位置引數累加 2terminalView
位置已經是檔案緩衝器的末尾,將它設定為緩衝區的開始位置嘗試去自己實現吧。如果你遇到問題,我們的方案下面給出來了:
1、我這裡的變數有點亂。為了方便起見,我用 taddr
儲存 textBuffer
的末尾位置。
.globl TerminalDisplayTerminalDisplay:push {r4,r5,r6,r7,r8,r9,r10,r11,lr}x .req r4y .req r5char .req r6col .req r7screen .req r8taddr .req r9view .req r10stop .req r11ldr taddr,=terminalStartldr view,[taddr,#terminalView - terminalStart]ldr stop,[taddr,#terminalStop - terminalStart]add taddr,#terminalBuffer - terminalStartadd taddr,#128*128*2mov screen,taddr
2、從 yLoop
開始執行。
mov y,#0yLoop$:
2.1、
mov x,#0xLoop$:
從 xLoop
開始執行。
2.1.1、為了方便起見,我把字元和顏色同時載入到 char
變數了
teq view,stopldrneh char,[view]
2.1.2、這行是對上面一行的補充說明:讀取黑色的刪除字元
moveq char,#0x7f
2.1.3、為了簡便我把字元和顏色同時載入到 col
裡。
ldrh col,[screen]
2.1.4、 現在我用 teq
指令檢查是否有資料變化
teq col,charbeq xLoopContinue$
2.1.5、我可以容易的儲存當前值
strh char,[screen]
2.1.6、我用位元偏移指令 lsr
和 and
指令從切分 char
變數,將顏色放到 col
變數,字元放到 char
變數,然後再用位元偏移指令 lsr
獲取背景色後呼叫 TerminalColour
。
lsr col,char,#8and char,#0x7flsr r0,col,#4bl TerminalColour
2.1.7、寫入一個彩色的刪除字元
mov r0,#0x7fmov r1,xmov r2,ybl DrawCharacter
2.1.8、用 and
指令獲取 col
變數的低半位元組,然後呼叫 TerminalColour
and r0,col,#0xfbl TerminalColour
2.1.9、寫入我們需要的字元
mov r0,charmov r1,xmov r2,ybl DrawCharacter
2.1.10、自增螢幕指標
xLoopContinue$:add screen,#2
2.1.11、如果可能自增 view
指標
teq view,stopaddne view,#2
2.1.12、很容易檢測 view
指標是否越界到緩衝區的末尾,因為緩衝區的地址儲存在 taddr
變數裡
teq view,taddrsubeq view,#128*128*2
2.1.13、 如果還有字元需要顯示,我們就需要自增 x
變數然後到 xLoop
迴圈執行
add x,#8teq x,#1024bne xLoop$
2.2、 如果還有更多的字元顯示我們就需要自增 y
變數,然後到 yLoop
迴圈執行
add y,#16teq y,#768bne 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
現在我有了自己 TerminalDisplay
方法,它可以自動顯示 terminalBuffer
內容到 terminalScreen
,因此理論上我們可以畫出文字。但是實際上我們沒有任何基於字元顯示的例程。 首先快速容易上手的方法便是 TerminalClear
, 它可以徹底清除終端。這個方法不用迴圈也很容易實現。可以嘗試分析下面的方法應該不難:
.globl TerminalClearTerminalClear:ldr r0,=terminalStartadd r1,r0,#terminalBuffer-terminalStartstr r1,[r0]str r1,[r0,#terminalStop-terminalStart]str r1,[r0,#terminalView-terminalStart]mov pc,lr
現在我們需要構造一個字元顯示的基礎方法:Print
函數。它將儲存在 r0
的字串和儲存在 r1
的字串長度簡單的寫到螢幕上。有一些特定字元需要特別的注意,這些特定的操作是確保 terminalView
是最新的。我們來分析一下需要做什麼:
terminalStop
和 terminalView
terminalStop
的 x 坐標bufferStop
到行末,同時寫入黑色刪除字元terminalColour
的字元terminalView
到 terminalStop
之間的字元數是否大於一屏terminalView
自增一行terminalView
是否為緩衝區的末尾,如果是的話將其替換為緩衝區的起始位置terminalStop
是否為緩衝區的末尾,如果是的話將其替換為緩衝區的起始位置terminalStop
是否等於 terminalStart
, 如果是的話 terminalStart
自增一行。terminalStart
是否為緩衝區的末尾,如果是的話將其替換為緩衝區的起始位置terminalStop
和 terminalView
試一下自己去實現。我們的方案提供如下:
1、這個是 Print
函數開始快速檢查字串為0的程式碼
.globl PrintPrint:teq r1,#0moveq pc,lr
2、這裡我做了很多設定。 bufferStart
代表 terminalStart
, bufferStop
代表terminalStop
, view
代表 terminalView
,taddr
代表 terminalBuffer
的末尾地址。
push {r4,r5,r6,r7,r8,r9,r10,r11,lr}bufferStart .req r4taddr .req r5x .req r6string .req r7length .req r8char .req r9bufferStop .req r10view .req r11mov string,r0mov length,r1ldr taddr,=terminalStartldr bufferStop,[taddr,#terminalStop-terminalStart]ldr view,[taddr,#terminalView-terminalStart]ldr bufferStart,[taddr]add taddr,#terminalBuffer-terminalStartadd taddr,#128*128*2
3、和通常一樣,巧妙的對齊技巧讓許多事情更容易。由於需要對齊 terminalBuffer
,每個字元的 x 坐標需要 8 位要除以 2。
and x,bufferStop,#0xfelsr x,#1
4.1、我們需要檢查新行
charLoop$:ldrb char,[string]and char,#0x7fteq char,#'\n'bne charNormal$
4.2、迴圈執行值到行末寫入 0x7f;黑色刪除字元
mov r0,#0x7fclearLine$:strh r0,[bufferStop]add bufferStop,#2add x,#1teq x,#128 blt clearLine$ b charLoopContinue$
4.3、儲存字串的當前字元和 terminalBuffer
末尾的 terminalColour
然後將它和 x 變數自增
charNormal$:strb char,[bufferStop]ldr r0,=terminalColourldrb r0,[r0]strb r0,[bufferStop,#1]add bufferStop,#2add x,#1
4.4、檢查 x 是否為行末;128
charLoopContinue$:cmp x,#128blt noScroll$
4.5、設定 x 為 0 然後檢查我們是否已經顯示超過 1 屏。請記住,我們是用的迴圈緩衝區,因此如果 bufferStop
和 view
之前的差是負值,我們實際上是環繞了緩衝區。
mov x,#0subs r0,bufferStop,viewaddlt r0,#128*128*2cmp r0,#128*(768/16)*2
4.6、增加一行位元組到 view
的地址
addge view,#128*2
4.7、 如果 view
地址是緩衝區的末尾,我們就從它上面減去緩衝區的長度,讓其指向開始位置。我會在開始的時候設定 taddr
為緩衝區的末尾地址。
teq view,taddrsubeq view,taddr,#128*128*2
4.8、如果 stop
的地址在緩衝區末尾,我們就從它上面減去緩衝區的長度,讓其指向開始位置。我會在開始的時候設定 taddr
為緩衝區的末尾地址。
noScroll$:teq bufferStop,taddrsubeq bufferStop,taddr,#128*128*2
4.9、檢查 bufferStop
是否等於 bufferStart
。 如果等於增加一行到 bufferStart
。
teq bufferStop,bufferStartaddeq bufferStart,#128*2
4.10、如果 start
的地址在緩衝區的末尾,我們就從它上面減去緩衝區的長度,讓其指向開始位置。我會在開始的時候設定 taddr
為緩衝區的末尾地址。
teq bufferStart,taddrsubeq bufferStart,taddr,#128*128*2
迴圈執行知道字串結束
subs length,#1add string,#1bgt charLoop$
5、儲存變數然後返回
charLoopBreak$:sub taddr,#128*128*2sub taddr,#terminalBuffer-terminalStartstr 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 跳脫(1b16)後面跟著一個 0 - f 的 16 進位制的數,就可以設定前景色為 CGA 顏色號。如果你自己想嘗試實現;在下載頁面有一個我的詳細的例子。
現在我們有一個可以列印和顯示文字的輸出終端。這僅僅是說對了一半,我們需要輸入。我們想實現一個方法:ReadLine
,可以儲存檔案的一行文字,文字位置由 r0
給出,最大的長度由 r1
給出,返回 r0
裡面的字串長度。棘手的是使用者輸出字元的時候要回顯功能,同時想要退格鍵的刪除功能和命令回車執行功能。它們還需要一個閃爍的下劃線代表計算機需要輸入。這些完全合理的要求讓構造這個方法更具有挑戰性。有一個方法完成這些需求就是儲存使用者輸入的文字和檔案大小到記憶體的某個地方。然後當呼叫 ReadLine
的時候,移動 terminalStop
的地址到它開始的地方然後呼叫 Print
。也就是說我們只需要確保在記憶體維護一個字串,然後構造一個我們自己的列印函數。
按照慣例,許多程式語言中,任意程式可以存取 stdin 和 stdin,它們可以連線到終端的輸入和輸出流。在圖形程式其實也可以進行同樣操作,但實際幾乎不用。
讓我們看看 ReadLine
做了哪些事情:
terminalStop
和 terminalStop
的當前值terminalView
和 terminalStop
的地址到記憶體Print
列印當前字串TerminalDisplay
KeyboardUpdate
KeyboardGetChar
Print
和 TerminalDisplay
為了方便讀者理解,然後然後自己去實現,我們的實現提供如下:
.globl ReadLineReadLine:teq r1,#0moveq r0,#0moveq pc,lr
2、考慮到常見的場景,我們初期做了很多初始化動作。input
代表 terminalStop
的值,view
代表 terminalView
。Length
預設為 0
。
string .req r4maxLength .req r5input .req r6taddr .req r7length .req r8view .req r9push {r4,r5,r6,r7,r8,r9,lr}mov string,r0mov maxLength,r1ldr taddr,=terminalStartldr input,[taddr,#terminalStop-terminalStart]ldr view,[taddr,#terminalView-terminalStart]mov length,#0
3、我們必須檢查異常大的讀操作,我們不能處理超過 terminalBuffer
大小的輸入(理論上可行,但是 terminalStart
移動越過儲存的 terminalStop`,會有很多問題)。
cmp maxLength,#128*64movhi 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,stringmov r1,lengthadd r1,#1bl Print
8、拷貝下一個文字到螢幕
bl TerminalDisplay
9、獲取最近一次鍵盤輸入
bl KeyboardUpdate
10、檢索鍵盤輸入鍵值
bl KeyboardGetChar
11、如果我們有一個確認鍵,迴圈中斷。如果有結束符和一個退格鍵也會同樣跳出迴圈。
teq r0,#'\n'beq readLoopBreak$teq r0,#0beq cursor$teq r0,#'\b'bne standard$
12、從 length
裡面刪除一個字元
delete$:cmp length,#0subgt length,#1b cursor$
13、寫回一個普通字元
standard$:cmp length,maxLengthbge 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,stringmov r1,lengthadd r1,#1bl Printbl TerminalDisplay
18、寫入一個結束符
mov r0,#0strb r0,[string,length]
19、返回長度
mov r0,lengthpop {r4,r5,r6,r7,r8,r9,pc}.unreq string.unreq maxLength.unreq input.unreq taddr.unreq length.unreq view
現在我們理論用終端和使用者可以互動了。最顯而易見的事情就是拿去測試了!刪除 main.s
裡 bl UsbInitialise
後面的程式碼後如下:
reset$: mov sp,#0x8000 bl TerminalClear ldr r0,=welcome mov r1,#welcomeEnd-welcome bl Printloop$: 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 PrintloopContinue$: bl TerminalDisplay b loop$echo: cmp r1,#5 movle pc,lr add r0,#5 sub r1,#5 b Printok: 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 2welcome: .ascii "Welcome to Alex's OS - Everyone's favourite OS"welcomeEnd:.align 2prompt: .ascii "\n> "promptEnd:.align 2command: .rept 128 .byte 0 .endrcommandEnd:.byte 0.align 2commandUnknown: .ascii "Command `%s' was not recognised.\n"commandUnknownEnd:.align 2formatBuffer: .rept 256 .byte 0 .endrformatEnd:.align 2commandStringEcho: .ascii "echo"commandStringReset: .ascii "reset"commandStringOk: .ascii "ok"commandStringCls: .ascii "cls"commandStringEnd:.align 2commandTable:.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 清空終端。
試試樹莓派的程式碼吧。如果遇到問題,請參照問題集錦頁面吧。
如果執行正常,祝賀你完成了一個作業系統基本終端和輸入系列的課程。很遺憾這個教學先講到這裡,但是我希望將來能製作更多教學。有問題請反饋至 [email protected]。
你已經在建立了一個簡易的終端作業系統。我們的程式碼在 commandTable 構造了一個可用的命令表格。每個表格的入口是一個整型數位,用來表示字串的地址,和一個整型數位表格程式碼的執行入口。 最後一個入口是 為 0 的 commandStringEnd
。嘗試實現你自己的命令,可以參照已有的函數,建立一個新的。函數的引數 r0
是使用者輸入的命令地址,r1
是其長度。你可以用這個傳遞你輸入值到你的命令。也許你有一個計算器程式,或許是一個繪圖程式或國際象棋。不管你的什麼點子,讓它跑起來!