终端I/O有两种不同的工作模式:
(1)规范模式:输入以行单位进行处理,每个读请求也最多返回一行。
(2)非规范模式:输入字符不装配成行。
终端设备是由通常位于内核中的终端驱动程序控制的。每个终端设备都有一个输入队列和一个输出队列。
- 如果打开了回显,输入队列和输出队列之间有一个隐含的连接
- 输入队列有一个MAX_INPUT的有限值
- 还有一个MAX_CANON,限制输入行的最大字节数
- 输出队列也是有限的,但是当它快要满的时候,内核会让它休眠,直到有可用空间
终端行规程(terminal line discipline)的模块中进行全部的规范处理。
所有可以检测和更改的终端设备特性都在termios结构中
#include <termios.h> struct termios { tcflag_t c_iflag; /* input flags */ tcflag_t c_oflag; /* output flags */ tcflag_t c_cflag; /* control flags */ tcflag_t c_lflag; /* local flags */ cc_t c_cc[NCCS]; /* control characters */ }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
c_cflag支持的常量名称 CBAUD 波特率的位掩码 B0 0波特率(放弃DTR) B1800 1800波特率 B2400 2400波特率 B4800 4800波特率 B9600 9600波特率 B19200 19200波特率 B38400 38400波特率 B57600 57600波特率 B115200 115200波特率 EXTA 外部时钟率 EXTB 外部时钟率 CSIZE 数据位的位掩码 CS5 5个数据位 CS6 6个数据位 CS7 7个数据位 CS8 8个数据位 CSTOPB 2个停止位(不设则是1个停止位) CREAD 接收使能 PARENB 校验位使能 PARODD 使用奇校验而不使用偶校验 HUPCL 最后关闭时挂线(放弃DTR) CLOCAL 本地连接(不改变端口所有者) LOBLK 块作业控制输出 CNET_CTSRTS 硬件流控制使能 c_iflag支持的常量名称 INPCK 奇偶校验使能 IGNPAR 忽略奇偶校验错误 PARMRK 奇偶校验错误掩码 ISTRIP 除去奇偶校验位 IXON 启动出口硬件流控 IXOFF 启动入口软件流控 IXANY 允许字符重新启动流控 IGNBRK 忽略中断情况 BRKINT 当发生中断时发送SIGINT信号 INLCR 将NL映射到CR IGNCR 忽略CR ICRNL 将CR映射到NL IUCLC 将高位情况映射到低位情况 IMAXBEL 当输入太长时回复ECHO c_cc 支持的常量名称 VINTR 中断控制,对应键为CTRL+C VQUIT 退出操作,对应键为CRTL+Z VERASE 删除操作,对应键为Backspace(BS) VKILL 删除行,对应键为CTRL+U VEOF 位于文件结尾,对应键为CTRL+D VEOL 位于行尾,对应键为Carriage return(CR) VEOL2 位于第二行尾,对应键为Line feed(LF) VMIN 指定了最少读取的字符数 VTIME 指定了读取每个字符的等待时间 串口控制函数 Tcgetattr 取属性(termios结构) Tcsetattr 设置属性(termios结构) cfgetispeed 得到输入速度 Cfgetospeed 得到输出速度 Cfsetispeed 设置输入速度 Cfsetospeed 设置输出速度 Tcdrain 等待所有输出都被传输 tcflow 挂起传输或接收 tcflush 刷清未决输入和/或输出 Tcsendbreak 送BREAK字符 tcgetpgrp 得到前台进程组ID tcsetpgrp 设置前台进程组ID
获取和设置termios结构
#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);
两个函数的返回值:成功0,出错-1
fd:若fd没有引用中断设备则出错返回-1
optional_actions:指定什么时候新的终端属性起作用。
TCSANOW 更改立即发生
TCSADRAIN 发送了所有输出后更改才发生。若更改输出参数则应使用此选项
TCSAFLUSH 发送了所有输出后更改才发生。更进一步,在更改发生时未读的所有输入数据都被丢弃(冲洗)
例子:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include "apue.h" 2 #include <termios.h> 3 4 int main(void) 5 { 6 struct termios term; 7 long vdisable; 8 9 if(isatty(STDIN_FILENO) == 0) 10 err_quit("standard input is not a terminal device"); 11 12 if((vdisable = fpathconf(STDIN_FILENO, _PC_VDISABLE)) < 0) 13 err_quit("fpathconf error or _POSIX_VDISABLE not in effect"); 14 15 if(tcgetattr(STDIN_FILENO, &term) < 0) /* fetch tty state */ 16 err_sys("tcgetattr error"); 17 18 term.c_cc[VINTR] = vdisable; /* disable INTR character */ 19 term.c_cc[VEOF] = 2; /* EOF is Control-B */ 20 21 if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &term) < 0) 22 err_sys("tcsetattr error"); 23 24 exit(0); 25 }
该程序先用 isatty 函数来测试 STDIN_FILENO 描述符所指向的文件是否是终端设备,如果测试的文件描述符是指向一个终端设备的话则返回1,否则返回0。
然后使用函数 fpathconf 获取系统中的 _PC_VDISABLE 的值,将这个值保存在 c_cc 数组中的相应位置就可以禁止使用这个位置所代表的特殊字符。
接着我们调用 tcgetattr 函数用来获取终端IO的属性,然后设置 c_cc 数组的 VINTR 的位置为 _PC_VDISABLE,表示禁止使用中断符号CTRL+C。而将VEOF的位置的值更改为2,即表示将文件的结束符号修改为CTRL+B,同理如果要修改为CTRL+A,则这个地方的值为1。
设置波特率
#incldue <termios.h> speed_t cfgetispeed(const struct termios *termios_p); spee_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);
两个函数的返回值:成功0,出错-1
termios_p:指向termios的指针
speed:波特率速度,是下列常量B50,B75,B110,B134,B150,B200,B300,B600,B1200,B1800,B2400,B4800,B9600,B19200,B38400
使用这些函数时,必须认识到输入、输出波特率是存储在设备的termios结构中的。在调用两个cfget函数中的任意一个之前,要先用tcgetattr获得设备的termios结构。
在调用cfset函数中的任意一个后,要做的就是在termios结构中设置波特率,为了更改到设备中,应当调用tcseattr函数。
行控制函数
#include <termios.h> #include <unistd.h> int tcdrain(int fd); //等待所有写入fd中的数据输出 int tcflow(int fd, int action); //挂起fd上的数据传输或接收 int tcflush(int fd, int queue_selector); //丢弃要写入fd,但尚未传输的数据,或者收到尚未读取的数据 int tcsendbreak(int fd, int duration); //传送连续0值比特流,持续一段时间,如果终端使用异步串行数据传输的话
4个函数返回值:成功0,出错-1
fd:引用一个终端设备
action:
TCSANOW:不等数据传输完毕就立即改变属性。
TCSADRAIN:等待所有数据传输结束才改变属性。
TCSAFLUSH:清空输入输出缓冲区才改变属性。
queue_selector:
TCIFLUSH:刷新收到的数据但是不读
TCOFLUSH:刷新写入的数据但是不传送
TCIOFLUSH:同时刷新收到的数据但是不读,并且刷新写入的数据但是不传送
duration:如果是0,至少传输0.25秒,不会超过0.5秒。非零,他发送的时间长度由实现定义
大多数控制终端的名字是/dev/tty,POSIX.1提供了一个运行时函数,可用来确定控制终端的名字。
#include <stdio.h> char *ctermid(char *s);
返回值:成功返回指向控制终端名的指针,出错返回指向空字符串的指针
s:如果非空,则被认为是一个指针,长度至少为L_ctermid字节的数组,进程的控制终端名存储在该数组中。
如果文件描述符引用一个终端设备,则isatty返回真。
ttyname返回的是在该文件描述符上打开的中断设备的路径名
#include <unistd.h> int isatty(int fd); 返回值:若为终端设备,返回1(真),否则返回0(假) char *ttyname(int fd); 返回值:指向终端路径名的指针,出错返回NULL fd:终端关联的描述符
内核为每个终端和伪终端都维护了一个winsize结构:
struct winsize { unsigned short ws_row; unsigned short ws_col; unsigned short ws_xpixel; unsgined short ws_ypixel; };