linux 异步IO通信
一. 回顾
做java开发的,一定对BIO,NIO,AIO通信很了解了,现在再在下面罗列一下:
同步阻塞IO(JAVA BIO):
同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
同步非阻塞IO(Java NIO) :
同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问。
异步阻塞IO(Java NIO):
此种方式下是指应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,那么为什么说是阻塞的呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄(如果从UNP的角度看,select属于同步操作。因为select之后,进程还需要读写数据),从而提高系统的并发性!
(Java AIO(NIO.2))异步非阻塞IO:
在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。
BIO、NIO、AIO适用场景分析:
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
针对Java开发,基本上也就上面的三种通信模型了。我们都知道Linux下的五种I/O模型
1)阻塞I/O(blocking I/O)
2)非阻塞I/O (nonblocking I/O)
3) I/O复用(select 和poll) (I/O multiplexing)
4)信号驱动I/O (signal driven I/O (SIGIO))
5)异步I/O (asynchronous I/O (the POSIX aio_functions))
前四种都是同步,只有最后一种才是异步IO。是的,异步IO是今天介绍的重点。
二. 异步IO通信版本
异步IO通信在linux下有两个版本:glibc版本,linux内核版本。
2.1 glibc版本
2.1.1 接口和实现信息
int aio_read(struct aiocb *aiocbp); /* 提交一个异步读 */
int aio_write(struct aiocb *aiocbp); /* 提交一个异步写 */
int aio_cancel(int fildes, struct aiocb *aiocbp); /* 取消一个异步请求(或基于一个fd的所有异步请求,aiocbp==NULL) */
int aio_error(const struct aiocb *aiocbp); /* 查看一个异步请求的状态(进行中EINPROGRESS?还是已经结束或出错?) */
ssize_t aio_return(struct aiocb *aiocbp); /* 查看一个异步请求的返回值(跟同步读写定义的一样) */
int aio_suspend(const struct aiocb * const list[], int nent, const struct timespec *timeout); /* 阻塞等待请求完成 */
其中,struct aiocb主要包含以下字段:
int aio_fildes; /* 要被读写的fd */
void * aio_buf; /* 读写操作对应的内存buffer */
__off64_t aio_offset; /* 读写操作对应的文件偏移 */
size_t aio_nbytes; /* 需要读写的字节长度 */
int aio_reqprio; /* 请求的优先级 */
struct sigevent aio_sigevent; /* 异步事件,定义异步操作完成时的通知信号或回调函数 */
glibc的aio实现是比较通俗易懂的:
1、异步请求被提交到request_queue中;
2、request_queue实际上是一个表结构,"行"是fd、"列"是具体的请求。也就是说,同一个fd的请求会被组织在一起;
3、异步请求有优先级概念,属于同一个fd的请求会按优先级排序,并且最终被按优先级顺序处理;
4、随着异步请求的提交,一些异步处理线程被动态创建。这些线程要做的事情就是从request_queue中取出请求,然后处理之;
5、为避免异步处理线程之间的竞争,同一个fd所对应的请求只由一个线程来处理;
6、异步处理线程同步地处理每一个请求,处理完成后在对应的aiocb中填充结果,然后触发可能的信号通知或回调函数(回调函数是需要创建新线程来调用的);
7、异步处理线程在完成某个fd的所有请求后,进入闲置状态;
8、异步处理线程在闲置状态时,如果request_queue中有新的fd加入,则重新投入工作,去处理这个新fd的请求(新fd和它上一次处理的fd可以不是同一个);
9、异步处理线程处于闲置状态一段时间后(没有新的请求),则会自动退出。等到再有新的请求时,再去动态创建;
2.2 linux内核版本
2.2.1 接口和实现信息
下面再来看看linux版本的异步IO。它主要包含如下系统调用接口:
int io_setup(int maxevents, io_context_t *ctxp); /* 创建一个异步IO上下文(io_context_t是一个句柄) */
int io_destroy(io_context_t ctx); /* 销毁一个异步IO上下文(如果有正在进行的异步IO,取消并等待它们完成) */
long io_submit(aio_context_t ctx_id, long nr, struct iocb **iocbpp); /* 提交异步IO请求 */
long io_cancel(aio_context_t ctx_id, struct iocb *iocb, struct io_event *result); /* 取消一个异步IO请求 */
long io_getevents(aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout) /* 等待并获取异步IO请求的事件(也就是异步请求的处理结果) */
其中,struct iocb主要包含以下字段:
__u16 aio_lio_opcode; /* 请求类型(如:IOCB_CMD_PREAD=读、IOCB_CMD_PWRITE=写、等) */
__u32 aio_fildes; /* 要被操作的fd */
__u64 aio_buf; /* 读写操作对应的内存buffer */
__u64 aio_nbytes; /* 需要读写的字节长度 */
__s64 aio_offset; /* 读写操作对应的文件偏移 */
__u64 aio_data; /* 请求可携带的私有数据(在io_getevents时能够从io_event结果中取得) */
__u32 aio_flags; /* 可选IOCB_FLAG_RESFD标记,表示异步请求处理完成时使用eventfd进行通知(百度一下) */
__u32 aio_resfd; /* 有IOCB_FLAG_RESFD标记时,接收通知的eventfd */
其中,struct io_event主要包含以下字段:
__u64 data; /* 对应iocb的aio_data的值 */
__u64 obj; /* 指向对应iocb的指针 */
__s64 res; /* 对应IO请求的结果(>=0: 相当于对应的同步调用的返回值;<0: -errno) */
io_context_t句柄在内核中对应一个struct kioctx结构,用来给一组异步IO请求提供一个上下文。其主要包含以下字段:
struct mm_struct* mm; /* 调用者进程对应的内存管理结构(代表了调用者的虚拟地址空间) */
unsigned long user_id; /* 上下文ID,也就是io_context_t句柄的值(等于ring_info.mmap_base) */
struct hlist_node list; /* 属于同一地址空间的所有kioctx结构通过这个list串连起来,链表头是mm->ioctx_list */
wait_queue_head_t wait; /* 等待队列(io_getevents系统调用可能需要等待,调用者就在该等待队列上睡眠) */
int reqs_active; /* 进行中的请求数目 */
struct list_head active_reqs; /* 进行中的请求队列 */
unsigned max_reqs; /* 最大请求数(对应io_setup调用的int maxevents参数) */
struct list_head run_list; /* 需要aio线程处理的请求列表(某些情况下,IO请求可能交给aio线程来提交) */
struct delayed_work wq; /* 延迟任务队列(当需要aio线程处理请求时,将wq挂入aio线程对应的请求队列) */
struct aio_ring_info ring_info; /* 存放请求结果io_event结构的ring buffer */
其中,这个aio_ring_info结构比较值得一提,它是用于存放请求结果io_event结构的ring buffer。它主要包含了如下字段:
unsigned long mmap_base; /* ring buffer的地始地址 */
unsigned long mmap_size; /* ring buffer分配空间的大小 */
struct page** ring_pages; /* ring buffer对应的page数组 */
long nr_pages; /* 分配空间对应的页面数目(nr_pages * PAGE_SIZE = mmap_size) */
unsigned nr, tail; /* 包含io_event的数目及存取游标 */
2.3 例子
#include <stdio.h>
#include <libaio.h>
#include <sys/eventfd.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string>
using std::string; #define TEST_FILE "./aiotestfile.txt"
#define ALIGN_SIZE 512
#define RD_SIZE 1024
#define RD_EVENT_NUM 5 bool create_test_file(const string &a_strFileName)
{
FILE *file = fopen(a_strFileName.c_str(), "w");
if(NULL == file)
{
perror("fopen");
return false;
}
fprintf(file, "my name is \n");
fprintf(file, "stars\n");
fprintf(file, "what's your name?\n");
fprintf(file, "what's your name?\n");
fprintf(file, "what's your name?\n");
fprintf(file, "what's your name?\n");
fprintf(file, "what's your name?\n");
fprintf(file, "what's your name?\n");
fprintf(file, "what's your name?\n");
fprintf(file, "what's your name?\n");
fprintf(file, "what's your name?\n");
fprintf(file, "stars\n");
fprintf(file, "stars\n");
fprintf(file, "stars\n");
fprintf(file, "stars\n");
fprintf(file, "stars\n"); fclose(file); return true;
} void aio_call_back(io_context_t ctx, struct iocb *iocb, long res, long res2)
{
printf("can read %d bytes, and in fact has read %d bytes\n", iocb->u.c.nbytes, res);
printf("the content is :\n%s", iocb->u.c.buf);
} int main(int argc, char* argv[])
{
if(!create_test_file(TEST_FILE))
{
perror("create_test_file");
return -;
} int efd = eventfd(, EFD_NONBLOCK|EFD_CLOEXEC);
if(efd == -)
{
perror("eventfd");
return -;
}
int epollfd = epoll_create();
if(epollfd == -)
{
perror("epoll_create");
return -;
}
struct epoll_event epevent;
epevent.events = EPOLLIN|EPOLLET;
epevent.data.ptr = NULL;
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, efd, &epevent) != )
{
perror("epoll_ctr");
return -;
} int fd = open(TEST_FILE, O_RDWR|O_DIRECT);
if(fd < )
{
perror("open");
return -;
} void *buf;
if(posix_memalign(&buf, ALIGN_SIZE, RD_SIZE))
{
perror("posix_memalign");
return -;
} struct iocb *ocbs[RD_EVENT_NUM];
for(int i = ; i < RD_EVENT_NUM; ++i)
{
ocbs[i] = (struct iocb*)malloc(sizeof(struct iocb));
io_prep_pread(ocbs[i], fd, buf, RD_SIZE, RD_SIZE * i);
io_set_eventfd(ocbs[i], efd);
io_set_callback(ocbs[i], aio_call_back);
} io_context_t ctx = NULL;
if(io_setup(, &ctx))
{
perror("io_setup");
return -;
}
if(io_submit(ctx, RD_EVENT_NUM, ocbs) != RD_EVENT_NUM)
{
perror("io_submit");
return -;
} struct epoll_event *events_list = (struct epoll_event *)malloc(sizeof(struct epoll_event) * );
int retnum = ;
while(retnum < RD_EVENT_NUM)
{
int epnum = epoll_wait(epollfd, events_list, , -);
if(epnum <= ){
if(errno != EINTR){
perror("epoll_wait");
exit();
}
}
else
{
int ready;
int n = read(efd, &ready, sizeof(ready));
if(n != ){
perror("read error");
exit();
}
printf("finished io number: %d\n", ready); while(ready > )
{
struct timespec tms;
tms.tv_sec = ;
tms.tv_nsec = ;
struct io_event *events = (struct io_event *)malloc(sizeof(struct io_event) * RD_EVENT_NUM);
int ret = io_getevents(ctx, , RD_EVENT_NUM, events, &tms);
if(ret > )
{
for(int v = ; v < ret; ++v)
{
((io_callback_t)(events[v].data))(ctx, events[v].obj, events[v].res, events[v].res2);
}
retnum += ret;
ready -= ret;
}
}
}
} close(epollfd);
free(buf);
close(fd);
io_destroy(ctx);
close(efd);
remove(TEST_FILE); for(int m = ; m < RD_EVENT_NUM; m++)
{
free(ocbs[m]);
} return ;
}
2.4 比较
从上面的流程可以看出,linux版本的异步IO实际上只是利用了CPU和IO设备可以异步工作的特性(IO请求提交的过程主要还是在调用者线程上同步完成的,请求提交后由于CPU与IO设备可以并行工作,所以调用流程可以返回,调用者可以继续做其他事情)。相比同步IO,并不会占用额外的CPU资源。
而glibc版本的异步IO则是利用了线程与线程之间可以异步工作的特性,使用了新的线程来完成IO请求,这种做法会额外占用CPU资源(对线程的创建、销毁、调度都存在CPU开销,并且调用者线程和异步处理线程之间还存在线程间通信的开销)。不过,IO请求提交的过程都由异步处理线程来完成了(而linux版本是调用者来完成的请求提交),调用者线程可以更快地响应其他事情。如果CPU资源很富足,这种实现倒也还不错。
还有一点,当调用者连续调用异步IO接口,提交多个异步IO请求时。在glibc版本的异步IO中,同一个fd的读写请求由同一个异步处理线程来完成。而异步处理线程又是同步地、一个一个地去处理这些请求。所以,对于底层的IO调度器来说,它一次只能看到一个请求。处理完这个请求,异步处理线程才会提交下一个。而内核实现的异步IO,则是直接将所有请求都提交给了IO调度器,IO调度器能看到所有的请求。请求多了,IO调度器使用的类电梯算法就能发挥更大的功效。请求少了,极端情况下(比如系统中的IO请求都集中在同一个fd上,并且不使用预读),IO调度器总是只能看到一个请求,那么电梯算法将退化成先来先服务算法,可能会极大的增加碰头移动的开销。
最后,glibc版本的异步IO支持非direct-io,可以利用内核提供的page cache来提高效率。而linux版本只支持direct-io,cache的工作就只能靠用户程序来实现了。
参考地址:
http://blog.csdn.net/u012398613/article/details/22897279 参考
http://lse.sourceforge.net/io/aio.html 使用帮助
http://www.ibm.com/developerworks/cn/linux/l-async/ 专业技术文档
http://www.jiangmiao.org/blog/2290.html API整理
http://blog.sina.com.cn/s/blog_87c80c500100yol3.html 有例子
http://blog.sina.com.cn/s/blog_3e3fcadd0100grgk.html 例子和API讲的很细
linux 异步IO通信的更多相关文章
- linux异步IO的两种方式【转】
转自:https://blog.csdn.net/shixin_0125/article/details/78898146 知道异步IO已经很久了,但是直到最近,才真正用它来解决一下实际问题(在一个C ...
- Linux异步IO【转】
转自:http://blog.chinaunix.net/uid-24567872-id-87676.html Linux® 中最常用的输入/输出(I/O)模型是同步 I/O.在这个模型中,当请求发出 ...
- Linux异步IO操作
Linux® 中最常用的输入/输出(I/O)模型是同步 I/O.在这个模型中,当请求发出之后,应用程序就会阻塞,直到请求满足为止.这是很好的一种解决方案,因为调用应用程序在等待 I/O 请求完成时不需 ...
- Linux 网络编程的5种IO模型:异步IO模型
Linux 网络编程的5种IO模型:异步IO模型 资料已经整理好,但是还有未竟之业:复习多路复用epoll 阅读例程, 异步IO 函数实现 背景 上一讲< Linux 网络编程的5种IO模型:信 ...
- ORACLE数据库异步IO介绍
异步IO概念 Linux 异步 I/O (AIO)是 Linux 内核中提供的一个增强的功能.它是Linux 2.6 版本内核的一个标准特性,当然我们在2.4 版本内核的补丁中也可以找到它.AIO 背 ...
- JAVA基础知识之网络编程——-基于AIO的异步Socket通信
异步IO 下面摘子李刚的<疯狂JAVA讲义> 按照POSIX标准来划分IO,分为同步IO和异步IO.对于IO操作分为两步,1)程序发出IO请求. 2)完成实际的IO操作. 阻塞IO和非阻塞 ...
- 同步IO和异步IO
链接: 同步IO和异步IO socket阻塞与非阻塞,同步与异步.I/O模型 Linux的IO系统常用系统调用及分析 linux异步IO的两种方式
- linux异步IO--aio
简述 linux下异步方式有两种:异步通知和异步IO(AIO),异步通知请参考:linux异步通知 Linux的I/O机制经历了一下几个阶段的演进: 1. 同步阻塞I/O: 用户进程进行I/O操作,一 ...
- Netty学习第二节Java IO通信
一.Java IO通信 名词解释: BIO通信: 采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端连接,在接收到客户端请求后,为每一个客户端建立一个新的线程负 ...
随机推荐
- pycurl mac 安装报错Curl is configured to use SSL,
1.使用安装第三方插件的方式安装pycurl:pip3 install pycurl 报错提示如下: Curl is configured to use SSL, but we have not be ...
- Codeforces Gym100952 A.Who is the winner? (2015 HIAST Collegiate Programming Contest)
A. Who is the winner? time limit per test 1 second memory limit per test 64 megabytes input stan ...
- poj1062 最短路径 dijkstra
题目连接:http://poj.org/problem?id=1062 Description 年轻的探险家来到了一个印第安部落里.在那里他和酋长的女儿相爱了,于是便向酋长去求亲.酋长要他用 1000 ...
- codeforces Round #440 A Search for Pretty Integers【hash/排序】
A. Search for Pretty Integers [题目链接]:http://codeforces.com/contest/872/problem/A time limit per test ...
- Linux查看内核信息或系统信息
先说说为什么会写这个.这是我去面试的时候面试官问的一个问题,我感觉是一个普遍会被问到的问题.为了让我自己记住,也便于收集下Linux运维方向考官的题目. 第一,查看内核信息 cat /proc/ver ...
- 【分块】bzoj3295 [Cqoi2011]动态逆序对
考虑每次删除pos位置一个数x后,所造成的的影响就是,逆序对的个数少了在1~pos-1中大于x的数的个数加上pos+1~n中小于x的数的个数. 那么我们需要的操作就只有查询区间内比某数大(小)的个数. ...
- 【分块】【链表】bzoj2738 矩阵乘法
http://www.cnblogs.com/jianglangcaijin/p/3460012.html 首先将矩阵的数字排序.设置size,每次将size个数字插入.插入时,我们用h[i][j]记 ...
- virtualenvwrapper的安装及问题解决
安装virtualenvwrapperyum install python-setuptools python-develpip install virtualenvwrapper # linux下 ...
- Problem B: 调用函数,求1!+2!+3!+......+10!
#include<stdio.h> double fact(int i); int main() { int i; ; ;i<=;i++) sum=sum+fact(i); prin ...
- ujmp使用心得
1)对矩阵转置运算时: Matrix test2 = oneMatrix.transpose(); Matrix test2 = oneMatrix.transpose(Ret.LINK); Matr ...