第九章学习总结
作者:互联网
第九章的主要内容是I/O库函数,包括以下几个方面:
- I/O库函数的概念以及作用的描述
- I/O库函数和系统调用之间比较
- I/O库函数的算法
- I/O库不同模式
- 文件流缓冲方案
一,知识点归纳:
- I/O库函数与系统调用
系统调用和库函数调用二者的最主要功能区别:系统调用只支持数据块的读写,而I/O库函数可以指出更多逻辑单元的读写,例如行、字符、结构化记录等。
例如 :系统调用函数:open()、read()、write()、lseek()、close();
I/O库函数:fopen()、fread()、fwrite()、flseek()、fclose()。
系统调用实际上就是指最底层的一个调用,在linux程序设计里面就是底层调用的意思面向的是硬件。而库函数调用则面向的是应用开发的,相当于应用程序的api,采用这样的方式有很多种原因,第一:双缓冲技术的实现。第二,可移植性。第三,底层调用本身的一些性能方面的缺陷。第四:让api也可以有了级别和专门的工作面向。
①系统调用(底层文件访问)
系统调用提供的函数如open, close, read, write, ioctl等
系统调用通常用于底层文件访问(low-level file access),例如在驱动程序中对设备文件的直接访问。
系统调用是操作系统相关的,因此一般没有跨操作系统的可移植性。
②库函数调用(标准IO库)
标准IO库函数提供的文件操作函数如fopen, fread, fwrite, fclose, fflush, fseek等,
库函数调用通常用于应用程序中对一般文件的访问。
库函数调用是系统无关的,因此可移植性好。
由于库函数调用是基于C库的,因此也就不可能用于内核空间的驱动程序中对设备的操作。
系统调用发生在内核空间,因此如果在用户空间的一般应用程序中使用系统调用来进行文件操作,会有用户空间到内核空间切换的开销。事实上,即使在用户空间使用库函数来对文件进行操作,因为文件总是存在于存储介质上,因此不管是读写操作,都是对硬件(存储器)的操作,都必然会引起系统调用。也就是说,库函数对文件的操作实际上是通过系统调用来实现的。例如C库函数fwrite()就是通过write()系统调用来实现的。
2、I/O库函数的算法
1.fread算法
(1)在第一次调用fread()时,FILE结构体的缓冲区是空的,fread()使用保存的文件描述符fd发出一个n=read(fd,fbuffer,BLKSIZE);
系统调用,用数据块填充内部的fbuf[]。然后,它会初始化fbuf[]的指针、计数器和状态变量以表明内部缓冲区中有一个数据块。接着,通过将数据复制到程序的缓冲区,尝试满足来自内部缓冲区的fread()调用。如果内部缓冲区没有足够的数据,则会再发出一个read()系统调用来填充内部缓冲区,将数据从内部缓冲区传输到程序缓冲区,直到满足所需的字节数(或者文件无更多数据)。将数据复制到程序的缓冲区之后,它会更新内部缓冲区的指针、计数器等,为下一个fread()请求做好准备。然后,它会返回实际读取的数据对象数量。
(2)在随后的每次fread()调用中,它都尝试满足来自FILE结构体内部缓冲区的调用。当缓冲区变为空时,它就会发出read()系统调用来重新填充内部缓冲区。因此,fread()一方面接受来自用户程序的调用,另一方面向操作系统内核发出read()系统调用。除了read()系统调用之外,所有fread()处理都在用户模式映像中执行。它只在需要时才会进入操作系统内核,并且以一种最高效匹配文件的方式进人。它会提供自动缓冲机制,因此用户程序不必担心这些具体操作。
2.fwrite算法
fwrite()算法与fread()算法相似,但数据传输方向不同。每次调动fwrite()时,它将数据写入缓冲区,并调整缓冲区的指针、计数器和状态变量,以跟踪缓冲区中的字节数。如果缓冲区已满,则发出write()系统调用,将整个缓冲区写入操作系统内核。
3.fclose()算法
若文件以写的方式被打开,fclose()会先关闭文件流的局部缓冲区。然后,它会发出一个close(fd)系统调用来关闭FILE结构体中的文件描述符。最后,它会释放FILE结构体并将FILE指针重置为NULL。
3、I/O库模式
fopen()中的模式参数可以指定为:"r"、" w"、"a”,分别代表读、写、追加。每个模式字符串可包含一个+号,表示同时读写,或者在写入、追加情况下,如果文件不存在则创建文件。
"r+":表示读/写,不会截断文件。
"w+":表示读/写,但是会先截断文件; 如果文件不存在,会创建文件。
"a+":表示通过追加进行读/写;如果文件不存在,会创建文件。
(1)字符模式I/O
int fgetc(FILE *fp);
int ungetc(int c,FILE *fp);
int fputc(int c,FILE *fp)。
(2) 行模式I/O
char *fgets(char *buf,int size,FILE *fp):从fp中读取最多为一行(以\n结尾)的字符。
int fputs(char *buf,FILE *fp):将buf中的一行写入fp中。
(3) 格式化I/O(最常用)
格式化输入:(FMT=格式字符串)
scanf(char *FMT,&items);
fscanf(fp,char *FMT,&items)。
格式化输出:
printf(char *FMT,items);
fprintf(fp,char *FMT,items)。
(4) 内存中的转换函数
sscanf(buf,FMT,&items);
sprintf(buf,FMT,items);
sscanf()和sprintf()并非I/0函数,而是内存中的数据转换函数。
(5) 其他I/O库函数
fseek()、ftell()、rewind():更改文件流中的读/写字节位置。
feof()、ferr()、fileno():测试文件流状态。
fdopen():用文件描述符打开文件流。
freopen():以新名称重新打开现有的流。
setbuf()、setvbuf():设置缓冲方案。
popen():创建管道,复刻子进程来调用sh。
(6) 限制混合fread-fwrite
当某文件流同时用于读/写时,就会限制使用合fread()和fwrite()调用。规范要求每对fread()和fwrite()之间至少有一个fseek()或ftell()。
4、文件流缓冲
(1) 通过fopen()创建文件流之后,在对其执行任何操作之前,用户均可发出一个
setvbuf(FILE *stream, char *buf, int mode, int size)
其中buf为缓冲区,mode为缓冲方案,size为缓冲区大小。
(2) 三种缓冲方案:
无缓冲(_IONBUF):从非缓冲流中写入或读取的字符将尽快单独传输到文件或从文件中传输。
行缓冲(_IOLBUF):遇到换行符时,写入行缓冲流的字符以块的形式传输。
全缓冲(_IOFBUF):写入全缓冲流或从中读取的字符以块大小传输到文件或从文件传输。这是文件流的正常缓冲方案。
二,学到了什么
库函数和系统调用的区别:
本质方面:函数调用数内核提供给应用程序的一部分,库函数是为了方便人们而编程设计的。
缓冲方面:系统调用无缓冲区,库函数有缓冲区。
空间方面:系统调用在内核地址空间执行,属于系统时间,开销大;库函数在用户空间执行,开销小。
系统相关方面:系统调用由操作系统决定,操作系统不同那么底层函数不同;库函数和系统无关,始终在C库中存放,代码不会变,根据系统不同会调用不同的底层函数,但接口不变,因此可移植性好。
三,问题与解决思路:
问题1:使用库函数也有系统调用的开销,为什么不直接使用系统调用呢?
解决方法:查资料
结论:读写文件通常是大量的数据(这种大量是相对于底层驱动的系统调用所实现的数据操作单位而言),这时,使用库函数就可以大大减少系统调用的次数。这一结果又缘于缓冲区技术。在用户空间和内核空间,对文件操作都使用了缓冲区,例如用fwrite写文件,都是先将内容写到用户空间缓冲区,当用户空间缓冲区满或者写操作结束时,才将用户缓冲区的内容写到内核缓冲区,当内核缓冲区满或写结束时才将内核缓冲区内容写到文件对应的硬件媒介。
问题2:open、write、read和fopen、fwrite、fread的区别到底是什么?
解决过程:csdn
open和fopen区别:https://blog.csdn.net/hairetz/article/details/4150193
write和fwrite区别:https://blog.csdn.net/ljlstart/article/details/49535005
read和fread区别:https://blog.csdn.net/qq_33832591/article/details/52268477
四,实践代码:
#include <stdio.h>
void main()
{
FILE *fp;
int c;
fp=fopen("exist","r");
while((c=fgetc(fp))!=EOF)
printf("%c",c);
fclose(fp);
}
标签:总结,文件,调用,第九章,fread,系统,学习,缓冲区,库函数 来源: https://www.cnblogs.com/MRC-/p/16684797.html