串行编程/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参数。
本节是一个存根。 您可以通过扩展它来帮助维基教科书。 |
本节是一个存根。 您可以通过扩展它来帮助维基教科书。 |
本节是一个存根。 您可以通过扩展它来帮助维基教科书。 |