当我们cat
调整文本文件的内容时,所有内容都会打印出来并且命令终止。但是,当我们 时cat /dev/ttyS0
,命令会挂起并等待新数据的到来。
这次暂停的原因是什么?
答案1
cat
在终端中进行试验
cat
正在等待更多数据,因为库恩格姆 说。当您输入cat
并按下Enter以便其输入是您的终端时,也会发生同样的事情。在终端中,您可以轻松地试验 的行为cat
。首先,这里描述了如何做到这一点。
当您在终端中键入Ctrl+时D,终端不会将文字Ctrl+D字符发送到程序。为此,您需要输入Ctrl+V来告诉终端下一个控制字符应该按字面意思处理,然后输入Ctrl+ D。反而:
- 如果输入缓冲区中有文本——也就是说,您输入了尚未发送到程序的文本——那么缓冲区将被刷新,也就是说它已被发送到程序。该程序将在下次读取时接收它。
- 如果输入缓冲区中没有文本,则终端向程序指示输入结束,这将导致程序在下一次读取操作时遇到文件结束条件,这(见下文)实际上是当前的读操作,在这种情况下,因为
cat
已经在读。
按下Enter,除了指示换行符,还Ctrl即使您没有按+ D,也会导致缓冲区被刷新。因此你会观察到:
输入文本并按Enter或Ctrl+D会将
cat
相同的文本写入您的终端。使用Enter,cat
也会写入换行符。Enter在行首按,或按Ctrl+后执行此操作D,会发送一个换行符并
cat
写入一个。在行首按Ctrl+或按+之后(即连续两次)将结束输入。DCtrlD
当
cat
看到从最后一个输入源中没有任何内容可读取时(请记住,“cat”代表“catenate”或“concatenate”,这是它接收多个文件名参数时的行为方式),它会退出。
就像cat
从控制终端读取数据时等待输入直到输入完成一样,它也会等待来自任何其他文件或设备(包括串行终端)的更多输入(如果您告诉它从中读取)。
Ctrl请注意,与+D和相关的特殊行为Enter会导致您的输入被发送到cat
,这是你的终端,而不是cat
。特别是,cat
它本身并不特殊对待换行符。
等待进程的状态
当cat
等待您的输入时,尝试从另一个终端检查其状态。
在大多数类 Unix 操作系统上,您可以通过运行pidof cat
或 来查找所有正在运行的 cat 进程的进程 ID pgrep -x cat
。至少一其中通常受到支持。或者在正在运行的同一终端中按Ctrl+来暂停它 - 这Zcat
实际上挂起进程——然后运行ps
以查找其 PID,然后通过运行 来恢复它fg
。或者tty
在第一个终端中运行以显示终端设备的路径名 - 在 GNU/Linux 中,这可能看起来像/dev/tty2
或/dev/pts/4
--then run cat
,然后在第二个终端中运行,替换ps -t tty
tty
tty
第一个终端的输出。
在第二个终端中,运行您替换的位置ps pid
pid
与 cat 进程的实际进程 ID。这将列出进程,包括显示其状态的列。 (即使您已经运行了tty -t
上面的代码,这在某些系统上也是必要的,包括 GNU/Linux,它们之前不会包含状态列。)
例如,我在 GNU/Linux 系统上这样做:
ek@Io:~$ ps 30196
PID TTY STAT TIME COMMAND
30196 pts/4 S+ 0:00 cat
你的可能看起来不同。我建议运行man ps
并查找出现在 STAT 或 S 列下的进程状态代码,这些代码在不同系统中略有不同。在那个 GNU/Linux 系统上,附注(1)显示进程的状态已报告为:
S interruptible sleep (waiting for an event to complete)
+ is in the foreground process group
在那个 GNU/Linux 系统上,如果我运行cat
然后用Ctrl+暂停它Z,如上所述,S+
更改为T
:
T stopped by job control signal
睡眠可中断,所以有不间断的睡觉?是的:
D uninterruptible sleep (usually IO)
当进程正在进行内核不允许取消的 IO 操作时,通常会发生这种情况。处于不间断睡眠状态的进程无法被终止。如果您使用或,一旦操作完成,它就会(分别)收到 TERM 或 KILL 信号。kill pid
kill -9 pid
追踪过程
通常要调试程序,您可以在调试器中打开它,例如gdb
或lldb
。然而,包括 GNU/Linux 在内的一些系统有一个名为strace
,您可以使用它来查看什么系统调用一个过程正在发生。当您用来strace
运行等待来自终端的输入的进程时,您可以看到阻止其执行的每个read
调用,直到更多输入发送到终端上的进程。如果您有 GNU/Linux 系统,例如 Fedora、CentOS、Debian 或 Ubuntu(这些只是示例,还有许多其他系统),请尝试运行以下命令:
strace cat
您将看到一大堆系统调用:首先是那些执行进程的系统调用(如果程序使用共享库,则后面execve
是涉及文件名的各种调用,这几乎肯定是这样),然后是为大多数程序运行的系统调用ld.so
使 C 库函数可以工作,包括mmap
and brk
...然后最终它将读取标准输入:
read(0,
这就是 的语法strace
。过程cat
是不是从字面上看,是在传递参数的过程中read
(因为该语法对于以前没有使用过的 C 程序员来说可能看起来很像strace
)。相反,它进行了read
系统调用,尚未返回。当读取数据或到达输入结束时它将返回。
尝试输入一些内容,例如foo
,然后按Enter。然后你会看到类似的内容:
"foo\n", 131072) = 4
write(1, "foo\n", 4foo
) = 4
read(0,
这意味着什么?如果您退出cat
(在行首添加Ctrl+ ,或在任意位置添加+ )并重新运行它,同时通过将其重定向到抑制其输出,您将能够更轻松地看到:DCtrlC/dev/null
strace cat >/dev/null
您仍然可以看到输出,strace
因为它被写入标准误并且你只重定向了标准输出。但现在cat
自己的输出 -foo
以及换行符,正如您所给出的那样 - 没有显示。键入foo
并按下后您看到的内容Enter如下所示:
"foo\n", 131072) = 4
write(1, "foo\n", 4) = 4
read(0,
这就是说,read
调用完成,读取foo
后跟一个换行符,显示为\n
。cat
然后调用write
将相同的字符串写入其输出。其 C 代码中可能cat
没有调用。write
相反,它可能使用 C 库函数(如fprintf
或 )fputs
来执行写入,这不会显示在 的输出中,strace
因为它不是系统调用,并且最终通过使用系统write
调用来工作。传递4
给它是write
为了告诉它——也就是说,告诉内核——应该写入四个字节:f
o
o
\n
。
请注意,在调用 后write
,再次cat
调用read
以继续读取输入。您可以继续给它输入并看到它调用write
,然后read
再次调用。例如,我输入abracadabra
并按下Enter:
"abracadabra\n", 131072) = 12
write(1, "abracadabra\n", 12) = 12
read(0,
完成后,在输入最后一个内容后按Enter(或Ctrl+ ),然后按+ 。输入结束,退出,并向您显示如下内容:DCtrlDcat
strace
read(0, "", 131072) = 0
munmap(0x7f856a591000, 139264) = 0
close(0) = 0
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++