代码改变世界

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。