Friday, January 30, 2009

一千零一夜之 Console I/O

這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。

當一個程式被執行時,它的 stdin/stdout/stderr 會從父程序繼承。如果沒有特別指定,它們通常會指向同一個諸如 /dev/ttyX 或 /dev/pts/X 的 TTY device。很難不去好奇,當我們 getchar() 或 printf() 時,對一個 TTY device 的 I/O,在 kernel 裡頭會發生什麼事?



口語裡的 console,指的通常是圖中 VT。VT 是 virtual terminal 的縮寫,對 userspace 來說,它就像是一部真實存在著的終端機;在 kernel 裡頭,VT(的功能之一)是一個 TTY driver,它是所有 /dev/ttyX 裝置的驅動程式。對 TTY device 的 I/O 動作,會經由 TTY core 送給 TTY driver。資料往來於 TTY 與 TTY driver 的過程中,或者說圖中連結 TTY 與 VT 的線,被稱做 TTY ldisc,TTY line discipline。

之前的文章中我們看到鍵盤向 TTY 送出的資料。來自於鍵盤的輸入,可能是特別的按鍵組合,目的並不是讓 userspace 去取得這些輸入。例如,我們會敲下 Ctrl-C 來中斷目前的程式;Ctrl-Z 來暫停目前的程式。又或者,我們不希望輸入的密碼會出現在螢幕上。這些需求都在 line discipline 中被滿足。因為所有的 I/O 都會經由 line discipline,於是它可以在使用者輸入特定字元時,對目前的程式送出 SIGINT 或 SIGTSTP;它也可以選擇是否要把使用者的輸入再 echo 回 VT。從這邊也可以看到,line discipline 不只可以對資料做緩衝與處理,它還被允計針對特定的輸入做特定的行為。當我們從 VT 換到實體的 COM port 時,且當與 COM port 連結的裝置不是終端機,而是數據機時,line discipline 甚至可以拒絕所有來自 userspace 的 I/O,轉而從 PPP network device 與 userspace 做資料交換。實際上,TTY device 只是一個 transport layer,可以跟它連結的裝置千變萬化。line discipline 於是也被設計成可以抽換;當從終端機換成數據機時,line discipline 也從 N_TTY 換成 N_PPP。

跟來自鍵盤的輸入一樣,使用者對 TTY device 寫入的資料,會經過 line discipline,VT,再進入螢幕。在 drivers/char/n_tty.c 的 do_output_char 函數中
switch (c) {
...
case '\r':
if (O_ONOCR(tty) && tty->column == 0)
return 0;
if (O_OCRNL(tty)) {
c = '\n';
if (O_ONLRET(tty))
tty->canon_column = tty->column = 0;
break;
}
tty->canon_column = tty->column = 0;
break;
case '\t':
spaces = 8 - (tty->column & 7);
if (O_TABDLY(tty) == XTABS) {
if (space <>column += spaces;
tty->ops->write(tty, " ", spaces);
return spaces;
}
tty->column += spaces;
break;
...
}

可以看到 line discipline 會對特殊字元,像是換行符號或 tab 等做排版。更多關於 N_TTY 這個 line discipline 的功能可以參考 stty(1)。

VT 的架構與實體的 terminal 與 host computer 架構是對應的。要把現在手上正在上網的電腦想像成它是 terminal 與 host computer 的組成可能不是那麼容易。一個相對常見、具體的情境,是用一條線把手上的電腦與 wifi router 或手機的 UART console 連結。上圖中的 Computer B 可以理解成手上的電腦,Computer A 則是跑著 linux 的嵌入式系統。

2 comments:

Unknown said...

olv 大大讀的到底是什麼書才會作這個筆記呀?

olv said...

我是讀 kernel 的 source code。