1、Linux文件I/O操作

1 概念

首先让我们看一下,什么是文件I/O:

所谓文件I/O就是输入/输出。文件IO的意思就是读写文件。

文件操作的一般步骤:

  1. 在Linux中要操作一个文件,一般是先open打开一个文件,得到文件描述符,然后对文件进行读写操作(或其他操作),最后是close关闭文件即可。
  2. 强调一点:我们对文件进行操作时,一定要先打开文件,打开成功之后才能操作,如果打开失败,就不用进行后边的操作了,最后读写完成后,一定要关闭文件,否则会造成文件损坏。
  3. 文件平时是存放在块设备中的文件系统中的,我们把这种文件叫静态文件,当我们去open打开一个文件时,linux内核做的操作包括:内核在进程中建立一个打开文件的数据结构,记录下我们打开的这个文件;内核在内存中申请一段内存,并且将静态文件的内容从块设备中读取到内核中特定地址管理存放(叫动态文件)。
  4. 打开文件以后,以后对这个文件的读写操作,都是针对内存中的这一份动态文件的,而并不是针对静态文件的。当然我们对动态文件进行读写以后,此时内存中动态文件和块设备文件中的静态文件就不同步了,当我们close关闭动态文件时,close内部内核将内存中的动态文件的内容去更新(同步)块设备中的静态文件。
  5. 为什么这么设计,不直接对块设备直接操作。块设备本身读写非常不灵活,是按块读写的,而内存是按字节单位操作的,而且可以随机操作,很灵活。

文件描述符:

  1. 对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或者创建一个新文件时,内核向进程返回一个文件描述符。当读写一个文件时,用open和creat返回的文件描述符标识该文件,将其作为参数传递给read和write。
  2. 按照惯例,UNIX shell使用文件描述符0与进程的标准输入相结合,文件描述符1与标准输出相结合,文件描述符2与标准错误输出相结合。STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO这几个宏代替了0、1、2这几个魔数。
  3. 文件描述符,这个数字在一个进程中表示一个特定含义,当我们open一个文件时,操作系统在内存中构建了一些数据结构来表示这个动态文件,然后返回给应用程序一个数字作为文件描述符,这个数字就和我们内存中维护的这个动态文件的这些数据结构绑定上了,以后我们应用程序如果要操作这个动态文件,只需要用这个文件描述符区分。
  4. 文件描述符的作用域就是当前进程,出了这个进程文件描述符就没有意义了。
  5. open函数打开文件,打开成功返回一个文件描述符,打开失败,返回-1。

2 Linux C文件操作

有两种方式操作文件:(1)通过系统调用,即POSIX接口;(2)C/C++库函数;

2.1 系统调用

系统调用发生在内核空间,因此如果在用户空间的一般应用程序中使用系统调用来进行文件操作,会有用户空间到内核空间切换的开销。但通过系统调用来访问文件是最直接的方式。系统调用函数直接作用于操作系统内核的设备驱动程序从而实现文件访问。文件描述符fd是一个非负整型值,每新打开一个文件,所获得的fd为当前最大fd加1。Linux系统默认分配了3个文件描述符值:0-standard input,1-standard output,2-standard error。

2.1.1 函数open

系统调用open的作用是打开一个文件,并返回这个文件的描述符。

1、如果两个程序同时打开一个文件,会得到两个不同的文件描述符

如果同时对两个文件进行操作,他们各自操作,互不影响,彼此相互覆盖(后写入的覆盖先写入的)。为了防止文件按读写冲突,可以使用文件锁的功能。【因为文件描述符是进程的私有属性】

2、新文件描述符总是使用未用描述符的最小值

例如如果一个程序关闭了自己的标准输出(这里标准输出完全等于1,也就是说描述符1一定是用来做输出的,只不过默认绑定到屏幕设备),然后再次调用open,文件描述符1就会被重新使用,并且标准输出(还是1,标准输出就是1)将被有效重定向到另一个文件或设备(现在绑定的)

3、运行中的程序能够一次打开的文件数目是有限制的

这个限制由头文件limits.h中的OPEN_MAX常数定义,它随着系统地不同而不同,但POSIX规范要求它至少要为16。

 

头文件:  
#include <fcntl.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
  
函数原型:  
int open(const char *path, int oflags);  
int open(const char *path, int oflags, mode_t mode );  
  
参数说明:  
path:准备打开的文件或设备名字。  
oflags:指出要打开文件的访问模式。  
    O_RDONLY  【3选1】以只读方式打开  
    O_WRONLY  【3选1】以只写方式打开  
    O_RDWR    【3选1】以读写方式打开  
    O_APPEDN  【|可选】以追加方式打开  
        O_TRUNC   【|可选】把文件长度设为零,丢弃以后的内容。  
        O_CREAT   【|可选】如果需要,就按参数mode中给出的访问模式创建文件。  
        O_EXCL    【|可选】与O_CREAT一起调用,确保调用者创建出文件。使用这个模式可防止两个程序同时创建一个文件,如果文件已经存在,open调用将失败。  
  
mode:当使用O_CREAT标志的open来创建文件时,我们必须使用三个参数格式的open调用。第三个参数mode 是几个标志按位OR后得到的。  
    S_IRUSR:读权限,文件属主  
    S_IWUSR:写权限,文件属主  
    S_IXUSR:执行权限,文件属主  
    S_IRGRP:读权限,文件所属组  
    S_IWGRP:写权限,文件所属组  
    S_IXGRP:执行权限,文件所属组  
    S_IROTH:读权限,其他用户  
    S_IWOTH:写权限,其他用户  
    S_IXOTH:执行权限,其他用户 

2.1.2 函数close

close系统调用用于“关闭”一个文件。文件描述符被释放,并能够重新使用。

当一个进程终止时,内核自动关闭它所有打开的文件。很多程序都利用了这一功能而不显式地用close关闭文件。

头文件:  
#include <unistd.h>  
  
函数原型:  
int close(int fd);  
  
参数说明:  
int:函数返回值close成功返回0,出错返回-1

2.1.3 函数read

从与文件描述符fd相关的文件中读取nbytes个字节的数据到buf中,返回实际读入的字节数:

头文件:  
#include <unistd.h>  
  
函数原型  
size_t read(int fd, void *buf, size_t nbytes);  
  
参数说明:  
fd:文件描述符,标识要读取的文件。如果为0,则从标准输入读数据。  
buf:缓冲区,用来存储读入的数据。  
nbytes:要读取的字符数。  
size_t:返回值,返回成功读取的字符数,它可能会小于请求的字节数。-1表示出现错误。

2.1.4 函数write

将字符串buf中前nbytes个字节的数据写入与文件描述符fd关联的文件中,返回实际写入的字节数:

头文件:  
#include <unistd.h>  
  
函数原型:  
size_t write(int fd, const void *buf, size_t nbytes);  
  
参数说明:  
fd:文件描述符,目标文件。例如:fd的值为1,就向标准输出写数据,  
buf:待写入的字符串。  
nbytes:要写入的字符数。  
size_t:函数返回值,返回成功写入文件的字符数。-1表示出现错误。

2.1.5 函数ioctl

系统ioctl用来提供一些与特定硬件设备有关的必要控制,所以它的用法随设备的不同而不同。例如:ioctl调用可以用于回绕磁带机或设置串行口的流控特性。因此,ioctl并不需要具备可移植性。此外,每个驱动程序都定义了自己的一组ioctl命令。
为了向用户提供一个统一的接口,设备驱动程序封装了所有与硬件相关的特性。硬件的特有功能可通过ioctl完成。ioctl提供了一个用于控制设备及其描述符行为和配置底层服务的接口。终端、文件描述符、甚至磁带机都可以又为他们定义的ioctl。

头文件:  
#include <unistd.h>  
  
函数原型:  
int ioctl(int fd, int cmd,,,,,,);

2.1.6 函数lseek

lseek对文件描述符fd指定文件的读写指针进行设置,也就是说,它可以设置文件的下一个读写位置。

头文件:  
#include <unistd.h>  
#include <sys/types.h>  
  
函数原型:  
off_t lseek(int filedes, off_t offset, int whence);  
参数说明:  
off_set:参数用来指定位置  
whence:参数定义该偏移值的用法。  
    whence可以取下列值  
    SEEK_SET:offset是一个绝对位置  
    SEEK_CUR:offset是相对于当前位置的一个相对位置  
    SEEK_END:offset是相对于文件尾的一个相对位置  
off_t:lseek返回从文件到文件指针被设置处的字节偏移,失败时返回-1.参数offset的类型off_t是一个与具体实现有关的整数类型,它定义在sys/types.h中 

2.1.7 函数fstat和stat和lstat

fstat系统调用返回与打开的文件描述符相关的文件的状态信息,该信息将会写到buf结构中,buf的地址以参数形式传递给fstat。

头文件:  
#include <unistd.h>   
#include <sys/stat.h>  
#include <sys/types.h>  
函数原型:  
int fstat(int fildes, struct stat *buf);  
int stat(const char *path, struct stat *buf);  
int lstat(const char *path, struct stat *buf); 

相关的函数stat和lstat返回的是通过文件名查到的状态信息。它们的结果基本一致,但当文件是一个符号链接时,lstat返回的是该符号链接本身的信息,而stat返回的是该链接指向的文件的信息。stat结构的成员在不同的UNIX系统上会有所变化,但一般会包括表中所示的内容。

stat成员 说 明  
st_mode 文件权限和文件类型信息  
st_ino 与该文件关联的inode  
st_dev 保存文件的设备  
st_uid 文件属主的UID号  
st_gid 文件属主的GID号  
st_atime 文件上一次被访问的时间  
st_ctime 文件的权限、属主、组或内容上一次被改变的时间  
st_mtime 文件的内容上一次被修改的时间  
st_nlink 该文件上硬连接的个数 

例如,如果想测试一个文件“代表的不是一个目录,设置了属主的执行权限,不再有其他权限”,我们可以使用如下的代码进行测试:

struct stat statbuf;  
mode_t modes;  
stat(“filename”,&statbuf);  
modes = statbuf.st_mode;  
if(!S_ISDIR(modes) && (modes & S_IRWXU) == S_IXUSR) 

stat结构中返回的st_mode标志还有一些与之关联的宏,它们定义在头文件sys/stat.h中。这些宏包括对访问权限、文件类型标志以及一些用于帮助测试特定类型和权限的掩码的定义。
访问权限标志与前面的open系统调用中的内容是一样的。

文件类型标志包括:  
S_IFBLK:文件是一个特殊的块设备。  
S_IFDIR:文件是一个目录。  
S_IFCHR:文件是一个特殊的字符设备。  
S_IFIFO:文件是一个FIFO设备(命名管道)  
S_IFREG:文件是一个普通文件。  
S_FLNK:文件是一个符号链接。  
其他模式标志包括:  
S_ISUID:文件设置了SUID位。  
S_ISGID:文件设置了SGID位。  
用于解释st_mode标志的掩码包括:  
S_IFMT:文件类型。  
S_IRWXU:属主的读/写/执行权限。  
S_IRWXG:属组的读/写/执行权限。  
S_IRWXO:其他用户的读/写/执行权限。

还有一些用来帮助确定文件类型的宏定义。它们只是对经过掩码处理的模式标志和相应的设备类型标志进行比较。它们包括:

S_ISBLK:测试是否是特殊的块设备文件。  
S_ISCHR:测试是否是特殊的字符设备文件。  
S_ISDIR:测试是否是目录。  
S_ISFIFO:测试是否是FIFO设备。  
S_ISREG:测试是否是普通文件。  
S_ISLNK:测试是否是符号链接。

2.1.8 函数dup和dup2

dup,dup2系统调用。dup提供了复制文件描述符的方法,使我们能够通过两个或者更多个不同的文件描述符来访问同一个文件。这可以用于在文件的不同位置对数据进行读写。

头文件:  
#include <unistd.h>  
  
dup系统调用复制文件描述符fildes,返回一个新的文件描述符。  
int dup(int fildes);  
dup2系统调用则是通过明确指定描述符来把一个文件描述符复制为另一个文件描述符  
int dup2(int fildes,int fildes2); 

2.2 C/C++库函数

读写文件通常是大量的数据(这种大量是相对于底层驱动的系统调用所实现的数据操作单位而言),这时,使用库函数就可以大大减少系统调用的次数。这一结果又缘于缓冲区技术。在用户空间和内核空间,对文件操作都使用了缓冲区,例如用fwrite写文件,都是先将内容写到用户空间缓冲区,当用户空间缓冲区满或者写操作结束时,才将用户缓冲区的内容写到内核缓冲区,同样的道理,当内核缓冲区满或写结束时才将内核缓冲区内容写到文件对应的硬件媒介。

2.2.1 函数fopen

函数原型:  
FILE *fopen(const char *filename, const char *mode);  
  
参数说明:  
filename:打开文件的文件名  
mode:打开的方式  
    "r"或"rb":以只读方式打开  
    "w"或"wb":以只写方式打开  
    "a"或"ab":以读方式打开,添加到文件的结尾处  
    "r+"或"rb+"或"r+b":打开更新(读和写)  
    "w+"或"wb+"或"w+b":打开更新,将其长度变为零  
    "a+"或"ab+"或"a+b":打开更新,添加到文件结尾处  
    [注意]b表明这个文件是二进制文件而不是文本文件.  
FILE:返回值,成功是返回一个非空的FILE *指针。失败返回NULL 

2.2.2 函数fread

函数原型:  
size_t  fread(void *ptr, size_t size, size_t nitems, FILE *stream);  
  
参数说明:
ptr: 要读取数据的缓冲区,也就是要存放读取数据的地方。  
size:指定每个单元的长度。  
nitems: 计数,给出要读取单元的个数。  
size_t:返回值,返回实际读取的单元个数,如果小于count,则可能文件结束或读取出错;可以用ferror()检测是否读取出错,用feof()函数检测是否到达文件结尾。

fread函数每次从stream中最多读取count个单元,每个单元大小为size个字节,将读取的数据放到ptr;文件流的位置指针后移 size * count 字节。

2.2.3 函数fwrite

函数原型:  
size_t  fwrite(const coid *ptr, size_t size , size_t nitimes, FILE *stream);  

实际上,由于库函数对文件的操作最终是通过系统调用实现的,因此,每打开一个文件所获得的FILE结构指针都有一个内核空间的文件描述符fd与之对应。同样有相应的预定义的FILE指针:stdin-standard input,stdout-standard output,stderr-standard error。

2.2.4 函数close

fclose库函数关闭指定的文件流stream,使所有尚未写出的数据都写出。因为stdio库会对数据进行缓冲,所以使用fclose是很重要的。如果程序需要确保数据已经全部写出,就应该调用fclose函数。虽然当程序正常结束时,会自动对所有还打开的文件流调用fclose函数,但这样做就没有 机会检查由fclose报告的错误了。

函数原型:  
int fclose(FILE *stream); 

2.2.5 函数fflush

fflush函数的作用是把文件流中所有未写出的数据全部写出。处于效率考虑,在使用库函数的时候会使用数据缓冲区,当缓冲区满的时候才进行写操作。使用fflush函数可以将缓冲区的数据全部写出,而不关心缓冲区是否满。fclose的执行隐含调用了fflush函数,所以不必再fclose执行之前调用fflush。

函数原型:  
int fflush(FILE *stream);

2.2.6 函数fseek

函数原型:  
int fseek(FILE *stream, long int offset, int whence);  
参数说明:  
off_set:参数用来指定位置  
whence:参数定义该偏移值的用法。  
    whence可以取下列值  
    SEEK_SET:offset是一个绝对位置  
    SEEK_CUR:offset是相对于当前位置的一个相对位置  
    SEEK_END:offset是相对于文件尾的一个相对位置  
int:函数返回值。0表示成功,-1表示失败。

2.2.7 函数fgetc和getc和getchar

函数原型:  
int fgetc ( FILE * stream );   
函数说明:  
函数实现,从流stream中读一个字符。可以将标准输入stdin作为它的实参,这时候从标准输入读取一个字符。fgetc函数读字符时遇到文件结束符,函数返回一个文件结束标记EOF。  
  
函数原型:  
int getc(FILE * stream);       
函数说明:  
宏实现,宏实现版的fgetc  
  
函数原型:  
int getchar ( void );       
函数说明:  
宏实现,实现为#define getchar() fgetc(stdin)。  
  
说明:getc、getchar都是通过宏定义借助fgetc实现。  

2.2.8 函数fgets和gets

由于gets不检查缓冲区大小,所以可能会有问题(蠕虫病毒的入口),所以一定要避免使用gets!因为输入多了,GETS还是会读进去,放在BUFFER装满后相邻的内存中,如果buffer相邻的内存没有要用的数据还好,要是存着要用的数据于是就悲剧了,数据就被冲洗掉了,所以gets这个函数很危险。
http://baike.baidu.com/view/700134.htm
注意,putchar和getchar等函数都是把字符当做int而不是char来使用的,这就允许文件尾(EOF)标识取值-1,这是一个超出字符数字编码范围的值。

函数原型:  
char * fgets (char * str, int num, FILE *stream);  
函数说明:  
从流stream中读入最多num个字符到字符数组str中,当遇到换行符时、或读到num-1个字符时停止。自动加上'\0'空字符结尾  
  
函数原型:  
char * gets ( char * str );  
函数说明:  
从标准输入stdin读取一个字符串,遇到换行或结束时候终止。不同于fgets,他没有指定num,所以需要注意字符数组str的大小。

2.2.9 函数fputc和putc和putchar

函数原型:  
int fputc ( int character, FILE * stream );  
函数说明:  
把一个字符写到输出流。失败则返回EOF。  
  
函数原型:  
int putc ( int character, FILE * stream );         
函数说明:  
宏实现,通过宏定义和fputc实现  
  
函数原型:  
int putchar ( int character );          
函数说明:  
宏实现,#define putchar(c) fputc(c, stdout) 

2.2.10 函数fputs和puts

int fputs ( const char * str, FILE * stream );  
int puts ( const char * str );  
说明:两者之间无宏定义实现关系。puts(const char *str)近似等效于fputs(cosnt char *str, stdout),不同点是前者还输出一个'\n'

2.2.11 函数printf和fprintf和sprintf

int printf(const char *format[,argument,...]);  
printf函数把输出送到[标准输出]。   
  
int fprintf(FILE *stream,const char *format[,argument,...])  
fprintf函数把输出送到[指定文件流]。  
  
int sprintf( char *buffer, const char *format [, argument] ... );  
sprintf函数把输出和一个结尾空字符写到作为参数传递过来的[字符串s]里。这个字符串必 须足够大以容纳所有的输出数据。

格式类型控制:

%d:%i:以十进制格式输出一个整数。  
%o:%x:以八进制或十六进制格式输出一个整数。  
%c:输出一个字符。  
%s:输出一个字符串。  
%f:输出一个(单精度)浮点数。  
%e:以科学计数法格式输出一个双精度浮点数。  
%g:以一般格式输出一个双精度浮点数。

2.2.12 函数scanf和fscanf和sscanf

int scanf(const char *format, ...);  
scanf函数从[标准输入]读入到format中。  
  
int fscanf(FILE *stream, const char *format, ...);  
fscanf函数从[文件流(FILE *stream)]读入到format中。  
  
int sscanf(const char *str, const char *format, ...);  
sscanf函数从[字符串str]读入到format中。 

2.2.13 函数setvbuf

设置缓冲区大小,位置。本函数应该在打开流后,立即调用,在任何对该流做输入输出前使用。

函数原型:  
int setvbuf(FILE *stream, char *buf, int type, unsigned size);  
参数说明:  
stream :指向流的指针 ;  
buf : 期望缓冲区的地址;  
type : 期望缓冲区的类型:  
_IOFBF(满缓冲):当缓冲区为空时,从流读入数据。或者当缓冲区满时,向流写入数 据。  
_IOLBF(行缓冲):每次从流中读入一行数据或向流中写入一行数据。  
_IONBF(无缓冲):直接从流中读入数据或直接向流中写入数据,而没有缓冲区。  
size : 缓冲区内字节的数量。

2.2.14 函数ferror

函数原型:  
int ferror(FILE *stream);

ferror 函数测试一个文件流的错误标识。如果该标识被设置就返回一个非零值,否则返回零。

2.2.15 函数feof

函数原型:  
int feof(FILE *stream); 

feof 函数测试一个文件流的文件尾标识。如果该标识被设置就返回一个非零值,否则返回零。

2.2.16 函数clearerr

函数原型:  
void clearerr(FILE *stream); 

clearerr 函数的作用是清除由 stream 指向的文件流的文件尾标识和错误标识。

2.3 系统调用与库函数的区别

库函数调用系统调用
#include <stdlib.h>#include <unistd.h>
移植性好,在所有的ANSI C编译器版本中,C库函数是相同的,系统无关移植性差,各个操作系统的系统调用是不同的
调用函数库中的一段程序(或函数)【本质通过系统调用实现】调用系统内核的服务
与用户程序相联系是操作系统的一个入口点
在用户地址空间执行 在内核地址空间执行
它的运行时间属于“用户时间”它的运行时间属于“系统”时间
属于过程调用,调用开销较小需要在用户空间和内核上下文环境间切换,开销较大
在C函数库libc中有大约300个函数在UNIX中大约有90个系统调用
存在缓冲机制无缓冲机制
使用流(stream)概念,它被实现为指向结构FILE的指针。
在启动程序时,有三个文件流是自动打开的。他们是:
stdin: 标准输入
stdout: 标准输出
stderr: 标准错误输出
使用文件描述符fd,实质为一个非负整型
Linux系统默认分配了3个文件描述符值:
0:标准输入
1:标准输出
2:标准错误输出

2.4 文件描述符与文件流转换

2.4.1 函数fileno

函数原型:  
int fileno(FILE *stream); 

fileno 函数确定文件流使用的文件描述符。

2.4.2 函数fdopen

函数原型:  
FILE *fdopen(int fildes, const char *mode);

fdopen 在一个已打开的文件描述符上创建一个新的文件流,实际上,这个函数的作用是为一个已经打开的文件描述符提供 stdio 缓冲区。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值