Unix I/O相关

高级I/O

简介

题目虽然叫做高级I/O,但是内容并不高级.本文主要介绍Unix下的异步IO,IO多路转接,带缓冲区的读写,以及存储映射

非阻塞式I/O

本文并不打算详细的介绍非阻塞式I/O,实际上在管道的文章里面已经提及过一些.读出或者写入不会阻塞,当有阻塞的情况出现时函数会立即返回一个error.
非阻塞I/O的一个缺点就是我们需要轮询直到我们的全部操作完成,这会对CPU造成很大的浪费.

I/O multiplexing(多路转接)

  • 想象一下这样的场景,我们需要将10个文件合并成为1个文件.假如此时我们只可以用read以及write,那么普通的写法我们应该依次read每次文件,然后write到目标文件中.
    重复这一过程直到所有文件读取完毕.但这样做的后果就是,我们每次读操作都是阻塞的(不考虑优化),这样大大影响了工作效率.
  • 面对这种情况,我们可以使用I/O multiplexing
    • select函数:

      • 函数原型:
        #include<sys/select.h>
      
        int select(int maxfdpl, fd_set *restrict readfds, fd_set *restrict writefds,
                    fd_set *restrict exceptfds, struct timeval *restrict tvptr);
                            //返回值:准备就绪的描述符数目,超时则返回0,出错则返回-1
      
      • 参数maxfdpl: 最大的文件描述符值加1.通过指定我们需要关注的最大描述符,内核就可以在这个范围内寻找打开的位.
      • 三个fds参数: 分别声明我们关心的read,write以及except行为的描述符.实际上该结构是long类型的数组(来源于百度百科).如果是NULL则表明我们不关心这个行为.
        当有小于maxfdpl的描述符处于该状态的时候,对应序号的值将被设置为1.每次select返回并被处理完后都需要重置该结构.
      • tvptr参数: 指定愿意等待的时间长度.
        tvptr == NULL : 永远等待,当指定的描述符准备好或捕捉到一个信号则返回.
        tvptr->tv_sec == 0 && tvptr->tv_usec == 0 : 不等待,测试所有描述符状态并马上返回.这是轮询系统找到多个描述符状态而不等待的方法.
        tvptr->tv_sec != 0 || tvptr->tv_usec != 0 : 等待指定的秒数或者毫秒数.如果超时则返回0.
      • 返回值可能有三种情况:
        • 0: 超时并且没有描述符可操作
        • -1: 异常,当没有描述符准备好但是捕获到信号的时候返回-1.
        • 大于0: 返回准备好的描述符之和.如果一个描述符既可读也可写,那么针对这个描述符则会返回2.
      • 对于可操作的定义:
        • 对于read,write来说,可进行该操作则被成为是准备好的.
        • 对于except来说,在某些描述符上有一个未决的异常条件则认为该描述符是准备好的.
    • pselect函数:

      • 函数原型:
        #include<sys/select.h>
      
        int pselect(int maxfdpl, fd_set *restrict readfds, fd_set *restrict writefds,
                    fd_set *restrict exceptfds, const struct timespec *restrict tsptr,
                    const sigset_t *restrict sigmask);
      
      • 除了以下几点,pselect和select相同
        • pselect中的timespec不同于select的timeval,timespec以秒与纳秒表示超时,而timeval以秒和微秒表示超时.
        • pselect通过sigmask参数指定一个信号屏蔽字.以原子操作的方式屏蔽该信号.
    • poll函数:

      • 函数原型:
        #include <poll.h>
        int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);
                        //返回值:准备就绪的描述符数目,若超时则返回0,出错则返回-1
      
        struct pollfd {
            int   fd;
            short events;
            short revents;
        }
      
      • fdarray参数:
        • events中测试了可读性,可写性以及异常.
        • revents由内核设置,用于说明描述符发生了哪些事件.
      • timeout参数:
        • timeout == -1 永远等待,当捕捉到信号时,poll返回-1,errno设置为EINTR
        • timeout == 0 不等待,测试所有描述符并立即返回.
        • timeout > 0 等待timeout毫秒,当指定的描述符之一已经准备好或者timeout超时则立即返回.

Posix异步I/O

  • Posix的异步I/O接口使用AIO控制块来描述I/O操作,aiocb结构定义了AIO控制块.
  struct aiocb{
      int                         aio_fildes;
      off_t                       aio_offset;
      volatile            void    *aio_buf;
      size_t                      aio_nbytes;
      int                         aio_reqprio;
      int struct sigevent         aio_sigevent;
      int                         aio_lio_opcode;
  }
- aio_fildes字段表示被打开用来读或者写的文件描述符.
- aio_offset字段表示读或者写操作的指定偏移量.(文件以O_APPEND方式打开该字段被忽略)
- aio_buf是用来作为异步的缓冲区.
- aio_nbytes表示要读或者写的字节数.
- aio_sigevent用来通知内核,在关心的事件完成后如何通知应用程序.

```
    struct sigevent{
        int         sigev_notify
        int         sigev_signo;
        union       sigev_value;
        void (*sigev_notify_function) (union sigval);
        pthread_attr_t *sigev_notify_attributes;
    }
```

sigev_notify: SIGEV_NONE 请求完成后不通知进程.  SIGEV_SIGNAL 请求完成后产生sigev_signo信号通知进程. SIGEV_THREAD 异步请求完成后调用sigev_notify_function并在分离的线程中执行.
  • 在进行异步操作之前,我们需要先初始化AIO控制块,然后调用aio_read,aio_write
  #include<aio.h>
  int aio_read(struct aiocb *aiocb);
  int aio_write(struct aiocb *aiocb);
                  //成功则返回0,出错则返回-1.
  • 当这些函数成功返回时,异步I/O请求就已经被内核放入等待队列中了.返回值与I/O的结构没有任何关系.

存储映射I/O

存储映射I/O能将一个磁盘文件映射到存储空间中的一个缓冲区上,于是当从缓冲区取出数据时,就相当于读文件中的相应字节.当将数据存入缓冲区时,相应字节也会自动写入文件.

  • 函数原型:
  #include<sys/mman.h>

  void *mmap(void *addr, size_t len, int prot, int flag, int fd, off_t off)
  • addr参数用于指定映射存储区的起始地址.通常将其设置为0,这表示由系统选择映射区的起始地址.

  • fd指定映射的文件描述符.

  • prot指定了映射存储区的保护要求 PROT_READ(只读),PROT_WRITE(只写),PROT_EXEC(可执行),PROT_NONE(不可访问). 需要注意的是不能超过open的访问权限.

  • off指定了从文件起始位置的偏移量.

  • 在后面的共享内存中会详细介绍关系存储映射的一些内容.

  • 共享内存的读写与操作系统的虚拟存储关系非常大,所以想把这些内容单独拿出来写一写.下面放一个复制文件的例子.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/mman.h>

#define COPYINCR (1024 * 1024 * 1024) //1GB

#define F_IN "testForIO.txt"
#define F_OUT "testForIO_OUT.txt"

int main(int argc, char const *argv[]) {
    int         fdin,fdout;
    void        *src,*dst;
    size_t      copysz;
    struct stat sbuf;
    off_t       fsz = 0;

    if((fdin = open(F_IN, O_RDONLY)) < 0)
        fprintf(stderr, "Can't open the file %s\n", F_IN);


    if((fdout = open(F_OUT, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)) < 0)
        fprintf(stderr, "Can't create the file %s\n", F_OUT);


    if(fstat(fdin, &sbuf) < 0) //得到文件的长度
        fprintf(stderr, "fstat error\n");


    if(ftruncate(fdout, sbuf.st_size) < 0) //设置输出文件的长度
        fprintf(stderr, "ftruncate error\n");

    while (fsz < sbuf.st_size) {
        if((sbuf.st_size - fsz) > COPYINCR)
            copysz = COPYINCR;
        else
            copysz = sbuf.st_size - fsz;

        if((src = mmap(0, copysz, PROT_READ, MAP_SHARED, fdin, fsz)) == MAP_FAILED)
            fprintf(stderr, "mmap error for input\n");
        if((dst = mmap(0, copysz, PROT_READ | PROT_WRITE, MAP_SHARED, fdout, fsz)) == MAP_FAILED)
            fprintf(stderr, "mmap error for output\n");

        memcpy(dst, src, copysz);//内存复制
        munmap(src, copysz);//解除映射区
        munmap(dst, copysz);

        fsz += copysz;
    }

    exit(0);
}

posted @ 2017-03-12 14:06  XLLL  阅读(188)  评论(0编辑  收藏  举报