第 62 章 终端 (Terminals)

      +

      核心结论

      • 终端驱动的两个输入模式:默认 canonical mode 行缓冲 + 行编辑(NL/EOF/ERASE/KILL 等生效);noncanonical mode 按字符返回给应用(vi / less 必备)。

      • termios 结构是终端属性配置中心c_iflag/c_oflag/c_cflag/c_lflag 四个位掩码 + c_cc[NCCS] 特殊字符数组;tcgetattr 取出、tcsetattr(fd, optional_actions, &tp) 推回(TCSANOW/TCSADRAIN/TCSAFLUSH 控制时序)。

      • 特殊字符驱动的信号生成C(INTR→SIGINT)、\(QUIT→SIGQUIT)、Z(SUSP→SIGTSTP)、D(EOF→read 返回 0)、^U(KILL→擦行);由 ISIG / ICANON / IEXTEN 控制;调试时常需禁用。

      • noncanonical 的 MIN/TIME 控制 read 何时返回:(MIN=0,TIME=0) polling;(MIN>0,TIME=0) blocking;(MIN=0,TIME>0) 读超时;(MIN>0,TIME>0) interbyte 超时——这是 escape sequence 解码(方向键)的关键。

      • 「cooked / cbreak / raw」是历史模式:在 POSIX termios 上要手动组合 flags;TLPI ttySetCbreak/ttySetRawISIG/ICANON/IEXTEN/ECHO/OPOST 等位的清零 / 设置模拟。

      • 窗口大小 / 终端识别:SIGWINCH + ioctl TIOCGWINSZ(winsize 结构);isatty/ttyname 判定 fd 是否终端 / 取设备名(/dev/pts/N/dev/ttyN)。

      本章主旨

      本章聚焦「程序如何与终端驱动对话」——background 分支进程组 / 中断字符 / 行编辑 / 行缓冲/非缓冲/raw 的对应 flags。本书主要关注软件终端(xterm / pty),硬件串行只作概述。读者需理解:(1) 如何用 tcgetattr/tcsetattr 安全修改 termios;(2) canonical vs noncanonical 的语义差别与何时切换;(3) MIN/TIME 何时选哪种组合;(4) 程序退出 / SIGINT / SIGTSTP 时如何还原终端属性(避免留下乱套的 shell)。

      一、核心概念

      本章围绕 6 个核心概念:termios 与 tcgetattr/tcsetattr、special 字符、c_iflag/c_oflag/c_cflag/c_lflag flag、canonical vs noncanonical、MIN/TIME 模式、cooked/cbreak/raw 历史模拟。

      概念 定义 + 重要性 实现提示

      termios 结构

      struct termios {c_iflag,c_oflag,c_cflag,c_lflag; c_cc[NCCS];};4 个位掩码字段 + 特殊字符数组;tcgetattr(fd, &tp) 取出当前配置;tcsetattr(fd, when, &tp) 推回修改。

      §62.2;<termios.h>;修改流程:getattr → 修改 → setattr;whenTCSANOW/TCSADRAIN/TCSAFLUSH

      特殊字符表 (c_cc)

      INTR(^\) / QUIT(^\) / ERASE(^?)/ KILL(^U)/ EOF(^D)/ START/STOP/XON-XOFF(Q/S)/ SUSP(^Z)/ EOL/EOL2/ LNEXT(^V)/ WERASE/ REPRINT。由 ISIG/ICANON/IEXTEN 等 flag 控制是否生效。

      §62.4;用 fpathconf(fd, _PC_VDISABLE) 禁用某字符;副作用:触发信号的字符不传给读进程。

      c_iflag / c_oflag / c_cflag / c_lflag flag 组

      c_iflag 控制输入(如 ICRNL 把 CR 映射成 NL);c_oflag 控制输出(OPOST 启用输出处理、ONLCR 把 NL 映射为 CR-NL);c_cflag 控制硬件(CS8=8bit 字长、PARENB 奇偶等);c_lflag 控制本地行为(ICANON/ISIG/ECHO/IEXTEN/TOSTOP)。

      §62.5;表 62-2 给全部 flag 默认值;修改前需 tcgetattr 备份。

      Canonical vs Noncanonical I/O

      Canonical = 行缓冲 + 行编辑 + 特殊字符解释;read 等到一行 (or EOF) 才返回;vi/less 须改非 canonical。Noncanonical 中读返回受 VMIN/VTIME 控制。

      §62.6;测试 echo 是默认 canonical;vi、ssh、read -n 1 都改非 canonical。

      MIN / TIME 参数

      决定 noncanonical read 何时返回:(0,0)=poll;(>0,0)=block until ≥ MIN;(0,>0)=timeout 至少 1 byte 或 0;(>0,>0)=interbyte timeout 解 escape sequence。

      §62.6.2;VMIN/VTIME 常量可能在一些 UNIX 上和 VEOF/VEOL 共用,需保存原 termios 防恢复时丢 EOF 默认。

      cooked / cbreak / raw 模式

      历史 UNIX 模式,POSIX 上要组合 flag;cbreak=非 canonical+关 echo+留 INTR/QUIT/SUSP;raw=关一切(ICANON/ISIG/IEXTEN/ECHO/OPOST/ICRNL/IXON…)。

      §62.6.3;TLPI Listing 62-3 ttySetCbreak/ttySetRaw 是经典实现范例;进入 raw 必须设信号处理器恢复属性。

      二、详细笔记

      62.1 终端驱动概览

      What:每个终端(含 pty)对应一个字符设备及终端驱动;驱动维护 input/output 两个队列(line-discipline 在两者之间),可分别处于 canonical mode 与 noncanonical mode。

      Why:理解「输入先被 line discipline 处理」是后续 termios 行为的前提——^D 触发 EOF、^C 触发 SIGINT 等都是驱动层而非应用层的事件。

      How

      元素 默认模式 改后模式

      Line discipline

      N_TTY 实现 canonical mode

      c_line 可选

      输入队列

      canonical:按行聚合

      noncanonical:按字符

      输出队列

      output processing 默认 (OPOST) 开启

      关闭 OPOST 时透传

      队列上限

      Linux 内核:4096 bytes(不查 MAX_INPUT)

      sysconf 给出 255 但未实际使用

      echo

      默认 ECHO on

      password 输入前应关

      When:(1) 写读 stdin 行处理程序——理解 canonical 缓冲;(2) 写字符实时处理程序——切 noncanonical;(3) 调试 SUSP/EOF 行为——查 c_cc + 触发 flag。

      Exampleioctl(fd, FIONREAD, &cnt) 取输入队列未读字节数(非 SUSv3)。

      62.2 tcgetattr / tcsetattr 与 termios

      Whattcgetattr(fd, &tp) 把当前终端属性读到 struct termiostcsetattr(fd, optional_actions, &tp) 把结构回写;optional_actions 三选一。

      Why:所有 termios 修改都走这条 API;ioctl 接口老且类型不安全,POSIX 1.0 引入 tcgetattr/tcsetattr 统一接口。

      How

      when 时机 典型用途

      TCSANOW

      立即生效

      修改只影响输入的处理

      TCSADRAIN

      等当前输出排干

      修改影响输出(如去掉 OPOST)

      TCSAFLUSH

      同 TCSADRAIN + 清输入队列

      修改前要丢弃待输入(如关 echo 时)

      经典循环:tcgetattr → 备份到 struct termios save = tp; → 位运算修改字段 → tcsetattr;退出前用 save 恢复。

      When:(1) 想关 echo → tp.c_lflag &= ~ECHO,TCSAFLUSH;(2) 想清 type-ahead → TCSAFLUSH;(3) 改 baud rate → cfsetispeed/cfsetospeed + tcsetattr(TCSANOW)。

      Example:TLPI Listing 62-2 关闭 echo 读取密码风格:

      // 摘自《The Linux Programming Interface》 第 62 章 Listing 62-2
      #include <termios.h>
      #include "tlpi_hdr.h"
      #define BUF_SIZE 100
      int main(int argc, char *argv[]) {
          struct termios tp, save;
          char buf[BUF_SIZE];
          if (tcgetattr(STDIN_FILENO, &tp) == -1) errExit("tcgetattr");
          save = tp;
          tp.c_lflag &= ~ECHO;
          if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tp) == -1) errExit("tcsetattr");
          printf("Enter text: ");
          fflush(stdout);
          if (fgets(buf, BUF_SIZE, stdin) == NULL)
              printf("Got end-of-file/error on fgets()\n");
          else
              printf("\nRead: %s", buf);
          if (tcsetattr(STDIN_FILENO, TCSANOW, &save) == -1) errExit("tcsetattr");
          exit(EXIT_SUCCESS);
      }
      注意事项
      • tcsetattr 在 background process group 中:会向 process group 发 SIGTTOU;orphaned 失败 EIO。

      • tcsetattr 即使部分字段未生效也成功——多字段修改后必要时二次 gettattr 校验。

      62.3 stty 命令

      Whatstty 是 tcgetattr/tcsetattr 的命令行版本——stty -a 看全部属性、stty sane 重置、stty intr ^L 改 INTR。

      Why:(1) 调试程序改坏了 terminal 用 Control-J stty sane Control-J 恢复(^J 是 ASCII 10,真 newline,shell 处理);(2) 看 min/time 当前值。

      Howstty -F /dev/ttyN 可读 / 改另一终端;不同 UNIX 上行为略不同;bash 内置命令行编辑会改 termios,--noediting 绕过。

      When:(1) 程序崩溃后终端乱 → stty sane;(2) 想看当前 ICANON 设置 → stty -a;(3) 想改 INTR → stty intr ^L

      Example:发现按 ^C 不能终止程序时,stty -a | grep intr 看是否被改了。

      62.4 终端特殊字符

      Whatc_cc[] 数组存特殊字符值,每个常量 VINTR/VEOF/VKILL/…​ 是数组下标。Linux 下 VMIN/VTIMEVEOF/VEOL 不重叠(SUSv3 允许重叠)。

      Why:理解驱动把哪些字符变信号 / 编辑操作,避免在「按下能被识别为键」的字符上做字符串解析。

      How(关键摘要):

      字符 默认值 行为 依赖 flag

      INTR

      ^C

      发 SIGINT 给前台进程组

      ISIG

      QUIT

      ^\

      发 SIGQUIT

      ISIG

      SUSP

      ^Z

      发 SIGTSTP

      ISIG

      EOF

      ^D

      行首时 read 返回 0

      ICANON

      ERASE

      ^?

      删一个字符(按 backspace 等价物)

      ICANON

      KILL

      ^U

      删整行

      ICANON

      WERASE

      ^W

      删一个 word

      ICANON+IEXTEN

      LNEXT

      ^V

      literal next,下一字符按字面量

      ICANON+IEXTEN

      REPRINT

      ^R

      重画未完成的输入行

      ICANON+IEXTEN+ECHO

      START/STOP

      Q/S

      software flow control

      IXON

      EOL/EOL2

      undef

      额外的行定界符

      ICANON [/ IEXTEN]

      fpathconf(fd, _PC_VDISABLE) 返回某常量(常 = 0)禁用某特殊字符。

      When:(1) 想禁用 INTR(不响应 ^C)→ tp.c_cc[VINTR] = fpathconf(STDIN_FILENO, _PC_VDISABLE);;(2) telnet 风格协议用 EOL2 做内嵌转义字符。

      Example:TLPI Listing 62-1 new_intr.c——改 INTR 为其它字符;用于教学(如换成 ^L 让学生验证)。

      62.5 终端 flag (c_iflag/c_oflag/c_cflag/c_lflag)

      What:表 62-2 列出全部 flag;按字段分四组。

      Why:(1) ECHO 控制密码输入;(2) ICANON 切 canonical/noncanonical;(3) OPOST 关输出处理;(4) IXON 关 software flow control;(5) ICRNLONLCR 控制 CR ↔ NL 转换。

      How(必记常用):

      Flag 字段 默认 用途

      ICANON

      c_lflag

      on

      canonical 模式

      ECHO

      c_lflag

      on

      echo 输入字符

      ISIG

      c_lflag

      on

      INTR/QUIT/SUSP 触发信号

      IEXTEN

      c_lflag

      on

      WERASE/REPRINT/LNEXT 等扩展处理

      OPOST

      c_oflag

      on

      输出后处理(NL→CR-NL 等)

      ICRNL

      c_iflag

      on

      输入 CR→NL

      INLCR

      c_iflag

      off

      输入 NL→CR

      IGNCR

      c_iflag

      off

      输入 CR 丢弃

      ONLCR

      c_oflag

      on

      输出 NL→CR-NL

      IXON

      c_iflag

      on

      START/STOP software flow control

      TOSTOP

      c_lflag

      off

      后台输出触发 SIGTTOU

      ISTRIP

      c_iflag

      off

      剥除 bit 8

      IXANY

      c_iflag

      off

      任何字符恢复输出

      CS8

      c_cflag

      on

      8-bit 字长

      PARENB

      c_cflag

      off

      奇偶校验

      HUPCL

      c_cflag

      on

      最后 close 时 hangup modem

      When:(1) 关 echo 读密码 → c_lflag &= ~ECHO;(2) 完全国际化 → OPOST | ICRNL | ONLCR 都关;(3) 写 password / passphrase prompt → 通常加 TCSAFLUSH 清 type-ahead。

      Example:TLPI Listing 62-2 仅关闭 ECHO(保留 ISIG 让 Ctrl-C 还能终止)。

      62.6 I/O 模式:canonical / noncanonical / cooked / cbreak / raw

      What:三种语义模式 + 两种现代术语对应:

      • canonical mode(ICANON 置位):行缓冲 + 行编辑。

      • noncanonical mode(ICANON 清零):字符即返回。

      • cooked = canonical + 全开处理(ICRNL、OCRNL、echo、ISIG 等默认全开)。

      • cbreak = noncanonical + 关 echo + 留 ISIG(仍响应 C/Z/^\)——TLPI Listing 62-3 实现。

      • raw = noncanonical + 关 ISIG/IEXTEN/ECHO/OPOST/ICRNL 等一切——「数据流原样穿过驱动」。

      Why:(1) 编辑器(vi)需要 raw / cbreak 实时响应按键;(2) shell / cat 用 cooked 完事;(3) curses 应用用 cbreak。

      How:MIN/TIME 决定 noncanonical 何时返回:

      MIN TIME read 行为

      0

      0

      poll:立即返回(≥ 1 byte 返字节数;0 返 0)

      > 0

      0

      block:直到 MIN 字节可用

      0

      > 0

      timeout:1 byte 或 TIME 0.1s 到即返回;0 返 0

      > 0

      > 0

      interbyte timeout:第一字节触发计时器,间 > TIME/10 s 时返

      TLPI Listing 62-3 给 cbreak / raw 函数:

      // 摘自《The Linux Programming Interface》 第 62 章 Listing 62-3
      #include <termios.h>
      #include "tty_functions.h"
      int ttySetCbreak(int fd, struct termios *prevTermios) {
          struct termios t;
          if (tcgetattr(fd, &t) == -1) return -1;
          if (prevTermios != NULL) *prevTermios = t;
          t.c_lflag &= ~(ICANON | ECHO);
          t.c_lflag |= ISIG;
          t.c_iflag &= ~ICRNL;
          t.c_cc[VMIN] = 1;        /* block 直到 1 byte */
          t.c_cc[VTIME] = 0;
          if (tcsetattr(fd, TCSAFLUSH, &t) == -1) return -1;
          return 0;
      }
      int ttySetRaw(int fd, struct termios *prevTermios) {
          struct termios t;
          if (tcgetattr(fd, &t) == -1) return -1;
          if (prevTermios != NULL) *prevTermios = t;
          t.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
          t.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR
                        | INPCK | ISTRIP | IXON | PARMRK);
          t.c_oflag &= ~OPOST;
          t.c_cc[VMIN] = 1;
          t.c_cc[VTIME] = 0;
          if (tcsetattr(fd, TCSAFLUSH, &t) == -1) return -1;
          return 0;
      }

      When:(1) 写 readline 风格输入 → canonical;(2) vi 实现 → raw;(3) pager (less) → cbreak;(4) 想解析方向键 → 用 MIN>0+TIME>0 interbyte timeout。

      Example:TLPI Listing 62-4 test_tty_functions.c——退出 / SIGINT / SIGTSTP 都恢复原始 termios(userTermios);TUCH 的 tstpHandler 切回原 termios 后 raise SIGTSTP、再保存恢复。

      62.7 线路速率 (cfgetispeed / cfsetispeed)

      Whatcfgetispeed/cfsetispeed 取 / 设输入速率;cfgetospeed/cfsetospeed 取 / 设输出速率;操作 termios 内的 speed_t(实际上藏在 c_cflag 的 CBAUD/CBAUDEX mask)。

      Why:与老式调制解调器 / 终端打交道时;现代 USB / 串口多 115200 baud。

      HowB300/B1200/B2400/B9600/B38400/B115200 为常用符号常量;cfsetispeed(0) = 「随 cfsetospeed」。

      When:(1) 串口 9600 / 38400 / 115200 切换 → 调 cfsetospeed + tcsetattr(TCSANOW);(2) 现代 Linux 上很少直接用到。

      Examplecfsetospeed(&tp, B38400); tcsetattr(fd, TCSANOW, &tp);

      62.8 线路控制 (tcsendbreak / tcdrain / tcflush / tcflow)

      What:四个 POSIX 线路控制函数——send BREAK、drain 等发送完成、flush 输入/输出队列、suspend/resume 数据流(software flow control)。

      Why:(1) tcdrain 在程序退出前保证输出排干(避免数据丢失);(2) tcflush(fd, TCIFLUSH) 清用户 type-ahead(密码输入前);(3) tcflow 配合 terminal 速度匹配。

      How(tcflush 队列选择):

      queue_selector 含义

      TCIFLUSH

      flush 输入队列

      TCOFLUSH

      flush 输出队列

      TCIOFLUSH

      flush 双队列

      tcflow

      action 含义

      TCOOFF

      suspend 输出

      TCOON

      resume 输出

      TCIOFF

      发 STOP 给对端(IXON 生效时)

      TCION

      发 START 给对端

      When:(1) password 提示前 → tcflush(STDIN_FILENO, TCIFLUSH) 清 type-ahead;(2) 程序退出前 → tcdrain(STDOUT_FILENO);(3) 想停终端输出 → tcflow(STDOUT_FILENO, TCOOFF)

      Example:TLPI 没给独立示例;这些函数多用于串口编程(modem 旧应用)。

      62.9 窗口大小:SIGWINCH + TIOCGWINSZ

      What:终端驱动记录 struct winsize {ws_row, ws_col, ws_xpixel, ws_ypixel};resize 事件向 foreground process group 发 SIGWINCH;程序通过 ioctl TIOCGWINSZ 读取。

      Why:curses / vim 等全屏程序须监听 resize 并重画;不解 SIGWINCH 会导致窗大小变化后「垃圾显示」。

      How

      #include <sys/ioctl.h>
      struct winsize ws;
      ioctl(STDIN_FILENO, TIOCGWINSZ, &ws);    /* 读 */
      ws.ws_row = 40; ws.ws_col = 100;
      ioctl(STDIN_FILENO, TIOCSWINSZ, &ws);    /* 写(触发 SIGWINCH) */

      注意:TIOCSWINSZ 不改变真实终端窗大小,只更新驱动记录并通知前台进程组;实际窗大小由 window manager / 终端模拟器决定。

      When:(1) curses / ncurses 应用 resize → 必须处理 SIGWINCH;(2) demo 见 TLPI Listing 62-5。

      Example

      // 摘自《The Linux Programming Interface》 第 62 章 Listing 62-5 (片段)
      static void sigwinchHandler(int sig) {}
      int main(int argc, char *argv[]) {
          struct winsize ws;
          sigaction(SIGWINCH, ...);  /* sigwinchHandler */
          for (;;) {
              pause();
              ioctl(STDIN_FILENO, TIOCGWINSZ, &ws);
              printf("Caught SIGWINCH, new window size: %d rows * %d columns\n",
                     ws.ws_row, ws.ws_col);
          }
      }

      62.10 终端识别:isatty / ttyname

      Whatisatty(fd) 判断 fd 是否终端;ttyname(fd) 返回设备名(/dev/pts/0/dev/tty1),查 /dev/dev/pts 找到匹配 st_rdev 的入口。

      Why:(1) 程序判断输入 / 输出是否终端(vim 决定 behavior、diff 输出颜色);(2) 调试:日志里贴终端名。

      Howttyname_r 是可重入版。

      When:(1) 编辑器等决定是否交互模式 → isatty(STDIN_FILENO);(2) 日志系统识别 session → ttyname(STDERR_FILENO);(3) tty(1) 是命令行版。

      三、关键图表

      关键系统调用 / 函数
      函数 语义 头文件

      tcgetattr / tcsetattr

      读 / 写 termios 属性

      <termios.h>

      cfgetispeed / cfsetispeed

      输入速率

      同上

      cfgetospeed / cfsetospeed

      输出速率

      同上

      tcsendbreak / tcdrain / tcflush / tcflow

      BREAK / 排干 / flush / flow

      同上

      isatty / ttyname

      fd 是否终端 / 取设备名

      <unistd.h>

      ioctl TIOCGWINSZ / TIOCSWINSZ

      读 / 写 winsize

      <sys/ioctl.h>

      read / write

      标准 fd 操作

      <unistd.h>

      termios 关键 flag
      Flag 默认 含义

      ICANON

      on

      canonical mode(行缓冲 + 行编辑)

      ECHO

      on

      输入字符自动回显

      ISIG

      on

      INTR/QUIT/SUSP 触发信号

      IEXTEN

      on

      扩展处理(WERASE/LNEXT/REPRINT)

      OPOST

      on

      输出后处理

      ICRNL

      on

      输入 CR→NL

      ONLCR

      on

      输出 NL→CR-NL

      IXON

      on

      START/STOP software flow control

      TOSTOP

      off

      后台输出触发 SIGTTOU

      CS8 / PARENB

      CS8 on

      8 bit 字长 / 奇偶校验

      MIN/TIME 组合
      MIN TIME read 行为

      0

      0

      polling:立即返(无数据返 0)

      > 0

      0

      block:直到 ≥ MIN bytes

      0

      > 0

      读超时:≥1 byte 或 TIME/10 s

      > 0

      > 0

      interbyte 超时(解 escape sequence)

      四、思维导图

      mindmap
        root((第 62 章 终端))
          终端驱动
            输入输出队列
            line discipline
            canonical default
          termios 结构
            c_iflag oflag
            c_cflag lflag
            c_cc 数组
          tcgetattr tcsetattr
            TCSANOW
            TCSADRAIN
            TCSAFLUSH
            标准流程
          特殊字符
            INTR QUIT SUSP
            EOF ERASE KILL
            LNEXT REPRINT
            WERASE START STOP
          flags
            ICANON ECHO ISIG
            OPOST ICRNL ONLCR
            IXON TOSTOP
            c_cflag 硬件
          输入模式
            canonical 非 canonical
            cooked cbreak raw
            MIN TIME 组合
          线路控制
            cfget cfset speed
            tcdrain tcflush
            tcflow tcsendbreak
          窗口大小
            SIGWINCH
            TIOCGWINSZ
            winsize
          终端识别
            isatty
            ttyname
            /dev/pts /dev

      五、重点与易错点

      1. 「修改 termios」标准流程:tcgetattr → 修改 → tcsetattr——直接赋值给单个字段可能损坏 termios(其它字段保持不变);务必先读完整结构。

      2. 不要忘记 atexit / exit handler 还原 termios——程序意外崩溃后 shell 仍持有乱套的 termios;TLPI test_tty 用 SIGINT/SIGTERM/SIGTSTP 各自 handler 恢复。

      3. TCSAFLUSH 在改 input 相关 flag 时最安全——避免 type-ahead 被错误解释(如关 echo 时还遗留字符)。

      4. pseudoserver 改 termios 前必须 tcgetattr 保存原值——不要假设启动时是 canonical;必须保存 user 设置。

      5. Canonical 模式 read 一次最多返一行——若 read 缓冲区比一行大,后续 read 才返剩余;要按行分帧。

      6. EOF (^D) 在行首导致 read 返 0;在行中只 flush 当前已输入部分,再读继续输入;与 fgets 类似行为。

      7. Noncanonical + MIN/TIME 选择决定交互模型——vim 用 MIN=1 TIME=0 单字符阻塞;less / more 可用 MIN=0 TIME>0 读超时;escape sequence 用 MIN>0 TIME>0 interbyte timeout。

      8. VMIN/VTIME 在 SUSv3 允许和 VEOF/VEOL 重叠——切回 canonical 模式前用保存的 termios 恢复;TLPI 警告 (Linux 上不重叠,但写 portable code 必须备份)。

      9. ICANON = 0 后 ISIG 通常仍开——cbreak 模式;raw 模式才关 ISIG;vi 等需要 ISIG 让 Ctrl-C 还能终止程序。

      10. OPOST 关 = no output processing——NL 不会自动变 CR-NL,光标不会下行;终端输出会有问题,不要随便关。

      11. IXON software flow control 可能干扰 binary 协议——ftp/ssh 会改 mod;binary raw socket 时关 IXON。

      12. SIGWINCH 默认被忽略——程序必须显式 install handler 才能感知 resize。

      13. isatty 在 piped stdin 上返 0——许多交互程序 (vim, less, top) 通过此判定;测试时不要 pipe 输入否则 program 进入 non-interactive 模式。

      14. ttyname 返回静态分配字符串——多线程下用 ttyname_r;否则要复制。

      15. stty 修改不跨 stty 会话持久——stty 改只是本次 shell;要永久生效写到 shell 启动脚本。

      16. 跨章衔接:第 64 章 pty 借用一切 termios 概念;第 34 章 job control(SIGTSTP/SIGTTOU)依赖 ISIG/TOSTOP;第 8 章密码输入 getpass() 是用 TCSAFLUSH 关 echo 的范例;第 60-61 章 fork-per-client server 也常涉及「进程在 background 时终端行为」(SIGTTOU)。