跳转到内容

串行编程/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

[编辑 | 编辑源代码]
华夏公益教科书