完成端口详解和EPOLL详解
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
DWORD nReadByte ; BYTE bBuf1[BUF_SIZE],bBuf2[BUF_SIZE],bBuf3[BUF_SIZE] ; OVERLAPPED ov1 = { 0, 0, 0, 0, NULL } ; OVERLAPPED ov2 = { 0, 0, 0, 0, NULL } ; OVERLAPPED ov3 = { 0, 0, 0, 0, NULL } ; HANDLE hFile = CreateFile ( ……, FILE_FLAG_OVERLAPPED, …… ); ReadFile ( hFile, bBuf1, sizeof(bBuf1), &nReadByte, &ov1 ) ; ReadFile ( hFile, bBuf2, sizeof(bBuf2), &nReadByte, &ov2 ) ; ReadFile ( hFile, bBuf3, sizeof(bBuf3), &nReadByte, &ov3 ) ; //假设三个I/O处理的时间比较长,到这里还没有结束 GetOverlappedResult ( hFile, &ov1, &nRead, TRUE ) ; 这里对于hFile有三个重叠的I/O操作,但他们的同步对象却 都为hFile。使用GetOverlappedResult进行等待操作,这里 看似在等待第一个I/O处理的完成,其实只要有任何一个 I/O处理完成,该函数就会返回,相当于忽略了其他两个 I/O操作的结果。
• 完成端口的特点 Win32重叠I/O(Overlapped I/O)机制允许发起一个操作,并 在操作完成之后接收信息。对于那种需要很长时间才能完 成的操作来说,重叠IO机制尤其有用,因为发起重叠操作 的线程在重 叠请求发出后就可以自由地做别的事情了。在 WinNT和Win2000上,提供的真正可扩展的I/O模型就是使 用完成端口(Completion Port)的重叠I/O。 完成端口---是一种WINDOWS内核对象。完成端口用于异 步方式的重叠I/0情况下,当然重叠I/O不一定非得使用完 成端口不可,同样设备内核对象、事件对象、告警I/0等也 可使用。但是完成端口内部提供了线程池的管理,可以避 免反复创建线程的开销,同时可以根据CPU的个数灵活地 决定线程个数,而且可以减少线程调度的次数从而提高性 能。其实类似于WSAAsyncSelect和select函数的机制更容易 兼容Unix,但是难以实现我们想要的“扩展性”。而且 windows完成端口机制在操作系统的内部已经作了优化, 从而具备了更高的效率。
由图可知,内核开始I/O操作到I/O结束这段时间,用户层可以做其他的操作, 然后,当内核I/O结束的时候,可以让I/O对象或者时间对象通知用户层,而用 户线程GetOverlappedResult来查看内核I/O的完成情况。
• 异步I/O操作有很多种实现方法,具体分为 以下三类: 1. 重叠I/O操作 2. 异步过程调用(APC),扩展I/O 3. 使用完成端口(IOCP) 下面我们来介绍下这三种实现方法:
由图可知,内核开始处理I/O操作到结束的时间段是T2~T3,这个时间 段中用户线程一直处于等待状态,如果这个时间段比较短,则不会有 什么问题,但是如果时间比较长,那么这段时间线程会一直处于挂起 状态,这就会很严重影响效率,所以我们可以考虑在这段时间做些事 情。
• 异步I/O
异步I/O操作则很好的解决了这个问题,它可以使得内核开始处理 I/O操作到结束的这段时间,让用户线程可以去做其他事情,从而提 高了使用效率。
完成端口
完成端口其实就是上面两项的联合使用基础上进行了一定的改 进。 一个完成端口其实就是一个通知队列,由操作系统把已经完成 的重叠I/O请求的通知放入其中。当某项I/O操作一旦完成,某 个可以对该操作结果进行处理的工作者线程就会收到一则通知。 而套接字在被创建后,可以在任何时候与某个完成端口进行关 联。 通常情况下,我们会在应用程序中创建一定数量的工作者线程 来处理这些通知。线程数量取决于应用程序的特定需要。理想 的情况是,线程数量等于处理器的数量,不过这也要求任何线 程都不应该执行诸如同步读写、等待事件通知等阻塞型的操作, 以免线程阻塞。每个线程都将分到一定的CPU时间,在此期间 该线程可以运行,然后另一个线程将分到一个时间片并开始执 行。如果某个线程执行了阻塞型的操作,操作系统将剥夺其未 使用的剩余时间片并让其它线程开始执行。也就是说,前一个 线程没有充分使用其时间片,当发生这样的情况时,应用程序 应该准备其它线程来充分利用这些时间片。
• 异步过程调用实现异步I/O操作 异步过程调用(APC),即在特定的上下文中异步的执行一个调用。 在异步I/O中可以使用APC,即让操作系统的IO系统在完成异步I/O后立 即调用你的程序。 这里需要注意三点: (1) APC总是在调用线程中被调用; (2) 当执行APC时,调用线程会进入可变等待状态; (3) 线程需要使用扩展I/O系列函数,例如ReadFileEx,WriteFileEx, 另 外可变等待函数也是必须的(至少下面其中之一): WaitForSingleObjectEx WaitForMultipleObjectEx SleepEx SignalObjectAndWait MsgWaitForMultipleObjectsEx 在使用ReadFileEx,WriteFileEx时,重叠结构OVERLAPPED中的hEvent成 员并非一定要指定,因为系统会忽略它。当多个IO操作共用同一个完 成例程时,可以使用hEvent来携带序号等信息,用于区别不同的I/O操 作,因为该重叠结构会传递给完成例程。如果多个IO操作使用的完成 例程都不相同时,则直接把hEvent设置为NULL就可以了。
பைடு நூலகம்
•
使用重叠I/O实现异步I/O 重叠的意思,从资料上查询可以理解为同一个线程对多个I/O对象进行I/O操作,不同的线程也可以 对同一个I/O对象进行操作。 在使用重叠I/O时,线程需要创OVERLAPPED结构以供I/O处理。 OVERLAPPED的结构定义如下: typedef struct _OVERLAPPED { ULONG_PTR Internal; //被系统内部赋值,用来表示系统状态 ULONG_PTR InternalHigh; //被系统内部赋值,表示传输的字节数 union { struct { DWORD Offset; //与OffsetHigh合成一个64位的整数,用来表示从文件头部的多少字节开 始操作 DWORD OffsetHigh; //如果不是对文件I/O来操作,则Offset必须设定为0 }; PVOID Pointer; }; HANDLE hEvent; //如果不使用,就务必设为0;否则请赋一个有效的Event句柄 } OVERLAPPED, *LPOVERLAPPED; 该结构中最重要的成员是hEvent,它是作为一个同步对象而存在,如果hEvent为NULL,那么此时的 同步对象即为文件句柄、管道句柄等I/O操作对象。当I/O完成后,会使这里的同步对象受信,从而 通知用户线程。 由于在进行I/O请求后会立即返回,但有时用户线程需要知道I/O当前的执行情况,此时就可以使用 GetOverlappedResult。如果该函数的bWait参数为true,那么改函数就会阻塞线程直到目标I/O处理完 成为止;如果bWait为false,那么就会立即返回,如果此时的I/O尚未完,调用GetLastError就会返回 ERROR_IO_INCOMPLETE。
完成端口
WindowsSockets应 用程序在调用 WSARecv()函数后立即 返回,线程继续运行。 当系统接收数据完成 后,向完成端口发送 通知包(这个过程对 应用程序不可见)。 应用程序在发起接收数据操作后,在完成端口上等 待操作结果。当接收到I/O操作完成的通知后,应用 程序对数据进行处理。
• 下面给出两个示例代码,方便大家理解 DWORD nReadByte ; BYTE bBuf[BUF_SIZE] ; OVERLAPPED ov = { 0, 0, 0, 0, NULL } ; // hEvent = NULL ; HANDLE hFile = CreateFile ( ……, FILE_FLAG_OVERLAPPED, …… ) ; ReadFile ( hFile, bBuf, sizeof(bBuf), &nReadByte, &ov ) ; // 由于此时hEvent=NULL,所以同步对象为hFile,下面两句的效果一样 WaitForSingleObject ( hFile, INFINITE ) ; //GetOverlappedResult ( hFile, &ov, &nRead, TRUE ) ; 这段代码在调用ReadFile后会立即返回,但在随后的 WaitForSingleObject或者GetOverlappedResult中阻塞,利用同步对象 hFile进行同步。 这段代码在这里可以实现正常的异步I/O,但存在一个问题,倘若现 在需要对hFile句柄进行多个I/O操作,就会出现问题。
其实,这里有一个很重要的原则:对于一个重叠句柄上有多于 一个I/O操作的时候,应该使用事件对象而不是文件句柄来实现 同步。 DWORD nReadByte ; BYTE bBuf1[BUF_SIZE],bBuf2[BUF_SIZE],bBuf3[BUF_SIZE] ; HANDLE hEvent1 = CreateEvent ( NULL, FALSE, FALSE, NULL ) ; HANDLE hEvent2 = CreateEvent ( NULL, FALSE, FALSE, NULL ) ; HANDLE hEvent3 = CreateEvent ( NULL, FALSE, FALSE, NULL ) ; OVERLAPPED ov1 = { 0, 0, 0, 0, hEvent1 } ; OVERLAPPED ov2 = { 0, 0, 0, 0, hEvent2 } ; OVERLAPPED ov3 = { 0, 0, 0, 0, hEvent3 } ; HANDLE hFile = CreateFile ( ……, FILE_FLAG_OVERLAPPED, …… ) ; ReadFile ( hFile, bBuf1, sizeof(bBuf1), &nReadByte, &ov1 ) ; ReadFile ( hFile, bBuf2, sizeof(bBuf2), &nReadByte, &ov2 ) ; ReadFile ( hFile, bBuf3, sizeof(bBuf3), &nReadByte, &ov3 ) ; //此时3个I/O操作的同步对象分别为hEvent1,hEvent2,hEvent3 GetOverlappedResult ( hFile, &ov1, &nRead, TRUE ) ; 这样,这个GetOverlappedResult就可以实现对第一个I/O处理的 等待
在系统调用完成例程有两个条件: (1) I/O操作必须完成 (2) 调用线程处于可变等待状态 对于第一个条件比较容易,显然完成例程只有在I/O 操作完成时才调用;至于第二个条件就需要进行认 为的控制,通过使用可变等待函数,让调用线程处 于可变等待状态,这样就可以执行完成例程了。这 里可以通过调节调用可变等待函数的时机来控制完 成例程的执行,即可以确保完成例程不会被过早的 执行。 当线程具有多个完成例程时,就会形成一个队列。 使用可变等待函数使线程进入可变等待状态时有一 个表示超时值的参数,如果使用INFINITE,那么只 有所有排队的完成例程被执行或者句柄获得信号时 该等待函数才返回。
Windows完成端口 Linux EPOLL 解析
目录
1. Windows完成端口介绍 2. Linux EPOLL介绍
1. Windows完成端口
• 同步I/O与异步I/O 说起完成端口,它的实现机制其实是重叠 I/O实现异步I/O操作,下面就结合同步I/O来 解释下什么是异步I/O。
• 同步I/O 首先我们来看下同步I/O操作,同步I/O操作就是对于同一个I/O对 象句柄在同一时刻只允许一个I/O操作,原理图如下: