我说得对吗,所有从键盘输入的输入都会经过一个控制终端?这意味着如果程序在没有控制终端的情况下运行,它将无法接收任何用户输入。这适用于 Linux 中的每种程序吗?
更新#1:为了澄清问题,我的Python寻呼机模块stdin 重定向时崩溃:
$ ./pager.py < README.rst
...
File "pager.py", line 566, in <module>
page(sys.stdin)
File "pager.py", line 375, in page
if pagecallback(pagenum) == False:
File "pager.py", line 319, in prompt
if getch() in [ESC_, CTRL_C_, 'q', 'Q']:
File "pager.py", line 222, in _getch_unix
old_settings = termios.tcgetattr(fd)
termios.error: (25, 'Inappropriate ioctl for device')
这是因为我尝试获取描述符以将键盘输入设置为fd = sys.stdin.fileno()
.重定向后stdin
,其文件描述符不再与任何键盘输入关联,因此尝试设置它会失败并出现input-output control
错误。
有人告诉我要得到这个controlling terminal
,但我不知道它从哪里来。我知道它是某种从用户向正在运行的进程发送信号的通道,但同时也可以在没有它的情况下运行进程。
所以问题是 - 我应该始终读取键盘输入吗controlling terminal
?如果寻呼机进程在没有它的情况下运行,会发生什么?键盘输入对用户仍然重要吗?我应该从其他来源获取它吗?
答案1
不会。终端应用程序从设备文件(在 Linux 上,类似/dev/ttyS0
或/dev/ttyUSB0
...对于串行设备,/dev/pts/0
对于伪终端设备)读取键盘输入,该设备文件与您正在使用的键盘的终端相对应。
该设备不一定是控制终端流程(或任何与此相关的流程)。
只要您拥有该设备文件的读取权限,您就可以执行此cat /dev/pts/x
操作,并且可以读取另一端终端(如果有)上键入的内容。
实际上,如果它是进程的控制终端,并且该进程不在该终端的前台进程组中,则该进程在尝试从中读取数据时通常会被挂起(如果它在前台进程组中,则它会被挂起)。如果您发送^C
//无论进程是否正在从终端设备读取数据,^Z
都会收到 SIGINT/SIGTSTP/SIGQUIT )。^\
如果终端设备不是终端设备,这些事情就不会发生控制终端进程的名称(如果该进程是不同会话的一部分)。就是这样控制终端是关于。这是为了作业控制由交互式 shell 实现的机制。除了 SIGTTIN/SIGTTOU 和 SIGINT/SIGTSTP/SIGQUIT 信号之外,控制终端还参与在终端挂起 hup 时发送 SIGHUP,它也是/dev/tty
重定向到的 tty 设备。
无论如何,这仅适用于终端输入:真实的如通过串行电缆连接的终端设备,模拟的如 X11 终端仿真器(例如 xterm
使用伪终端设备),或由内核模拟,如 Linux 上的虚拟终端与进程交互/dev/tty<x>
(并且支持比标准终端接口更多的功能)。
像 X 服务器这样的应用程序通常从键盘驱动程序获取键盘输入。在 Linux 上使用通用输入抽象层。 X 服务器又提供了一种事件机制,将键盘事件传递给连接到它的应用程序。例如,xterm
将接收 X11 键盘事件,将其转换为将字符写入伪终端设备的主端,这转换为在“内部”运行的进程xterm
在从相应的伪终端从属设备读取时读取相应的字符 ( /dev/pts/x
) 。
现在,已经不存在这样的事情了终端应用。我们所说的终端应用以上是通常在终端中使用的应用程序,这些应用程序预计将显示在终端中并从终端(如vi
、交互式 shell 或 )获取输入less
。但是任何应用程序都可以由终端控制,并且任何读取或写入文件或其 stdin/stdout/stderr 的应用程序都可以对终端设备执行 I/O。
例如,如果您运行firefox
,则从运行于 的 shell 中连接到 X 服务器以进行用户 I/O 的应用程序将xterm
继承firefox
控制终端来自其外壳父级。^C
如果 shell 在前台启动它,则在终端中会杀死它。它还将在该文件上打开其文件描述符 0、1 和 2(stdin、stdout 和 stderr)/dev/pts/<x>
(同样是从其 shell 父级继承的)。并且firefox
很可能最终会在 fd 2 (stderr) 上写入某种错误(如果将其置于后台并且终端设备配置了stty tostop
,则它将收到 SIGTTOU 并被挂起)。
相反,如果firefox
由您的 X 会话管理器或 Windows 管理器启动(当您单击某些菜单上的某些 Firefox 图标时),它可能不会获得任何控制终端,并且不会有任何文件描述符连接到任何终端(您将看到ps -fp <firefox-pid>
显示?
因为 和tty
上lsof -p <firefox-pid>
没有显示文件描述符/dev/pts/*
或/dev/tty*
)。但是,如果您浏览到file:///dev/pts/<x>
,firefox
仍然可以对终端设备执行一些 I/O。如果它在没有O_NOCTTY
标志的情况下打开该文件,并且它恰好是会话领导者,并且/dev/pts/<x>
尚未附加会话,则该设备最终将成为该firefox
进程的控制终端。
更多阅读:
编辑
编辑后澄清了一些问题并添加了一些上下文。
上面应该清楚地表明,进程可以从它们喜欢的任何终端设备读取输入(如果进程不在其前台进程组中,则控制终端除外),但这并不是您真正感兴趣的。
您的问题是:对于交互式终端应用程序,当 stdin 不再指向终端时,从哪里获取用户输入。
应用程序喜欢tr
从标准输入获取输入并在标准输出上写入。当 stdin/stdout 是另一端有终端的 tty 设备时,它们恰好是交互式的,因为它们从用户读取数据或向用户写入数据。
当 stdin 不再是终端时,某些终端文本编辑器(例如ed
/ex
甚至某些实现)会继续从 stdin 读取输入,因此它们可以编写脚本。vi
寻呼机是一种典型的应用程序,即使用户的输入不是终端(至少当其输出仍然到达终端时),它仍然需要与用户交互。所以他们需要另一个渠道这终端设备接受用户输入。问题是:他们应该使用哪种终端设备?
是的,应该是控制终端。因为这通常就是控制终端的含义。当您按下 Ctrl-C/Z 时,该设备会向寻呼机发送 SIGINT/SIGTSTP,因此寻呼机从同一终端读取其他击键是有意义的。
在控制终端上获取文件描述符的典型方法是打开/dev/tty
并重定向到那里(请注意,即使进程已更改 euid,因此它没有对原始设备的读取权限,它也可以工作。这比尝试要好得多找到原始设备的路径(无论如何都无法移植))。
即使 stdin 是 tty 设备,某些寻呼机也会喜欢less
或most
打开/dev/tty
(毕竟,可以less < /dev/ttyS0
从终端仿真器内查看通过串行发送的内容)。
如果打开/dev/tty
失败,通常是因为您没有控制终端。有人可能会说这是因为你已经明确地超然的从终端,所以不应该尝试进行用户交互,但有潜在的(不寻常的)情况,你没有控制终端设备,但你的标准输入/标准输出仍然是一个 tty 设备,你仍然想要进行用户交互(就像 initrd 中的紧急 shell)。
因此,如果它是终端,您可以回退以从标准输入获取用户交互。
有人可能会说你想检查 stdout 是否是终端设备和它指向与控制设备相同的终端设备(以考虑所做的事情,man -l /dev/stdin < /dev/ttyS0 > /dev/ttyS1
例如您不希望生成寻呼机man
进行用户交互),但这可能不值得打扰,特别是考虑到它并不容易便携地做。这也可能会破坏其他奇怪的用例,只要标准输出是终端设备,寻呼机就可以交互。