2549 字
13 分钟
终端IO:终端IO属性与控制【Unix编程】

struct termios#

struct termios 是用于控制终端 I/O(输入输出)行为的一个结构体,用于设置和获取终端设备的参数,例如输入模式、输出模式、控制模式、局部模式、特殊字符等。

struct termios
{ tcflag_t c_iflag; /* input mode flags */
tcflag_t c_oflag; /* output mode flags */
tcflag_t c_cflag; /* control mode flags */
tcflag_t c_lflag; /* local mode flags */
cc_t c_line; /* line discipline */
cc_t c_cc[NCCS]; /* control characters */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
};

以下是按照标志常量分组并转换为表格格式的内容:


c_iflag标志常量#

标志常量描述
IGNBRK忽略输入上的BREAK条件
BRKINTBREAK引起输入和输出队列刷新,并可能发送SIGINT信号
IGNPAR忽略帧错误和奇偶校验错误
PARMRK标记有奇偶校验或帧错误的输入字节
INPCK启用输入奇偶校验检查
ISTRIP去掉第8位
INLCR输入时将NL转换为CR
IGNCR忽略输入的回车符
ICRNL输入时将回车符转换为换行符
IUCLC输入时将大写字符映射为小写字符(非POSIX)
IXON启用输出上的XON/XOFF流控制
IXANY任何字符都能重启停止的输出
IXOFF启用输入上的XON/XOFF流控制
IMAXBEL输入队列满时响铃
IUTF8输入为UTF8(Linux 2.6.4起)

c_oflag标志常量#

标志常量描述
OPOST启用实现定义的输出处理
OLCUC输出时将小写字符映射为大写字符(非POSIX)
ONLCR输出时将NL映射为CR-NL
OCRNL输出时将CR映射为NL
ONOCR在第0列不输出CR
ONLRETNL字符假定执行回车功能
OFILL发送填充字符以延迟,而不是使用定时延迟
OFDEL填充字符为ASCII DEL
NLDLY换行延迟掩码
CRDLY回车延迟掩码
TABDLY水平制表符延迟掩码
BSDLY退格延迟掩码
VTDLY垂直制表符延迟掩码
FFDLY换页延迟掩码

c_cflag标志常量#

标志常量描述
CBAUD波特率掩码(非POSIX)
CBAUDEX额外波特率掩码(非POSIX)
CSIZE字符大小掩码
CSTOPB设置两个停止位
CREAD启用接收器
PARENB启用输出奇偶校验生成和输入奇偶校验检查
PARODD设置奇校验,否则为偶校验
HUPCL最后一个进程关闭设备后降低调制解调器控制线
CLOCAL忽略调制解调器控制线
LOBLK阻止非当前shell层的输出(非POSIX)
CIBAUD输入速度掩码(非POSIX)
CMSPAR使用”粘性”(标记/空格)奇偶校验
CRTSCTS启用RTS/CTS(硬件)流控制

c_lflag标志常量#

标志常量描述
ISIG接收INTR、QUIT、SUSP或DSUSP字符时生成相应信号
ICANON启用规范模式
XCASE终端为大写模式(非POSIX,Linux不支持)
ECHO回显输入字符
ECHOEERASE字符擦除前一个输入字符
ECHOKKILL字符擦除当前行
ECHONL即使未设置ECHO也回显NL字符
ECHOCTL回显特殊字符为^X格式
ECHOPRT擦除字符时打印它们
ECHOKEKILL通过擦除每字符来回显
DEFECHO仅在进程读取时回显(Linux不实现)
FLUSHO正在刷新输出(Linux不支持)
NOFLSH生成信号时不刷新输入和输出队列
TOSTOP向后台进程组发送SIGTTOU信号
PENDIN重新打印输入队列中的所有字符(Linux不支持)
IEXTEN启用实现定义的输入处理

特殊字符(c_cc数组)#

标志常量描述
VDISCARD切换丢弃待处理输出
VDSUSP延迟挂起字符
VEOF文件结束字符
VEOL额外的行结束字符
VEOL2另一个行结束字符
VERASE擦除字符
VINTR中断字符
VKILL杀死字符
VLNEXT字面下一个字符
VMIN非规范读取的最小字符数
VQUIT退出字符
VREPRINT重新打印未读字符
VSTART开始字符
VSTATUS状态字符
VSTOP停止字符
VSUSP挂起字符
VSWTCH切换字符
VTIME非规范读取的超时时间
VWERASE单词擦除字符

函数#

tcgetattr 是一个用于获取终端属性的函数。以下是其相关手册内容的中文翻译:

termios, tcgetattr, tcsetattr, tcsendbreak, tcdrain, tcflush, tcflow, cfmakeraw, cfgetospeed, cfgetispeed, cfsetispeed, cfsetospeed, cfsetspeed - 获取和设置终端属性、线路控制、获取和设置波特率

#include <termios.h>
#include <unistd.h>
int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
int tcsendbreak(int fd, int duration);
int tcdrain(int fd);
int tcflush(int fd, int queue_selector);
int tcflow(int fd, int action);
void cfmakeraw(struct termios *termios_p);
speed_t cfgetispeed(const struct termios *termios_p);
speed_t cfgetospeed(const struct termios *termios_p);
int cfsetispeed(struct termios *termios_p, speed_t speed);
int cfsetospeed(struct termios *termios_p, speed_t speed);
int cfsetspeed(struct termios *termios_p, speed_t speed);

tcgetattr#

int tcgetattr(int fd, struct termios *termios_p);

作用 tcgetattr() 用于获取与文件描述符 fd 关联的终端参数,并将这些参数存储在 termios_p 指向的 termios 结构体中。

参数

  • int fd:指定终端设备的文件描述符。
  • struct termios *termios_p:指向 termios 结构体的指针,用于存储获取到的终端参数。

tcsetattr#

int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);

作用 tcsetattr() 用于设置与终端相关的参数,这些参数从 termios_p 指向的 termios 结构体中获取。optional_actions 参数指定更改何时生效。

参数

  • int fd:指定终端设备的文件描述符。
  • int optional_actions:指定更改生效的时间。
选项说明
TCSANOW更改立即生效。
TCSADRAIN更改在所有写入 fd 的输出传输完成后生效。当更改影响输出的参数时,应使用此选项。
TCSAFLUSH更改在所有写入 fd 的输出传输完成后生效,并且在更改生效前,所有已接收但未读取的输入将被丢弃。

cfsetspeed#

int cfsetispeed(struct termios *termios_p, speed_t speed);
int cfsetospeed(struct termios *termios_p, speed_t speed);
int cfsetspeed(struct termios *termios_p, speed_t speed);

作用 cfsetspeed 函数用于设置终端的输入波特率。它修改 termios 结构中的输入速度字段,但不直接改变终端的设置。要使更改生效,必须使用 tcsetattr 函数将修改后的 termios 结构应用到终端。

参数

  • struct termios *termios_p:指向 termios 结构的指针,该结构包含终端的参数。
  • speed_t speed:指定的输入波特率。该值必须是定义在 <termios.h> 中的有效波特率常量之一。 常用的波特率选项:
常量波特率
B48004800
B96009600
B1920019200
B3840038400
B5760057600
B115200115200
B230400230400
B460800460800
B500000500000
B576000576000
B921600921600
B10000001000000
B11520001152000
B15000001500000
B20000002000000

返回值

  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno 指示错误类型。

cfgetspeed#

speed_t cfgetispeed(const struct termios *termios_p);
speed_t cfgetospeed(const struct termios *termios_p);

作用 cfgetispeed(cfgetospeed) 函数用于获取存储在 termios 结构体中的输入/输出波特率。

参数

  • const struct termios *termios_p:指向 termios 结构体的指针,该结构体包含了终端的配置信息,包括波特率。

返回值

  • 返回存储在 termios 结构体中的输入波特率。返回值是波特率宏常量

tcflush#

int tcflush(int fd, int queue_selector);

作用 tcflush 函数用于丢弃与文件描述符 fd 相关联的终端设备中尚未传输的数据或尚未读取的数据,具体取决于 queue_selector 参数的值。

参数

  • int fd:与终端设备相关联的文件描述符。
  • int queue_selector:指定要刷新的数据队列。
选项说明
TCIFLUSH刷新已接收但尚未读取的数据。
TCOFLUSH刷新已写入但尚未传输的数据。
TCIOFLUSH刷新已接收但尚未读取的数据,以及已写入但尚未传输的数据。

返回值

  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno 以指示错误类型。

利用终端IO传输数据#

将USB-TLL的RX和TX相连形成一个回环。这样我们发送的数据会原封不动地返回接收端。

步骤#

  1. 打开串口设备文件:使用 open() 函数打开设备节点,通常需要 O_RDWR | O_NOCTTY | O_NDELAY 标志:
  2. 配置串口参数:使用tcgetattr``tcsetattr设置串口参数,数据位,奇偶校验,波特率等。
  3. 使用read``write函数读写串口。

示例#

#include "fcntl.h"
#include "termios.h"
#include "unistd.h"
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define RDBUF 512
int set_opt(int fd, int speed, int databits, int stopbits, char parity,
struct termios *oldtio);
int reset_opt(int fd, const struct termios *oldtio);
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage://\n");
exit(1);
}
const char *dev_path = argv[1];
int fd;
fd = open(dev_path, O_RDWR | O_NOCTTY);
if (fd < 0) {
perror("open()");
exit(1);
}
struct termios oldtio;
if (set_opt(fd, 115200, 8, 1, 'n', &oldtio) != 0) {
close(fd);
exit(1);
}
char readbuf[RDBUF];
char *linebuf = NULL;
size_t linesize = 0;
int get_n;
int w_n;
int r_n;
get_n = getline(&linebuf, &linesize, stdin);
tcflush(fd, TCIOFLUSH);
w_n = write(fd, linebuf, get_n);
if (w_n < 0) {
perror("write()");
exit(1);
}
if (tcdrain(fd) != 0) {
perror("tcdrain failed");
}
int total_r_n = 0;
while (1) {
r_n = read(fd, readbuf, RDBUF);
if (r_n > 0) {
readbuf[r_n] = '\0'; // 确保字符串结尾(可选)
write(STDOUT_FILENO, readbuf, r_n);
} else if (r_n == 0) {
printf("read timeout or no data\n");
} else {
perror("read failed");
}
total_r_n += r_n;
if (total_r_n >= get_n) {
break;
}
}
reset_opt(fd, &oldtio);
return 0;
}
// static struct termios oldtio;
int set_opt(int fd, int speed, int databits, int stopbits, char parity,
struct termios *oldtio) {
struct termios newtio;
if (tcgetattr(fd, oldtio) < 0) {
perror("tcgetattr()");
exit(1);
}
memcpy(&newtio, &oldtio, sizeof(struct termios));
newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG | IEXTEN);
newtio.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL | INLCR | IGNCR);
newtio.c_oflag &= ~OPOST;
newtio.c_cflag |= (CLOCAL | CREAD);
speed_t speed_;
switch (speed) {
case 9600:
speed_ = B9600;
break;
case 19200:
speed_ = B19200;
break;
case 38400:
speed_ = B38400;
break;
case 57600:
speed_ = B57600;
break;
case 115200:
speed_ = B115200;
break;
default:
perror("ERROR");
exit(1);
}
cfsetispeed(&newtio, speed_);
cfsetospeed(&newtio, speed_);
newtio.c_cflag &= ~CSIZE;
newtio.c_cflag &= ~PARENB;
newtio.c_cflag &= ~CSTOPB;
switch (databits) {
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
default:
fprintf(stderr, "Unsupported data size\n");
return -1; // 改进:返回错误码
}
switch (parity) {
case 'o':
case 'O':
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'e':
case 'E':
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'n':
case 'N':
newtio.c_cflag &= ~PARENB;
break;
default:
newtio.c_cflag &= ~PARENB;
break;
}
if (stopbits == 2) {
newtio.c_cflag |= CSTOPB;
}
newtio.c_cc[VTIME] = 1;
newtio.c_cc[VMIN] = 0;
tcflush(fd, TCIFLUSH);
if (tcsetattr(fd, TCSANOW, &newtio) < 0) {
perror("tcsetattr()");
exit(1);
}
return 0;
}
int reset_opt(int fd, const struct termios *oldtio) {
tcsetattr(fd, TCSANOW, oldtio);
return 0;
}

上述代码仅仅是用于简单测试,程序的收发。

结构化数据传输#

串口的速度非常缓慢,发送端的一次发送,接收端可能需要多次读取才能完全读完。
终端设备是基于字节流(Stream)的通讯协议,如果需要结构化的数据,需要基于流来规定数据报的获取方式。

  1. 约定结束符号:我们可以已一个或多个数用作数据的结尾。
  2. 在数据头规定长度。读到规定长度后截止。
  3. 制定完整的通讯协议。可以制定完整的通讯协议,规定帧头,长度,数据,校验位,帧尾。
终端IO:终端IO属性与控制【Unix编程】
https://milkfunc.top/posts/unix环境高级编程学习摘要/终端io/终端io终端io属性控制/
作者
CapCake
发布于
2025-08-30
许可协议
CC BY-NC-SA 4.0