跳轉到內容

序列程式設計/Unix V7

來自華夏公益教科書,自由的教科書,共建自由的世界

Unix V7 序列程式設計

[編輯 | 編輯原始碼]

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 程式碼不相容,......

總而言之,如果可以使用 termiotermios 等更新的終端 API,則使用更新的 API 代替 Unix V7 或 V7 類似 API 會更有意義。如果確實使用了 Unix V7 API,則需要對特定系統的文件和包含檔案進行廣泛的研究。然而,有可能發現 API 在一些經典的 Unix 原始碼中被使用,尤其是在從那個時代倖存到今天的自由軟體中。

最初,終端(序列)特定的 ioctl(2) 系統呼叫所需的定義可以在sgtty.hsys/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 - 工作模式

[編輯 | 編輯原始碼]

如前所述,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) 呼叫中使用的形式呈現。這樣做是為了能夠同時呈現傳遞的資料型別。以下列表中,引數資料型別以下劃線標出。
線路規則
[編輯 | 編輯原始碼]
ioctl(fd, TIOCGETD, int *dscp)
獲取線路規則。見下文。
ioctl(fd, TIOCSETD, int *dscp

設定線路規則。

這些命令確實存在於 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 sgttybsg_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)
?

更多原始 Unix V7 sgtty.h ioctls

[編輯 | 編輯原始碼]

以下 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 檔案使用非阻塞模式。

隨著時間的推移而獲得的終端 ioctls

[編輯 | 編輯原始碼]

隨著時間的推移,已經獲得了一些額外的 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引數。

設定/獲取引數

[編輯 | 編輯原始碼]

設定/獲取特殊字元

[編輯 | 編輯原始碼]

設定/獲取更多特殊字元

[編輯 | 編輯原始碼]

Unix V7 - struct chars

[編輯 | 編輯原始碼]

Unix V7 - struct tchars

[編輯 | 編輯原始碼]

Unix V7 -struct sgttyb

[編輯 | 編輯原始碼]
華夏公益教科書