glibc 知:手册17:底层终端接口

1. 前言

The GNU C Library Reference Manual for version 2.35

2. 底层终端接口

本章介绍特定于终端设备的功能。您可以使用这些函数来执行诸如关闭输入回显之类的操作;设置串行线路特性,如线路速度和流量控制;并更改用于文件结尾、命令行编辑、发送信号和类似控制功能的字符。

本章中的大多数函数都对文件描述符进行操作。有关什么是文件描述符以及如何为终端设备打开文件描述符的更多信息,请参阅底层输入/输出

2.1. 识别终端

Identifying Terminals

本章介绍的功能仅适用于终端设备对应的文件。您可以使用 isatty 函数找出文件描述符是否与终端相关联。

本节中的函数原型在头文件 unistd.h 中声明。

函数:int isatty (int filedes)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

如果filedes 是与打开的终端设备关联的文件描述符,则此函数返回1,否则返回0。

如果文件描述符与终端关联,您可以使用 ttyname 函数获取其关联的文件名。另请参见识别控制终端中描述的 ctermid 函数。

函数:char * ttyname (int filedes)

Preliminary: | MT-Unsafe race:ttyname | AS-Unsafe heap lock | AC-Unsafe lock fd mem | See POSIX Safety Concepts.

如果文件描述符filedes 与终端设备相关联,则ttyname 函数返回一个指向静态分配的、以空结尾的字符串的指针,该字符串包含终端文件的文件名。如果文件描述符未与终端关联,或者无法确定文件名,则该值为空指针。

函数:int ttyname_r (int filedes, char *buf, size_t len)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem fd | See POSIX Safety Concepts.

ttyname_r 函数类似于 ttyname 函数,不同之处在于它将其结果放入用户指定的缓冲区,该缓冲区从 buf 开始,长度为 len。

ttyname_r 的正常返回值为 0。否则返回错误号以指示错误。为此函数定义了以下 errno 错误条件:

EBADF

filedes 参数不是有效的文件描述符。

ENOTTY

文件不与终端关联。

ERANGE

缓冲区长度 len 太小,无法存储要返回的字符串。

ENODEV

该文件与作为从属伪终端的终端设备相关联,但无法确定与该设备相关联的文件名。这是一个 GNU 扩展。

2.2. I/O 队列

I/O Queues

本节中的许多其余功能都涉及终端设备的输入和输出队列。这些队列在内核中实现了一种缓冲形式,独立于 I/O 流实现的缓冲(请参阅输入/输出流)。

终端输入队列有时也称为其预输入缓冲区。它保存已从终端接收但尚未被任何进程读取的字符。

输入队列的大小由 MAX_INPUT 和 _POSIX_MAX_INPUT 参数描述;请参阅文件系统容量限制。保证队列大小至少为 MAX_INPUT,但队列可能更大,甚至可能动态更改大小。如果通过设置 IXOFF 输入模式位(请参阅输入模式)启用输入流控制,则终端驱动程序会在必要时将 STOP 和 START 字符传输到终端以防止队列溢出。否则,如果从终端输入太快,输入可能会丢失。在规范模式下,所有输入都保留在队列中,直到收到换行符为止,因此当您键入很长的行时,终端输入队列可能会填满。请参阅两种输入方式:规范或非规范

终端输出队列和输入队列一样,只是用于输出;它包含已由进程写入但尚未传输到终端的字符。如果通过设置 IXON 输入模式位(请参阅输入模式)启用输出流控制,则终端驱动程序遵循终端发送的 START 和 STOP 字符来停止和重新启动输出传输。

清除终端输入队列意味着丢弃任何已接收但尚未读取的字符。同样,清除终端输出队列意味着丢弃任何已写入但尚未传输的字符。

2.3. 两种输入方式:规范或非规范

Two Styles of Input: Canonical or Not

POSIX 系统支持两种基本的输入模式:规范和非规范。

在规范输入处理模式中,终端输入在以换行符 (‘\n’)、EOF 或 EOL 字符终止的行中处理。在用户键入整行之前,无法读取输入,并且无论请求多少字节,读取函数(请参阅输入和输出原语)最多返回单行输入。

在规范输入模式下,操作系统提供输入编辑函数:一些字符被专门解释以在当前文本行内执行编辑操作,例如 ERASE 和 KILL。请参阅用于输入编辑的字符

常量 _POSIX_MAX_CANON 和 MAX_CANON 参数化可能出现在单行规范输入中的最大字节数。请参阅文件系统容量限制。保证最大行长度至少为 MAX_CANON 字节,但最大值可能更大,甚至可能动态更改大小。

在非规范输入处理模式下,字符不分组为行,不执行 ERASE 和 KILL 处理。在非规范输入模式下读取字节的粒度由 MIN 和 TIME 设置控制。请参阅非规范输入

大多数程序使用规范输入模式,因为这为用户提供了一种逐行编辑输入的方法。使用非规范模式的通常原因是程序接受单字符命令或提供自己的编辑工具。

规范或非规范输入的选择由 struct termios 的 c_lflag 成员中的 ICANON 标志控制。请参阅本地模式

2.4. 终端模式

Terminal Modes

本节介绍控制输入和输出完成方式的各种终端属性。函数、数据结构和符号常量都在头文件 termios.h 中声明。

不要将终端属性与文件属性混淆。与终端关联的设备专用文件具有文件属性中所述的文件属性。这些与本节讨论的终端设备本身的属性无关。

2.4.1. 终端模式数据类型

Terminal Mode Data Types

终端的整个属性集合存储在 struct termios 类型的结构中。此结构与函数 tcgetattr 和 tcsetattr 一起使用以读取和设置属性。

数据类型:struct termios

struct termios 记录了终端的所有 I/O 属性。该结构至少包括以下成员:

tcflag_t c_iflag

指定输入模式标志的位掩码;请参阅输入模式

tcflag_t c_oflag

指定输出模式标志的位掩码;请参阅输出模式

tcflag_t c_cflag

指定控制模式标志的位掩码;请参阅控制模式

tcflag_t c_lflag

指定本地模式标志的位掩码;请参阅本地模式

cc_t c_cc[NCCS]

一个数组,指定哪些字符与各种控制功能相关联;请参阅特殊字符

struct termios 结构还包含对输入和输出传输速度进行编码的成员,但未指定表示形式。有关如何检查和存储速度值的信息,请参见行速度

以下部分描述了 struct termios 结构成员的详细信息。

数据类型:tcflag_t

这是一个无符号整数类型,用于表示终端标志的各种位掩码。

数据类型:cc_t

这是一个无符号整数类型,用于表示与各种终端控制功能相关的字符。

宏:int NCCS

该宏的值是 c_cc 数组中的元素数。

2.4.2. 终端模式功能

Terminal Mode Functions

函数:int tcgetattr (int filedes, struct termios *termios-p)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数用于检查具有文件描述符文件的终端设备的属性。属性以 termios-p 指向的结构返回。

如果成功,则 tcgetattr 返回 0。返回值 -1 表示错误。为此函数定义了以下 errno 错误条件:

EBADF

filedes 参数不是有效的文件描述符。

ENOTTY

文件不与终端关联。

函数:int tcsetattr (int filedes, int when, const struct termios *termios-p)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数使用文件描述符文件设置终端设备的属性。新属性取自 termios-p 指向的结构。

when 参数指定如何处理已经排队的输入和输出。它可以是以下值之一:

TCSANOW

立即进行更改。

TCSADRAIN

等到所有排队的输出都被写入后进行更改。在更改影响输出的参数时,您通常应该使用此选项。

TCSAFLUSH

这类似于 TCSADRAIN,但也会丢弃任何排队的输入。

TCSASOFT

这是一个标志位,您可以将其添加到上述任何替代方案中。其含义是禁止改变终端硬件的状态。它是一个 BSD 扩展;它仅在 BSD 系统和 GNU/Hurd 系统上受支持。

使用 TCASSOFT 与在 termios-p 指向的结构的 c_cflag 成员中设置 CIGNORE 位完全相同。有关 CIGNORE 的说明,请参阅控制模式

如果从其控制终端上的后台进程调用此函数,通常会向进程组中的所有进程发送一个 SIGTTOU 信号,就像进程试图写入终端一样。例外情况是调用进程本身忽略或阻塞 SIGTTOU 信号,在这种情况下执行操作并且不发送信号。请参阅作业控制

如果成功,则 tcsetattr 返回 0。返回值 -1 表示错误。为此函数定义了以下 errno 错误条件:

EBADF

filedes 参数不是有效的文件描述符。

ENOTTY

文件不与终端关联。

EINVAL

when 参数的值无效,或者 termios-p 参数中的数据有问题。

尽管 tcgetattr 和 tcsetattr 使用文件描述符指定终端设备,但属性是终端设备本身的属性,而不是文件描述符的属性。这意味着改变终端属性的效果是持久的;如果稍后另一个进程打开终端文件,它将看到更改的属性,即使它与您最初在更改属性时指定的打开文件描述符没有任何关系。

同样,如果单个进程对同一终端设备具有多个或重复的文件描述符,则更改终端属性会影响所有这些文件描述符的输入和输出。这意味着,例如,您无法在正常的行缓冲、回显模式下打开一个文件描述符或流以从终端读取;并同时为您用来以单字符、非回显模式读取的同一终端拥有另一个文件描述符。相反,您必须明确地在两种模式之间来回切换终端。

2.4.3. 正确设置终端模式

Setting Terminal Modes Properly

当你设置终端模式时,你应该首先调用 tcgetattr 来获取特定终端设备的当前模式,只修改你真正感兴趣的那些模式,然后用 tcsetattr 存储结果。

简单地将 struct termios 结构初始化为一组选定的属性并将其直接传递给 tcsetattr 是一个坏主意。您的程序可能会在数年后在支持本手册中未记录的成员的系统上运行。避免将这些成员设置为不合理值的方法是避免更改它们。

此外,不同的终端设备可能需要不同的模式设置才能正常运行。所以你应该避免盲目地将属性从一个终端设备复制到另一个终端设备。

当一个成员包含一组独立标志时,就像 c_iflag、c_oflag 和 c_cflag 成员所做的那样,即使设置整个成员也是一个坏主意,因为特定的操作系统有自己的标志。相反,您应该从成员的当前值开始,只更改值在您的程序中重要的标志,而保持任何其他标志不变。

这是一个示例,说明如何在 struct termios 结构中设置一个标志 (ISTRIP),同时正确保留结构中的所有其他数据:

int
set_istrip (int desc, int value)
{
  struct termios settings;
  int result;

  result = tcgetattr (desc, &settings);
  if (result < 0)
    {
      perror ("error in tcgetattr");
      return 0;
    }
  settings.c_iflag &= ~ISTRIP;
  if (value)
    settings.c_iflag |= ISTRIP;
  result = tcsetattr (desc, TCSANOW, &settings);
  if (result < 0)
    {
      perror ("error in tcsetattr");
      return 0;
   }
  return 1;
}

2.4.4. 输入模式

Input Modes

本节描述控制输入处理的相当低级方面的终端属性标志:奇偶校验错误、中断信号、流控制以及 RET 和 LFD 字符的处理。

所有这些标志都是 struct termios 结构的 c_iflag 成员中的位。该成员是一个整数,您可以使用运算符 &、| 更改标志和^。不要试图为 c_iflag 指定整个值——相反,只更改特定的标志,其余的保持不变(请参阅正确设置终端模式)。

宏:tcflag_t INPCK

如果该位置位,则启用输入奇偶校验。如果未设置,则根本不检查输入的奇偶校验错误;字符只是传递给应用程序。

输入处理的奇偶校验与是否启用底层终端硬件上的奇偶校验检测和生成无关;请参阅控制模式。例如,您可以清除 INPCK 输入模式标志并设置 PARENB 控制模式标志以忽略输入上的奇偶校验错误,但仍会在输出上生成奇偶校验。

如果设置了该位,则在检测到奇偶校验错误时会发生什么取决于是否设置了 IGNPAR 或 PARMRK 位。如果这些位均未设置,则带有奇偶校验错误的字节将作为“\0”字符传递给应用程序。

宏:tcflag_t IGNPAR

如果该位被置位,任何有成帧或奇偶校验错误的字节都会被忽略。这仅在设置了 INPCK 时才有用。

宏:tcflag_t PARMRK

如果设置了该位,则在传递给程序时会标记具有奇偶校验或帧错误的输入字节。仅当设置了 INPCK 且未设置 IGNPAR 时,该位才有意义。

错误字节的标记方式是使用前两个字节,377 和 0。因此,程序实际上从终端接收到的一个错误字节读取三个字节。

如果有效字节的值为 0377,并且未设置 ISTRIP(见下文),则程序可能会将其与标记奇偶校验错误的前缀混淆。因此,在本例中,有效字节 0377 作为两个字节 0377 0377 传递给程序。

宏:tcflag_t ISTRIP

如果设置了该位,则有效输入字节被剥离为 7 位;否则,所有八位都可供程序读取。

宏:tcflag_t IGNBRK

如果该位被设置,中断条件将被忽略。

中断条件在异步串行数据传输的上下文中定义为一系列长于单个字节的零值位。

宏:tcflag_t BRKINT

如果设置了该位且未设置 IGNBRK,则中断条件会清除终端输入和输出队列,并为与终端关联的前台进程组发出 SIGINT 信号。

如果 BRKINT 和 IGNBRK 均未设置,则如果未设置 PARMRK,则将中断条件作为单个 ‘\0’ 字符传递给应用程序,否则作为三个字符序列 ‘\377’、‘\0’、‘\ 0’。

宏:tcflag_t IGNCR

如果设置了该位,则在输入时会丢弃回车符 (‘\r’)。当您键入 RET 键时,在发送回车和换行的终端上,放弃回车可能很有用。

宏:tcflag_t ICRNL

如果设置了该位且未设置 IGNCR,则作为输入接收的回车符 (‘\r’) 将作为换行符 (‘\n’) 传递给应用程序。

宏:tcflag_t INLCR

如果设置了该位,则作为输入接收的换行符 (‘\n’) 将作为回车符 (‘\r’) 传递给应用程序。

宏:tcflag_t IXOFF

如果设置该位,则启用输入的启动/停止控制。换句话说,计算机会根据需要发送 STOP 和 START 字符,以防止输入比程序读取它的速度更快。这个想法是,生成输入数据的实际终端硬件通过暂停传输来响应 STOP 字符,并通过恢复传输来响应 START 字符。请参阅流控制的特殊字符

宏:tcflag_t IXON

如果设置此位,则启用输出启动/停止控制。换句话说,如果计算机接收到一个 STOP 字符,它会暂停输出,直到接收到一个 START 字符。在这种情况下,STOP 和 START 字符永远不会传递给应用程序。如果该位未设置,则 START 和 STOP 可以作为普通字符读取。请参阅流控制的特殊字符

宏:tcflag_t IXANY

如果该位置位,任何输入字符都会在输出被 STOP 字符挂起时重新开始输出。否则,只有 START 字符重新开始输出。

这是一个 BSD 扩展;它只存在于 BSD 系统和 GNU/Linux 和 GNU/Hurd 系统上。

宏:tcflag_t IMAXBEL

如果设置了该位,则填充终端输入缓冲区会向终端发送一个 BEL 字符(代码 007)以响铃。

这是一个 BSD 扩展。

2.4.5. 输出模式

Output Modes

本节介绍控制输出字符如何被翻译和填充以供显示的终端标志和字段。所有这些都包含在 struct termios 结构的 c_oflag 成员中。

c_oflag 成员本身是一个整数,您可以使用运算符 &、| 和 ^ 更改标志和字段。不要试图为 c_oflag 指定整个值,而是只更改特定的标志,其余的保持不变(请参阅正确设置终端模式)。

宏:tcflag_t OPOST

如果设置了该位,则以某种未指定的方式处理输出数据,以便在终端设备上正确显示。这通常包括将换行符 (‘\n’) 映射到回车和换行对。

如果未设置该位,则字符按原样传输。

以下三位只有在设置了 OPOST 时才有效。

宏:tcflag_t ONLCR

如果设置了该位,则将输出的换行符转换为一对字符,回车后跟换行符。

宏:tcflag_t OXTABS

如果设置了此位,则将输出上的制表符转换为适当数量的空格,以模拟每八列的制表位。该位仅存在于 BSD 系统和 GNU/Hurd 系统上;在 GNU/Linux 系统上,它以 XTABS 的形式提供。

宏:tcflag_t ONOEOT

如果设置了该位,则在输出时丢弃 C-d 字符(代码 004)。这些字符会导致许多拨号终端断开连接。该位仅存在于 BSD 系统和 GNU/Hurd 系统上。

2.4.6. 控制模式

Control Modes

本节描述了通常与异步串行数据传输相关的控制参数的终端标志和字段。这些标志对于其他类型的终端端口(例如网络连接伪终端)可能没有意义。所有这些都包含在 struct termios 结构的 c_cflag 成员中。

c_cflag 成员本身是一个整数,您可以使用运算符 &、| 和 ^ 更改标志和字段。不要尝试为 c_cflag 指定整个值,而是只更改特定的标志,其余的保持不变(请参阅正确设置终端模式)。

宏:tcflag_t CLOCAL

如果设置了该位,则表明终端是“本地”连接的,调制解调器状态线(如载波检测)应被忽略。

在许多系统上,如果未设置此位并且您在没有设置 O_NONBLOCK 标志的情况下调用 open,则 open 会阻塞,直到建立调制解调器连接。

如果未设置该位并且检测到调制解调器断开连接,则会将 SIGHUP 信号发送到终端的控制进程组(如果有的话)。通常,这会导致进程退出;请参阅信号处理。断开连接后从终端读取会导致文件结束条件,写入会导致返回 EIO 错误。终端设备必须关闭并重新打开才能清除该状况。

宏:tcflag_t HUPCL

如果设置了该位,则当所有打开终端设备的进程都关闭文件或退出时,将生成调制解调器断开连接。

宏:tcflag_t CREAD

如果设置了该位,则可以从终端读取输入。否则,输入在到达时被丢弃。

宏:tcflag_t CSTOPB

如果设置了该位,则使用两个停止位。否则,仅使用一个停止位。

宏:tcflag_t PARENB

如果设置该位,则启用奇偶校验位的生成和检测。有关如何处理输入奇偶校验错误的信息,请参阅输入模式

如果未设置此位,则不向输出字符添加奇偶校验位,并且不检查输入字符的奇偶校验是否正确。

宏:tcflag_t PARODD

该位仅在 PARENB 置位时有用。如果设置了 PARODD,则使用奇校验,否则使用偶校验。

控制模式标志还包括每个字符的位数字段。您可以使用 CSIZE 宏作为掩码来提取值,如下所示:settings.c_cflag & CSIZE。

宏:tcflag_t CSIZE

这是每个字符的位数的掩码。

宏:tcflag_t CS5

这指定每个字节五位。

宏:tcflag_t CS6

这指定每字节六位。

宏:tcflag_t CS7

这指定每字节七位。

宏:tcflag_t CS8

这指定每个字节八位。

以下四位是 BSD 扩展;这些仅存在于 BSD 系统和 GNU/Hurd 系统上。

宏:tcflag_t CCTS_OFLOW

如果设置了该位,则启用基于 CTS 线(RS232 协议)的输出流控制。

宏:tcflag_t CRTS_IFLOW

如果设置了该位,则启用基于 RTS 线(RS232 协议)的输入流控制。

宏:tcflag_t MDMBUF

如果设置了该位,则启用基于载波的输出流控制。

宏:tcflag_t CIGNORE

如果设置了该位,则表示完全忽略控制模式和行速度值。这仅在调用 tcsetattr 时才有意义。

cfgetispeed 和 cfgetospeed 返回的 c_cflag 成员和线路速度值将不受调用影响。如果您想在其他成员中设置所有软件模式,但保留 c_cflag 中的硬件详细信息不变,CIGNORE 很有用。(这就是 tcsettattr 的 TCSASOFT 标志的工作方式。)

在 tcgetattr 填充的结构中,该位从不设置。

2.4.7. 本地模式

Local Modes

本节介绍 struct termios 结构的 c_lflag 成员的标志。这些标志通常控制输入处理的更高级别方面,而不是输入模式中描述的输入模式标志,例如回显、信号以及规范或非规范输入的选择。

c_lflag 成员本身是一个整数,您可以使用运算符 &、| 和 ^ 更改标志和字段。不要试图为 c_lflag 指定整个值,而是只更改特定的标志,其余的保持不变(请参阅正确设置终端模式)。

宏:tcflag_t ICANON

如果设置此位,则启用规范输入处理模式。否则,将以非规范模式处理输入。请参阅两种输入方式:规范或非规范

宏:tcflag_t ECHO

如果设置了该位,则启用将输入字符回显到终端。

宏:tcflag_t ECHOE

如果设置了该位,则回显通过擦除屏幕上当前行中的最后一个字符来指示使用 ERASE 字符擦除输入。否则,擦除的字符会重新回显以显示发生了什么(适用于打印终端)。

该位仅控制显示行为;ICANON 位本身控制 ERASE 字符的实际识别和输入的擦除,没有它,ECHOE 根本就无关紧要。

宏:tcflag_t ECHOPRT

该位与 ECHOE 一样,能够以适合硬拷贝终端的方式显示 ERASE 字符。当您键入 ERASE 字符时,会打印一个“\”字符,然后是擦除的第一个字符。再次键入 ERASE 字符只会打印下一个已擦除的字符。然后,下次键入普通字符时,会在字符回显之前打印一个“/”字符。

这是一个 BSD 扩展,仅存在于 BSD 系统和 GNU/Linux 和 GNU/Hurd 系统中。

宏:tcflag_t ECHOK

该位通过在正常回显 KILL 字符后移至新行来启用 KILL 字符的特殊显示。ECHOKE(下图)的行为更好看。

如果未设置此位,则 KILL 字符会像不是 KILL 字符时一样回显。然后由用户记住 KILL 字符已经删除了前面的输入;屏幕上没有任何指示。

该位仅控制显示行为;ICANON 位本身控制 KILL 字符的实际识别和输入的擦除,没有它,ECHOK 根本无关紧要。

宏:tcflag_t ECHOKE

该位类似于 ECHOK。它通过在屏幕上擦除已被杀死的整行来启用 KILL 字符的特殊显示。这是一个 BSD 扩展,仅存在于 BSD 系统和 GNU/Linux 和 GNU/Hurd 系统中。

宏:tcflag_t ECHONL

如果设置了该位并且还设置了 ICANON 位,则即使未设置 ECHO 位,也会回显换行符 (‘\n’)。

宏:tcflag_t ECHOCTL

如果设置了该位并且 ECHO 位也设置了,则使用“^”回显控制字符,后跟相应的文本字符。因此,控制 A 回显为“^A”。这通常是交互式输入的首选模式,因为将控制字符回显到终端可能会对终端产生一些不良影响。

这是一个 BSD 扩展,仅存在于 BSD 系统和 GNU/Linux 和 GNU/Hurd 系统中。

宏:tcflag_t ISIG

该位控制是否识别 INTR、QUIT 和 SUSP 字符。当且仅当该位被设置时,与这些字符相关的功能才会被执行。处于规范或非规范输入模式对这些字符的解释没有影响。

禁用对这些字符的识别时应小心谨慎。不能以交互方式中断的程序对用户非常不友好。如果您清除该位,您的程序应该提供一些替代接口,允许用户以交互方式发送与这些字符相关的信号,或者从程序中逃脱。

请参阅引起信号的字符

宏:tcflag_t IEXTEN

POSIX.1 给出了 IEXTEN 实现定义的含义,因此您不能在所有系统上都依赖这种解释。

在 BSD 系统和 GNU/Linux 和 GNU/Hurd 系统上,它启用 LNEXT 和 DISCARD 字符。请参阅其他特殊字符

宏:tcflag_t NOFLSH

通常,INTR、QUIT 和 SUSP 字符会导致终端的输入和输出队列被清除。如果设置了该位,则不会清除队列。

宏:tcflag_t TOSTOP

如果设置了该位并且系统支持作业控制,则 SIGTTOU 信号由尝试写入终端的后台进程生成。请参阅访问控制终端

以下位是 BSD 扩展;它们只存在于 BSD 系统和 GNU/Hurd 系统上。

宏:tcflag_t ALTWERASE

该位决定 WERASE 字符应该擦除多远。WERASE 字符擦除回到单词的开头;问题是,单词从哪里开始?

如果该位被清除,则单词的开头是一个非空白字符,紧跟一个空白字符。如果设置了该位,则单词的开头是字母数字字符或下划线,后面的字符不是这些字符。

有关 WERASE 字符的更多信息,请参阅用于输入编辑的字符

宏:tcflag_t FLUSHO

这是当用户键入 DISCARD 字符时切换的位。当该位被设置时,所有输出都被丢弃。请参阅其他特殊字符

宏:tcflag_t NOKERNINFO

设置该位禁用 STATUS 字符的处理。请参阅其他特殊字符

宏:tcflag_t PENDIN

如果该位被设置,则表明有一行输入需要重新打印。键入 REPRINT 字符设置该位;该位保持设置,直到重新打印完成。请参阅用于输入编辑的字符

2.4.8. 行速度

终端行速度告诉计算机在终端上读取和写入数据的速度。

如果终端连接到真正的串行线路,则您指定的终端速度实际上控制着线路——如果它与终端自己的速度概念不匹配,则无法进行通信。真正的串行端口只接受某些标准速度。此外,特定硬件甚至可能不支持所有标准速度。指定速度为零会挂起拨号连接并关闭调制解调器控制信号。

如果终端不是真正的串行线(例如,如果是网络连接),那么线速不会真正影响数据传输速度,但有些程序会使用它来确定所需的填充量。最好指定与实际终端的实际速度相匹配的线路速度值,但您可以安全地尝试不同的值来改变填充量。

每个端子实际上有两种行速度,一种用于输入,一种用于输出。您可以独立设置它们,但大多数情况下终端在两个方向上使用相同的速度。

速度值存储在 struct termios 结构中,但不要尝试直接在 struct termios 结构中访问它们。相反,您应该使用以下函数来读取和存储它们:

函数:speed_t cfgetospeed (const struct termios *termios-p)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数返回存储在结构 *termios-p 中的输出行速度。

函数:speed_t cfgetispeed (const struct termios *termios-p)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数返回存储在结构 *termios-p 中的输入行速度。

函数:int cfsetospeed (struct termios *termios -p, speed_t speed)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数将速度存储在 *termios-p 中作为输出速度。正常返回值为0;-1 值表示错误。如果 speed 不是速度,则 cfsetospeed 返回 -1。

函数:int cfsetispeed (struct termios *termios -p, speed_t speed)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数将速度存储在 *termios-p 中作为输入速度。正常返回值为0;-1 值表示错误。如果 speed 不是速度,则 cfsetospeed 返回 -1。

函数:int cfsetspeed (struct termios *termios -p, speed_t speed)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数将速度存储在 *termios-p 中作为输入和输出速度。正常返回值为0;-1 值表示错误。如果 speed 不是速度,则 cfsetspeed 返回 -1。这个函数是 4.4 BSD 的扩展。

数据类型:speed_t

speed_t 类型是用于表示行速度的无符号整数数据类型。

函数 cfsetospeed 和 cfsetispeed 仅报告系统根本无法处理的速度值的错误。如果你指定一个基本可以接受的速度值,那么那些函数就会成功。但他们不会检查特定的硬件设备是否真的可以支持指定的速度——事实上,他们不知道您打算为哪个设备设置速度。如果您使用 tcsetattr 将特定设备的速度设置为它无法处理的值,则 tcsetattr 返回 -1。

可移植性说明:在 GNU C 库中,上述函数接受以每秒位数为单位的速度作为输入,并返回以每秒位数为单位的速度值。其他库需要用特殊代码表示速度。对于 POSIX.1 的可移植性,您必须使用以下符号之一来表示速度;它们的精确数值取决于系统,但每个名称都有固定的含义:B110 代表 110 bps,B300 代表 300 bps,依此类推。除了这些之外,没有任何可移植的方式来表示任何速度,但这些是典型串行线路可以支持的唯一速度。

B0 B50 B75 B110 B134 B150 B200
B300 B600 B1200 B1800 B2400 B4800
B9600 B19200 B38400 B57600 B115200
B230400 B460800

BSD 定义了两个额外的速度符号作为别名:EXTA 是 B19200 的别名,EXTB 是 B38400 的别名。这些别名已过时。

2.4.9. 特殊字符

Special Characters

在规范输入中,终端驱动程序识别许多执行各种控制功能的特殊字符。其中包括用于编辑输入的 ERASE 字符(通常是 DEL)和其他编辑字符。用于发送 SIGINT 信号的 INTR 字符(通常为 C-c)和其他引发信号的字符可用于规范或非规范输入模式。所有这些字符都在本节中描述。

使用的特定字符在 struct termios 结构的 c_cc 成员中指定。该成员是一个数组;每个元素指定特定角色的角色。每个元素都有一个符号常量,代表该元素的索引——例如,VINTR 是指定 INTR 字符的元素的索引,因此在 termios.c_cc[VINTR] 中存储“=”将指定“=”作为 INTR特点。

在某些系统上,您可以通过为该角色指定值 _POSIX_VDISABLE 来禁用特定的特殊字符功能。此值不等于任何可能的字符代码。有关如何判断您使用的操作系统是否支持 _POSIX_VDISABLE 的更多信息,请参阅文件支持中的可选功能

2.4.9.1. 输入编辑字符

Characters for Input Editing

这些特殊字符仅在规范输入模式下有效。请参阅两种输入方式:规范或非规范

宏:int VEOF

这是特殊控制字符数组中 EOF 字符的下标。termios.c_cc[VEOF] 包含字符本身。

EOF 字符仅在规范输入模式下被识别。它以与换行符相同的方式充当行终止符,但如果在行首键入 EOF 字符,则会导致 read 返回字节计数为零,表示文件结束。EOF 字符本身被丢弃。

通常,EOF 字符是 C-d。

宏:int VEOL

这是特殊控制字符数组中 EOL 字符的下标。termios.c_cc[VEOL] 包含字符本身。

EOL 字符仅在规范输入模式下被识别。它充当行终止符,就像换行符一样。EOL 字符不被丢弃;它被读取为输入行中的最后一个字符。

您不需要使用 EOL 字符来使 RET 结束一行。只需设置 ICRNL 标志。事实上,这是默认状态。

宏:int VEOL2

这是特殊控制字符数组中 EOL2 字符的下标。termios.c_cc[VEOL2] 包含字符本身。

EOL2 字符的工作方式与 EOL 字符类似(见上文),但它可以是不同的字符。因此,您可以通过将 EOL 设置为其中一个字符并将 EOL2 设置为另一个字符来指定两个字符来终止输入行。

EOL2 字符是 BSD 扩展;它只存在于 BSD 系统和 GNU/Linux 和 GNU/Hurd 系统上。

宏:int VERASE

这是特殊控制字符数组中 ERASE 字符的下标。termios.c_cc[VERASE] 包含字符本身。

ERASE 字符仅在规范输入模式下被识别。当用户键入擦除字符时,先前键入的字符将被丢弃。(如果终端生成多字节字符序列,这可能会导致多于一个字节的输入被丢弃。)这不能用于擦除当前文本行开头之后的内容。ERASE 字符本身被丢弃。

通常,ERASE 字符是 DEL。

宏:int VWERASE

这是特殊控制字符数组中 WERASE 字符的下标。termios.c_cc[VWERASE] 包含字符本身。

WERASE 字符仅在规范模式下被识别。它会删除之前输入的整个单词,以及之后的任何空格;单词前的空白字符不会被删除。

“单词”的定义取决于 ALTWERASE 模式的设置;请参阅本地模式

如果未设置 ALTWERASE 模式,则单词被定义为除空格或制表符之外的任何字符的序列。

如果设置了 ALTWERASE 模式,则将单词定义为仅包含字母、数字和下划线的字符序列,可以选择后跟一个不是字母、数字或下划线的字符。

WERASE 字符通常是 C-w。

这是一个 BSD 扩展。

宏:int VKILL

这是特殊控制字符数组中 KILL 字符的下标。termios.c_cc[VKILL] 保存角色本身。

KILL 字符仅在规范输入模式下被识别。当用户键入 kill 字符时,当前输入行的全部内容都将被丢弃。杀死角色本身也被丢弃。

KILL 字符通常是 C-u。

宏:int VREPRINT

这是特殊控制字符数组中 REPRINT 字符的下标。termios.c_cc[VREPRINT] 包含字符本身。

REPRINT 字符仅在规范模式下被识别。它重新打印当前输入行。如果在您键入时出现了一些异步输出,这可以让您再次清楚地看到您正在键入的行。

REPRINT 字符通常是 C-r。

这是一个 BSD 扩展。

2.4.9.2. 引起信号的字符

Characters that Cause Signals

这些特殊字符可能在规范或非规范输入模式下有效,但仅在设置 ISIG 标志时才有效(请参阅本地模式)。

宏:int VINTR

这是特殊控制字符数组中 INTR 字符的下标。termios.c_cc[VINTR] 保存字符本身。

INTR(中断)字符为与终端关联的前台作业中的所有进程引发 SIGINT 信号。然后丢弃 INTR 字符本身。有关信号的更多信息,请参阅信号处理

通常,INTR 字符是 C-c。

宏:int VQUIT

这是特殊控制字符数组中 QUIT 字符的下标。termios.c_cc[VQUIT] 保存字符本身。

QUIT 字符为与终端关联的前台作业中的所有进程引发 SIGQUIT 信号。然后丢弃 QUIT 字符本身。有关信号的更多信息,请参阅信号处理。

通常,QUIT 字符是 C-\。

宏:int VSUSP

这是特殊控制字符数组中 SUSP 字符的下标。termios.c_cc[VSUSP] 包含字符本身。

SUSP(挂起)字符仅在实现支持作业控制时才被识别(请参阅作业控制)。它会导致将 SIGTSTP 信号发送到与终端关联的前台作业中的所有进程。然后丢弃 SUSP 字符本身。有关信号的更多信息,请参阅信号处理。

通常,SUSP 字符是 C-z。

很少有应用程序会禁用对 SUSP 字符的正常解释。如果您的程序这样做,它应该为用户提供一些其他机制来停止作业。当用户调用这个机制时,程序应该向进程的进程组发送一个 SIGTSTP 信号,而不仅仅是进程本身。请参阅向另一个进程发送信号

宏:int VDSUSP

这是特殊控制字符数组中 DSUSP 字符的下标。termios.c_cc[VDSUSP] 包含字符本身。

仅当实现支持作业控制时才识别 DSUSP(挂起)字符(请参阅作业控制)。它会发送一个 SIGTSTP 信号,就像 SUSP 字符一样,但不会立即发送——仅当程序尝试将其作为输入读取时。并非所有具有作业控制的系统都支持 DSUSP;只有与 BSD 兼容的系统才可以(包括 GNU/Hurd 系统)。

有关信号的更多信息,请参阅信号处理

通常,DSUSP 字符是 C-y。

2.4.9.3. 流控制的特殊字符

Special Characters for Flow Control

这些特殊字符可能在规范或非规范输入模式中处于活动状态,但它们的使用由标志 IXON 和 IXOFF 控制(请参阅输入模式)。

宏:int VSTART

这是特殊控制字符数组中 START 字符的下标。termios.c_cc[VSTART] 保存字符本身。

START 字符用于支持 IXON 和 IXOFF 输入模式。如果设置了 IXON,则接收到 START 字符会恢复挂起的输出;START 字符本身被丢弃。如果设置了 IXANY,则接收任何字符都会恢复挂起的输出;除非它是 START 字符,否则不会丢弃恢复字符。如果设置了 IXOFF,系统也可以向终端发送 START 字符。

START 字符的通常值是 C-q。您可能无法更改此值 - 无论您指定什么,硬件都可能坚持使用 C-q。

宏:int VSTOP

这是特殊控制字符数组中 STOP 字符的下标。termios.c_cc[VSTOP] 保存字符本身。

STOP 字符用于支持 IXON 和 IXOFF 输入模式。如果设置了 IXON,接收到 STOP 字符会导致输出暂停;STOP 字符本身被丢弃。如果设置了 IXOFF,系统还可以向终端发送 STOP 字符,以防止输入队列溢出。

STOP 字符的通常值为 C-s。您可能无法更改此值 - 无论您指定什么,硬件都可能坚持使用 C-s。

2.4.9.4. 其他特殊字符

Other Special Characters

宏:int VLNEXT

这是特殊控制字符数组中 LNEXT 字符的下标。termios.c_cc[VLNEXT] 包含字符本身。

LNEXT 字符仅在设置 IEXTEN 时被识别,但在规范和非规范模式下。它禁用用户键入的下一个字符的任何特殊意义。即使字符通常会执行某些编辑功能或生成信号,它也会被读取为普通字符。这类似于 Emacs 中的 C-q 命令。“LNEXT”代表“字面上的下一个”。

LNEXT 字符通常是 C-v。

此字符在 BSD 系统和 GNU/Linux 和 GNU/Hurd 系统上可用。

宏:int VDISCARD

这是特殊控制字符数组中 DISCARD 字符的下标。termios.c_cc[VDISCARD] 保存字符本身。

DISCARD 字符仅在设置了 IEXTEN 时才被识别,但在规范和非规范模式下都是如此。它的作用是切换丢弃输出标志。当设置此标志时,所有程序输出都将被丢弃。设置该标志还会丢弃当前在输出缓冲区中的所有输出。键入任何其他字符都会重置标志。

此字符在 BSD 系统和 GNU/Linux 和 GNU/Hurd 系统上可用。

宏:int VSTATUS

这是特殊控制字符数组中 STATUS 字符的下标。termios.c_cc[VSTATUS] 保存字符本身。

STATUS 字符的作用是打印出有关当前进程如何运行的状态消息。

STATUS 字符仅在规范模式下被识别,并且仅在未设置 NOKERNINFO 时才被识别。

该字符仅在 BSD 系统和 GNU/Hurd 系统上可用。

2.4.10. 非规范输入

Noncanonical Input

在非规范输入模式下,ERASE 和 KILL 等特殊编辑字符将被忽略。用于用户编辑输入的系统工具在非规范模式下被禁用,因此所有输入字符(除非它们是特殊的用于信号或流控制目的)都将完全按照键入的方式传递给应用程序。如果合适,由应用程序为用户提供编辑输入的方法。

非规范模式提供称为 MIN 和 TIME 的特殊参数,用于控制是否等待输入可用以及等待多长时间。您甚至可以使用它们来避免等待——无论输入是否可用,都可以立即返回,或者没有输入。

MIN 和 TIME 存储在 c_cc 数组的元素中,该数组是 struct termios 结构的成员。这个数组的每个元素都有一个特定的角色,每个元素都有一个符号常量,代表该元素的索引。VMIN 和 VTIME 是 MIN 和 TIME 槽数组中索引的名称。

宏:int VMIN

这是 c_cc 数组中 MIN 槽的下标。因此,termios.c_cc[VMIN] 就是值本身。

MIN slot 仅在非规范输入模式下有意义;它指定输入队列中必须可用的最小字节数,以便读取返回。

宏:int VTIME

这是 c_cc 数组中 TIME 槽的下标。因此,termios.c_cc[VTIME] 就是值本身。

TIME 槽只在非规范输入模式下有意义;它指定在返回之前等待输入的时间,以 0.1 秒为单位。

MIN 和 TIME 值相互作用以确定何时读取应返回的标准;它们的确切含义取决于它们中的哪些是非零的。有四种可能的情况:

  • TIME 和 MIN 均非零。

    在这种情况下,TIME 指定在每个输入字符后等待多长时间以查看是否有更多输入到达。在接收到第一个字符后,read 会一直等待,直到 MIN 字节全部到达,或者 TIME 过去了而没有进一步的输入。

    read 总是阻塞直到第一个字符到达,即使 TIME 先过去。如果队列中碰巧超过 MIN,则 read 可以返回超过 MIN 个字符。

  • MIN 和 TIME 都为零。

    在这种情况下,read 总是立即返回队列中可用的字符数,最多为请求的字符数。如果没有立即可用的输入,则 read 返回零值。

  • MIN 为零,但 TIME 具有非零值。

    在这种情况下,read 等待时间 TIME 以使输入变为可用;单个字节的可用性足以满足读取请求并导致读取返回。当它返回时,它会返回尽可能多的可用字符,直到请求的数量。如果在计时器到期之前没有可用的输入,则 read 返回值为零。

  • TIME 为零,但 MIN 具有非零值。

    在这种情况下,读取会一直等待,直到队列中至少有 MIN 个字节可用。那时,read 返回尽可能多的可用字符,直到请求的数量。如果队列中碰巧超过 MIN,则 read 可以返回超过 MIN 个字符。

如果 MIN 为 50 并且您要求仅读取 10 个字节,会发生什么?通常,read 会等待直到缓冲区中有 50 个字节(或者,更一般地说,满足上述等待条件),然后读取其中的 10 个,将另外 40 个保留在操作系统中,以供后续调用 read。

可移植性说明:在某些系统上,MIN 和 TIME 时隙实际上与 EOF 和 EOL 时隙相同。这不会导致严重问题,因为 MIN 和 TIME 槽仅用于非规范输入,而 EOF 和 EOL 槽仅用于规范输入,但不是很干净。GNU C 库为这些用途分配了单独的插槽。

函数:void cfmakeraw (struct termios *termios-p)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这个函数提供了一种简单的方法来为 BSD 中传统上称为“原始模式”的设置 *termios-p。这使用非规范输入,并关闭大多数处理以向终端提供未修改的通道。

它正是这样做的:

  termios-p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
                                |INLCR|IGNCR|ICRNL|IXON);
  termios-p->c_oflag &= ~OPOST;
  termios-p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
  termios-p->c_cflag &= ~(CSIZE|PARENB);
  termios-p->c_cflag |= CS8;

2.5. BSD 终端模式

BSD Terminal Modes

获取和设置终端模式的常用方法是使用终端模式中描述的功能。但是,在某些系统上,您可以使用本节中的 BSD 派生函数来执行某些相同的操作。在许多系统上,这些功能不存在。即使使用 GNU C 库,在许多内核(包括 Linux)中,函数也会因 errno = ENOSYS 而失败。

本节中使用的符号在 sgtty.h 中声明。

数据类型:struct sgttyb

此结构是 gtty 和 stty 的输入或输出参数列表。

char sg_ispeed

输入行速度

char sg_ospeed

输出行速度

字符 sg_erase

擦除字符

char sg_kill

杀死角色

int sg_flags

各种旗帜

函数:int gtty (int filedes, struct sgttyb *attributes)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数获取终端的属性。

gtty 设置*attributes 来描述使用文件描述符filedes 打开的终端的终端属性。

函数:int stty (int filedes, const struct sgttyb *attributes)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数设置终端的属性。

stty 将使用文件描述符filedes 打开的终端的终端属性设置为由*attributes 描述的那些。

2.6. 行控制函数

Line Control Functions

这些函数在终端设备上执行各种控制动作。至于终端访问,它们被视为输出:如果这些功能中的任何一个被其控制终端上的后台进程使用,通常会向进程组中的所有进程发送一个 SIGTTOU 信号。例外情况是调用进程本身忽略或阻塞 SIGTTOU 信号,在这种情况下执行操作并且不发送信号。请参阅作业控制

函数:int tcsendbreak (int filedes, int duration)

Preliminary: | MT-Unsafe race:tcattr(filedes)/bsd | AS-Unsafe | AC-Unsafe corrupt/bsd | See POSIX Safety Concepts.

此函数通过在与文件描述符字段关联的终端上传输零位流来生成中断条件。中断的持续时间由 duration 参数控制。如果为零,则持续时间在 0.25 到 0.5 秒之间。非零值的含义取决于操作系统。

如果终端不是异步串行数据端口,则此函数不执行任何操作。

返回值通常为零。如果发生错误,则返回值 -1。为此函数定义了以下 errno 错误条件:

EBADF

文件不是有效的文件描述符。

ENOTTY

文件不与终端设备相关联。

函数:int tcdrain (int filedes)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

tcdrain 函数一直等待,直到所有到终端文件的排队输出都已传输完毕。

这个函数是多线程程序中的一个取消点。如果线程在调用 tcdrain 时分配了一些资源(如内存、文件描述符、信号量或其他),则会出现问题。如果线程被取消,这些资源将保持分配状态,直到程序结束。为了避免这种对 tcdrain 的调用,应该使用取消处理程序来保护。

返回值通常为零。如果发生错误,则返回值 -1。为此函数定义了以下 errno 错误条件:

EBADF

文件不是有效的文件描述符。

ENOTTY

文件不与终端设备相关联。

EINTR

操作因传递信号而中断。请参阅被信号中断的原语

函数:int tcflush (int filedes, int queue)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

tcflush 函数用于清除与终端文件filedes 关联的输入和/或输出队列。queue 参数指定要清除的队列,可以是以下值之一:

TCIFLUSH

清除已接收但尚未读取的任何输入数据。

TCOFLUSH

清除任何已写入但尚未传输的输出数据。

TCIOFLUSH

清除排队的输入和输出。

返回值通常为零。如果发生错误,则返回值 -1。为此函数定义了以下 errno 错误条件:

EBADF

文件不是有效的文件描述符。

ENOTTY

文件不与终端设备相关联。

EINVAL

提供了错误值作为队列参数。

不幸的是,这个函数被命名为 tcflush,因为术语“flush”通常用于完全不同的操作——等到所有输出都传输完毕——并且用它来丢弃输入或输出会令人困惑。不幸的是,名称 tcflush 来自 POSIX,我们无法更改它。

函数:int tcflow (int filedes, int action)

Preliminary: | MT-Unsafe race:tcattr(filedes)/bsd | AS-Unsafe | AC-Safe | See POSIX Safety Concepts.

tcflow函数用于对filedes指定的终端文件进行XON/XOFF流控相关的操作。

action 参数指定要执行的操作,可以是以下值之一:

TCOOFF

暂停输出的传输。

TCOON

重新启动输出传输。

TCIOFF

发送一个 STOP 字符。

TCION

发送一个 START 字符。

有关 STOP 和 START 字符的详细信息,请参阅特殊字符

返回值通常为零。如果发生错误,则返回值 -1。为此函数定义了以下 errno 错误条件:

EBADF

文件不是有效的文件描述符。

ENOTTY

文件不与终端设备相关联。

EINVAL

提供了错误值作为操作参数。

2.7. 非规范模式示例

Noncanonical Mode Example

这是一个示例程序,它显示了如何设置终端设备以在非规范输入模式下读取单个字符,而无需回显。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>

/* Use this variable to remember original terminal attributes. */

struct termios saved_attributes;

void
reset_input_mode (void)
{
  tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);
}

void
set_input_mode (void)
{
  struct termios tattr;
  char *name;

  /* Make sure stdin is a terminal. */
  if (!isatty (STDIN_FILENO))
    {
      fprintf (stderr, "Not a terminal.\n");
      exit (EXIT_FAILURE);
    }

  /* Save the terminal attributes so we can restore them later. */
  tcgetattr (STDIN_FILENO, &saved_attributes);
  atexit (reset_input_mode);

  /* Set the funny terminal modes. */
  tcgetattr (STDIN_FILENO, &tattr);
  tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
  tattr.c_cc[VMIN] = 1;
  tattr.c_cc[VTIME] = 0;
  tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
}

int
main (void)
{
  char c;

  set_input_mode ();

  while (1)
    {
      read (STDIN_FILENO, &c, 1);
      if (c == '\004')          /* C-d */
        break;
      else
        putchar (c);
    }

  return EXIT_SUCCESS;
}

该程序在退出或以信号终止之前会小心地恢复原始终端模式。它使用 atexit 函数(请参阅退出时的清理)来确保这是通过退出完成的。

当进程停止或继续时,shell 应该负责重置终端模式;请参阅作业控制。但是一些现有的 shell 实际上并没有这样做,因此您可能希望为重置终端模式的作业控制信号建立处理程序。上面的例子就是这样做的。

2.8. 阅读密码短语

Reading Passphrases

在阅读密码短语时,最好避免将其显示在屏幕上,以帮助保密。以下函数以方便的方式处理此问题。

函数:char * getpass (const char *prompt)

Preliminary: | MT-Unsafe term | AS-Unsafe heap lock corrupt | AC-Unsafe term lock corrupt | See POSIX Safety Concepts.

getpass 输出提示,然后从终端读取一个字符串而不回显它。如果可能,它会尝试连接到真正的终端 /dev/tty,以鼓励用户不要将明文密码短语放在文件中;否则,它使用标准输入和标准错误。getpass 还使用 ISIG 终端属性禁用终端上的 INTR、QUIT 和 SUSP 字符(请参阅本地模式)。终端在 getpass 之前和之后被刷新,因此输入错误的密码短语的字符不会意外可见。

在其他 C 库中,getpass 可能只返回密码短语的前 PASS_MAX 个字节。GNU C 库没有限制,所以 PASS_MAX 是未定义的。

这个函数的原型在 unistd.h 中。PASS_MAX 将在limits.h 中定义。

这组精确的操作可能并不适合所有可能的情况。在这种情况下,建议用户编写自己的 getpass 替代品。例如,一个非常简单的替代品如下:

#include <termios.h>
#include <stdio.h>

ssize_t
my_getpass (char **lineptr, size_t *n, FILE *stream)
{
  struct termios old, new;
  int nread;

  /* Turn echoing off and fail if we can’t. */
  if (tcgetattr (fileno (stream), &old) != 0)
    return -1;
  new = old;
  new.c_lflag &= ~ECHO;
  if (tcsetattr (fileno (stream), TCSAFLUSH, &new) != 0)
    return -1;

  /* Read the passphrase */
  nread = getline (lineptr, n, stream);

  /* Restore terminal. */
  (void) tcsetattr (fileno (stream), TCSAFLUSH, &old);

  return nread;
}

替代品采用与 getline 相同的参数(参见 Line-Oriented Input);用户必须打印所需的任何提示。

2.9. 伪终端

Pseudo-Terminals

伪终端是一种特殊的进程间通信通道,其作用类似于终端。通道的一端称为主端或主伪终端设备,另一端称为从端。写入主端的数据被从端接收,就好像它是用户在普通终端上键入的结果一样,写入从端的数据被发送到主端,就好像它是写在一个普通终端上一样。

伪终端是 xterm 和 emacs 等程序实现其终端仿真功能的方式。

2.9.1. 分配伪终端

Allocating Pseudo-Terminals

本小节描述了用于分配伪终端和使该伪终端可用于实际使用的函数。这些函数在头文件 stdlib.h 中声明。

函数:int getpt (void)

Preliminary: | MT-Safe | AS-Safe | AC-Safe fd | See POSIX Safety Concepts.

getpt 函数为下一个可用的主伪终端返回一个新的文件描述符。getpt 的正常返回值是一个非负整数文件描述符。在发生错误的情况下,将改为返回值 -1。为此函数定义了以下 errno 条件:

ENOENT

没有可用的免费主伪终端。

这个函数是一个 GNU 扩展。

函数:int grantpt (int filedes)

Preliminary: | MT-Safe locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.

grantpt函数改变与文件描述符filedes关联的主伪终端设备对应的从伪终端设备的所有权和访问权限。所有者是从调用进程的真实用户 ID 设置的(请参阅进程的角色),组设置为特殊组(通常是 tty)或从调用进程的真实组 ID 设置。设置访问权限,使得文件对所有者可读和可写,并且只能由组写。

在某些系统上,此功能是通过调用特殊的 setuid 根程序来实现的(请参阅应用程序如何更改角色)。因此,为 SIGCHLD 信号安装信号处理程序(请参阅作业控制信号)可能会干扰对 grantpt 的调用。

grantpt 的正常返回值为 0;如果失败,则返回值 -1。为此函数定义了以下 errno 错误条件:

EBADF

filedes 参数不是有效的文件描述符。

EINVAL

filedes 参数与主伪终端设备无关。

EACCES

无法访问与filedes关联的master对应的slave伪终端设备。

函数:int unlockpt (int filedes)

Preliminary: | MT-Safe | AS-Unsafe heap/bsd | AC-Unsafe mem fd | See POSIX Safety Concepts.

unlockpt函数解锁对应于与文件描述符filedes关联的主伪终端设备的从伪终端设备。在许多系统上,slave 只能在解锁后打开,因此便携式应用程序应始终在尝试打开 slave 之前调用 unlockpt。

unlockpt 的正常返回值为 0;如果失败,则返回值 -1。为此函数定义了以下 errno 错误条件:

EBADF

filedes 参数不是有效的文件描述符。

EINVAL

filedes 参数与主伪终端设备无关。

函数:char * ptsname (int filedes)

Preliminary: | MT-Unsafe race:ptsname | AS-Unsafe heap/bsd | AC-Unsafe mem fd | See POSIX Safety Concepts.

如果文件描述符filedes 与主伪终端设备相关联,则ptsname 函数返回一个指针,该指针指向一个静态分配的、以空结尾的字符串,其中包含相关从属伪终端文件的文件名。此字符串可能会被后续调用 ptsname 覆盖。

函数:int ptsname_r (int filedes, char *buf, size_t len)

Preliminary: | MT-Safe | AS-Unsafe heap/bsd | AC-Unsafe mem fd | See POSIX Safety Concepts.

ptsname_r 函数类似于 ptsname 函数,不同之处在于它将其结果放入用户指定的缓冲区,该缓冲区从 buf 开始,长度为 len。

这个函数是一个 GNU 扩展。

以下示例说明了这些函数的典型用法:

int
open_pty_pair (int *amaster, int *aslave)
{
  int master, slave;
  char *name;

  master = getpt ();
  if (master < 0)
    return 0;

  if (grantpt (master) < 0 || unlockpt (master) < 0)
    goto close_master;
  name = ptsname (master);
  if (name == NULL)
    goto close_master;

  slave = open (name, O_RDWR);
  if (slave == -1)
    goto close_master;

  *amaster = master;
  *aslave = slave;
  return 1;

close_slave:
  close (slave);

close_master:
  close (master);
  return 0;
}

2.9.2. 打开伪终端对

Opening a Pseudo-Terminal Pair

这些从 BSD 派生的函数在单独的 libutil 库中可用,并在 pty.h 中声明。

函数:int openpty (int *amaster, int *aslave, char *name, const struct termios *termp, const struct winsize *winp)

Preliminary: | MT-Safe locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.

该函数分配并打开一个伪终端对,在 *amaster 中返回 master 的文件描述符,在 *aslave 中返回 slave 的文件描述符。如果参数名称不是空指针,则从伪终端设备的文件名存储在*name 中。如果 termp 不是空指针,则从站的终端属性设置为 termp 指向的结构中指定的属性(请参阅终端模式)。同样,如果 winp 不是空指针,则从站的屏幕大小设置为 winp 指向的结构中指定的值。

openpty 的正常返回值为 0;如果失败,则返回值 -1。为此函数定义了以下 errno 条件:

ENOENT

没有可用的免费伪终端对。

警告:使用名称未设置为 NULL 的 openpty 函数非常危险,因为它无法防止字符串名称溢出。您应该使用 *slave 中返回的文件描述符上的 ttyname 函数来查找从属伪终端设备的文件名。

函数:int forkpty (int *amaster, char *name, const struct termios *termp, const struct winsize *winp)

Preliminary: | MT-Safe locale | AS-Unsafe dlopen plugin heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.

此函数类似于 openpty 函数,但除此之外,它会派生一个新进程(请参阅创建进程)并使新打开的从伪终端设备成为子进程的控制终端(请参阅进程的控制终端)。

如果操作成功,则父进程和子进程都有,都看到forkpty返回,但值不同:在子进程中返回值0,在父进程中返回子进程ID。

如果伪终端对的分配或进程创建失败,则 forkpty 在父进程中返回值 -1。

警告:forkpty 函数在 name 参数方面与 openpty 有相同的问题。

3. 参考

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值