http://hi.baidu.com/jtntiivtemcnsue/item/90689ce172ee912c5a7cfb1b
編寫過MS-DOS程式的人通常都會尋找Linux下等同於kbhit的函數,這個函數會檢測一個按鍵是否被按下而並不實際的讀取。不幸的是他們並沒有找到這樣的函數,因為並沒有直接等同的函數。Unix程式員並不會注意到這個遺漏,因為Unix的編程方式通常為程式應準備好等待事件的發生。因為這就是通常的kbhit的用法,所以Unix和Linux將其忽略了。
然而,當我們要由MS-DOS移植程式時,通常需要類比kbhit,此時我們可以用非正規輸入模式來做到。
實驗--我們自己的kbhit
1 首先我們需要定義標準的標頭檔並且為終端設定聲明了一個結構。peek_character用於測試一個按鍵是否被按下。然後我們定義了我們將會用到的函數的原型。
#include <stdio.h>
#include <termios.h>
#include <term.h>
#include <curses.h>
#include <unistd.h>
static struct termios initial_settings, new_settings;
static int peek_character = -1;
void init_keyboard();
void close_keyboard();
int kbhit();
int readch();
2 main函數調用init_keyboard函數來配置終端,然後一秒迴圈一次,調用kbhit函數。如果按鍵檢測為q,close_keyboard函數會返回正常行為並且退出程式。
int main()
{
int ch = 0;
init_keyboard();
while(ch != ‘q’) {
printf(“looping\n”);
sleep(1);
if(kbhit()) {
ch = readch();
printf(“you hit %c\n”,ch);
}
}
close_keyboard();
exit(0);
}
3 init_keyboard與close_keyboard在程式的開始和結束配置終端。
void init_keyboard()
{
tcgetattr(0,&initial_settings);
new_settings = initial_settings;
new_settings.c_lflag &= ~ICANON;
new_settings.c_lflag &= ~ECHO;
new_settings.c_lflag &= ~ISIG;
new_settings.c_cc[VMIN] = 1;
new_settings.c_cc[VTIME] = 0;
tcsetattr(0, TCSANOW, &new_settings);
}
void close_keyboard()
{
tcsetattr(0, TCSANOW, &initial_settings);
}
4 下面是檢測鍵盤按鍵的函數:
int kbhit()
{
char ch;
int nread;
if(peek_character != -1)
return 1;
new_settings.c_cc[VMIN]=0;
tcsetattr(0, TCSANOW, &new_settings);
nread = read(0,&ch,1);
new_settings.c_cc[VMIN]=1;
tcsetattr(0, TCSANOW, &new_settings);
if(nread == 1) {
peek_character = ch;
return 1;
}
return 0;
}
5 按下的按鍵是由下一個函數,readch,讀取的,然後將peek_character為下一次迴圈設定為-1。
int readch()
{
char ch;
if(peek_character != -1) {
ch = peek_character;
peek_character = -1;
return ch;
}
read(0,&ch,1);
return ch;
}
當我們運行這個程式時,我們會得到下面的輸出:
$ ./kbhit
looping
looping
looping
you hit h
looping
looping
looping
you hit d
looping
you hit q
$
工作原理
在init_keyboard中配置終端在返回之前(MIN=1,TIME=0)讀取一個字元。kbhit將其改變為檢測輸入並且立即返回(MIN=0,TIME=0),然後在程式退出前恢複原始設定。
注意,我們必須讀取被按下的鍵,但是卻是在局部儲存,從而可以在需要的時候返回。
虛擬控制台
Linux提供了一個稱之為虛擬控制台的特性。有許多的終端裝置可用,所有的裝置都共用PC的螢幕,鍵盤以及滑鼠。通常,一個Linux的安裝配置12個這樣的虛擬控制台。
虛擬控制台是通過字元裝置/dev/ttyN來訪問的,在這裡N是一個數字,由1開始。
如果我們的Linux系統使用文本登陸,那麼在Linux啟動運行時我們就會得到一個登陸提示。然後我們可以使用使用者名稱與密碼進行登陸。此時我們使用的裝置就是第一個虛擬控制台,終端裝置/dev/tty1。
使用who與ps命令,我們可以看到登陸的使用者,shell與在這個虛擬控制台上正啟動並執行程式:
$ who
neil tty1 Mar 8 18:27
$ ps -e
PID TTY TIME CMD
1092 tty1 00:00:00 login
1414 tty1 00:00:00 bash
1431 tty1 00:00:00 emacs
從這裡我們可以看出登陸的使用者為neil,並且在控制台裝置/dev/tty1上運行Emacs。
Linux通常啟動一個getty進程運行在前六個虛擬控制台上,這樣就可以使用相同的螢幕,鍵盤與滑鼠登陸六次。我們可以使用ps看到這些進程:
$ ps -e
PID TTY TIME CMD
1092 tty1 00:00:00 login
1093 tty2 00:00:00 mingetty
1094 tty3 00:00:00 mingetty
1095 tty4 00:00:00 mingetty
1096 tty5 00:00:00 mingetty
1097 tty6 00:00:00 mingetty
在這裡我們可以看到SuSE預設的getty程式,mingetty,運行在另外五個虛擬控制台上,等待使用者登陸。
我們可以使用一個特殊的按鍵組合Ctrl+Alt+F<N>來在虛擬控制台之間進行切換,在這裡N為我們要切換到的虛擬控制台號。所以要切換到第2個虛擬控制台,我們需要按下Alt+Ctrl+F2,而Ctrl+Alt+F1會返回到第一個控制台。(當由字元登陸而不是圖形登陸切換時,Ctrl+F<N>組合也可以起作用)
如果Linux啟動一個圖形登陸,或者是通過startx或者是通過一個顯示管理器,例如xdm,X Window系統將會使用第一個閒置虛擬控制台來啟動登陸,通常為/dev/tty7。當使用X Window系統時,我們可以使用Ctrl+Alt+F<N>切換到文本控制台,並使用Ctrl+Alt+F7返回到圖形控制台。
通常在Linux上會運行多個會活。如果我們這樣做,例如,使用命令
$ startx - :1
Linux會在下一個閒置虛擬控制台上啟動X伺服器,在這種情況下,通常為/dev/tty8,然後我們可以使用Ctrl+Alt+F8與Ctrl+Alt+F7在他們之間進行切換。
在所有其他方面,虛擬控制台的行為與終端類似,正如我們在這一章所描述的。如果一個進程具有正確的許可權,虛擬控制台可以使用與通常的終端相的方式執行開啟,讀取與寫入等操作。
偽終端
許類Unix系統,包括Linux,具有一個名為偽終端的特性。這些裝置的行為與我們在這一章所使用的終端相類似,所不同的是他們並沒有與其相關聯的硬體。他們可以用來為其他的程式提供一個類終端介面。
例如,使用兩個偽終端就可以使得兩個象棋程式彼此交戰,儘管程式本身是設定用來與人在終端進行互動的。作為中介的程式將一個程式的動作傳遞給另一個。可以使得偽終端可以使得程式在不提供終端的情況下像通常一樣運行。
偽終端曾經是以特定系統的方式實現的。現在他們已經進入Single Unix規範的Unix98偽終或是PTY標準中。
總結
在這一章,我們瞭解了控制終端的三個不同方面。在這一章的第一部分,我們瞭解了重新導向檢測以及如何與一個終端互動,儘管標準的檔案描述符已經進行重新導向。我們討論的硬體模型及其曆史。然後我們瞭解了通用終端介面以及在Linux終端處理上提供詳細控制的termios結構。我們也看到了如何使用termios資料庫以及以獨立於終端的方式管理螢幕的相關函數,同時我們也瞭解了立即檢測按鍵。最後,我們討論了Linux虛擬控制台以及偽終端。
另外:
linux下如何處理鍵盤響應,有沒有類似bioskey(0)的函數