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);
}