首页 > 代码库 > open参数O_DIRECT的学习

open参数O_DIRECT的学习

open有两个原形:


int open(const char *pathname, int flags); 


int open(const char *pathname, int flags, mode_t mode); 


这三个参数比较容易看出它们的含义,pathname是文件路径,flags打开文件的标志, mode是打开的模式,返回值应该是打开文件的句柄。


flags标志有下面的定义:


O_RDONLY 以只读的方式打开文件


O_WRONLY 以只写的方式打开文件


O_RDWR 以读写的方式打开文件


O_APPEND 以追加的方式打开文件


O_CREAT  创建一个文件


O_EXEC 如果使用了O_CREAT而且文件已经存在,就会发生一个错误


O_NOBLOCK 以非阻塞的方式打开一个文件


O_TRUNC  如果文件已经存在,则删除文件的内容 


O_RDONLY、O_WRONLY、O_RDWR三个标志只能使用任意的一个。


扩展阅读: http://blog.csdn.net/yao_guet/article/details/6460900  


open的flags参数中,有一个打开标识:O_DIRECT


网上对于该标志的解释如下:
O_DIRECT (Since Linux 2.4.10)
       Try to minimize cache effects of the I/O to and from this file.  In general this
       will degrade performance, but it is useful in special situations, such  as  when
       applications do their own caching.  File I/O is done directly to/from user space
       buffers.  The I/O is synchronous, that is, at the completion  of  a  read(2)  or
       write(2), data is guaranteed to have been transferred.  See NOTES below for 
       further discussion.


       A semantically similar (but deprecated) interface for block devices is described
       in raw(8).


翻译:
该标示是为了写文件或者读文件的I/O高度缓存开销的最小化。一般情况下,该标志会降低性能,但是,在特殊情况下,还是有作用的,例如当应用程序使用自己的高速缓存的时候。文件I/O直接接触到用户内存。I/O操作是同步的,也就是说,一旦read(2)或者write(2)完成,数据可以保证传输完毕。见下面的注释关于更多的讨论。
一个语义相似(但是已经弃用)的块设备处理的接口函数是详见raw(8).


CONFORMING TO
       SVr4, 4.3BSD, POSIX.1-2001.  The  O_DIRECTORY,  O_NOATIME,  and  O_NOFOLLOW  flags  are
       Linux-specific, and one may need to define _GNU_SOURCE to obtain their definitions.


       The  O_CLOEXEC flag is not specified in POSIX.1-2001, but is specified in POSIX.1-2008.


       O_DIRECT is not specified in POSIX; one has to define _GNU_SOURCE to  get  its  defini-
       tion.


NOTES
   O_DIRECT
       The  O_DIRECT  flag  may  impose  alignment  restrictions  on the length and address of
       userspace buffers and the file offset of I/Os.  In Linux alignment restrictions vary by
       file  system  and  kernel  version and might be absent entirely.  However there is cur-
       rently no file system-independent  interface  for  an  application  to  discover  these
       restrictions  for  a  given  file  or file system.  Some file systems provide their own
       interfaces for doing so, for example the XFS_IOC_DIOINFO operation in xfsctl(3).


       Under Linux 2.4, transfer sizes, and the alignment of the user buffer and the file off-
       set  must  all  be multiples of the logical block size of the file system.  Under Linux
       2.6, alignment to 512-byte boundaries suffices.


       The O_DIRECT flag was introduced in SGI IRIX, where it has alignment restrictions simi-
       lar  to  those of Linux 2.4.  IRIX has also a fcntl(2) call to query appropriate align-
       ments, and sizes.  FreeBSD 4.x introduced a flag of the same name, but  without  align-
       ment restrictions.


       O_DIRECT  support  was added under Linux in kernel version 2.4.10.  Older Linux kernels
       simply ignore this flag.  Some file systems may not implement the flag and open()  will
       fail with EINVAL if it is used.


       Applications  should  avoid  mixing O_DIRECT and normal I/O to the same file, and espe-
       cially to overlapping byte regions in the same file.  Even when the  file  system  cor-
       rectly handles the coherency issues in this situation, overall I/O throughput is likely
       to be slower than using either mode alone.  Likewise, applications should avoid  mixing
       mmap(2) of files with direct I/O to the same files.


       The behaviour of O_DIRECT with NFS will differ from local file systems.  Older kernels,
       or kernels configured in certain ways, may not support this combination.  The NFS  pro-
       tocol does not support passing the flag to the server, so O_DIRECT I/O will only bypass
       the page cache on the client; the server may still cache the I/O.  The client asks  the
       server  to  make the I/O synchronous to preserve the synchronous semantics of O_DIRECT.
       Some servers will perform poorly under these circumstances, especially if the I/O  size
       is  small.   Some servers may also be configured to lie to clients about the I/O having
       reached stable storage; this will avoid the performance penalty at some  risk  to  data
       integrity  in the event of server power failure.  The Linux NFS client places no align-
       ment restrictions on O_DIRECT I/O.


       In summary, O_DIRECT is a potentially powerful tool that should be used  with  caution.
       It is recommended that applications treat use of O_DIRECT as a performance option which
       is disabled by default.


              "The thing that has always disturbed me about O_DIRECT is that the whole  inter-
              face  is  just  stupid,  and  was probably designed by a deranged monkey on some
              serious mind-controlling substances." — Linus


纵使加上open需要所有的头文件,但是,如果使用该标志的话,还需要在引用头文件“fcntl.h”之前,添加:
//O_DIRECT
#define __USE_GNU 1
#include <fcntl.h>
否则,依旧会出现类似下面的错误:
file.c:28: 错误:‘O_DIRECT’未声明(在此函数内第一次使用)
file.c:28: 错误:(即使在一个函数内多次出现,每个未声明的标识符在其
file.c:28: 错误:所在的函数内也只报告一次。)


扩展阅读: http://bbs.chinaunix.net/thread-683493-1-1.html (O_DIRECT未声明错误解决)


写入数据在用户内存中的首地址要为512的整数倍(如果为块大小的整数倍就更没有问题),所以不能使用数组保存数据,而要使用malloc来申请空间。
const int SIZE = 4096*10(每次写入文件数据块大小)
char *p = malloc(SIZE+512); //多申请512空间
char *q = 0;
q = p&(~511);// 使q是512字节对齐的,q空间的地址范围为SIZE。
int fd = open("文件路径", O_RDWR|O_DIRECT);//省略打开失败判断
write(fd, q, SIZE);//写入
扩展阅读: http://blog.csdn.net/hhtang/article/details/6605951 


mmap分配的话,首地址就会是4K对齐。
malloc分配的内存,不管多大,开始地址都不固定。


因此,如果使用malloc,那么,就需要内存首地址的对齐。如果使用mmap的话,则不用考虑这些。


在使用write写入文件的时候:
ssize_t write(int fd, const void *buf, size_t count);
参数count也应该是512的整数倍,因此,如果我希望写count个字节的话,那么就需要在write之前,进行如下的操作:
uint32_t  wLen = (pos + 511) & ~511U;
write(fd, buffer, wLen);


如果是512倍数大小写入文件的话,文件的内容不一定是512的整数倍,因此,最后一步对于文件的处理就是:
truncate(fullpath, count);


注意:
1、因为文件的长度是按照512对齐的,而文件内容大小不一定是512的整数倍,因此最后需要进行truncate处理
2、因为写入文件是按照512对齐的,所以,追加文件时,内存开始的地址不一定是512的倍数,因此,该方式不适合文档内容的追加,只适合一次性写入文档。
3、使用“O_DIRECT”参数适合一次性写入文件内容比较大的情况,平常情况下,无需使用该标志。


下面我的一个测试用例,数据只是简单地几十个字节,只是为了说明使用方法罢了。如下:


/******************************************************************
 *  @file       file_O_DIRECT.c
 *  @version    v1.0 
 *  @author     ymm 
 *  @date       2015/01/29
 *  @brief      open函数O_DIRECT的使用
 *  @history      
 *  1、2015/01/29  author ymm    初步完成
 ******************************************************************/
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
//O_DIRECT
#define __USE_GNU 1
#include <fcntl.h>
#include <stdint.h>


#include <sys/mman.h>
#include <errno.h>


#define READFILE_BUFFER_SIZE 1 * 1024 * 1024
char fullpath[20]="a.txt";


int Deal()
{
    int fd = open(fullpath, O_DIRECT|O_CREAT|O_RDWR|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);    
    if( fd < 0 ){       
        printf("Fail to open output protocol file: \'%s\'", fullpath);
        return -1;
    }


    uint32_t bufferLen = (READFILE_BUFFER_SIZE) ;
    char *buffer = (char *)mmap(0, bufferLen, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);    
    if ( MAP_FAILED == buffer )
    {
        printf("mmap error!errno=%d\n",errno);
        return -1;
    }
    //mmap返回地址一直4k对齐
    printf("mmap first address:%p\n",buffer);
    uint32_t address=(uint32_t)buffer;
    printf("address=%u;address%4096=%u\n",address,address%4096);


    uint32_t pos = 0, len = 0;
    len = snprintf((char *)(buffer + pos), 128, "my name is yangmingmingming\r\n"); 
    pos += len;


    if( pos ){
        buffer[pos] = '\0';        
        printf("pos=%d,buffer=%s\n",pos,buffer);
        uint32_t wLen = (pos + 4095) & ~4095U;       
        write(fd, buffer, wLen);  
        truncate(fullpath, pos);
    }    
    if( buffer ){
        munmap(buffer, bufferLen);
        buffer = NULL;
    }
    if( fd > 0 ){   
        close(fd);        
    }
    return 0;
}


int main()
{
    Deal();
    return 0;
}



open参数O_DIRECT的学习