APUE 第 5.4 章中关于行缓冲区的一段话:
- 行缓冲。在这种情况下,标准 I/O 库在输入或输出中遇到换行符时执行 I/O。这允许我们一次输出一个字符(使用标准 I/O fputc 函数),知道只有当我们写完每一行时才会发生实际的 I/O。当流引用终端(例如标准输入和标准输出)时,通常在流上使用行缓冲。行缓冲有两个注意事项。首先,标准 I/O 库用于收集每一行的缓冲区大小是固定的,因此如果我们在写入换行符之前填充该缓冲区,则可能会发生 I/O。其次,每当通过标准 I/O 库从 (a) 无缓冲流或 (b) 行缓冲流(需要从内核请求数据)请求输入时,所有行缓冲输出流都会被刷新。 (b) 上限定符的原因是请求的数据可能已经在缓冲区中,这不需要从内核读取数据。显然,来自无缓冲流的任何输入(项目(a))都需要从内核获取数据。
我不太明白这两个警告。有人可以举个例子吗?
答案1
- 基本上来说,例如,如果您的程序正在将文本写入终端,一次一个字符,速度非常慢,并且该行(例如)520 个字符长,那么库可能会将前 512 个字符写入终端在程序完成写入(甚至生成)该行之前。
- 表示如果您的程序正在向终端写入文本,一次一个字符(或其他一些小片段),然后从终端(即键盘)读取,则库将向终端写入以下部分:您的程序到目前为止已生成的输出行。这通常是您想要的,因为写入终端的部分行可能会提示输入,但有时可能不是。
答案2
在探讨两个警告之前,有三件事需要牢记。
无缓冲流调用系统调用每一个 read
或者write
。而行缓冲流会在每个线(每当缓冲区遇到换行符时)。
此外,stdin
和stdout
都是标准 I/O 流,通常是行缓冲的。当进程启动时,这些流会自动打开。
最后,虽然 APUE 是学习 UNIX 的圣经,但有些部分已经过时了。我并不声称我的答案是最新的。我会尝试从书中举例,以便您的问题在教科书的背景下得到适当的回答。
第一个警告
正如您的报价所述,存在限制多久一条线就可以。超过限制会产生与换行相同的效果。
在开始介绍第一个注意事项的示例之前,我们必须记住
write
系统调用实际上是排队的(APUE 第 3 章第 6 节,p86)。
假设我们有一个名为 的程序,每 30 秒Jasmine
写入一个字符。stdout
Jasmine
写入“Hello!\n”(7 个字节),需要 210 秒才能完成。
请记住,缓冲区有一个限制。如果我们的缓冲区限制为 10 个字节,内核可以等待 210 秒并write
在最后(遇到换行符后)发出单个调用。这是预期的行为。
然而,如果缓冲区限制为 4 个字节,内核将发行4 个字节后调用一次write
(“Hell”),然后在换行符后调用另一个调用(“\n”)。
这是第一个警告。
用户可能期望write
出现一次(终端上打印“Hello!\n”)。正如我们在示例中看到的那样,情况并非如此。根据系统的繁忙程度,用户可能会在 210 秒内看到两个输出打印到终端。这是因为即使stream( stdout
)是行缓冲的,该行的内容也超出了限制(4字节限制)。每 4 个字节,标准 I/O 库就会调用一次,write
就像遇到换行符一样。该示例总共有 7 个字节(“Hello!\n”),这导致了两次写入调用。
第二个警告
对于第二个警告,这里的关键点是相互作用和kernel
。
kernel
根据我们与它的交互,它的行为会有所不同,从而导致行缓冲区的另一个“意外”写入。
这是您引用的第二个警告:
kernel
将两次刷新所有行缓冲流:
- 当通过无缓冲标准 I/O 请求输入时。
- 当通过行缓冲标准 I/O 请求输入时。
我们可以将其改写如下:
kernel
将刷新所有行缓冲流,满足以下先决条件:
- 请求输入
kernel
。- 该请求是通过无缓冲或行缓冲的标准 I/O 流进行的。
第一个要求是“必须向内核请求数据”。这允许行缓冲标准 I/O 出现异常。
例外的是kernel
将不会如果请求不需要来自 的任何数据,则刷新kernel
。这可能是当行缓冲区已将所有数据存储在缓冲区中时的情况。
作为一个简单的例子,假设行缓冲流中的缓冲区具有“World”字符。用户(调用者)请求从流中读取 1 个字节。对于返回所请求的单字节(“W”)的流,它不需要 的任何帮助
kernel
。在这种情况下,kernel
不会刷新任何行缓冲流。
考虑到这个例外,让我们继续第一个警告中的示例。
(我们应该将缓冲区限制保持为 10 个字节)
输出“Hello!\n”后,我们的程序Jasmine
会写入“Name?”到stdout
。
“姓名?”没有换行符,且小于 10 个字节的限制。标准 I/O 库尚未发出调用write
。
然后Jasmine
从(另一个行缓冲流)请求输入stdin
,并发生以下情况:
- 流上的行缓冲区
stdin
是空的,因此它从 请求数据kernel
。 kernel
刷新所有打开的行缓冲流(其中包括“Name?”stdout
)。- 结果,“名字?”作为输出打印到终端。
kernel
等待用户在终端上的输入(键盘)。
这是第二个警告。
从用户的角度来看,看到“姓名?”终端上的信息可能会令人困惑,因为write
尚未发布任何信息。不幸的是,在尝试获取请求的数据之前,kernel
问题是否会write
调用所有当前打开的行缓冲流。kernel
这是因为行缓冲流和交互kernel
已经满足了两个先决条件。