Windows核心编程(四)

十、同步设备I/O与异步设备I/O

HANDLE WINAPI CreateFile(
  __in          LPCTSTR lpFileName,
  __in          DWORD dwDesiredAccess,
  __in          DWORD dwShareMode,
  __in          LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  __in          DWORD dwCreationDisposition,
  __in          DWORD dwFlagsAndAttributes,
  __in          HANDLE hTemplateFile
);
CreateFile既可以用来创建和打开磁盘文件,也可以打开很多其他设备,比如串口,并口,邮槽客户端等。

dwFlagsAndAttributes中的几个重要的标志:

FILE_FLAG_NO_BUFFERING:这个标志表示在访问文件的时候不要使用任何数据缓存。高速缓存管理器在对数据进行缓存的时候,它可能会提前读取一些数据,这样当我们要读取下一个字节的时候,数据很可能已经在内存中了(速度的提升是通过从文件中读取超出实际需要的数据量来达到的),如果不再从文件中读取数据,那么可能会浪费内存。通过指定这个标志,让高速缓存管理器不对任何数据进行缓存(我们会自己对数据进行缓存),这个标志可以提高应用程序的性能和内存使用效率。此时必须遵循1)在访问文件的时候,使用的偏移量必须正好是磁盘卷的扇区大小的整数倍(GetDiskFreeSpace确定扇区大小);2)读取/写入文件的字节数必须正好是扇区大小的整数倍;3)必须确保缓存在进程地址空间中的起始地址正好是扇区大小的整数倍。

FILE_FLAG_OVERLAPPED:以异步的方式来与某些设备进行通信。


GetFileSizeEx返回一个文件的逻辑大小,GetCompressedFileSize返回的是文件的物理大小(磁盘上实际占用的字节数)。

BOOL WINAPI GetFileSizeEx(
  __in          HANDLE hFile,
  __out         PLARGE_INTEGER lpFileSize
);
DWORD WINAPI GetCompressedFileSize(
  __in          LPCTSTR lpFileName,
  __out         LPDWORD lpFileSizeHigh
);

SetFilePointerEx移动文件的指针,会在pliNewFilePointer参数中返回文件指针的新值。

BOOL WINAPI SetFilePointerEx(
  __in          HANDLE hFile,
  __in          LARGE_INTEGER liDistanceToMove,
  __out_opt     PLARGE_INTEGER lpNewFilePointer,
  __in          DWORD dwMoveMethod
);


SetEndOfFile会根据文件对象的文件指针当前所在的位置来截断文件的大小或增大文件的大小。

BOOL WINAPI SetEndOfFile(
  __in          HANDLE hFile
);

最方便和最常用的对设备数据进行读/写的函数是ReadFile和WriteFile。如果想要强制系统将缓存数据写入到设备,可以调用FlushFileBuffers。

如果要将一个给定线程尚未完成的同步I/O取消,使用函数CancelSynchronousIo。


在执行异步设备I/O的时候,必须在pOverlapped参数中传入一个已初始化的OVERLAPPED结构。

typedef struct _OVERLAPPED {
  DWORD Internal;
  DWORD InternalHigh;
  DWORD Offset;
  DWORD OffsetHigh;
  HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;
Offset和OffsetHigh构成一个64位的偏移量,表示访问文件的时候应该从哪里开始进行I/O。同步I/O的时候,系统通过文件指针指向的位置开始访问。而异步I/O请求必须在OVERLAPPED结构中指定起始偏移量。

hEvent用来标识一个事件内核对象。当一个异步I/O请求完成的时候,设备驱动程序会检查该成员是否为NULL,如果不为NULL,驱动程序会调用SetEvent来触发事件。

Internal成员用来保存已处理的I/O请求的错误码。刚发出异步I/O请求的时候,Internal被设为STATUS_PENDING,表示没有错误,因为操作尚未开始。

InternalHigh当异步I/O请求完成的时候,这个成员用来保存已传输的字节数。

设备驱动程序如何通知我们I/O请求已经完成。Windows提花了四种不同的方法。

1)触发设备内核对象。I/O请求添加到队列之前,会先将设备内核对象设为未触发状态,请求完成之后,驱动程序会将设备内核对象设为触发状态。从而得到通知。但这无法处理多个I/O请求,因为任何一个操作完成,都会触发,从而无法判断是哪个操作完成。

2)触发事件内核对象。利用OVERLAPPED中的hEvent成员,当它被触发时,操作完成。于是,对不同的I/O使用不同的OVERLAPPED及关联的事件对象,就能确切的知道是哪个操作完成。

3)可提醒I/O。系统创建线程的时候,会同时创建一个异步过程调用(asynchronous procedure call, APC)队列。当发出一个I/O请求的时候,可以告诉驱动程序在调用线程的APC队列中添加一项。使用ReadFileEx和WriteFileEx传入一个回调函数(称为完成函数(completion routine)。

4)I/O完成端口。首先创建一个I/O完成端口对象(使用函数CreateIoCompletionPort),然后将设备与完成端口关联起来(仍使用函数CreateIoCompletionPort,句柄参数设为设备句柄),创建一个线程池来处理请求,线程数量通常是CPU数量乘以2,所有的线程执行同一个函数,在一个循环内部调用函数GetQueuedCompletionStatus使线程将自己切换到睡眠状态,来等待设备I/O请求完成并进入完成端口。当I/O请求完成或PostQueuedCompletionStatus被调用时(它们被放在一个队列中(完成队列),先入先出),该完成端口会唤醒这些等待线程中的一个(最后一个调用GetQueuedCompletionStatus的线程,即后入先出)。这个线程会得到已完成I/O项中的所有信息:已传输的字节数、完成键以及OVERLAPPED结构的地址,这些通过GetQueuedCompletionStatus的参数返回的。

BOOL WINAPI GetQueuedCompletionStatus(
  __in          HANDLE CompletionPort,
  __out         LPDWORD lpNumberOfBytes,
  __out         PULONG_PTR lpCompletionKey,
  __out         LPOVERLAPPED* lpOverlapped,
  __in          DWORD dwMilliseconds
);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值