ana*_*nik 2 terminal ioctl controlling-terminal
从键盘输入的所有输入都通过控制终端是否正确?这意味着如果程序在没有控制终端的情况下运行,它将无法接收任何用户输入。这是否适用于 Linux 中的每种程序?
更新 #1:为了澄清这个问题,当 stdin 被重定向时,我的 Python 寻呼机模块崩溃了:
$ ./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')
Run Code Online (Sandbox Code Playgroud)
这是因为我尝试获取描述符以将键盘输入设置为fd = sys.stdin.fileno()
. 当stdin
被重定向,它的文件描述符不再与任何键盘输入相关联,因此试图设置它失败input-output control
的错误。
我被告知要得到这个controlling terminal
,但我不知道它来自哪里。我知道这是一种从用户向正在运行的进程发送信号的通道,但同时也可以在没有它的情况下运行进程。
所以问题是 - 我应该总是从 读取我的键盘输入controlling terminal
吗?如果在没有它的情况下运行寻呼机进程会发生什么?键盘输入对用户仍然重要吗?我应该关心从其他来源获取它吗?
不。终端应用程序从与您正在键入的键盘的终端对应的设备文件(在 Linux 上,类似于/dev/ttyS0
或/dev/ttyUSB0
... 对于串行设备,/dev/pts/0
对于伪终端设备)读取键盘输入。
该设备不必是进程(或任何与此相关的进程)的控制终端。
您可以这样做,cat /dev/pts/x
前提是您对该设备文件具有读取权限,并且可以读取另一端终端(如果有)上键入的内容。
实际上,如果它是进程的控制终端并且该进程不在该终端的前台进程组中,则该进程通常会在尝试从中读取时被挂起(如果它在前台进程组中,它如果发送将获得SIGINT / SIGTSTP / SIGQUIT一个^C
/ ^Z
/^\
无论过程是否从终端设备读取或没有)。如果终端设备不是进程的控制终端(如果进程是不同会话的一部分),这些事情就不会发生。这就是控制终端的意义所在。用于作业控制由交互式 shell 实现的机制。除了那些 SIGTTIN/SIGTTOU 和 SIGINT/SIGTSTP/SIGQUIT 信号,控制终端在终端挂起时参与 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
,一个连接到 X 服务器以进行用户 I/O 的应用程序,从在 中运行的 shell 内xterm
,将从它的 shell 父级firefox
继承控制终端。^C
如果它是由 shell 在前台启动的,则在终端中会杀死它。它还将在该文件上打开其文件描述符 0、1 和 2(stdin、stdout 和 stderr)/dev/pts/<x>
(再次从其父壳继承)。并且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
甚至某些vi
实现)会继续从 stdin 读取它们的输入,因此它们可以编写脚本。
寻呼机虽然是一个典型的应用程序,但即使他们的输入不是终端(至少当他们的输出仍然进入终端时),它仍然需要与用户交互。因此,他们需要另一个信道给所述终端装置采取的用户输入。问题是:他们应该使用哪种终端设备?
是的,它应该是控制终端。因为这通常是控制终端的意思。当您按下 Ctrl-C/Z 时,该设备会向寻呼机发送 SIGINT/SIGTSTP,因此寻呼机从同一终端读取其他按键是有意义的。
在控制终端上获取文件描述符的典型方法是在/dev/tty
那里打开重定向(请注意,即使进程更改了 euid,它也可以工作,因此它没有对原始设备的读取权限。这比尝试要好得多找到原始设备的路径(无论如何都无法便携地完成))。
即使 stdin 是一个 tty 设备,一些寻呼机也会喜欢less
或most
打开/dev/tty
(毕竟,人们可以less < /dev/ttyS0
在终端模拟器中查看通过串行发送的内容)。
如果打开/dev/tty
失败,那通常是因为您没有控制终端。有人可能会争辩说,这是因为您已经明确地与终端分离,因此不应该尝试进行用户交互,但是存在潜在的(不寻常的)情况,您没有控制终端设备,但您的 stdin/stdout 仍然是一个 tty设备并且您仍然希望进行用户交互(例如 initrd 中的紧急外壳)。
因此,如果它是终端,您可以退回以从 stdin 获取用户交互。
有人可能会争辩说,您想检查 stdout 是否是一个终端设备,并且它指向与控制设备相同的终端设备(以说明man -l /dev/stdin < /dev/ttyS0 > /dev/ttyS1
您不希望生成的寻呼机man
执行的操作)用户交互)但这可能不值得打扰,特别是考虑到它不容易移植。这也可能会破坏其他奇怪的用例,只要 stdout 是终端设备,就可以预期寻呼机是交互式的。