IO是程序与外部通信的主要方式,程序通过API可以与以下设备通信,windows将这些抽象为设备通过CreateFile函数打开设备,通过CloseHandle关闭设备。
HANDLE CreateFile(
PCTSTR pszName, //普通文件名或者设备文件名
DWORD dwDesiredAccess, //访问模式(写/读)
DWORD dwShareMode, //共享模式
PSECURITY_ATTRIBUTES psa, //指向安全属性的指针
DWORD dwCreationDisposition, //如何创建
DWORD dwFlagsAndAttributes, //文件属性
HANDLE hFileTemplate //用于复制文件句柄
);
各个参数的说明如下:
psaName既表示设备类型也表示该类设备一个实例。
dwDesiredAccess用来指定我们以何种方式和设备通信。可以传入以下值:
0 不允许读写,但可以改变设备属性。
GENERIC_READ 只读访问
GENERIC_WRITE 只写访问
GENERIC_READ|GENERIC_WRITE 读写访问
dwSharedMode用来指定共享权限:
0 独占对设备的访问。如果设备已经打开,我们 的CreateFile会失败。
FILE_SHARE_READ 只读共享,不允许修改内容。如果设备已经以写入或独占方式打开,我们的CreateFile会失败。
FILE_SHARE_WRITE 写共享,不允许读取内容。如果设备已经以读取或独占方式打开,我们的CreateFile会失败。
FILE_SHARE_READ|FILE_SHARE_WRITE 不关心向设备读还是写数据。如果设备已经以独占方式打开,我们的CreateFile会失败。
FIEL_SHARE_DELETE 先将文件标记待删除,所有对该文件引用的句柄都关闭之后,才将其真正的删除。
psa指向一个PSECURITY_ATTRIBUTES结构,用来指定安全属性。
dwCreationDisposition参数对文件的含义更重大。它可以是以下值:
CREATE_NEW 创建一个新文件。如果同名文件存在则失败。
CREATE_ALWAYS 文件同名文件存在与否都创建文件。存在时会覆盖。
OPEN_EXISTING 打开一个已存在文件。如不存在,则失败。
OPEN_ALWAYS 打开一个已存在文件。如不存在,则创建。
TRUNCATE_EXISTING 打开一个已存在文件,将文件大小截断为0,如果不存在则调用失败。
dwFlagsAndAttributes有两个用途:一,允许我们设置一些标志微调与设备的通信。二:如果设备是文件,还可以设置文件属性。
FILE_ATTRIBUTE_ARCHIVE 标记归档属性
FILE_ATTRIBUTE_COMPRESSED 将文件标记为已压缩,或者标记为文件在目录中的默认压缩方式
FILE_ATTRIBUTE_NORMAL 默认属性
FILE_ATTRIBUTE_HIDDEN 隐藏文件或目录
FILE_ATTRIBUTE_READONLY 文件为只读
FILE_ATTRIBUTE_SYSTEM 文件为系统文件
FILE_FLAG_WRITE_THROUGH 操作系统不得推迟对文件的写操作
FILE_FLAG_OVERLAPPED 允许对文件进行重叠操作
FILE_FLAG_NO_BUFFERING 禁止对文件进行缓冲处理。文件只能写入磁盘卷的扇区块
FILE_FLAG_RANDOM_ACCESS 针对随机访问对文件缓冲进行优化
FILE_FLAG_SEQUENTIAL_SCAN 针对连续访问对文件缓冲进行优化
FILE_FLAG_DELETE_ON_CLOSE 关闭了上一次打开的句柄后,将文件删除。特别适合临时文件
hFileTemplate既可以标识一个已经打开的文件句柄,也可以是NULL。
如果是一个文件句柄,那么CreateFile会完全忽略dwFlagsAndAttributes参数,转而使用hFileTemplate标识的文件属性。此时,hFileTemplate标识的文件句柄必须是一个用GENERIC_READ标志打开的文件。
对于文件而言有些操作需要了解到:
GetFileSizeEx //获得文件大小, 文件大小为LARGE_INTEGER,64位
GetCompressedFileSize //获得文件的真实大小
SetFilePointerEx //设置文件指针位置
SetEndOfFile //在当前文件指针位置截断文件
系统将文件,网络等抽象为设备之后,若需要输入或输出信息则通过接口来实现:
BOOL ReadFile(
HANDLE hFile, //设备句柄
LPVOID lpBuffer, //用于保存读入数据的一个缓冲区
DWORD nNumberOfBytesToRead, //要读入的字节数
LPDWORD lpNumberOfBytesRead, //指向实际读取字节数的指针
LPOVERLAPPED lpOverlapped //同步时为NULL,异步时必须指定
);
BOOL WriteFile(
HANDLE hFile,//设备句柄
LPCVOID lpBuffer,//数据缓存区
DWORD nNumberOfBytesToWrite, //字节数
LPDWORD lpNumberOfBytesWritten, //用于保存实际写入字节数的存储区域的指针
LPOVERLAPPED lpOverlapped //同步时为NULL,异步时必须指定
);
当设备句柄打开时不设定FILE_FLAG_OVERLAPPED标识说明是同步IO,同步IO会阻塞线程导致线程挂起直到IO完成。同步IO一般还需要用到如下接口:
BOOL FlushFileBuffers(HANDLE hFile); // 强制刷新缓存到磁盘上
BOOL CancelSynchronousIo(HANDLE hThread); //取消同步IO激活因为等待IO而挂起的线程,结束线程时也会取消
同步IO阻塞线程所以效率不高,采用异步IO可以有效提高效率,所谓异步IO就是将IO操作提交到设备的队列中由设备去做IO操作,在IO完成后通知应用程序。要使用异步IO只需要打开设备时设定FILE_FLAG_OVERLAPPED标识,并在ReadFile和WriteFile最后传入LPOVERLAPPED结构的地址,同时还需要保证IO中所用到的内存可用。而IO完成后通知应用程序时,LPOVERLAPPED的结构十分重要,异步IO需要基于这个来做:
typedef struct _OVERLAPPED
{
DWORD Internal;//错误代码。
DWORD InternalHigh;//已传输字节数。
DWORD Offset;//低32位文件偏移。
DWORD OffsetHigh;//高32位文件偏移。
HANDLE hEvent;//事件对象句柄。
}OVERLAPPED,*LPOVERLAPPED;
同样异步IO也是可以被取消的,线程终止时,系统会自动取消该线程发出的所有IO请求。但如果请求的句柄具有与之相关联的IO完成端口,那么不在被取消之列。取消异步IO的接口如下:
BOOL CancelIo(HANDLE hFile); //取消设备队列中的所有请求
BOOL CancelIoEx(HANDLE hFile,LPOVERLAPPED pOverlapped); //取消指定的请求, pOverlapped为NULL时取消所有请求
而当异步IO完成是通知应用程序的方式方法决定了程序的效率:
1.触发设备内核对象。IO完成时设备内核对象会被触发,线程发出一个异步IO请求后,将继续执行。线程在一点上等待设备内核对象被触发。如果在一个线程中多次调用了异步IO操作将不会知道哪个IO操作被完成。
2.触发事件内核对象。当一个异步IO请求完成的时候,设备驱动程序会检查OVERLAPPED结构的hEvent成员是否为NULL。如果hEvent不为NULL,那么驱动程序会调用SetEvent来触发事件。为了检查异步IO是否完成可以等待事件内核对象被触发。
3.可提醒IO。当系统创建线程时会同时创建一个与线程相关联的队列。这个队列被称为异步过程调用队列(Asynchronous Procedure Call)。线程可以在执行的某个点上将自己置可提醒状态,当APC中有项目(进入可提醒之前或者在可提醒之后IO设备添加)时线程去执行APC指定的函数(APC项中包含OVERLAPPED和完成回调函数)。使用可提醒IO需要使用如下接口:
BOOL ReadFileEx(
HANDLE hFile, //文件的句柄
LPVOID lpBuffer, //用于接收数据的缓冲区
DWORD nNumberOfByteToRead, //允许接收的最大字节数
LPOVERLAPPED lpOverlapped, //一个OVERLAPPED结构的指针
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine //异步读取完成后调用的回调函数
);
BOOL WINAPIWriteFileEx(
HANDLE hFile, //文件的句柄
LPCVOID lpBuffer, //数据的缓冲区
DWORD nNumberOfBytesToWrite, //写入的字节数
LPOVERLAPPED lpOverlapped, //一个OVERLAPPED结构的指针
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine //异步写入完成后调用的回调函数
);
VOID WINAPI CompletionRoutine(
DWORD dwError, //错误类型
DWORD dwNumBytes, //完成的字节数
OVERLAPPED *po //设备队列保存的OVERLAPPED结构的指针
);
//调用以下函数会使得线程进入可提醒状态
SleepEx
WaitForSingleObjectEx
GetQueuedCompletionStatusEx
GetQueuedCompletionStatusEx
MsgWaitForMultipleObjectEx
//创建完成端口并与设备相管理
HANDLE CreateIoCompletionPort(
HANDLE hFile, //设备句柄
HANDLE hExistingCompletionPort, //与设备关联的IO完成端口句柄。为NULL时,系统会创建新的完成端口。
ULONG_PTR CompletionKey, //在完成队列中回传的值,由我们自己设置
DWORD dwNumberOfConcurrentThreads //同一时间最多能有多少进程处于可运行状态。如果传入0,那么将使用默认值(并发的线程数量等于cpu数量)
);
//令线程池中的线程等待IO完成
BOOL GetQueuedCompletionStatus(
HANDLE hCompletionPort, //线程希望对哪个完成端口进行监视
PDWORD pdwNumberOfBytesTransferred, //返回在异步IO完成时传输的字节数
ULONG_PTR pCompletionKey, //返回完成键
OVERLAPPED** ppOverlapped, //返回异步IO开始时传入的OVERLAPPED结构地址
DWORD dwMilliSeconds //指定等待时间
);
完成端口用处巨大,在线程通信中也同样有用,可以模拟完成信号:
BOOL PostQueuedCompletionStatus(
HANDLE CompletlonPort, //完成端口句柄
DW0RD dwNumberOfBytesTrlansferred, //IO完成时传输的字节数
DWORD dwCompletlonKey, //完成键
LPOVERLAPPED lpoverlapped, //异步IO开始时传入的OVERLAPPED结构地址
);