标准输入输出 stdio 流缓冲 buffering in standard streams
**From : http://www.pixelbeat.org/programming/stdio_buffering/ **
**译者:李秋豪**
我发现找出标准流用的是什么缓冲是一件困难的事。
例如下面这个使用unix shell 管道的例子:
$ command1 | command2
下图显示了shell fork了两个进程并通过一个管道将他们联系起来。在这个连接中移动使用了三个缓冲.

内核中的缓冲区室友pipe系统函数生成的,它的大小取决于操作系统的页大小。我们无法也没必要控制这个缓冲区的大小,因为它会立即转送数据(至少在linux上是这样)。[更新:这个pipe buffer 已经变化为 circular buffers (16 x 4KiB)并且有一个新的 proposed patch 使得它的大小是动态的]
另外两个缓冲是关于流的,为了提高效率,仅仅在第一次使用流的时候申请缓冲区空间。三个标准流(stdin, stdout, stderr)会在几乎所有的unix GNU C程序开始执行自动被创建,新的流也可以被创建用来连接文件、套接字、管道等等。你可以通过控制缓冲策略(无缓冲,行缓冲,满缓冲)来控制数据的读写方法。我使用这个程序来确定标准流的默认缓冲区的特性:
/* Output info about the default buffering parameters
* applied by libc to stdin, stdout and stderr.
* Note the info is sent to stderr, as redirecting it
* makes no difference to its buffering parameters.
* Note gnulib has fbufmode() to make this portable.
*/
#include <stdio_ext.h>
#include <unistd.h>
#include <stdlib.h>
FILE* fileno2FILE(int fileno){
switch(fileno) {
case 0: return stdin;
case 1: return stdout;
case 2: return stderr;
default: return NULL;
}
}
const char* fileno2name(int fileno){
switch(fileno) {
case 0: return "stdin";
case 1: return "stdout";
case 2: return "stderr";
default: return NULL;
}
}
int main(void)
{
if (isatty(0)) {
fprintf(stderr,"Hit Ctrl-d to initialise stdin\n");
} else {
fprintf(stderr,"Initialising stdin\n");
}
char data[4096];
fread(data,sizeof(data),1,stdin);
if (isatty(1)) {
fprintf(stdout,"Initialising stdout\n");
} else {
fprintf(stdout,"Initialising stdout\n");
fprintf(stderr,"Initialising stdout\n");
}
fprintf(stderr,"Initialising stderr\n"); //redundant
int i;
for (i=0; i<3; i++) {
fprintf(stderr,"%6s: tty=%d, lb=%d, size=%d\n",
fileno2name(i),
isatty(i),
__flbf(fileno2FILE(i))?1:0,
__fbufsize(fileno2FILE(i)));
}
return EXIT_SUCCESS;
}
默认缓冲策略:
- stdin总是缓冲的
- stderr总是无缓冲的
- 如果stdout是终端的话缓冲是行缓冲的,否则是满缓冲的。(补充一下,GNU里面定义的可交互设备,显然终端是可交互设备)
默认缓冲大小:
- 缓冲大小只会直接影响缓冲策略
- 内核的pipe buffer 已经变化为 circular buffers (16 x 4KiB)并且有一个新的 proposed patch 使得它的大小是动态的
- 如果stdin/stdout 连接的是交互设备那么默认大小是1024,否则是4096
stdio 输出缓冲的问题
现在来考虑一个问题:数据源的信息是间隔发送的,并且接受者希望立即收到新产生的数据。
例如,一个人想要过滤 tcpdump -l 或者 tail -f 的输出等等(注意有一些过滤器比如sort要求一次缓存所有数据到内部,所以这里不能使用)。
考虑下面这个操作,从动态网络日志终端数据中过滤出不一样的IP地址:
$ tail -f access.log | cut -d' ' -f1 | uniq
问题在于,如果按照上面这个命令,我们将不能实时的看到增加的主机IP,示例图如下:

高亮的缓冲区导致了问题的发生。由于该缓存区连接了一个管道缓冲区,他会等到数据达到4096字节后再作为一个块传送给uniq。注意到tail的标准输出也有这个问题,但是tail -f调用会自动清除缓冲区当有新的数据输入时,所以这里不会产生影响( tcpdump -l, grep --line-buffered sed --unbuffered 也是这样)。另外,由于uniq标准输出连接的是一个可交互设备,所以当有新的一行数据到达时也会自动清除缓冲区,不会产生影响。
stdio 输入缓冲问题
正如向stdout一样,stdin也使用缓冲区以增加效率。
如果一个一个字节的读入显然会有更多控制的空间,但是这样是不实际的。
考虑以下命令:
$ printf "one\ntwo\nthree\n" | ( sed 1q ; sed 1q ; sed 1q )
one
(译者注:这里的q是sed流编辑器的退出命令,1q表示当输出到达第一行结束时退出。参考The q or quit command)
正如你所见到的,第一个sed进程读取了所有数据,导致后面的sed没办法读入数据。注意仅仅将stdin缓冲区设置为行缓冲是没有用的,因为只有当输出缓冲区被清除的时候才会产生控制效果(译者注,如果没有输出的话,第一个sed还是会”一行一行的把输入数据读完)。以上的sed标准输入都是行缓冲Reading lines from stdin.通常你只能控制一个进程能否从stdin读入数据,或者读入特定规模的数据然后禁止读入。以下是这样的一个例子:
$ printf "one\ntwo\nthree\n" | ( ssh localhost printf 'zero\\n' ; cat )
zero
(译者注:后面的cat命令用于从stdin中读取数据输出到屏幕,防止printf的输出存储在缓冲区中。)
这个远程printf命令并不会从stdin读取数据(译者注:'zero\n'是参数),但是ssh client并不知道这个,所以他会读取前面printf传入的数据即stdin中读取数据。为了告诉ssh远程命令不需要读入数据,可以加上-n这个参数:
$ printf "one\ntwo\nthree\n" | ( ssh -n localhost printf 'zero\n' ; cat )
zero
one
two
three
常见的经历是你想要吧ssh放在后台当你知道远程命令不会读取数据的时候(利于常见的图像化程序),设置ssh client阻止读入数据可以防止远程应用程序停滞。你可以通过-f参数告诉ssh忽略stdin并且fork到后台。例如:ssh -fY localhost xterm(译者注:-Y Enables trusted X11 forwarding)。
stdio 缓冲控制
省略...(关键词:stdbuf, BUF_X_=Y where X = 0 (stdin), 1 (stdout), 2 (stderr) )
标准输入输出 stdio 流缓冲 buffering in standard streams的更多相关文章
- 标准输入输出 stdio 流缓冲
**From : http://www.pixelbeat.org/programming/stdio_buffering/** 我发现找出标准流用的是什么缓冲是一件困难的事. 例如下面这个使用uni ...
- 标准输入输出() & 打印流 &配置文件
public static void main(String[] args) { //System 类 的 public final static InputStream in = null; // ...
- stdio - 标准输入输出库函数
SYNOPSIS 总览 #include <stdio.h> FILE *stdin; FILE *stdout; FILE *stderr; DESCRIPTION 描述 标注 I/O ...
- C语言 流缓冲 Stream Buffering
From : https://www.gnu.org/software/libc/manual/html_node/Stream-Buffering.html 译者:李秋豪 12.20 流缓冲 通常情 ...
- C 标准库 - <stdio.h>
一般地,在C语言或C++中,会把用来#include的文件的扩展名叫 .h,称其为头文件. #include文件的目的就是把多个编译单元(也就是c或者cpp文件)公用的内容,单独放在一个文件里减少整体 ...
- C语言的标准输入输出
1. 标准输入输出 标准输入.输出主要由缓冲区和操作方法两部分组.缓冲区实际上可以看做内存中的字符串数组,而操作方法主要是指printf.scanf.puts.gets,getcha.putcahr等 ...
- C语言 流缓冲
**From : https://www.gnu.org/software/libc/manual/html_node/Stream-Buffering.html** 12.20 流缓冲 通常情况下, ...
- c++流缓冲学习---rdbuf()
我们使用STL编程的时候有时候会想到把一个流对象指向的内容用另一个流对象来输出,比如想把一个文件的内容输出到显示器上,我们可以用简单的两行代码就可以完成: ifstream infile(" ...
- UNIX环境编程学习笔记(13)——文件I/O之标准I/O流
lienhua342014-09-29 1 标准 I/O 流 之前学习的都是不带缓冲的 I/O 操作函数,直接针对文件描述符的,每调用一次函数可能都会触发一次系统调用,单次调用可能比较快捷.但是,对于 ...
随机推荐
- Cogs 1995. Yukari
1995. Yukari ★★☆ 输入文件:camera.in 输出文件:camera.out 简单对比时间限制:1 s 内存限制:128 MB 题目背景: 幻想乡的创始人之一,八云紫 ...
- 洛谷P1065 作业调度方案
P1065 作业调度方案 题目描述 我们现在要利用m台机器加工n个工件,每个工件都有m道工序,每道工序都在不同的指定的机器上完成.每个工件的每道工序都有指定的加工时间. 每个工件的每个工序称为一个操作 ...
- CLR Via C#: CLR 的执行模型
CLR(Common Language Runtime)公共语言运行时:是一个可由多种编程语言使用的“运行时”. 编译源代码文件的过程: C# 源代码文件 -> C# 编译器->托管模块( ...
- es6的箭头函数转换为普通函数,以及将await/async函数转为普通函数
箭头函数转为普通函数: 1. 安装babel-preset-es2015 npm install babel-preset-es2015 --save-dev 2.在.babelrc文件夹中: & ...
- Javascript专题(三)b.各种轮播和细节分析--上下滚动轮播
这一次,我们用原生JS实现上下滚动方式的轮播.顺带学习一下用JS来创建HTML元素. 上一次写的轮播是淡入淡出效果的,相对来说其实是比较简单的. github源码: 上下轮播源码-github A. ...
- Python网络爬虫(三)
AJAX学习 AJAX=Asynchronous JavaScript and XML(异步的 JavaScript 和 XML).通俗来说,AJAX是一种无需加载整个网页的情况下,通过在后台与服务器 ...
- 4、kvm相关网卡操作(添加、删除)
查看虚拟机的网卡信息 virsh domiflist privi-server 添加临时网卡 virsh attach-interface privi-server --type bridge --s ...
- ACM-ICPC 2018 徐州赛区网络预赛 B(dp || 博弈(未完成)
传送门 题面: In a world where ordinary people cannot reach, a boy named "Koutarou" and a girl n ...
- Nginx实战(三) 日志配置与切割
访问日志主要记录客户端访问Nginx的每一个请求,格式可以自定义.通过访问日志,你可以得到用户地域来源.跳转来源.使用终端.某个URL访问量等相关信息. Nginx中访问日志相关指令主要有两条,一条是 ...
- Windows沾滞键设置
控制面板----轻松使用-----轻松使用设置中心------更改键盘的工作方式