在看APU时,第8章进程时,
1 #include <stdio.h> 2 #include <unistd.h> 3 4 int globalvar = 6; 5 char buf[] = "a write to stdout "; 6 7 int main ( int argc, char *argv[] ) 8 { 9 int var; 10 pid_t pid; 11 12 var = 88; 13 14 if ( write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1) 15 { 16 perror("write"); 17 return -1; 18 } 19 20 printf("before fork "); 21 //fflush(stdout); 22 if ( ( pid = fork() ) < 0 ) 23 { 24 perror("fork"); 25 return -1; 26 } 27 else if ( pid == 0 ) 28 { 29 globalvar++; 30 var++; 31 } 32 else 33 { 34 sleep(2); 35 } 36 37 printf("pid = %ld, glob = %d, var = %d ", (long)getpid(), globalvar, var); 38 return 0; 39 }
编译执行程序,得到:
thomas@thomas-laptop:~/test/apu$ ./a
a write to stdout before fork pid = 3210, glob = 7, var = 89 pid = 3209, glob = 6, var = 88
换一个方式执行,将程序执行结果重定向到一个文件:
thomas@thomas-laptop:~/test/apu$ ./a > out thomas@thomas-laptop:~/test/apu$ cat out a write to stdout before fork pid = 3312, glob = 7, var = 89 before fork pid = 3311, glob = 6, var = 88
发现前后2此结果不一样,第一个printf输出了2次,这是因为printf是属于标准IO,他的输出是带缓冲的,程序上面使用write进行输出却不会这样,write是不带缓冲的。
缓冲的目的是尽量减少对write read的调用,以达到提高IO效率的目的,比如连续的多个printf,如果不带缓冲,势必每调用一次printf就得进行一次系统调用,调用wirte(STDOUT_FILENO,buf, sizeof(buf)),如果申请一块内存,先将printf的输出内容存放在这块内存里面,等到这块内存满了再调用write进行输出,这么一来就大大减少了系统调用。
标准I/O提供了三种类型的缓冲:
1、全缓冲。这种情况下,在填满标准I/O缓冲区后才进行实际I/O操作。对于驻留在磁盘上的文件通常是由标准I/O库实施全缓冲。一个流上执行第一次I/O操作时,相关标准I/O函数通常调用malloc获得需使用的缓冲区。
术语冲洗说明I/O缓冲区的写操作。缓冲区可由标准I/O例程自动冲洗,或者可以调用函数fflush冲洗一个流。值得引起注意的是在UNIX环境
中,flush有两种意思。在标准I/O库方面,flush意味着将缓冲区中的内容写到磁盘上。在终端驱动程序方面flush表示丢弃已存储在缓冲区中的数据。
2、行缓冲。在这种情况下,当在输入和输出中遇到换行符时,标准I/O库执行I/O操作。这允许我们一次输出一个字符,但只有在写了一行之后才进行实际I/O操作。当流涉及一个终端时,通常使用行缓冲。
对于行缓冲有两个限制。第一,因为标准I/O库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,那么即使没有写一个换行符,也进行I/O操
作。第二,任何时候只要通过标准I/O库要求从a一个不带缓存的流,或者b一个行缓冲的流得到输入数据,那么就会造成冲洗所有行缓冲输出流。在b中带了一
个在括号中的说明,其理由是,所需的数据可能已在缓冲区中,他并不需求在需要数据时才从内核读数据。很明显,从不带缓冲的一个流中进行输入要求当时从内核得到数据。
3、不带缓冲。标准I/O库不对字符进行缓冲存储。例如,如果用I/O函数fputs写15个字符到不带缓冲的流中,则该函数很可能用write系统调用函数将这些字符立即写至相关联的打开文件中。
标准出错流stderr通常是不带缓冲的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个换行符。
执行./a时,由于是在一个shell终端中执行,所以printf是行缓冲,这样一来遇到了‘ ’就立即输出了,但是如果使用./a > out进行重定向,此时是上面蓝色字体那种情况,所有执行的是全缓冲,明显它的输出肯定还不至于填满缓冲区,所以fork前并不会输出,而是缓冲到了标准IO库malloc出来的一片内存里面,经过fork,这片区域在子进程里面生成了一个副本(fork会将父进程的堆栈区,数据段,bss段进行复制,生成拷贝,它们只共享代码段,而mallc位于堆上),等到程序执行结束时,全缓冲才输出,这样一来就出现了2个"before fork"。