一、在Linux下对文件操作有两种方式:Linux系统调用和ANSI C文件操作。
1、Linux系统调用调用常用于I/O文件操作,系统调用常用的函数有open、close、read、write、lseek、ulink等。
2、 ANSI C文件操作
ANSI C文件操作方法是所有操作系统通用的文件操作方法,它的操作是被缓冲过的被修改的文件并不会立即反应到磁盘中,它在内存中开辟一个“缓冲区”,为程序中的每一个文件操作所使用,当执行读文件的操作时,从磁盘文件中将数据先读入内存“缓冲区“,装满后再从内存“缓冲区”依次读入接收的数据。这种文件操作方式又被称作流式文件操作。ANSI C文件操作常用的函数有fopen、fclose、fread、fwrite、fseek、ftell、rewind、fgetc、fputc、fgets、fputs及remove等
三、下面用两个函数说明Linux系统调用和ANSI C文件操作的区别。
1、由于缓冲区的存在,因此流中的数据与对应文件的数据可能不一致,当系统掉电时,文件若没有及时close(),缓冲区的数据就会丢失,为了同步缓冲区,此时可以调用fflush()函数实现。
fflush()函数需要的头文件:
#include <stdio.h>
函数原型:
int fflush(FILE *stream);
参数说明:
stream:文件句柄。
返回值:
成功返回0;
失败返回EOF。
应用示例:
fflush函数应用示例代码如程序清单1所示。示例程序用来用while死循环模拟掉电状态,打开或创建当前目录下的“./test_2”文件并写入“Hello,world!”字符串。当调用fflush函数后,文件成功写入“Hello,world!”字符串,否则将未写入该字符串。
程序清单1
#include <stdio.h>
#define SyncTestOn 0
int main(void)
{
FILE *fp;
int iCount;
char *str = "Hello world!";
fp = fopen("./test_2", "w"); // 打开文件以获得操作该文件的句柄
iCount = fwrite(str, 12, 1, fp);
printf("%d block(12 bytes per block) read, check it out!\n", iCount);
if (SyncTestOn) {
fflush(fp);
while(1);
} else {
while(1);
}
fclose(fp);
}
说明:SyncTestOn 为0时,程序没有调用fflush(),运行e10时,程序进入while循环,用“Ctrl”+“C”强制退出程序(模拟掉电状态),用cat test_2命令查看文件,数据此时并没有写入;SyncTestOn 为1时,程序调用fflush(),运行e10,程序进入while,用“Ctrl”+“C”强制退出程序,然后用cat test_2查看文件,数据此时已经写入。
2、由于缓冲区的存在,为了同步缓冲区,可以调用fflush()实现。但是当掉电时,未调用fflush()时,此时缓冲区的数据仍旧会丢失。而setvbuf()函数可以修改缓冲区的工作模式,也可以解决这一问题。
setvbuf函数需要的头文件:#include <stdio.h>函数原型:int setvbuf(FILE *stream,char *buf,int mode,size_t size);
参数说明: stream:文件句柄。 buf:如果buf未NULL,由编译器决定如何建立流的缓冲区,否则其应该指向一段大小为size的内存。 mode:指定了缓冲区的类型,_IOFBF(全缓冲),_IOLBJ(行缓冲),_IONBF(无缓冲)。 size:指定了缓冲区的大小。如果指定一个不带缓冲区的流,则忽略 buf 和 size 参数。返回值: 成功返回0; 失败返回非零值。
应用示例:setvbuf函数应用示例代码如程序清单2所示。
示例程序用来用while死循环模拟掉电状态,打开或创建当前目录下的“./test_3”文件并写入“Hello,world!”字符串。当调用setvbuf函数将缓冲区的类型设置为_IONBF后,文件可以正常写入“Hello,world!”字符串,否则将未写入该字符串。
<pre name="code" class="cpp">#include <stdio.h>
#define SycnTestOn 1
int main(void)
{
FILE *fp;
int iCount;
char *str = "Hello world!";
fp = fopen("./test_3", "w"); // 打开文件以获得操作该文件的句柄
if(Sycn_Test_On){
setvbuf(fp,NULL,_IONBF,0);
}
iCount = fwrite(str, 12, 1, fp);
printf("%d block(12 bytes per block) read, check it out!\n", iCount);
while(1);
fclose(fp);
}
说明:测试方法同fflush()一致;setvbuf()函数如果要设置流的缓冲区,则函数必须在打开文件后立即调用,一旦操作了流,就不能再调用此函数了,否则结果不可预知。非缓冲的文件操作访问方式,每次对文件进行一次读写操作时,都需要使用linux系统调用来处理此操作,执行一次linux系统调用将涉及到CPU状态的切换,即从用户空间切换到内核空间,实现进程上下文的切换,这将损耗一定的CPU时间,频繁的磁盘访问对程序的执行效率会造成较大的影响。
除此以外,setbuf也可以实现setvbuf用法,
需要的头文件:
#include <stdio.h>函数原型:
int setbuf(FILE *stream,char *buf);
参数说明:
stream:文件句柄。
buf:参数buf须指向一个长度为BUFSIZ的缓冲区,如果将buf设置为NULL,则
关闭缓冲区。
返回值:
成功返回0;
失败返回非零值。
四、通过以上内容,总结Linux系统调用和ANSI C文件操作的区别。
1、 Linux系统调用
Linux系统调用实际上就是指最底层的一个调用,在linux程序设计里面就是底层调用,面向的是硬件。而ANSI C则是库函数调用,是面向的是应用开发的,相当于应用
程序的API(应用程序接口)。我觉得采用这样的方式有以下几个原因:
第一,双缓冲技术的实现;
第二,可移植性的考虑;
第三,底层调用本身的一些性能缺陷(如频繁擦写影响文件存储介质的寿命);
第四,让API也可以有具体分层和专门的应用方向。
Linux系统调用是操作系统相关的,因此一般没有跨操作系统的可移植性。Linux系统调用发生在内核空间,因此如果在用户空间的一般应用程序中使用系统调用来进行文件操作,会有用户空间到内核空间切换的开销。
事实上,即使在用户空间使用ANSI C文件操作,因为文件总是存在于存储介质上,因此不管是读写操作,都是对硬件(存储器)的操作,都必然会引起Linux系统调用。也就是说,ANSI C文件操作实际上是通过系统调用来实现的。例如C库函数fwrite()就是通过write()系统调用来实现的。这样的话,使用库函数也有系统调用的开销,为什么不直接使用系统调用呢?这是因为读写文件通常是大量的数据(这种大量是相对于底层驱动的系统调用所实现的数据操作单位而言),这时,使用ANSI C文件操作就可以大大减少系统调用的次数。这一结果又缘于缓冲区技术。在用户空间和内核空间,对文件操作都使用了缓冲区,例如用fwrite写文件,都是先将内容写到用户空间缓冲区,当用户空间缓冲区满或者写操作结束时,才将用户缓冲区的内容写到内核缓冲区,同样的道理,当内核缓冲区满或写结束时才将内核缓冲区内容写到文件对应的硬件媒介。
2.4.2 ANSI C文件操作
ANSI C文件操作通常用于应用程序中对一般文件的访问。ANSI C文件操作是系统无关的,因此可移植性好,由于ANSI C文件操作是基于C库的,因此也就不可能用于内核空间的驱动程序中对设备的操作。