檢測按鍵(Linux中kbhit()函數的實現)

編寫過MS-DOS程序的人通常都會查找Linux下等同於kbhit的函數,這個函數會檢測一個按鍵是否被按下而並不實際的讀取。不幸的是他們並沒有找到這樣的函數,因爲並沒有直接等同的函數。Unix程序員並不會注意到這個遺漏,因爲Unix的編程方式通常爲程序應準備好等待事件的發生。因爲這就是通常的kbhit的用法,所以Unix和Linux將其忽略了。

然而,當我們要由MS-DOS移植程序時,通常需要模擬kbhit,此時我們可以用非正規輸入模式來做到。

試驗--我們自己的kbhit

1 首先我們需要定義標準的頭文件並且爲終端設置聲明瞭一個結構。peek_character用於測試一個按鍵是否被按下。然後我們定義了我們將會用到的函數的原型。

#include 
#include 
#include 
#include 
#include 
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爲我們要切換到的虛擬控制檯號。所以要切換到第2個虛擬控制檯,我們需要按下Alt+Ctrl+F2,而Ctrl+Alt+F1會返回到第一個控制檯。(當由字符登陸而不是圖形登陸切換時,Ctrl+F組合也可以起作用)

如果Linux啓動一個圖形登陸,或者是通過startx或者是通過一個顯示管理器,例如xdm,X Window系統將會使用第一個空閒的虛擬控制檯來啓動登陸,通常爲/dev/tty7。當使用X Window系統時,我們可以使用Ctrl+Alt+F切換到文本控制檯,並使用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虛擬控制檯以及僞終端。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章