文件I/O:文件I/O也称为不带缓冲的I/O(unbuffered I/O)。不带缓冲指的是每个read,write都调用内核中的一个系统调用。也就是一般所说的低级磁盘I/O,遵循POSIX相关标准,任何兼容POSIX标准的操作系统上都支持文件I/O。——是操作系统提供的基本IO服务,与OS绑定,特定于Linux或Unix平台。
标准I/O:标准I/O是ANSI C建立的一个标准I/O模型,是一个标准函数包和stdio.h头文件中的定义,不依赖系统内核,所以移植性强。又称为高级磁盘I/O,遵循ANSI C相关标准。只要开发环境中有标准I/O库,标准I/O就可以使用。(Linux 中使用的是glibc,它是标准C库的超集。不仅包含ANSI C中定义的函数,还包括POSIX标准中定义的函数。因此,Linux 下既可以使用标准I/O,也可以使用文件I/O)。标准I/O库处理很多细节,例如缓冲分配,以优化长度执行I/O等。
文件I/O:读写文件时,每次操作都会执行相关系统调用。这样处理的好处是直接读写实际文件,坏处是频繁的系统调用会增加系统开销。使用时一般用户需要自己维护一个用户缓冲区,不过在Linux系统中,都使用内核高速缓冲用于提高效率,因此文件I/O的读写系统调用实际上是在内核缓冲区和进程的用户缓冲区之间进行的数据复制。
标准I/O:可以看成在文件I/O的基础上由标准I/O库封装并维护了缓冲机制(流缓冲,都在用户空间)。比如调用fopen函数,不仅打开一个文件,而且建立了一个流缓冲(读写模式下将建立两个缓冲区),还创建了一个包含文件和缓冲区相关信息的FILE结构,从而先读写流缓冲区,必要时再访问实际文件,从而减少了系统调用的次数,使用库函数在用户空间的流缓冲上和用户交互的效率高于直接从内核读写数据的效率,因此提高了I/O效率。
带缓存的I/O操作是C标准库实现的,第一次调用带缓存的文件操作函数时标准库会自动分配内存(通常是调用malloc在用户空间上分配堆内存)作为流缓存并且读出一段固定大小的内容存储在缓存中。所以以后每次的读写操作并不是针对磁盘上的文件直接进行的,而是针对标准库的流缓存的。何时从磁盘中读取文件或者向磁盘中写入文件有标准库的机制控制。
文件I/O:所有I/O函数都是围绕文件描述符进行的。当打开一个文件时,即返回一个文件描述符,后续的I/O操作也都使用该文件描述符进行操作。可以访问不同类型的文件如普通文件、设备文件和管道文件等。
标准I/O:所有操作都是围绕流(stream)进行的。当用标准I/O库打开或创建一个文件时,即将一个流和一个文件相关联。通常只用来访问普通文件(???)。
不带缓冲I/O:指进程不提供缓冲(但内核还是提供缓冲的),每调用一次write或read函数,直接进行系统调用。系统内核对磁盘的读写都会提供一个块缓冲(也被称为内核高速缓冲),用write函数对其写数据时,直接调用系统调用,将数据写入到块缓冲,当块缓冲满时才会数据写入磁盘。
如果要写数据到文件上(就是写入磁盘),内核先将数据写入到内核中所设的缓冲区,假如这个缓冲储存器的长度是100个字节,调用系统调用 ssize_t write (int fd,const void * buf,size_t count);
写操作时,设每次写入长度count=10个字节,那么需要调用10次系统调用才能把内核缓冲区写满,之前数据还是在缓冲区,并没有写入到磁盘,缓冲区满时才进行实际上的IO操作,即数据写入到磁盘。因此“不带缓冲”不是没有缓冲而是没有直接写进磁盘。
带缓冲I/O:指进程提供一个流缓冲,当用fwrite函数往磁盘写数据时,先把数据写入流缓冲区中,当达到一定条件(比如流缓冲区满,或主动刷新流缓冲)时才会把数据一次送往内核提供的块缓冲,再经块缓冲写入磁盘。(即双重缓冲)
上面的例子,内核缓冲区长度100字节,调用不带缓冲的IO函数write()需要调用10次系统调用,这样系统效率低。而使用标准I/O函数时,用户层建立另一个缓冲区(流缓冲),假设流缓冲的长度是50字节,用标准C库函数fwrite()将数据写入这个流缓冲区,流缓冲区满50字节后再一次调用系统调用write()将数据写入内核缓冲内,如果内核缓冲也被填满,那么内核缓冲区内数据就被写入到文件(实质是磁盘)。由此可以看出: ① 标准IO操作最终还是要调用无缓冲IO系统调用(带缓冲I/O本身就是在不带缓冲I/O基础上提供缓冲实现的),它们并不直接读写磁盘 ; ② 增加用户/流缓冲区可以减少系统调用的次数。
正常情况下,和磁盘交互的读写文件的大致流程:
当应用程序尝试读取某块数据的时候, ① 如果这块数据已经存放在页缓存(也就是上面提到的内核高速缓存)中,那么这块数据就可以立即返回给应用程序,而不需要经过实际的物理读盘操作。 ② 如果数据在应用程序读取之前并未被存放在页缓存中,那么就需要先将数据从磁盘读到页缓存中去。
对于写操作来说,应用程序也会将数据先写到页缓存中去(如果是调用标准库I/O进行写操作,那么首先是写到标准库的流缓冲区,在一定条件之后,再写到页缓冲内;如果是系统调用,那么直接写到页缓冲内),数据是否被立即写到磁盘上取决于应用程序所采用的写操作机制:
无缓存IO操作的数据流向:数据——内核缓存区——磁盘
标准IO操作的数据流向:数据——流缓存区——内核缓存区——磁盘
标准I/O中,一般由系统选择缓存的长度,并自动分配。标准I/O库在关闭流的时候自动释放缓存。
在标准I / O库中,一个效率不高的不足之处是需要复制的数据量。 当使用每次一行函数fgets和fputs时,通常需要复制两次数据:一次是在内核高速缓存和标准I/O缓存之间(当调用read和write时),第二次是在标准I/O缓存(通常系统分配和管理)和用户程序中的缓存(fgets的参数就需要一个用户行缓存指针)之间。
程序在读写文件时既可以调用C标准I/O库函数,也可以直接调用底层POSIX标准的的Unbuffered I/O函数,那么用哪一组函数好呢?
fflush将流所有未写的数据送入(刷新)到内核(内核缓冲区),fsync将所有内核缓冲区的数据写到文件(磁盘)。至于究竟写到了文件中还是内核缓冲区中对于进程来说是没有差别的,如果进程A和进程B打开同一文件,进程A写到内核I/O缓冲区中的数据从进程B也能读到,因为内核空间是进程共享的。而C标准库的I/O缓冲区则不具有这一特性,因为进程的用户空间是完全独立的。
※※※ 带缓冲I/O 和不带缓冲I/O的区别与联系
※ 标准I/O小结(缓冲区,I/O函数及其他相关问题)
※ 底层I/O(无缓冲)与 C标准I/O
1、库函数错误报告
《C和指针》:标准库的许多函数包括I/O函数都会调用操作系统完成任务,若执行失败,则需要反馈给用户错误的原因。因此标准库在一个外部整形变量errno(在errno.h中定义)中保存错误代码之后把这个信息传递给用户程序,提示操作失败的准确原因。而perror函数即向用户报告这些错误。
#include
//若message指向一个非空字符串,则打印一条错误代码的信息,格式为 message: ...
void perror(char const * message);
2、I/O函数错误判断
《Unix环境编程》:在大多数实现中,为每个流在FILE结构中维护两个标志:
例如fgetc函数已到达文件尾端或出错都返回EOF,因此可以调用ferror、feof函数区分。
#include
int ferror(FILE* fp); // 判断是否为出错返回:条件为真,返回真(非0);否则返回假(0)
int feof(FILE* fp); //判断是否为结束返回:条件为真,返回真(非0);否则返回假(0)
总结:在标准库I/O函数的结果判断中,用ferror和feof比较好。
对于流,《C和指针》里有一段解释得很好:
ANSI C进一步对I/O的概念进行了抽象。就C程序而言,所有的I/O操作只是简单地从程序移进或移出字节的事情。因此,毫不惊奇的是,这种字节流便被称为流(stream)。程序只需要关心创建正确的输出字节数据,以及正确地解释从输入读取的字节数据。特定I/O设备的细节对程序员是隐藏的。
简单地说,流是对信息的一种抽象。C在处理文件(文本文件和二进制文件)时,并不区分类型,都看成是字符流,按字节进行处理。
C标准库提供两种类型的流:二进制流(binary stream)和文本流(text stream)。二进制流是有未经处理的字节构成的序列;文本流是由文本行(每行有0个或多个字符,并以’\n’结束)组成的序列。注意:在Unix中,并不区别这两种流。
FILE* fopen(const char* restrict pathname, const* restrict type);
// 成功返回文件指针,失败返回NULL,errno提示出错的性质———需要判断打开流的执行情况
![打开流type参数](D:/Program Files/share/Typora插入图片/打开流type参数.PNG)
打开流的6种方式 ,如下表:
![打开流的方式](D:/Program Files/share/Typora插入图片/打开流的方式.PNG)
fopen函数的用法:
FILE* fp;
FILE=fopen("test.txt","w+");
if(FILE==NULL)
{
perror("test.txt"); //必须处理打开流的错误情况
exit(EXIT_FAILURE);
}
// ...
当一个进程启动时,会自动打开标准输入、标准输出和标准错误输出三个文件以及三个流并对应到默认的物理终端。当一个进程正常终止时(直接调用exit(),或从main返回),所有打开的文件、标准I/O流都会被关闭,所有未写缓冲数据的I/O流都会被冲洗(刷新)。
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
FILE结构包含了管理流所需要的所有信息:实际文件I/O的文件描述符、指向流缓存的指针(标准I/O缓存,由malloc分配,又称为用户态进程空间的缓存,区别于内核所设的缓存)、缓存长度、当前在缓存中的字节数、出错标志等。
// Linux
struct _IO_FILE {
int _flags;
char* _IO_read_ptr;
char* _IO_read_end;
char* _IO_read_base; // 读缓冲区起始地址
char* _IO_write_base; // 写缓冲区起始地址
char* _IO_write_ptr;
char* _IO_write_end; // write_end - write_base = 当前写缓存的字节数 ???
char* _IO_buf_base; // 缓冲区起始地址
char* _IO_buf_end; // buf_end - buf_base = 缓存长度
char *_IO_save_base;
char *_IO_backup_base;
char *_IO_save_end;
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno; // 打开文件的文件描述符
int _flags2;
int _mode;
};
FILE结构貌似给定3个缓冲区,实际上_IO_read_base, _IO_write_base, _IO_buf_base都指向同一个缓冲区 。它们在第一次buffered I/O操作时由库函数自动申请空间,按照缓冲类型自动填充/刷新,最后由相应库函数负责释放。
标准I/O库提供缓冲的目的是尽可能地减少使用read和write调用的次数,从而提高I/O效率。标准I/O库也对每个I/O流自动地进行缓冲管理,从而避免了应用程序需要考虑这一点所带来的麻烦。(一个流上执行第一次I/O操作时,相关标准I/O函数通常调用malloc获得需使用的缓冲区,最后由标准I/O函数负责释放)
标准I/O提供了三种类型的缓冲:
全缓冲。写时,填满流缓冲区才进行实际I/O写操作;读时,读完流缓冲区才进行实际I/O读操作。对于驻留在磁盘上的文件访问通常是由标准I/O库实施全缓冲。
术语“冲洗”说明标准I/O缓冲区的写操作(输出缓冲流)。缓冲区可由标准I/O例程自动冲洗,或者可以调用函数fflush强制冲洗一个流。
行缓冲。写时,遇到换行符或者填满流缓冲才进行实际I/O写操作;读时,遇到换行符或者读完流缓冲才进行实际I/O读操作。这允许我们一次输出一个字符(使用标准I/O函数fputc),但只有在写了一行之后才进行实际I/O操作。当流涉及一个终端时,通常使用行缓冲。
对于行缓冲有两个限制。第一,因为标准I/O库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,那么即使没有写一个换行符,也进行I/O操作。第二,任何时候只要通过标准I/O库要求从 (a) 一个不带缓冲的流,或者 (b) 一个行缓冲的流得到输入数据,那么就会造成冲洗所有行缓冲输出流。在 (b) 中所需的数据可能已在缓冲区中,并不必须在需要数据时才从内核读数据。很明显,从不带缓冲的一个流中进行输入要求立刻从内核得到数据。
不带缓冲。标准I/O库不对字符进行缓冲存储。相当于直接调用系统调用读写。标准出错流stderr通常是不带缓冲的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个换行符。
注意:
ISO C要求下列缓冲特征:
但是,这并没有告诉我们如果标准输入和标准输出涉及交互式设备时,他们是不带缓冲的还是行缓冲的;以及标准出错时不带缓冲的还是行缓冲的。
许多系统默认使用下列类型的缓冲:
对任何一个给定的流,如果我们并不喜欢这些系统默认的情况,则可调用下列函数中的一个更改缓冲类型:
void setbuf(FILE *restrict fp, char *restrict buf)
int setvbuf(FILE *restrict fp, char *restrict buf,int mode,size_t size) //成功,返回0;出错,非0
更改换成类型的这两个函数一定要在流已经被打开之后和对流执行任何操作之前调用。原因很简单:这些函数都需要一个有效的文件指针作为参数。
setbuf——打开或关闭缓冲区:
setvbuf——精确更改缓冲类型:
具体细节如下表:
#include
#include
#include
#include
#include
int main(void)
{
char buf[30]; //用户自定义缓冲
FILE *myfile = fopen("bbb.txt","r+"); //"read+"打开流,因磁盘文件,因此缺省全缓冲
if(FILE==NULL){
perror("test.txt"); //必须处理打开流的错误情况
exit(EXIT_FAILURE);
}
//setvbuf(myfile,NULL,_IOLBF,BUFSIZ); //设置为行缓冲
printf("myfile->_IO_read_base:%p\n", myfile->_IO_read_base);
printf("myfile->_IO_read_ptr:%p\n", myfile->_IO_read_ptr);
printf("myfile->_IO_read_end:%p\n", myfile->_IO_read_end);
printf("myfile->_IO_read_ptr - myfile->_IO_read_base:%ld\n", myfile->_IO_read_ptr - myfile->_IO_read_base);
printf("myfile->_IO_read_end - myfile->_IO_read_base:%ld\n", myfile->_IO_read_end - myfile->_IO_read_base);
printf("myfile->_IO_write_base:%p\n", myfile->_IO_write_base);
printf("myfile->_IO_write_ptr:%p\n", myfile->_IO_write_ptr);
printf("myfile->_IO_write_end:%p\n", myfile->_IO_write_end);
printf("myfile->_IO_write_ptr - myfile->_IO_write_base:%ld\n", myfile->_IO_write_ptr - myfile->_IO_write_base);
printf("myfile->_IO_write_end - myfile->_IO_write_base:%ld\n", myfile->_IO_write_end - myfile->_IO_write_base);
printf("myfile->_IO_buf_base:%p\n", myfile->_IO_buf_base);
printf("myfile->_IO_buf_end - myfile->_IO_buf_base:%ld\n", myfile->_IO_buf_end - myfile->_IO_buf_base);
printf("\n"); //stdout:行缓冲
//"bbb.txt"文件初始内容:
//---------------------------
//1\n234\n56789回车
//\012回车
//12345回车
//123回车
//12回车
//回车
//---------------------------
int i;
for(i=0;i!=5;i++){ //执行5次流输入
fgets(buf, 6, myfile); //全缓冲——4096字节;fgets函数格式化处理缓冲区字符:读取一行的一部分或资格完整行,并且都强制加一个NUL标识字符串
printf("%s-",buf);
}
printf("\n");
//fflush(myfile);
printf("after read:\n");
printf("myfile->_IO_read_base:%p\n", myfile->_IO_read_base);
printf("myfile->_IO_read_ptr:%p\n", myfile->_IO_read_ptr);
printf("myfile->_IO_read_end:%p\n", myfile->_IO_read_end);
printf("myfile->_IO_read_ptr - myfile->_IO_read_base:%ld\n", myfile->_IO_read_ptr - myfile->_IO_read_base);
printf("myfile->_IO_read_end - myfile->_IO_read_base:%ld\n", myfile->_IO_read_end - myfile->_IO_read_base);
printf("myfile->_IO_write_base:%p\n", myfile->_IO_write_base);
printf("myfile->_IO_write_ptr:%p\n", myfile->_IO_write_ptr);
printf("myfile->_IO_write_end:%p\n", myfile->_IO_write_end);
printf("myfile->_IO_write_ptr - myfile->_IO_write_base:%ld\n", myfile->_IO_write_ptr - myfile->_IO_write_base);
printf("myfile->_IO_write_end - myfile->_IO_write_base:%ld\n", myfile->_IO_write_end - myfile->_IO_write_base);
printf("myfile->_IO_buf_base:%p\n", myfile->_IO_buf_base);
printf("myfile->_IO_buf_end - myfile->_IO_buf_base:%ld\n", myfile->_IO_buf_end - myfile->_IO_buf_base);
//读磁盘文件5次之后写,因此时从5次读之后的ptr处写入
char* str1="str"; //一行的一部分
fputs(str1,myfile);
char* str2="string\n"; //一个完整行
fputs(str2,myfile);
char* str3="string1\nstring2\n"; //两行
fputs(str3,myfile);
printf("\n");
printf("after write:\n");
printf("myfile->_IO_read_base:%p\n", myfile->_IO_read_base);
printf("myfile->_IO_read_ptr:%p\n", myfile->_IO_read_ptr);
printf("myfile->_IO_read_end:%p\n", myfile->_IO_read_end);
printf("myfile->_IO_read_ptr - myfile->_IO_read_base:%ld\n", myfile->_IO_read_ptr - myfile->_IO_read_base);
printf("myfile->_IO_read_end - myfile->_IO_read_base:%ld\n", myfile->_IO_read_end - myfile->_IO_read_base);
printf("myfile->_IO_write_base:%p\n", myfile->_IO_write_base);
printf("myfile->_IO_write_ptr:%p\n", myfile->_IO_write_ptr);
printf("myfile->_IO_write_end:%p\n", myfile->_IO_write_end);
printf("myfile->_IO_write_ptr - myfile->_IO_write_base:%ld\n", myfile->_IO_write_ptr - myfile->_IO_write_base);
printf("myfile->_IO_write_end - myfile->_IO_write_base:%ld\n", myfile->_IO_write_end - myfile->_IO_write_base);
printf("myfile->_IO_buf_base:%p\n", myfile->_IO_buf_base);
printf("myfile->_IO_buf_end - myfile->_IO_buf_base:%ld\n", myfile->_IO_buf_end - myfile->_IO_buf_base);
int ret=fflush(myfile);
if(ret != 0)
printf("fflush error\n");
fgets(buf, 10, myfile);
printf("second read:%s-",buf);
printf("\n");
printf("second read:\n");
printf("myfile->_IO_read_base:%p\n", myfile->_IO_read_base);
printf("myfile->_IO_read_ptr:%p\n", myfile->_IO_read_ptr);
printf("myfile->_IO_read_end:%p\n", myfile->_IO_read_end);
printf("myfile->_IO_read_ptr - myfile->_IO_read_base:%ld\n", myfile->_IO_read_ptr - myfile->_IO_read_base);
printf("myfile->_IO_read_end - myfile->_IO_read_base:%ld\n", myfile->_IO_read_end - myfile->_IO_read_base);
printf("myfile->_IO_write_base:%p\n", myfile->_IO_write_base);
printf("myfile->_IO_write_ptr:%p\n", myfile->_IO_write_ptr);
printf("myfile->_IO_write_end:%p\n", myfile->_IO_write_end);
printf("myfile->_IO_write_ptr - myfile->_IO_write_base:%ld\n", myfile->_IO_write_ptr - myfile->_IO_write_base);
printf("myfile->_IO_write_end - myfile->_IO_write_base:%ld\n", myfile->_IO_write_end - myfile->_IO_write_base);
printf("myfile->_IO_buf_base:%p\n", myfile->_IO_buf_base);
printf("myfile->_IO_buf_end - myfile->_IO_buf_base:%ld\n", myfile->_IO_buf_end - myfile->_IO_buf_base);
//while(1); //全缓冲、行I/O方式下:以 行字符处理方式 读完或写满才执行实际I/O操作。因此此处不执行阻塞操作,使进程正常终止,刷新流写入磁盘文件
return 0;
}
/* stdout 内容 */ myfile->_IO_read_base:(nil) myfile->_IO_read_ptr:(nil) myfile->_IO_read_end:(nil) myfile->_IO_read_ptr - myfile->_IO_read_base:0 myfile->_IO_read_end - myfile->_IO_read_base:0 myfile->_IO_write_base:(nil) myfile->_IO_write_ptr:(nil) myfile->_IO_write_end:(nil) myfile->_IO_write_ptr - myfile->_IO_write_base:0 myfile->_IO_write_end - myfile->_IO_write_base:0 myfile->_IO_buf_base:(nil) myfile->_IO_buf_end - myfile->_IO_buf_base:0 1\n23-4\n56-789 -\012 -12345- after read: myfile->_IO_read_base:0x55dbc3736650 myfile->_IO_read_ptr:0x55dbc3736668 myfile->_IO_read_end:0x55dbc3736670 myfile->_IO_read_ptr - myfile->_IO_read_base:24 myfile->_IO_read_end - myfile->_IO_read_base:32 myfile->_IO_write_base:0x55dbc3736650 myfile->_IO_write_ptr:0x55dbc3736650 myfile->_IO_write_end:0x55dbc3736650 myfile->_IO_write_ptr - myfile->_IO_write_base:0 myfile->_IO_write_end - myfile->_IO_write_base:0 myfile->_IO_buf_base:0x55dbc3736650 myfile->_IO_buf_end - myfile->_IO_buf_base:4096 after write: myfile->_IO_read_base:0x55dbc3736670 myfile->_IO_read_ptr:0x55dbc3736670 myfile->_IO_read_end:0x55dbc3736670 myfile->_IO_read_ptr - myfile->_IO_read_base:0 myfile->_IO_read_end - myfile->_IO_read_base:0 myfile->_IO_write_base:0x55dbc3736668 myfile->_IO_write_ptr:0x55dbc3736682 myfile->_IO_write_end:0x55dbc3737650 myfile->_IO_write_ptr - myfile->_IO_write_base:26 myfile->_IO_write_end - myfile->_IO_write_base:4072 myfile->_IO_buf_base:0x55dbc3736650 myfile->_IO_buf_end - myfile->_IO_buf_base:4096 second read:12345- second read: myfile->_IO_read_base:0x55dbc3736650 myfile->_IO_read_ptr:0x55dbc3736650 myfile->_IO_read_end:0x55dbc3736650 myfile->_IO_read_ptr - myfile->_IO_read_base:0 myfile->_IO_read_end - myfile->_IO_read_base:0 myfile->_IO_write_base:0x55dbc3736650 myfile->_IO_write_ptr:0x55dbc3736650 myfile->_IO_write_end:0x55dbc3736650 myfile->_IO_write_ptr - myfile->_IO_write_base:0 myfile->_IO_write_end - myfile->_IO_write_base:0 myfile->_IO_buf_base:0x55dbc3736650 myfile->_IO_buf_end - myfile->_IO_buf_base:4096
/* bbb.txt 最终内容 */ 1\n234\n56789回车 \012回车 12345strstring回车 string1回车 string2回车 回车
可以看出:
#include
#include
#include
int globa = 4;
int main (void )
{
pid_t pid;
int vari = 5;
printf ("before fork\n" );
if ((pid = fork()) < 0){
printf ("fork error\n");
exit (0);
}
else if (pid == 0){
globa++ ;
vari--;
printf("Child changed\n");
}
else
printf("Parent did not changed\n");
printf("globa = %d vari = %d\n",globa,vari);
exit(0);
}
执行结果:
输出到标准输出
[root@happy bin]# ./simplefork
before fork
Child changed
globa = 5 vari = 4
Parent did not changed
globa = 4 vari = 5
重定向到文件时before fork输出两边
[root@happy bin]# ./simplefork>temp
[root@happy bin]# cat temp
before fork
Child changed
globa = 5 vari = 4
before fork
Parent did not changed
globa = 4 vari = 5
分析直接运行程序时标准输出是行缓冲的,很快被新的一行冲掉。而重定向后,标准输出是全缓冲的。当调用fork时before fork这行仍保存在缓冲中,并随着数据段复制到子进程缓冲中。这样,这一行就分别进入父子进程的输出缓冲中,余下的输出就接在了这一行的后面。(子进程复制了父进程的进程空间,缓冲区也复制。)
标准I/O缓冲区详解
标准I/O函数定义了三种不同类型的非格式化I/O操作对打开的流进行读写操作。若带缓冲,三种不同类型本质上是对缓冲区的字符以不同的方式进行解析/处理。
// 字符I/O
//读字符:成功,返回读取到的字符;已到达文件尾端或出错,返回EOF
int fgetc(FILE* fp); //从指定流读
int getchar(void); //从stdin读:行缓冲
//写字符:成功,返回c;出错,返回EOF
int fputc(int c, FILE* fp); //往指定流写字符
int putchar(int c); //往stdout写字符:行缓冲
//行I/O
//读一行或一行的一部分:成功,返回buf指针;已到达文件尾端或出错,返回NULL
char* fgets(char* buf, int n, FILE* fp);
//写字符串(对写磁盘文件时,可能是一行或一行的一部分或多行):成功,返回非负值;出错返回EOF
char* fputs(const char* str, FILE* fp);
//块I/O
//两个函数都返回:读或写的对象数,若到达尾端或出错,返回数字比请求的元素个数少
size_t fread(void* ptr, size_t size, size_t nobj, FILE* fp);
size_t fwrite(const void* ptr, size_t size, size_t nobj, FILE* fp);
注:块I/O必须处理规定字符个数的字符作为一个块。
fgets:缓冲区类型不同时,从流缓冲区(全缓冲或行缓冲)或内核缓冲区读入。 ① 读取到一个换行符并存储到用户缓冲区即停止;(读取一整行) ② 未读到换行符,但达到n-1时也停止读取。(读取一行的一部分)
fputs:str缓冲区必须包含一个字符串,这个字符串默认以NUL结尾,并且其中可以包含多个换行符。因此fgets只能读取一行的一部分,一个完整行。而fputs时可以根据换行符的多少写入一行的一部分,一个完整行,多行。(分别为char* str=”str”; char* str=”string\n”; char* str=”string1\nstring2\n”);。
字符串总是要被强制处理标识,由用户或标准库函数自己处理字符串的字符 :
由例子2可以看出读写磁盘文件时,字符串处理在不同缓冲类型和I/O方式时的差别。
判断文件结尾:如果尝试读取达到文件结尾,标准IO的getc会返回特殊值EOF。而fgets碰到EOF会返回NULL,而对于Linux的read函数,情况有所不同:read读取指定的字节数,最终读取的数据可能没有所要求的那么多,而当读到结尾再要读的话,read函数将返回0。
随机存取:fseek()、ftell()和lseek():标准I/O使用fseek和ftell用于文件的随机存取,先看看fseek函数原型
先看看使用标准I/O版本的:
#include
#include
void oops(char *,char *);
int main(int ac,char *av[])
{
FILE *in,*out;
intch;
if(ac!=3){
fprintf(stderr,"Useage:%s source-file target-file.\n",av[0]);
exit(1);
}
if((in=fopen(av[1],"r"))==NULL)
oops("can not open ",av[1]);
if((out=fopen(av[2],"w"))==NULL)
oops("can not open ",av[2]);
while((ch=getc(in))!=EOF)
putc(ch,out);
if(fclose(in)!=0||fclose(out)!=0)
oops("can not close files.\n"," ");
return 0;
}
void oops(char *s1,char* s2)
{
fprintf(stderr,"Error:%s %s\n",s1,s2);
exit(1);
}
再看一个使用Linux io的版本:
#include
#include
#include
#define BUFFERSIZE 4096
#define COPYMODE 0644
void oops(char *,char *);
int main(int ac,char *av[])
{
intin_fd,out_fd,n_chars;
char buf[BUFFERSIZE];
if(ac!=3){
fprintf(stderr,"useage:%s source-file target-file.\n",av[0]);
exit(1);
}
if((in_fd=open(av[1],O_RDONLY))==-1)
oops("Can't open ",av[1]);
if((out_fd=creat(av[2],COPYMODE))==-1)
oops("Can't open ",av[2]);
while((n_chars=read(in_fd,buf,BUFFERSIZE))>0)
if(write(out_fd,buf,n_chars)!=n_chars)
oops("Write error to ",av[2]);
if(n_chars==-1)
oops("Read error from ",av[1]);
if(close(in_fd)==-1||close(out_fd)==-1)
oops("Error closing files","");
return 0;
}
void oops(char *s1,char *s2)
{
fprintf(stderr,"Error:%s",s1);
perror(s2);
exit(1);
}
显然,在使用Linux i/o的时候,你要更多地关注缓冲问题以提高效率,而stdio则不需要考虑。
参考自:Linux系统编程(1)——文件与I/O之C标准I/O函数与系统调用I/O
为什么总是需要将数据由内核缓冲区换到用户缓冲区或者相反呢?
答:用户进程是运行在用户空间的,不能直接操作内核缓冲区的数据。 用户进程进行系统调用的时候,会由用户态切换到内核态,待内核处理完之后再返回用户态 。
应用缓冲技术能很明显的提高系统效率。内核与外围设备的数据交换,内核与用户空间的数据交换都是比较费时的,使用缓冲区就是为了优化这些费时的操作。其实内核到用户空间的操作本身是无缓冲的,是由I/O库用buffer来优化了这个操作。比如read本来从内核读取数据时是比较费时的,所以一次取出一块,以避免多次陷入内核。
应用内核缓冲区的 主要思想就是一次读入大量的数据放在缓冲区,需要的时候从缓冲区取得数据。
管理员模式和用户模式之间的切换需要消耗时间,但相比之下,磁盘的I/O操作消耗的时间更多,为了提高效率,内核也使用缓冲区技术来提高对磁盘的访问速度。磁盘是数据块 的集合,内核会对磁盘上的数据块做块缓冲。内核将磁盘上的数据块复制到内核缓冲区中,当一个用户空间中的进程要从磁盘上读数据时,内核一般不直接读磁盘,而 是将内核缓冲区中的数据复制到进程的缓冲区中。当进程所要求的数据块不在内核缓冲区时,内核会把相应的数据块加入到请求队列,然后把该进程挂起,接着为其他进程服务。一段时间之后(其实很短的时间),内核把相应的数据块从磁盘读到内核缓冲区,然后再把数据复制到进程的缓冲区中,最后唤醒被挂起的进程。
注:理解内核缓冲区技术的原理有助于更好的掌握系统调用read&write,read把数据从内核缓冲区复制到进程缓冲区,write把数据从进程缓冲区复制到内核缓冲区,它们不等价于数据在内核缓冲区和磁盘之间的交换。
从理论上讲,内核可以在任何时候写磁盘,但并不是所有的write操作都会导致内核的写动作。内核会把要写的数据暂时存在缓冲区中,积累到一定数量后再一 次写入。有时会导致意外情况,比如断电,内核还来不及把内核缓冲区中的数据写道磁盘上,这些更新的数据就会丢失。
应用内核缓冲技术导致的结果是:提高了磁盘的I/O效率;优化了磁盘的写操作;需要及时的将缓冲数据写到磁盘。
![输入输出](D:/Program Files/share/输入输出.jpg)