Linux上有哪些不同的I/O模型,如图所示:

总共有4种组合:
同步阻塞,同步非阻塞,异步阻塞,异步非阻塞(AIO)
什么是I/O多路复用?
I/O多路复用允许应用在多个文件描述符上同时阻塞,并在其中某个可以读写时收到通知。
Linux提供了三种I/O多路复用方案:select poll epoll。
显然,I/O多路复用的select和poll属于异步阻塞的IO模型。接下来介绍下select和poll。
select
#define TIMEOUT 5 /* select timeout in seconds */
#define BUF_LEN 1024 /* read buffer in bytes */
int main( void )
{
struct timeval tv;
fd_set readfds;
int ret;
/* Wait on stdin for input. */
FD_ZERO( &readfds );
FD_SET( STDIN_FILENO, &readfds );
/* Wait up to five seconds. */
tv.tv_sec = TIMEOUT;
tv.tv_usec = 0;
/* All right, now block! */
ret = select( STDIN_FILENO + 1, &readfds, NULL, NULL, &tv );
if ( ret == -1 )
{
perror( "select "); return(1);
}else if ( !ret )
{
printf( "%d seconds elapsed.\n", TIMEOUT ); return(0);
}
/*
* Is our file descriptor ready to read?
* (It must be, as it was the only fd that
* we provided and the call returned
* nonzero, but we will humor ourselves.)
*/
if ( FD_ISSET( STDIN_FILENO, &readfds ) )
{
char buf[BUF_LEN + 1];
int len;
/* guaranteed to not block */
len = read( STDIN_FILENO, buf, BUF_LEN );
if ( len == -1 )
{
perror( "read "); return(1);
}
if ( len )
{
buf[len] = '\0'; printf( "read: %s \n ", buf );
}
return(0);
}
fprintf( stderr, "This should not happen !\n ");
return(1);
}
上面是select的demo,具体细节可查阅Linux select的API;
poll
#define TIMEOUT 5 /* poll timeout, in seconds */
int main( void )
{
struct pollfd fds[2]; int ret;
/* watch stdin for input */
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
/* watch stdout for ability to write (almost always true) */
fds[1].fd = STDOUT_FILENO;
fds[1].events = POLLOUT;
/* All set, block! */
ret = poll( fds, 2, TIMEOUT * 1000 );
if ( ret == -1 )
{
perror( "poll" );
return(1);
}
if ( !ret )
{
printf( "%d seconds elapsed.\n", TIMEOUT );
return(0);
}
if ( fds[0].revents & POLLIN )
printf( " stdin is readable \n");
if ( fds[1].revents & POLLOUT )
printf( " stdout is writable \n" );
return(0);
}
上面是poll的demo,具体细节可查阅Linux poll的API。
epoll
对于poll和select,内核必须遍历所有被监视的文件描述符。当这个表变得很大时,每次调用的遍历就成为了明显的瓶颈。另一方面poll和selectIO复用受系统进程打开文件数量限制。而epoll改进了上述问题。
#define MAX_EVENTS 64
struct epoll_event *events;
int nr_events, i, epfd;
events = malloc( sizeof(struct epoll_event) * MAX_EVENTS );
if ( !events )
{
perror( ” malloc ” ); return(1);
}
nr_events = epoll_wait( epfd, events, MAX_EVENTS, -1 );
if ( nr_events < 0 )
{
perror( ” epoll_wait ” ); free( events ); return(1);
}
for ( i = 0; i < nr_events; i++ )
{
printf( ” event = % ld on fd = % d \ n ”, events[i].events, events[i].data.fd ); /* * We now can, per events[i].events, operate on
* events[i].data.fd without blocking. */
}
free( events );
我epoll性能高效的奥秘:
当我们执行epoll_ctl添加一个文件时,会把要监控的文件句柄保存在红黑树中(便于查找),还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪链表里。 执行epoll_wait反馈就绪事件时,仅仅返回就绪的文件句柄。
参考资料:
《linux系统编程》