linux文件I/O
2011-10-29 19:11 马哈鱼 阅读(2872) 评论(0) 编辑 收藏 举报Linux系统中的大多数文件I/O只用到5个函数:open,read,write,lseek及close。本专题所涉及的函数都被称为不带缓冲的I/O,不带缓冲指的是read或read都是通过内核的一个系统调用实现的,它们是POSIX.1和Single UNIX Specification的组成部分。我们将进一步讨论多个进程间的文件共享及所涉及的内核数据结构。
文件描述符
文件描述符是一个非负整数,当使用open或create时,会返回一个文件描述符来标识该文件,可将其作为参数传递给read或write使用。文件描述符0(符号常量:STDIN_FILENO)与进程的标准输入相关联,文件描述符1(符号常量:STDOUT_FILENO)与进程的标准输出相关联,文件描述符2(符号常量:STDERR_FILENO)与进程的标准输出相关联,这些常量都定义在<unistd.h>中。文件描述符的范围是0~OPEN_MAX,OPEN_MAX的值可以通过函数调用sysconf( _SC_OPEN_MAX );取得,下面是一个示例:
#include<stdio.h>
#include<unistd.h>
int
main( void )
{
long open_max = sysconf( _SC_OPEN_MAX );
printf( "%d\n", open_max );
return 0;
}
输出:1024
open函数
#include<fcntl.h>
int open( const char* pathname, int oflag, .../* mode_t mode */ );
用以下一个或多个常量进行“或”运算构成oflag参数(这些常量在<fcntl.h>中定义):
O_RDONLY,O_WRONLY,O_RDWR 这三个常量必须且只能指定一个。
以下常量可选择一个或多个:
O_APPEND 在文件尾端追加
O_CREAT 若此文件不存在,则创建。使用此选项时,要用到mode参数以设定新文件的访问权限。
O_EXCL 测试一个文件是否存在。
O_TRUNC 如果文件成功打开,则将其长度截短为0。
O_NOCTTY 如果pathname参数指的是终端设备,则不将该设备作为此进程的控制终端。
O_NONBLOCK 如果pathname指的是一个FIFO,块特殊文件或字符特殊文件,则该选项为文件的本次打开操作和后续的I/O操作设置非阻塞模式。
注:*非阻塞模式:在I/O操作不能完成时,调用立即出错并返回,而不是永远等待。
O_DSYNC 使该文件所有的write操作等待,直到对该文件所有的I/O操作都完成之后,但是如果write操作不影响读取刚写入的数据时,则不用等待文件属性被更新。
O_RSYNC 使该文件所有的read操作等待,直到对该文件所有的write操作都完成之后。
O_SYNC 使该文件所有的write操作等待,直到对该文件所有的I/O操作都完成之后。
注:*Linux2.4.22将O_DSYNC和O_RSYNC处理成与O_SYNC相同,即文件的数据和属性总是同步更新。
close函数
#include<unistd.h>
int close( int file_des );
关闭一个文件时,会释放该文件上的所有记录锁。进程终止时,内核会自动关闭它所打开的所有文件,但是显式调用close函数更安全。
lseek函数
#include<unistd.h>
off_t lseek( int file_des, off_t offset, int whence );
whence取值:
SEEK_SET 将文件的读写偏移量设置为距离文件首段offset个字节处。
SEEK_CUR 将文件的读写偏移量设置为距离当前值加offset个字节处,offset可为正负。
SEEK_END 将文件的读写偏移量设置为距离文件尾端offset个字节处,offset可为正负。
成功时返回新的文件偏移量,失败时返回-1。利用返回值可以测试文件描述符是否能够设置读写偏移量(管道,FIFO和网络套接字不能设置偏移量)。当读写偏移量大于文件长度时,写操作将会在文件中构成一个空洞,空洞被读为0,但是空洞并不占用磁盘空间,其处理方式与文件系统的实现有关。下面的程序将创建一个具有空洞的文件:
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
char buf[] = "1234567890";
char buf2[] = "abcdefghij";
int
main( void )
{
remove( "file.hole" );
int file_des = open( "file.hole", O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR|S_IXUSR );
if ( file_des == -1 )
{
write( STDOUT_FILENO, "OPEN FILE ERROR!\n", 20 );
exit( 0 );
}
if ( lseek( file_des, 5, SEEK_SET ) == -1 )
{
write( STDOUT_FILENO, "FIRST SEEK ERROR!\n", 20 );
exit( 0 );
}
size_t write_bytes = write( file_des, buf, strlen( buf ) );
if ( write_bytes < strlen( buf ) )
{
write( STDOUT_FILENO, "FIRST WRITE ERROR!\n", 20 );
exit( 0 );
}
if ( lseek( file_des, 5, SEEK_END ) == -1 )
{
write( STDOUT_FILENO, "SECOND SEEK ERROR!\n", 20 );
exit( 0 );
}
write_bytes = write( file_des, buf2, strlen( buf ) );
if ( write_bytes < strlen( buf2 ) )
{
write( STDOUT_FILENO, "SECOND WRITE ERROR!", 20 );
exit( 0 );
}
close( file_des );
return 0;
}
使用命令od查看文件file.hole的内容:
[root@localhost CC++]# od -c file.hole
0000000 \0 \0 \0 \0 \0 1 2 3 4 5 6 7 8 9 0 \0
0000020 \0 \0 \0 \0 a b c d e f g h i j
可以看到,文件的开始处有一个空洞,中间有一个空洞,都被读为0。
read和write函数:
#include<unistd.h>
ssize_t read( int file_des, void *buf, size_t nbytes );
返回值:成功返回读到的字节数,若已到文件结尾返回0,出错返回-1。
ssize_t write( int file_des, const void *size_t, size_t nbytes );
返回值:成功返回已写的字节数,出错返回-1。
pread和pwrite函数:
#include<unistd.h>
ssize_t pread(int file_des, void *buf, size_t nbytes, off_t off_set);
ssize_t pwrite( int file_des, const void *buf, size_t nbytes, off_t off_set );
注:*pread和pwrite函数使lseek和read/write操作成为一个原子操作,pread/pwrite和lseek/read/write的重要区别在于:
调用pread/pwrite时,无法中断定位和读(写)操作;
pread/pwrite不能更新文件指针,可以使用ftell或fgetpos函数获取当前读写位置。
dup和dup2函数:
#include<unistd.h>
int dup( int file_des );
int dup2( int file_des, int file_des2 );
dup和dup2都用于复制一个现有的文件描述符,成功返回新的文件描述符,出错返回-1,区别是:
dup一定返回当前可用的最小文件描述符;
dup2指定参数file_des2为新的文件描述符,当file_des2打开时,先将其关闭,若file_des和file_des2相等,返回file_des2而不关闭它。
注:*fcntl函数也可以复制文件描述符,稍后将会介绍。
sync,fsync和fdatasync函数:
#include<unistd.h>
int sync( void );
int fsync( int file_des );
int fdatasync( int file_des );
以上函数都用于保证物理文件和高速缓存数据的一致性,区别是:sync只是将所有修改过的块缓冲区排入写队列,并不等待写磁盘操作的完成就返回;fsync只是针对由文件描述符指向的单一文件,并且等待写磁盘操作完成再返回,但它只影响文件的数据部分;fdatasync和fsync类似,但除了数据之外,fdatasync还会同步更新文件的属性。
fcntl函数:
#include<fcntl.h>
int fcntl( int files_des, int cmd, .../* int arg */ );
fcntl函数可以改变已经打开的文件的性质。
cmd参数取值(一共有10中,这里先介绍前7种):
F_DUPFD 复制文件描述符files_des,新文件描述符作为函数返回值返回。新描述符有自己的文件描述符标志,其FD_CLOEXEC文件描述符标志被清除。
F_GETFD 对应于files_des的文件描述符标志作为函数值返回,当前只定义了一个文件描述符标志FD_CLOEXEC。
F_SETFD 对于files_des设置文件描述符标志,新标志值按第三个参数设置。
F_GETFL 对应于files_des的文件状态标志作为函数值返回。文件状态标志在open函数已经说明。
F_SETFL 将文件状态标志设置为第三个参数的值。可以更改的标志是:O_APPEND,O_NONBLOCK,O_SYNC,O_DSYNC,O_RSYNC,O_FSYNC,0_ASYNC。
F_GETOWN 取当前接收SIGIO和SIGURG信号的进程ID或进程租ID。
F_SETOWN 设置接收SIGIO和SIGURG信号的进程ID或进程租ID。正的arg参数表示一个进程ID,负的arg参数表示等于arg的绝对值的进程租ID。