2010-07-22 105 views
6

我在ubuntu下使用nasm。順便說一句,我需要從用戶的鍵盤獲得單一輸入字符(就像程序要求你輸入y/n時一樣),按下鍵並不按下輸入鍵,我需要讀取輸入的字符。我搜索了很多,但是我發現的所有內容都與此行(int 21h)有關,導致了「分段錯誤」。請幫我弄清楚如何獲得單個字符或如何克服這個分段錯誤。如何在ubuntu下使用nasm(assembly)從鍵盤讀取單字符輸入?

回答

11

它可以從裝配完成,但並不容易。你不能使用int 21h,這是一個DOS系統調用,它在Linux下不可用。

要從類UNIX操作系統(如Linux)的終端獲取字符,請從STDIN(文件編號0)中讀取。通常情況下,讀取的系統調用將被阻塞,直到用戶按下輸入。這被稱爲規範模式。要讀取單個字符而不等待用戶按下輸入,您必須先禁用規範模式。當然,如果您稍後需要行輸入,並且在程序退出之前,您必須重新啓用它。

要在Linux上禁用規範模式,請使用ioctl系統調用將IOCTL(IO控制)發送到STDIN。我假設你知道如何從彙編程序進行Linux系統調用。

ioctl系統調用有三個參數。第一個是將命令發送到(STDIN)的文件,第二個是IOCTL編號,第三個通常是指向數據結構的指針。 ioctl在成功時返回0,或者在失敗時返回負面的錯誤代碼。

您需要的第一個IOCTL是TCGETS(編號0x5401),它在termios結構中獲取當前終端參數。第三個參數是一個指向termios結構的指針。從內核源,termios結構被定義爲:

struct termios { 
    tcflag_t c_iflag;    /* input mode flags */ 
    tcflag_t c_oflag;    /* output mode flags */ 
    tcflag_t c_cflag;    /* control mode flags */ 
    tcflag_t c_lflag;    /* local mode flags */ 
    cc_t c_line;     /* line discipline */ 
    cc_t c_cc[NCCS];    /* control characters */ 
}; 

其中tcflag_t是32位長,cc_t是一個字節長,並且NCCS當前被定義爲19.查看如何可以方便地定義NASM手冊併爲這樣的結構保留空間。

所以,一旦你有當前的termios,你需要清除規範標誌。該標誌位於c_lflag字段中,掩碼爲ICANON(0x00000002)。要清除它,計算c_lflag AND(不是ICANON)。並將結果存儲回c_lflag字段。

現在您需要通知內核您對termios結構的更改。使用TCSETS(編號0x5402)ioctl,第三個參數設置termios結構的地址。

如果一切順利,終端現在處於非規範模式。您可以通過設置規範標誌(通過ORON c_lflag與ICANON)並再次調用TCSETS ioctl來恢復規範模式。在退出之前總是恢復規範模式

正如我所說,這並不容易。

0

簡單的方法:對於文本模式的程序,使用libncurses來訪問鍵盤;對於圖形程序,請使用Gtk+。困難的方法:假設一個文本模式程序,你必須告訴內核你需要單字符輸入,然後你必須做大量的簿記和解碼。這真的很複雜。沒有相當於舊的DOS的例程。你可以開始瞭解如何在這裏:Terminal I/O。圖形程序更加複雜;最低級別的API是Xlib

無論哪種方式,你會瘋狂編碼無論這是在組裝;用C代替。

+1

雖然你說的所有內容對於C都是正確的,但如果OP正在嘗試學習裝配,那麼它並不是真正的相關答案。 – 2010-07-22 01:31:10

+1

這是因爲OP *不應該用匯編語言編程*。在彙編語言中手工編碼任何東西的唯一好理由是它是否是性能關鍵的計算子程序,或是操作系統內核中極少數不能以任何其他方式編碼的低級部分之一。用戶交互不符合條件。在Unix下,OP所要做的甚至不是一個好的*學習練習。 – zwol 2010-07-22 18:07:06

+0

話雖如此,沒有什麼能夠阻止OP編寫調用libncurses的彙編語言,儘管它看起來像是對時間和理智的深刻的浪費指向我(但它不會像彙編語言那樣糟糕,而Unix終端I/O)。 – zwol 2010-07-22 18:12:00

5

我需要最近做到這一點,和卡勒姆的excellent answer啓發,我寫了下面:

termios:  times 36 db 0 
stdin:   equ 0 
ICANON:   equ 1<<1 
ECHO:   equ 1<<3 

canonical_off: 
     call read_stdin_termios 

     ; clear canonical bit in local mode flags 
     push rax 
     mov eax, ICANON 
     not eax 
     and [termios+12], eax 
     pop rax 

     call write_stdin_termios 
     ret 

echo_off: 
     call read_stdin_termios 

     ; clear echo bit in local mode flags 
     push rax 
     mov eax, ECHO 
     not eax 
     and [termios+12], eax 
     pop rax 

     call write_stdin_termios 
     ret 

canonical_on: 
     call read_stdin_termios 

     ; set canonical bit in local mode flags 
     or dword [termios+12], ICANON 

     call write_stdin_termios 
     ret 

echo_on: 
     call read_stdin_termios 

     ; set echo bit in local mode flags 
     or dword [termios+12], ECHO 

     call write_stdin_termios 
     ret 

read_stdin_termios: 
     push rax 
     push rbx 
     push rcx 
     push rdx 

     mov eax, 36h 
     mov ebx, stdin 
     mov ecx, 5401h 
     mov edx, termios 
     int 80h 

     pop rdx 
     pop rcx 
     pop rbx 
     pop rax 
     ret 

write_stdin_termios: 
     push rax 
     push rbx 
     push rcx 
     push rdx 

     mov eax, 36h 
     mov ebx, stdin 
     mov ecx, 5402h 
     mov edx, termios 
     int 80h 

     pop rdx 
     pop rcx 
     pop rbx 
     pop rax 
     ret 

那麼你可以這樣做:

call canonical_off 

如果您正在閱讀的文本行,你可能也想這樣做:

call echo_off 

這樣每個字符在輸入時都不會被回顯。

可能有更好的方法來做到這一點,但它適用於64位Fedora安裝。

有關更多信息,請參見手冊頁termios(3)termbits.h source