zoukankan      html  css  js  c++  java
  • 文件I/O详解

    对文件进行读写操作前,进程为什么要先打开文件?

    内核会为每个进程配合一个文件表(file table),这文件表是以fd(文件描述符)进行索引的。对应的值是一个指向inode的指针和文件的一些元数据(文件内容位置,访问权限)。

    进程利用fd文件描述符就能拿到inode指针得到文件的物理位置进行对文件的读写;

    所以打开文件的操作就是让内核创建一个新的fd文件描述符和inode指针和元数据的索引;

    目录与文件:

    每个目录下的内容是一个个链接:文件名映射到inode的连接。

    当在目录下访问一个文件内容时,内核找到文件名对应的inode编号,将inode编号发送给文件系统由文件系统拿到inode下记录文件在磁盘的物理位置。

    文件:

    read()函数:

    read()函数对于:

    1.普通文件:read函数是不会阻塞的,函数会在规定时间内返回结果;

    2.终端:read函数会一直阻塞等待读取直到读取到换行符后才返回;

    3.网络文件描述符:read函数会一直阻塞直到有数据到来;

    一个简单且充满bug的read()函数使用:

    char buf[10] = {0};
    size_t len = sizeof(buf);
    ssize_t ret = read(fd, buf, len);
    if (-1 == ret)
    {
         //error      
    }

    read函数并不能保证返回值读取到的数据一定等于指定的sizeof(buf)大小;

    read函数返回值有几种情况:

    1. 比指定大小要小的非0整数值:read函数系统调用被信号打断、可供读取的数据少于指定值、管道被破坏;
    2. 返回值为0:read文件位置指针已经读到文件末尾EOF;
    3. 返回值为-1:
      1. errno被设置为EINTR,read函数被信号打断,可以重新调用read();
      2. errno被设置为EAGAIN,在read非阻塞模式下表示还未有数据可以读。稍后应该再进行read函数的调用;
      3. errno被设置为非前两个,表示更严重的错误发生;

    为了符合以上所有返回值情况并确保读取到所有应读取的数据,read函数的正确调用如下:

    char buf[10] = {0};
    char *p = buf;
    size_t len = sizeof(buf);
    ssize_t = ret;
    while(0 != (ret = read(fd, p, len)))
    {
        if (-1 == ret)
      {
        if (errno == EINTR)
          continue;
        else
          break;
      }
    len -= ret; p += ret; }

    write()函数:

    write()函数同理要考虑部分写的问题(read()函数考虑部分读问题);

    write()系统调用以后数据是写到了内核的缓冲区但并没有立即写回磁盘,有几个系统调用用于写同步。即立马写回磁盘。

    多路复用:

    select()函数系列:

    int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    FD_CLR(int fd, fd_set *set);
    FD_ISSET(int fd, fd_set *set);
    FD_SET(int fd, fd_set *set);
    FD_ZERO(fd_set *set);

    工作方式:

    select将文件描述符分为三类集合:可读文件描述符集合、可写文件描述符集合、异常文件描述符集合。

    将需要监视的文件描述符放入指定的集合中传给select函数,函数返回后会在三个集合中保留可进行无阻塞操作的文件描述符。下一次调用是应调用FD_ZERO对集合初始化清空。

    用户空间(标准空间)I/O:

    出现的原因:

    所有的磁盘操作都是以块为单位进行的,如果I/O的大小是块的整数倍那么性能将有所提升。

    另外将尽可能减少系统调用次数,进程陷入内核态再到用户态所耗费的时间对性能也有一定的影响。

    因此就出现了用户空间(用户缓冲式)I/O。每次I/O其实是操作用户空间上的缓冲区,当用户空间的缓冲区大小等于块大小才执行系统调用进行磁盘操作。

    操作:

    fopen打开文件:一个已经打开的文件叫做一个流。fopen会新建一个文件描述符fd索引,返回指向文件描述符fd的指针。

    fclose():关闭流。关闭前会将缓冲区内容刷新至磁盘。

    操作流程:

    标准I/O的写操作:从数据所在的缓冲区(用户空间上)——标准I/O缓冲区(用户空间上)——调用write()函数复制到内核缓冲区

    标准I/O的读操作:从内核缓冲区——调用read()函数复制到标准I/O缓冲区——复制到程序缓冲区(用户空间)

    系统I/O的写操作:从数据所在的缓冲区(用户空间上)——调用write()函数复制到内核缓冲区

    系统I/O的读操作:从内核缓冲区——调用read()函数复制到程序缓冲区(用户空间)

    mmap内存映射:

    优点:

    1.直接将磁盘数据映射到用户空间内存中,避免了标准I/O和系统调用I/O多余的数据拷贝。同时还减少了系统调用次数,性能提升明显。

    2.多个进程映射到同一个文件对象的时候,数据是在进程间共享的。私有的内存区域写入时是写时复制。

    缺点:

    1.内存映射是以页为单位映射,若是文件太小则会造成过多的内存碎片。

    几个术语:

    synchronized(同步化):

    对于写入操作:进程会等待数据刷新至磁盘后才返回。确保缓冲区数据和磁盘数据是一致的。

    对于读取操作:读取操作总是同步化的,因为读取旧的数据没有意义。可以理解为读取磁盘的数据。

    nonsynchronized(非同步化):

    synchronous(同步):

    对于写入操作:进程会等待写入操作写进内核缓冲区以后才返回。

    对于读取操作:进程会等待所要读取的数据写到用户空间缓冲区后才返回。

    asynchronous(异步):

    对于写入操作:进程发起写操作请求就返回,无论是否有数据还在用户空间缓冲区。

    对于读取操作:进程发起读操作请求就返回,无论是否有数据可供读取。

  • 相关阅读:
    PLSQL过程创建和调用
    约束定义及相关用法
    序列和索引
    控制用户访问
    ORACLE常用数据字典
    管理对象与数据字典
    Oracle enterprise linux系统的安装以及ORACLE12C的安装
    SUSE12的虚拟机安装以及ORACLE12C的安装
    PLSQL developer开发工具相关配置
    设计模式之六则并进
  • 原文地址:https://www.cnblogs.com/jialin0x7c9/p/12031877.html
Copyright © 2011-2022 走看看