天天看点

Linux下的文件操作(Linux系统调用和ANSIC文件操作)

1、Linux系统调用

系统调用常用于 I/O 文件操作,系统调用常用的函数有 open、 close、 read、write、 lseek、ulink 等。

  • open:打开或创建文件
  • close:关闭文件
  • read :从指定的文件描述符中读出的数据放到缓冲区,并返回实际读出的字节数
  • write:把指定缓冲区的数据写入指定的文件描述符中,并返回实际写入的字节数
  • lseek:在指定的文件描述符中将文件指针定位到相应的位置
  • ulink:删除文件

open 函数:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char* pathname, int flags);
int open(const char* pathname, int flags, int perms);
参数说明:
pathname:被打开的文件名(包括路径名);
flags:文件打开方式标志,其取值如下:
O_RDONLY:表示“以只读方式打开”;O_WRONLY:表示“以只写方式打开”;
O_RDWR:表示“以读写方式打开”;
O_CREAT:表示“若文件不存在则新建文档并打开”;
O_TRUNC:表示“若文件存在,将其长度缩短为 0,属性不变”;
O_APPEND:表示“打开文件后文件指针指向末尾”;
O_EXCL:和 O_CREAT 一起使用,用于在执行“O_CREAT”前判断文件是否存在,若存在将导致返回失败(避免原文件被覆写)。
返回值:
成功返回文件描述符fd;
失败返回-1。
           

close 函数:

#include <unistd.h>
int close(int fd); 
参数说明:
 fd:文件描述符。
返回值:
 0:成功;
 -1:出错。
           

说明:在文件操作过程中,每一个通过 open 打开的文件,在文件操作完后都需要调用close 进行关闭,否则这个文件资源将长期被占用,影响其他程序对文件的读写操作。

read 函数:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数说明:
 fd:文件描述符;
 buf:存储内容的内存空间(指定存储读出数据的缓冲区);
 count:读取的字节数。
返回值:
 >0:成功读取的字节数;
 <0:出错;
 0:表示遇到文件末尾 EOF。
           

write 函数

#include <unistd.h>
ssize_t write(int fd, void *buf, size_t count);
参数说明:
 fd:文件描述符;
 buf:需要写入内容的内存空间(缓冲区的指针);
 count:写入的字节数。
返回值:
 >0:成功写入的字节数;
 <0:出错;
 0:表示遇到文件末尾 EOF。
           

lseek 函数

#include <unistd.h>
#include <sys/types.h>
ssize_t lseek(int fd, off_t offset, int whence);
函数说明:
每一个已打开的文件都有一个读写位置,当打开文件时通常其读写位置是指向文件开
头,若是以追加的方式打开文件(如 O_APPEND),则读写位置会指向文件尾。当 read()或
write()时,读写位置会随之增加, lseek()便是用来控制该文件的读写位置。
参数说明:
 fd:文件描述符;
 offset:偏移量(可正可负);
 whence:取值为 SEEK_SET 时表示“文件开头+offset”为新读写位置,取值为
SEEK_CUR 时表示“当前读写位置+offset”为新位置,取值为 SEEK_END 时表示
“文件结尾+offset”为新位置。
返回值:
 >0:定位后文件操作位置相对于文件头的偏移量;
 <0:出错,通常返回-1。
常用方式:
将读写位置移到文件开头:
lseek(fd, 0, SEEK_SET)
将读写位置移到文件尾:
lseek(fd, 0, SEEK_END)
将取得目前文件位置:
lseek(fd, 0, SEEK_CUR)
           

unlink 函数

int unlink(const char * path)
函数说明:删除文件的一个硬链接。
参数说明:
 path:需要删除的包含文件名的文件路径。
返回值:
 成功返回 0;
 失败返回-1。
           

2、ANSI C 文件操作

ANSI C 文件操作方法是所有操作系统通用的文件操作方法,它的操作是被缓冲过的,被修改的文件并不会立即反应到磁盘中,它在内存中开辟一个“缓冲区”,为程序中的每一个文件操作所使用,当执行读文件的操作时,从磁盘文件中将数据先读入内存“缓冲区”,装满后再从内存“缓冲区”依次读入接收的数据。这种文件操作方式又被称作流式文件操作。

ANSI C 文件操作常用的函数有 fopen、 fclose、 fread、 fwrite、 fseek、 ftell、 rewind、 fgetc、fputc、 fgets、 fputs 及 remove 等。

函数 作用
fopen 打开或创建文件
fclose 关闭文件
fread 从文件中读取一个字节
fwrite 将数据块写入文件流
fseek 移动文件流的读写位置
ftell 查询文件的读写文字设置
rewind 把文件的读写位置设置在文件头
fgetc 从文本文件中读取一个字符
fputc 向文本文件中写入一个字符
fgets 从文本文件中读取一个字符串(一行数据,以\n 结尾)
fputs 向文本文件中写入一个字符串
remove 删除文件

fopen 函数

#include <stdio.h>
FILE * fopen(const char* path, const char * mode);
参数说明:
 path:带文件路径的文件名;
 mode:文件打开状态,其具体取值及对应的含义如表
返回值:
 成功则返回指向该流(流式文件操作)的文件指针(即文件句柄) fp;
 失败则返回 NULL。
**说明**:文件句柄 fp 是一个指向 FILE 结构的指针, FILE 是由系统定义的一个结构,该
结构中含有文件名、文件状态和文件当前位置等信息。在编写应用程序时通常不必关心 FILE
结构的细节,但调用 fclose、 fread、 fwrite、 fseek、 ftell、 rewind、 fgetc、 fputc、 fgets 及 fputs
等函数前均需要先调用 fopen 函数打开文件,以获取操作该文件的句柄 fp。
           
参数 作用
r 打开只读文件,文件必须存在
r+ 打开读写文件,文件必须存在
w 打开只写文件,若文件存在则清除内容,不存在则新建该文件
w+ 打开可读写文件,若文件存在则清除内容,不存在则新建该文件
a 以附加方式打开只写文件,若文件不存在则建立该文件;若存在则写入的数据被加到文件尾
a+ 以附加方式打开可读写文件,若文件不存在则建立该文件;若存在则写入的数据被加到文件尾
备注 上面的参数后面可以再加上一个 b,表示打开的二进制文件,而不是纯文本文件,如 rb、 w+b、 ab+等

fclose 函数

#include <stdio.h>
int fclose(FILE * stream);
参数说明:
 stream:文件句柄。
返回值:
 成功返回 0;
 出错返回 EOF。
说明:与前面介绍的“系统调用”相类似,每一个通过 fopen 打开的文件,在文件操作
完后均需要调用 fclose 函数进行关闭,否则这个文件资源将长期被占用,影响其他程序对文
件的读写操作。对于流式文件写操作,调用 fclose 还具有重要作用——让内存“缓冲区”中
未及时写入存储介质的数据最终写入存储介质。
           

fread 函数

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数说明:
 ptr:指向一块存储空间,用来存放本次读取到的数据;
 size:读取文件一条记录的字节数大小;
 nmemb:本次读取文件记录的数目;
 stream:将要读取的文件流句柄。
返回值:
 成功则返回实际读取到的数目 nmemb;
 出错则返回 EOF。
           

fwrite 函数

#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
参数说明:
 ptr: 需写入的数据地址;
 size: 写入文件一条记录的字节数大小;
 nmemb: 写入文件记录的数目;
 stream: 将要写入数据的文件流句柄。
返回值:
 成功则返回实际写入到的数目 nmemb;
 出错返回 EOF。
           

fseek 函数

#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
参数说明:
 stream:文件句柄;
 offset:偏移量(可正可负);
 whence:取值为 SEEK_SET 时表示“文件开头+offset”为新读写位置,取值为
SEEK_CUR 时表示“当前读写位置+offset”为新位置,取值为 SEEK_END 时表示
“文件结尾+offset”为新位置。
返回值:
 成功返回 0;
 出错返回-1。
           

ftell 函数

#include <stdio.h>
int ftell(FILE *stream);
函数说明: 简单地返回当前位置。
参数说明:
 stream:文件句柄。
返回值:
 >0:文件流当前操作位置相对于文件头的偏移量;
 <0:出错。
           

rewind 函数

#include <stdio.h>
void rewind(FILE *stream);
函数说明:把文件指针位置设置为 0,即把文件指针设置到文件的起始位置。
参数说明:
 stream:文件句柄。
返回值:
 无。
           

fgetc 函数

#include <stdio.h>
int fgetc(FILE *stream);
函数说明:从文件中读取一个字符。
参数说明:
 stream:文件句柄。
返回值:
 >0:成功读取字符内容;
 EOF:出错。
           

fputc 函数

#include <stdio.h>
int fputc(int ch, FILE *stream);
函数说明:向文件中写入一个字符。
参数说明:
 ch:需要写入文件的字符内容;
 stream:文件句柄。
返回值:
 >0:成功写入的字符值;
 EOF:出错。
           

fgets 函数

#include <stdio.h>
char * fgets(char *s, int n, FILE *stream);
函数说明:从文件中读取一行字符串。
参数说明:
 s:读取字符串的存储内存指针;
 n:读取字符串内存的大小;
 stream:文件句柄。
返回值:
 非空:成功;
 NULL:出错。
           

fputs 函数

#include <stdio.h>
int fputs(char *string, FILE *stream);
函数说明:向文件中写入一个字符串。
参数说明:
 string:需要写入文件的字符串内容;
 stream:文件句柄。
返回值:
 0:成功
 <0:出错
           

remove 函数

int remove(const char *path);
参数说明:
 path:需要删除的包含文件名的文件路径。
返回值:
 成功返回 0;
 失败返回-1。
           

fflush 函数

由于缓冲区的存在,因此流中的数据与对应文件的数据可能不一致,当系统掉电时,文件若没有及时 close(),缓冲区的数据就会丢失,为了同步缓冲区,此时可以调用 fflush()函数实现。

#include <stdio.h>
int fflush(FILE *stream);
参数说明:
 stream:文件句柄。
返回值:
 成功返回 0;
 失败返回 EOF。
实例:
fflush 函数应用示例代码如程序清单,对应已编译出来的程序文件为附件中的
“e10”。示例程序用来用 while 死循环模拟掉电状态,打开或创建当前目录下的“./test_2”
文件并写入“Hello,world!”字符串。当调用 fflush 函数后,文件成功写入“Hello,world!”
字符串, 否则将未写入该字符串。

#include <stdio.h>
#define Sync_Test_On 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 (Sync_Test_On)
	{
		fflush(fp);
		while(1);
	}
	else
	{
		while(1);
	}
	fclose(fp);
}
说明: Sync_Test_On 为 0 时,程序没有调用 fflush(),运行 e10 时,程序进入 while 循环,
用“Ctrl” +“C”强制退出程序(模拟掉电状态),用 cat test_2 查看文件,数据没有写入;
Sync_Test_On 为 1 时,程序调用 fflush(),运行 e10,程序进入 while, 用“Ctrl” +“C”强制
退出程序, 然后用 cat test_2 查看文件,数据同步。
           

setvbuf 函数

由于缓冲区的存在,为了同步缓冲区,可以调用 fflush()实现。但是当掉电时,未调用fflush()时,此时缓冲区的数据仍旧会丢失。而 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 函数应用示例代码如程序清单,对应已编译出来的程序文件为附件中的“e11”。示例程序用来用 while 死循环模拟掉电状态,打开或创建当前目录下的“./test_3”

文件并写入“Hello,world!”字符串。当调用 setvbuf 函数将缓冲区的类型设置为_IONBF 后,

文件可以正常写入“Hello,world!”字符串, 否则将未写入该字符串。

#include <stdio.h>
#define Sycn_Test_On 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 函数

#include <stdio.h>
int setbuf(FILE *stream,char *buf);
参数说明:
 stream:文件句柄。
 buf:参数 buf 须指向一个长度为 BUFSIZ 的缓冲区,如果将 buf 设置为 NULL,则
关闭缓冲区。
返回值:
 成功返回 0;
 失败返回非零值。
说明: setbuf 用法和 setvbuf 用法类似, 不再赘述。
           

3、Linux 系统调用和 ANSI C 文件操作的区别

Linux 下对文件操作有两种方式:

  • Linux 系统调用
  • ANSI C文件操作

Linux 系统调用实际上就是指最底层的一个调用,在 linux 程序设计里面就是底层调用, 面向的是硬件。而 ANSI C 则是库函数调用,是面向的是应用开发的,相当于应用程序的 API(应用程序接口)。采用这样的方式有很多种原因:

  • 双缓冲技术的实现;
  • 可移植性的考虑;
  • 底层调用本身的一些性能缺陷(如频繁擦写影响文件存储介质的寿命);
  • 让API也可以有具体分层和专门的应用方向。

Linux 系统调用

Linux 系统调用是操作系统相关的,因此一般没有跨操作系统的可移植性。Linux 系统调用发生在内核空间,因此如果在用户空间的一般应用程序中使用系统调用来进行文件操作,会有用户空间到内核空间切换的开销。事实上,即使在用户空间使用 ANSIC 文件操作,因为文件总是存在于存储介质上,因此不管是读写操作,都是对硬件(存储器)的操作,都必然会引起 Linux 系统调用。也就是说, ANSI C 文件操作实际上是通过系统调用来实现的。例如 C 库函数 fwrite()就是通过 write()系统调用来实现的。

这样的话,使用库函数也有系统调用的开销,为什么不直接使用系统调用呢?这是因为读写文件通常是大量的数据(这种大量是相对于底层驱动的系统调用所实现的数据操作单位而言),这时,使用 ANSI C 文件操作就可以大大减少系统调用的次数。这一结果又缘于缓冲区技术。在用户空间和内核空间,对文件操作都使用了缓冲区,例如用 fwrite 写文件,都是先将内容写到用户空间缓冲区,当用户空间缓冲区满或者写操作结束时,才将用户缓冲区

的内容写到内核缓冲区,同样的道理,当内核缓冲区满或写结束时才将内核缓冲区内容写到文件对应的硬件媒介。

ANSI C 文件操作

ANSI C 文件操作通常用于应用程序中对一般文件的访问。ANSI C 文件操作是系统无关的,因此可移植性好, 由于 ANSI C 文件操作是基于 C 库的,因此也就不可能用于内核空间的驱动程序中对设备的操作。

继续阅读