Linux IO模型:阻塞IO、非阻塞IO、IO多路复用、信号驱动IO、异步IO
1. 阻塞IO
最简单直接的IO模型。
调用过程:
进程调用recvfrom
系统调用,进程阻塞;等待数据,数据准备完成,
将数据从内核拷贝到用户空间,系统调用返回;进程继续处理。
优点是简单直接,缺点是进程阻塞,浪费资源。
2. 非阻塞IO
非阻塞IO采用轮询的方式隔一段时间发起系统调用看数据是否就绪,
与阻塞IO不同的是它会马上返回结果。
如果数据准备就绪,就将数据拷贝到用户空间,返回成功。
否则kernal
立即返回EWOUDBLOCK
这个错误。
因此在准备数据阶段,不会阻塞进程。
优点:进程发起I/O操作时,不会因为数据未就绪而阻塞。
缺点:会多次调用系统调用,消耗CPU。
3. I/O多路复用
I/O多路复用可以理解为,单线程监听多个文件描述符。
I/O多路利用向kernel
发起系统调用(select、poll、epoll)
,
传入文件描述符和相应的动作read、write
让kernel
去监测,当
其中有文件描述有相应的动作发生,就返回结果。进程再
发起真正的I/O
操作。
4. 信号驱动IO
通过信号来通知数据是否到来,
因此在等待数据到来这个阶段进程不会阻塞。
在信号触发后,调用相应的信号处理函数,开始真正的I/O
操作:
将数据从内核拷贝到用户空间,这时候进程会阻塞。
下面是找得一段例子,sigaction
对信号绑定相应的处理函数,相应的处理
函数就是sa.sa_handler
。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>#include <linux/if_tun.h>extern int errno;int bridge_term = 0;
int f1, f2;void sig_io(int sig)
{static char buf[1600];register int r;while( (r=read(f1, buf, sizeof(buf))) > 0 )write(f2, buf, r);if( r < 0 && (errno != EAGAIN && errno != EINTR) ) {bridge_term = 1;return;}while( (r=read(f2, buf, sizeof(buf))) > 0 )write(f1, buf, r);if( r < 0 && (errno != EAGAIN && errno != EINTR) ) {bridge_term = 1;return;}
}int main(int argc, char *argv[])
{struct sigaction sa;char buf[20];if(argc < 2) {printf("Usage: bridge tap|tun\n");exit(1);}sprintf(buf,"/dev/%s%d",argv[1],0);f1 = open(buf, O_RDWR);sprintf(buf,"/dev/%s%d",argv[1],1);f2 = open(buf, O_RDWR);ioctl(f1, TUNSETNOCSUM, 1);ioctl(f2, TUNSETNOCSUM, 1);fcntl(f1, F_SETFL, O_NONBLOCK | O_ASYNC);fcntl(f2, F_SETFL, O_NONBLOCK | O_ASYNC);memset(&sa, 0, sizeof(sa));sa.sa_handler = sig_io;sigaction(SIGIO, &sa, NULL); while( !bridge_term )sleep(1000);
}
5. 异步I/O
在异步I/O
中,I/O
操作不会引起进程的阻塞。等待数据和拷贝数据都发生在了内核之中。数据就绪后,发送一个signal
到进程中去,进程再进行处理。
下面给出posix aio
的一个例子
先给出aiocb
的结构体定义
struct aiocb
{int aio_fildes; /* File descriptor. */int aio_lio_opcode; /* Operation to be performed. */int aio_reqprio; /* Request priority offset. */volatile void *aio_buf; /* Location of buffer. */size_t aio_nbytes; /* Length of transfer. */struct sigevent aio_sigevent; /* Signal number and value. *//* Internal members. */struct aiocb *__next_prio;int __abs_prio;int __policy;int __error_code;__ssize_t __return_value;#ifndef __USE_FILE_OFFSET64__off_t aio_offset; /* File offset. */char __pad[sizeof (__off64_t) - sizeof (__off_t)];
#else__off64_t aio_offset; /* File offset. */
#endifchar __glibc_reserved[32];
};
再给出示例程序,主要是用aio
从文件中进行读取。
我们可以运行
./a.out /dev/stdin /dev/stdin
来观察程序运行的结果。
#include <fcntl.h>#include <stdlib.h>#include <unistd.h>#include <stdio.h>#include <errno.h>#include <aio.h>#include <signal.h>#define BUF_SIZE 20 /* Size of buffers for read operations */#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)struct ioRequest { /* Application-defined structure for trackingI/O requests */int reqNum;int status;struct aiocb *aiocbp;};static volatile sig_atomic_t gotSIGQUIT = 0;/* On delivery of SIGQUIT, we attempt tocancel all outstanding I/O requests */static void /* Handler for SIGQUIT */quitHandler(int sig){gotSIGQUIT = 1;}#define IO_SIGNAL SIGUSR1 /* Signal used to notify I/O completion */static void /* Handler for I/O completion signal */aioSigHandler(int sig, siginfo_t *si, void *ucontext){if (si->si_code == SI_ASYNCIO) {write(STDOUT_FILENO, "I/O completion signal received\n", 31);/* The corresponding ioRequest structure would be available asstruct ioRequest *ioReq = si->si_value.sival_ptr;and the file descriptor would then be available viaioReq->aiocbp->aio_fildes */}}intmain(int argc, char *argv[]){struct sigaction sa;int s;int numReqs; /* Total number of queued I/O requests */int openReqs; /* Number of I/O requests still in progress */if (argc < 2) {fprintf(stderr, "Usage: %s <pathname> <pathname>...\n",argv[0]);exit(EXIT_FAILURE);}numReqs = argc - 1;/* Allocate our arrays. */struct ioRequest *ioList = calloc(numReqs, sizeof(*ioList));if (ioList == NULL)errExit("calloc");struct aiocb *aiocbList = calloc(numReqs, sizeof(*aiocbList));if (aiocbList == NULL)errExit("calloc");/* Establish handlers for SIGQUIT and the I/O completion signal. */sa.sa_flags = SA_RESTART;sigemptyset(&sa.sa_mask);sa.sa_handler = quitHandler;if (sigaction(SIGQUIT, &sa, NULL) == -1)errExit("sigaction");sa.sa_flags = SA_RESTART | SA_SIGINFO;sa.sa_sigaction = aioSigHandler;if (sigaction(IO_SIGNAL, &sa, NULL) == -1)errExit("sigaction");/* Open each file specified on the command line, and queuea read request on the resulting file descriptor. */for (size_t j = 0; j < numReqs; j++) {ioList[j].reqNum = j;ioList[j].status = EINPROGRESS;ioList[j].aiocbp = &aiocbList[j];ioList[j].aiocbp->aio_fildes = open(argv[j + 1], O_RDONLY);if (ioList[j].aiocbp->aio_fildes == -1)errExit("open");printf("opened %s on descriptor %d\n", argv[j + 1],ioList[j].aiocbp->aio_fildes);ioList[j].aiocbp->aio_buf = malloc(BUF_SIZE);if (ioList[j].aiocbp->aio_buf == NULL)errExit("malloc");ioList[j].aiocbp->aio_nbytes = BUF_SIZE;ioList[j].aiocbp->aio_reqprio = 0;ioList[j].aiocbp->aio_offset = 0;ioList[j].aiocbp->aio_sigevent.sigev_notify = SIGEV_SIGNAL;ioList[j].aiocbp->aio_sigevent.sigev_signo = IO_SIGNAL;ioList[j].aiocbp->aio_sigevent.sigev_value.sival_ptr =&ioList[j];s = aio_read(ioList[j].aiocbp);if (s == -1)errExit("aio_read");}openReqs = numReqs;/* Loop, monitoring status of I/O requests. */while (openReqs > 0) {sleep(3); /* Delay between each monitoring step */if (gotSIGQUIT) {/* On receipt of SIGQUIT, attempt to cancel each of theoutstanding I/O requests, and display status returnedfrom the cancelation requests. */printf("got SIGQUIT; canceling I/O requests: \n");for (size_t j = 0; j < numReqs; j++) {if (ioList[j].status == EINPROGRESS) {printf(" Request %zu on descriptor %d:", j,ioList[j].aiocbp->aio_fildes);s = aio_cancel(ioList[j].aiocbp->aio_fildes,ioList[j].aiocbp);if (s == AIO_CANCELED)printf("I/O canceled\n");else if (s == AIO_NOTCANCELED)printf("I/O not canceled\n");else if (s == AIO_ALLDONE)printf("I/O all done\n");elseperror("aio_cancel");}}gotSIGQUIT = 0;}/* Check the status of each I/O request that is stillin progress. */printf("aio_error():\n");for (size_t j = 0; j < numReqs; j++) {if (ioList[j].status == EINPROGRESS) {printf(" for request %zu (descriptor %d): ",j, ioList[j].aiocbp->aio_fildes);ioList[j].status = aio_error(ioList[j].aiocbp);switch (ioList[j].status) {case 0:printf("I/O succeeded\n");break;case EINPROGRESS:printf("In progress\n");break;case ECANCELED:printf("Canceled\n");break;default:perror("aio_error");break;}if (ioList[j].status != EINPROGRESS)openReqs--;}}}printf("All I/O requests completed\n");/* Check status return of all I/O requests. */printf("aio_return():\n");for (size_t j = 0; j < numReqs; j++) {ssize_t s;s = aio_return(ioList[j].aiocbp);printf(" for request %zu (descriptor %d): %zd\n",j, ioList[j].aiocbp->aio_fildes, s);}exit(EXIT_SUCCESS);}
参考
wiyi
linux-aio