跳转到内容

串口编程/Unix V7

来自 Wikibooks,开放的书籍,开放的世界
(重定向自 串口编程: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)。一些系统使用 32 位值表示 sgttyb 结构中的 sg_flags 条目,而另一些系统使用 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) 另一方面 controls I/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 */

总而言之,串行设备的配置通常遵循以下语句模板(伪代码,错误处理等除外):
注意

所有 STREAMS 内容都不会出现在原始 Unix V7 代码中。在那里,一个简单的 #include <gstty.h> 就足够了。

Unix V7 - 终端 I/O 控制命令

[编辑 | 编辑源代码]

模板:Stubpar

概述

[编辑 | 编辑源代码]

如前所述,可以使用 ioctl() 通过 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
新的 tty 线路规程(无论是什么

典型的用法如下所示。执行 ioctl() 时,会进行 #ifdef 测试以避免在 TIOCSETD 参数语义未定义的系统上执行(例如原始 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 ioctl

[编辑 | 编辑源代码]

以下 ioctl 都在 Unix V7 tty 接口中定义,但除了少数例外,没有正确记录。D ioctl 可能旨在控制拨号器(ACU)。F ioctl 来自文件 I/O。M ioctl 实际上是为 Unix/V7 特殊的多路复用(mxp)文件(Unix/V7 中用于进程间通信的机制)而设计的。为什么这些 ioctl 在 sgtty.h 中定义尚不清楚,其他 mpx ioctl 则定义在 sys/mx.h 中。

DIOCLSTN
DIOCNTRL
DIOCMPX
DIOCNMPX
DIOCSCALL
DIOCRCALL
DIOCPGRP
DIOCGETP
每个线路规程获取数据。
DIOCSETP
每个线路规程设置数据。
DIOCLOSE
DIOCTIME
DIOCRESET
FIOCLEX
为该文件描述符设置 close-on-exec 标志。如果进程被生成(分叉/执行),则该文件描述符将被 exec() 系统调用关闭。这样,生成后的进程就不会继承打开的终端接口。
FIONCLEX
清除该文件描述符的 close-on-exec 标志。如果进程被生成(分叉/执行),则该文件描述符不会被 exec() 系统调用关闭。这样,生成后的进程就会继承打开的终端接口。
MXLSTN
将 mpx 文件置于监听模式。
MXNBLK
为 mpx 文件使用非阻塞模式。

随着时间的推移而获得的终端 ioctl

[编辑 | 编辑源代码]

随着时间的推移,已经获得了一些额外的 I/O 控制,这些控制增强了 Unix V7 电传打字机 API。以下是一些比较常见的控制。后期接口(如 termio 和 termios)也添加了 I/O 控制。这些在这里没有列出。


可以使用以下两个命令发送中断信号

ioctl(fd, TIOCSBRK, NULL)
设置中断标志。通过串行线发送中断信号。中断是一个全零帧错误。
ioctl(fd, TIOCCBRK, NULL)
清除break标志。停止发送break信号。

发送一秒钟break信号的简化方法如下所示

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)
停止输出,就像输入了stop字符一样。
ioctl(fd, TIOCSTART, data_p)
重新启动输出,就像输入了start字符一样。

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参数。

设置/获取参数

[edit | edit source]

设置/获取特殊字符

[edit | edit source]

设置/获取更多特殊字符

[edit | edit source]

Unix V7 - struct chars

[edit | edit source]

Unix V7 - struct tchars

[edit | edit source]

Unix V7 -struct sgttyb

[edit | edit source]
华夏公益教科书