序列程式設計/Unix V7
Unix V7 是早期 Unix 版本之一,它在大學之外也獲得了更廣泛的傳播。它於 1978 年商業化釋出。因此,Unix V7 訪問和控制終端的方式在相當長的一段時間內成為 Unix 中終端和序列通訊的標準。V7 API 不包含任何特定的 C 函式呼叫。相反,序列裝置的設定和配置透過標準的 ioctl(2) 系統呼叫完成,資料傳輸透過標準的 read(2) 和 write(2) 系統呼叫完成。
雖然 API 起源於 Unix V7,但它當然沒有保持不變。例如,XENIX 對 tchars 資料結構(tc)使用不同的名稱。某些系統對 sgttyb 結構中的 sg_flags 條目使用 32 位值,而其他系統使用 16 位值。BSD 添加了許多 ioctls,BSD 4.3 添加了許多選項程式碼(有時統稱為 BSD I/O 環境),部分與 Unix SVR3 程式碼不相容,......
總而言之,如果可以使用 termio 或 termios 等更新的終端 API,則使用更新的 API 代替 Unix V7 或 V7 類似 API 會更有意義。如果確實使用了 Unix V7 API,則需要對特定系統的文件和包含檔案進行廣泛的研究。然而,有可能發現 API 在一些經典的 Unix 原始碼中被使用,尤其是在從那個時代倖存到今天的自由軟體中。
最初,終端(序列)特定的 ioctl(2) 系統呼叫所需的定義可以在sgtty.h和sys/tty.h>標頭檔案中找到。但是,在後面的 Unix 版本中,內容被移至sys/ttold.h,其中的一部分也最終出現在termios.h中,或者 API 完全被刪除。
提到termios.h這裡聽起來很奇怪,因為它是一個更新的 API。但是 termio 和 termios 從 V7 API 中借鑑了內容,而仍然支援 V7 API 的系統通常透過在正常的 termio 或 termios 介面之上使用相容的裝置驅動程式來模擬 API。這是透過在 termio 或 termios 裝置驅動程式之上推送額外的 STREAMS 裝置驅動程式模組來完成的。該 STREAMS 模組通常被稱為ttycompat。特定 Unix 系統如何配置為自動將模組推送到裝置,如何手動推送模組,或者 STREAMS 裝置框架的工作原理超出了本說明的範圍。
如前所述,Unix V7 控制終端裝置的方法是使用 ioctl(2) 系統呼叫。ioctl(2) 不是專門針對序列裝置的。系統呼叫是 Unix 通用裝置驅動程式介面的一部分。該介面中使用更廣泛的系統呼叫是 read(2) 和 write(2)。read(2) 和 write(2) 傳輸“有效載荷”(資料),而 ioctl(2) 另一方面則controlsI/O 操作本身。也就是說,它使程式能夠配置裝置。
系統呼叫的通用簽名是
#include <unistd.h> /* Unix SVR4 family */ int ioctl(int fd, int command, ...);
或
#include <sys/ioctl.h> /* Unix BSD family */ int ioctl(int fd, int command, ...);
或
ioctl(fd, command, data) /* Unix V7 - K&R C */ int fd, command; char *data;
使用以下引數
fd- 是指向已開啟裝置的檔案描述符。
command- 是一個整數,它告訴裝置該做什麼,在邏輯上需要裝置知道。序列裝置應該理解的命令列表在sys/ttold.h(較新的 Unix 系統)或sgtty.h加上sys/tt.h(舊的 Unix 系統,如 Unix V7)中定義為宏。在本節的剩餘部分sys/ttold.h被使用。通常,sys/ttold.h本身被sys/ioctl.h包含。但是,明確包含它以確保萬無一失並沒有壞處。
- 核心終端 I/O 控制命令宏的名稱以
TIO...開頭。此外,一些檔案 I/O 控制命令也適用。它們的名稱以FIO...開頭。我們將在後面討論這些命令及其用途,但是,裝置特定的命令(DIO...、MX...)不會討論。
...- ioctl() 的 vararg 省略號
...引數的實際引數通常只是一個指向某個資料結構的指標——至少對於終端控制命令而言。這與舊的 ioctl() 格式匹配,後者有一個單獨的char *data引數。
data- 對於終端控制,
data指標指向型別為struct sgttyb,struct tchars或struct ltchars.
的資料結構。這些資料結構用於在裝置驅動程式和應用程式程式之間通訊不同型別的資訊。它們控制應該執行哪些設定,或者允許獲取當前為裝置設定的哪些引數。
通常 struct sgttyb 看起來像
struct sgttyb {
char sg_ispeed; /* input line speed */
char sg_ospeed; /* output line speed */
char sg_erase; /* erase char */
char sg_kill; /* kill char */
int sg_flags; /* flags */
};
如您所見,它允許程式通訊基本設定,如線路速度和線路規則的基本字元。請記住,該介面是為與終端通訊而建立的,因此它為線路規則提供了支援。
通常 struct tchars 包含有關終端控制字元的更多資訊,看起來像
struct tchars {
char t_intrc; /* interrupt character */
char t_quitc; /* quit character */
char t_startc; /* start output character */
char t_stopc; /* stop output character */
char t_eofc; /* end-of-file character */
char t_brkc; /* input delimiter character */
};
除了這些資料結構之外,BSD 在
struct ltchars {
char t_suspc; /* stop-process character */
char t_dsuspc; /* delayed stop-process character */
char t_rprntc; /* reprint line character */
char t_flushc; /* flush output character */
char t_werasc; /* word erase character */
char t_lnextc; /* literal next character */
};
中添加了更多終端控制字元。
#include <sys/stream.h> /* for ldterm */
#include <sys/termios.h> /* for ldterm - could of course also be used directly */
#include <sys/stropts.h> /* for handling the STREAMS module */
#include <sys/ioctl.h> /* ioctl() signature */
#include <sys/ttold.h> /* terminal control */
#include <sys/types.h> /* for open() */
#include <sys/stat.h> /* for open() */
#include <fcntl.h> /* for open() */
:
.
struct sgttyb data;
int fd;
:
.
/*
* Open the device.
* O_RDRW - open for reading and writing
* O_NDELAY - ignore the state of the DCD line. Otherwise
* the ioctl() blocks until DCD indicates the
* remote side is ready. Also affects behavior of
* the read(2) system call, which will now not block
* if there is no input data available.
* O_NOCTTY - Do not become a controlling terminal. Has no effect for STREAMS
*/
fd = open("/dev/ttya", O_RDWR | O_NDELAY | O_NOCTTY);
/*
* Assume we have a "modern" STREAMS device implementation
* and need to push the STREAMS modules on the stream to
* get the desired V7 compatibility emulation.
* TODO: Add error handling.
*/
if(ioctl(fd, I_FIND, "ldterm") == 0) {
ioctl(fd, I_PUSH, "ldterm"); /* termios STREAMS terminal line discipline module */
}
if(ioctl(fd, I_FIND, "ttcompat") == 0) {
ioctl(fd, I_PUSH, "ttcompat"); /* Unix V7 STREAMS compatibility module */
}
/* done with setting up the device */
:
.
/* Probably set some values in data here */
ioctl(fd, TIO..., &data); /* configure serial line */
原始 Unix V7 程式碼中不會存在所有 STREAMS 相關的內容。在那裡,一個簡單的 #include <gstty.h> 就足夠了。
Unix V7 - 終端 I/O 控制命令模板:Stubpar
概述如前所述,可用於透過 ioctl() 控制終端裝置的可能的終端 I/O 控制命令定義為宏。本節將討論它們的含義和在 ioctl() 系統呼叫中的用法。
不幸的是,命令集很混亂。幾乎每個 Unix 實現都認為新增自己的不相容命令會很酷。更糟糕的是,一些流行的 Unix 版本定義了特定的命令,但幾十年來一直沒有實現它們,然後又放棄了它們。為了讓更多程式設計師高興,一半的命令從未以任何方式認真記錄或描述過。程式設計師顯然認為只有那些能夠訪問核心原始碼的人才配得上編寫序列介面程式。
原始 Unix V7 ioctls本節列出了原始 Unix V7 I/O 控制命令。它們以它們在 ioctl(2) 呼叫中使用的形式呈現。這樣做是為了能夠同時呈現傳遞的資料型別。以下列表中,引數資料型別以下劃線標出。
線路規則設定線路規則。
- 這些命令確實存在於 V7 中,並且已實現。但是,它們沒有記錄。特別是,引數的含義沒有記錄(可能也沒有使用過)。後來,添加了以下引數解釋
- OTTYDISC 或 0
- Unix V7 tty 驅動程式行為
- NETLDISC 或 1
- BSD Unix 驅動程式行為
- NTTYDISC 或 2
一個典型的用法如下所示。#ifdef 測試用於避免在具有未定義 TIOCSETD 引數語義的系統上執行 ioctl()(如原始 V7,其中命令的引數未記錄)。以下將由控制代碼 fd 標識的裝置的行規程更改為 Unix V7 tty 驅動程式行為。
#ifdef OTTYDISC int dscp = OTTYDISC; ioctl(fd, TIOCSETD, &dscp); #endif
當 DT(資料終端,計算機)不再準備在介面上傳送和接收序列資料時,它應該放下該序列介面的 DTR(資料終端就緒)RS-232 控制線。以下命令允許自動執行此操作。
- ioctl(fd, TIOCHPCL, NULL)
- 設定 最後關閉時結束通話 標誌。通常情況下,這被實現為在裝置上最後呼叫 close(2) 時放下序列介面上的 DTR(資料終端就緒)引腳。請注意,V7 中沒有直接的逆向操作。但是,在某些實現中,可以透過
struct sgttyb的sg_flags成員清除該標誌。
事件的典型順序如下所示
/**********************************************************
* Set hang up on last close flag
*********************************************************/
/*
/* open device */
fd = open("/dev/ttya", O_RDWR | O_NDELAY | O_NOCTTY);
/* device configuration (omitted) */
/* Set hang up on last close flag to drop DTR on last close */
ioctl(fd, TIOCHPCL, NULL);
/* communicate via serial interface */
/*
* Close device. If this is the last close (which it typically is), DTR
* will be dropped/
*/
close(fd);
/********************************************************** * Clear hang up on last close flag via TIOCSETP * Note the slightly different flag name HUPCL here * vs. HPCL in TIOCHPCL. *********************************************************/
struct sgttyb tparam; ... fd = open( ... ); ... ioctl(fd, TIOCGETP, &tparam); /* get current values */ tparam.sg_flags &= ~HUPCL; /* only clear H[U]PCL flag */ ioctl(fd, TIOCSETP, &tparam); /* apply changed parameters */
Unix V7 具有未記錄且可能不支援的命令,用於獲取和設定 調變解調器狀態。
- ioctl(fd, TIOCMODG, int *state)
- 獲取調變解調器狀態。通常未實現且未使用。請參閱 TIOCMGET 以獲取 RS-232 控制線狀態。
- ioctl(fd, TIOCMODS, int *state)
- 設定調變解調器狀態。通常未實現且未使用。請參閱 TIOCMSET 以設定 RS-232 控制線。
state 變數的原始語義已在時間的迷霧中丟失。在某些實現中,TIOCMGET/TIOCMSET 的 TIOCM_x 宏可用,並且也可以與 TIOCMODx 一起使用。TIOCM_x 標誌是
- TIOCM_LE
- (非 V7) 線路使能訊號
- TIOCM_DTR
- (非 V7) 資料終端就緒
- TIOCM_RTS
- (非 V7) 傳送請求
- TIOCM_ST
- (非 V7) 次要傳輸(?)
- TIOCM_SR
- (非 V7) 次要接收(?)
- TIOCM_CTS
- (非 V7) 傳送就緒
- TIOCM_CD, TIOCM_CAR
- (非 V7) 載波檢測
- TIOCM_RI, TIOCM_RNG
- (非 V7) 振鈴指示器
- TIOCM_DSR
- (非 V7) 資料集就緒
/********************************************************** * Set the RTS line if possible. * This mixes a TIOCM_x macro intended for TIOCMGET/TIOCMSET * with TIOCMODx and is not recommended. *********************************************************/
int state; ... #if defined(TIOCM_RTS) && defined(TIOCMODG) ioctl(fd, TIOCMODG, &state); state |= TIOCM_RTS; ioctl(fd, TIOCMODS, &state); #endif
- ioctl(fd, TIOCGETP, struct sgttyb *data)
- 獲取終端引數。它們儲存在
data中。 - gtty(fd, struct sgttyb *data)
- 某些源自 V7 的系統上的 TIOCGETP ioctl 的簡短版本。
- ioctl(fd, TIOCSETP, struct sgttyb *data)
- 將介面設定為提供的引數。等待輸出清空。清除(丟棄)任何掛起的輸入資料,然後應用引數。
- stty(fd, struct sgttyb *data)
- 某些源自 V7 的系統上的 TIOCSETP ioctl 的簡短版本。
- ioctl(fd, TIOCSETN, struct sgttyb *data)
- 將介面設定為提供的引數,但不等待清空的輸出,也不清除輸入資料。根據硬體,這曾經會生成一些垃圾輸入/輸出字元。
/********************************************************** * Print out current line configuration *********************************************************/
/* * Map speed constants (not available in all V7-style implementations) * to actual speed value. * Some platforms might offer more constants. Some platforms do allow * to use a simpler mapping, e.g. because of the numbering schema of their * Bx constants. */ long speed2long(int speed) { switch(speed) { case B0: return 0; case B50: return 50; case B75: return 75; case B110: return 110; case B134: return 134; case B150: return 150; case B200: return 200; case B300: return 300; case B600: return 600; case B1200: return 1200; case B1800: return 1800; case B2400: return 2400; case B4800: return 4800; case B9600: return 9600; case EXTA: return 19200; case EXTB: return 38400; /* * untypical, non V7 constants, better check if defined */ #ifdef B19200 case B19200: return 19200; #endif #ifdef B38400 case B38400: return 38400; #endif #ifdef B57600 case B57600: return 57600; #endif #ifdef B115200 case B115200: return 115200; #endif #ifdef B230400 case B230400: return 230400; #endif #ifdef B460800 case B460800: return 460800; #endif } return -1; /* unknown, better update the code */ }
/* * TODO: implement conversion function to decode flags * * flags are: * flag2str() * #ifdef HUPCL * if(flag & HUPCL) ...; // not original V7 * #endif * if(flag & TANDEM) ...; * if(flag & CBREAK) ...; * if(flag & LCASE) ...; * if(flag & ECHO) ...; * if(flag & CRMOD) ...; * if(flag & RAW) ...; * if(!(flag & ANYP)) { * ...; // no parity * } else if((flag & ANYP) == ANYP) { * ...; * } else * if(flag & ODDP) ...; * if(flag & EVENP) ...; * } * if((flag & ALLDELAY) { delay2str(flag); } * * delay2str(flag) * flag = flag & ALLDELAY; * * if((flag & BSDELAY) == BS0) ...; * if((flag & BSDELAY) == BS1) ...; * if((flag & VTDELAY) == FF0) ...; * if((flag & VTDELAY) == FF1) ...; * if((flag & CRDELAY) == CR0) ...; * if((flag & CRDELAY) == CR1) ...; * if((flag & CRDELAY) == CR2) ...; * if((flag & CRDELAY) == CR3) ...; * if((flag & TBDELAY) == TAB0) ...; * if((flag & TBDELAY) == TAB1) ...; * if((flag & TBDELAY) == TAB2) ...; * #ifdef TAB3 * if((flag & TBDELAY) == TAB3) ...; // not original V7, there it was called XTABS * #endif * #ifdef XTABS * if((flag & TBDELAY) == XTABS) ...; * #endif * if((flag & NLDELAY) == NL0) ...; * if((flag & NLDELAY) == NL1) ...; * if((flag & NLDELAY) == NL2) ...; * if((flag & NLDELAY) == NL3) ...; */
struct sgttyb tparam; ... fd = open( ... ); ... ioctl(fd, TIOCGETP, &tparam); printf("Input line speed: %ld\n", speed2long(tparam.sg_ispeed)); printf("Output line speed: %ld\n", speed2long(tparam.sg_ospeed)); printf("Erase char: %x\n", tparam.erase); printf("Kill char: %x\n", tparam.kill); printf("Flags: %x\n", tparam.flags); /* printf("Flags: %s\n", flag2str(tparam.flags));
/********************************************************** * Change line speed to 2400 baud * Keep all other interface parameters as-is. *********************************************************/
struct sgttyb tparam; ... fd = open( ... ); ... ioctl(fd, TIOCGETP, &tparam); /* get current values */ tparam.sg_ispeed = tparam.sg_ospeed = B2400; ioctl(fd, TIOCSETP, &tparam); /* apply changed parameters */
有關 struct sgttyb 結構元件的使用詳細資訊,請參閱下面的 #Unix V7 - struct sgttyb 部分。
- ioctl(fd, TIOCEXCL, NULL)
- 開啟獨佔模式。不允許對裝置進行其他 open() 操作。
- ioctl(fd, TIOCNXCL, NULL)
- 關閉獨佔模式。該裝置可以多次開啟。
- ioctl(fd, TIOCFLUSH, NULL) /* 原始 */
- 輸入和輸出資料被重新整理。
- ioctl(fd, TIOCTSTP, NULL)
TIOCFLUSH的其他名稱- ioctl(fd, TIOCFLUSH, int *mode) /* 較新版本 */
- 根據模式重新整理輸入和/或輸出。常量定義在sys/file.h
- 0 或 (FREAD | FWRITE)
- 重新整理輸入和輸出。
- FREAD
- 重新整理輸入。
- FWRITE
- 重新整理輸出。
- ioctl(fd, TIOHMODE, data_p)
- ?
- ioctl(fd, TIOCGETC, struct tchars *data)
- 從裝置獲取終端狀態特殊字元。
- ioctl(fd, TIOCSETC, struct tchars *data)
- 設定終端狀態特殊字元。
- ioctl(fd, TIOCSETP, struct tchars *data)
- ?
以下 ioctls 都定義在 Unix V7 tty 介面中,但除少數例外外,沒有記錄。D ioctls 可能用於控制撥號器(ACUs)。F ioctls 來自檔案 I/O。M ioctls 實際上是為 Unix/V7 特殊的多路複用(mxp)檔案(Unix/V7 用於程序間通訊的機制)設計的。這些 ioctls 為什麼定義在 sgtty.h 中尚不清楚,其他 mpx ioctls 則定義在 sys/mx.h 中。
- DIOCLSTN
- DIOCNTRL
- DIOCMPX
- DIOCNMPX
- DIOCSCALL
- DIOCRCALL
- DIOCPGRP
- DIOCGETP
- 每行規程獲取資料。
- DIOCSETP
- 每行規程設定資料。
- DIOCLOSE
- DIOCTIME
- DIOCRESET
- FIOCLEX
- 為該檔案描述符設定 close-on-exec 標誌。如果程序被生成(fork/exec),該檔案描述符將被 exec() 系統呼叫關閉。這樣,生成的程序就不會繼承開啟的終端介面。
- FIONCLEX
- 清除該檔案描述符的 close-on-exec 標誌。如果程序被生成(fork/exec),該檔案描述符將不會被 exec() 系統呼叫關閉。這樣,生成的程序就會繼承開啟的終端介面。
- MXLSTN
- 將 mpx 檔案置於偵聽模式。
- MXNBLK
- 對 mpx 檔案使用非阻塞模式。
隨著時間的推移,已經獲得了一些額外的 I/O 控制,這些控制增強了 Unix V7 電傳打字機 API。以下是比較常見的控制。較新的介面(如 termio 和 termios)也添加了 I/O 控制。此處未列出它們。
可以使用以下兩個命令傳送斷開訊號
- ioctl(fd, TIOCSBRK, NULL)
- 設定 斷開 標誌。透過序列線路傳送 斷開 訊號。斷開 是一個全零幀錯誤。
- ioctl(fd, TIOCCBRK, NULL)
- 清除 斷開 標誌。停止傳送 斷開 訊號。
傳送一秒鐘斷開訊號的簡化方法如下所示
ioctl(fd, TIOCSBRK, NULL); /* start break signal */ /* * The following blocks the process for one second. * This is "suboptimal" in real applications. * Using asynchronous I/O and alarm(2)/setitimer(2)/signal(2) * or similar system calls, plus a callback which, when called, * turns the break of, is a better alternative. * Also, the granularity of sleep() is to rough, since * breaks of 250 ... 500 milliseconds are typical in * serial communication. */ sleep(1); /* wait one second */ ioctl(fd, TIOCCBRK, NULL); /* stop sending break signal */
以下命令可用於控制 RS-232 控制線
- ioctl(fd, TIOCSDTR, NULL)
- 設定 DTR(資料終端就緒)訊號。
- ioctl(fd, TIOCCDTR, NULL)
- 清除 DTR(資料終端就緒)訊號。
- ioctl(fd, TIOCMODG, int *state)
- 獲取調變解調器狀態。請參閱下方。
- ioctl(fd, TIOCMODS, int *state)
- 設定調變解調器狀態。允許訪問所有 RS-232 控制線。每條線的狀況由
state引數中的一個位表示。這些位具有以下符號名稱- TIOCM_CAR 或 TIOCM_CD
- DCD . 資料載波檢測
- TIOCM_CTS
- CTS - 傳送就緒
- TIOCM_DTR
- DTR - 資料終端就緒
- TIOCM_LE 或 TIOCM_DSR
- DSR - 資料集就緒
- TIOCM_RNG 或 TIOCM_RI
- RNG - 振鈴指示器
- TIOCM_RTS
- RTS - 傳送請求
- TIOCM_SR
- 次要 RxD
- TIOCM_ST
- 次要 TxD
根據上述命令中哪個可用,例如,DTR 可以如下設定
ioctl(fd, TIOCSDTR, NULL);
或
int state; ioctl(fd, TIOCMGET, &state); state |= TIOCM_DTR; ioctl(fd, TIOCMSET, &state);
- ioctl(fd, TIOCSTOP, NULL)
- 停止輸出,就好像輸入了 停止 字元一樣。
- ioctl(fd, TIOCSTART, data_p)
- 重新啟動輸出,就好像輸入了 開始 字元一樣。
- ioctl(fd, TIOCLGET, int *flags)
- 獲取終端標誌。請參閱
struct sgttyp的元素sg_flags以瞭解詳細資訊。 - ioctl(fd, TIOCLBIS, int *flags)
- 設定特定的終端標誌。這些標誌將與當前已設定的標誌進行或運算。
- ioctl(fd, TIOCLBIC, int *flags)
- 清除特定的終端標誌。這些標誌將與現有標誌進行“非與”運算。
- ioctl(fd, TIOCLSET, int *flags)
- 設定終端標誌。
- ioctl(fd, TIOCGLTC, struct ltchars *data)
- 獲取 ltchars 資料。
- ioctl(fd, TIOCSLTC, struct ltchars *data)
- 設定 ltchars 資料。
- ioctl(fd, FIORDCHK, NULL)
- 返回輸入緩衝區中當前可用的輸入字元數量。該數字作為函式呼叫的返回值返回。
- ioctl(fd, FIONREAD, int *nbr)
- 返回輸入緩衝區中當前可用的輸入字元數量。該數字被寫入*nbt引數。
| 本節是一個存根。 您可以透過擴充套件它來幫助華夏公益教科書。 |
| 本節是一個存根。 您可以透過擴充套件它來幫助華夏公益教科書。 |
| 本節是一個存根。 您可以透過擴充套件它來幫助華夏公益教科書。 |