所谓同步IO是指线程在发起IO请求后会被挂起,IO完成后继续执行。
异步IO是指:线程发起IO请求后并不会挂起而是继续执行。IO完毕后会得到设备的通知。而IO完成端口就是实现这种通知的很好的一种方式。
CreateFile当然可以创建和打开磁盘文件。但是不要被它的名字所迷惑。它同样可以打开其他设备。根据传入参数的不同可以让CreateFile打开不同的设备。
HANDLE CreateFile(
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
psaName既表示设备类型也表示该类设备一个实例。
dwDesiredAccess可以是0,代表我们不希望从设备中读取或写入数据,只是想改变它的配置,如时间戳,该值还可以是GENERIC_READ等
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结构,用来指定安全属性。只有当我们在具备安全性的文件系统中,如NTFS中创建文件时才会用到此结构。在其他情况下都只需要传入NULL就可以了,此时会用默认的安全属性来创建文件,并且返回的句柄是不可继承的。
dwCreationDisposition参数对文件的含义更重大。它可以是以下值:
CREATE_NEW 创建一个新文件。如果同名文件存在则失败。
CREATE_ALWAYS 文件同名文件存在与否都创建文件。存在时会覆盖。
OPEN_EXISTING 打开一个已存在文件。如不存在,则失败。
OPEN_ALWAYS 打开一个已存在文件。如不存在,则创建。
TRUNCATE_EXISTING 打开一个已存在文件,将文件大小截断为0,如果不存在则调用失败。
dwFlagsAndAttributes有两个用途:一,允许我们设置一些标志微调与设备的通信。二:如果设备是文件,还可以设置文件属性。这些标志大多数是一些信号,用来告诉系统我们打算以何种方式来访问设备,这样系统就可以对缓存算法进行优化。此处不再介绍。
hFileTemplate,既可以标识一个已经打开的文件句柄,也可以是NULL。如果是一个文件句柄,那么CreateFile会完全忽略dwFlagsAndAttributes参数,转而使用hFileTemplate标识的文件属性。此时,hFileTemplate标识的文件句柄必须是一个用GENERIC_READ标志打开的文件。
CreateFile成功的创建或打开设备那会返回设备句柄。否则返回INVALID_HANDLE_VALUE。一定要注意返回值不是NULL哦。
GetFileSizeEx用于得到文件大小。
BOOL GetFileSizeEx(HANDLE hFile, PLARGE_INTEGER pliFileSize);
hFile表示一个一打开文件的句柄。pliFileSize表示文件大小。定义如下:
typedef union _LARGE_INTEGER
{
struct
{
DWORD LowPart;
LONG HighPart;
};
LONGLONG QuadPart;
}LARGE_INTEGER,*PLARGE_INTEGER;
它允许我们以一个64位有符号数或者是两个32位值来表示一个64位数。
另外一个很重要的函数是GetCompressedFileSize:
DWORD GetCompressedFileSize(PCTSTR pszFileName,PDWORD pdwFileSizeHigh);
这个函数返回文件物理大小,而GetFileSizeEx是返回文件逻辑大小。
DWORD SetFilePointer(
HANDLE hFile,
LONG lDistanceToMove, //低32位移动距离,可以为负值
PLONG lpDistanceToMoveHigh, //高32位移动距离,可以为NULL
DWORD dwMoveMethod //FILE_BEGIN,FILE_CURRENT,FILE_END
);
BOOL SetFilePointerEx(
HANDLE hFile,
LARGE_INTEGER liDistanceToMove, //移动距离
PLARGE_INTEGER lpNewFilePointer, //当前文件指针位置,如果移动距离为0,就可以查询当前指针位置
DWORD dwMoveMethod
);
SetFilePointer的几点说明:
1:将文件指针设置超过文件大小是可行的,并且可以在该位置写入数据,这样文件大小将增大,还可以通过调用SetEndOfFile()增大或者切断文件
2:如果文件是被FILE_FLAG_NO_BUFFERING打开的,则文件指针只能被设置为扇区大小的整数倍
4.3:SetEndOfFile()可以切断或增大文件大小
同步异步IO基本函数
BOOL ReadFile(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead, //异步IO情况下此值没有意义,应传NULL
LPOVERLAPPED lpOverlapped
);
BOOL WriteFile(
HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten, //异步IO情况下此值没有意义,应传NULL
LPOVERLAPPED lpOverlapped
);
BOOL FlushFileBuffers(
HANDLE hFile
);
异步IO在CreateFile()中要制定FILE_FLAG_OVERLAPPED
typedef struct _OVERLAPPED
{
ULONG_PTR Internal; //错误码,一旦发出一个异步IO请求,此值被初始化为STATUS_PENDING,如果IO完成,则变为其他值
//我们可以通过宏HasOverlappedIoCompleted(pOverlapped)检查IO是否完成,这个宏本质就是返回
//return Internal!=STATUS_PENDING
ULONG_PTR InternalHigh; //IO请求完成时,保存已传输的字节数
DWORD Offset; //下面解释
DWORD OffsetHigh;
HANDLE hEvent; //以后解释
}OVERLAPPED;
采用异步IO打开文件设备,文件指针是没有意义的,因为有多个异步操作同时进行,我们无法对文件指针进行同步,所以Offset和OffsetHigh指定从文件那个地方开始执行异步IO操作
如果不是文件设备,必须\将Offset和OffsetHigh设为0
异步IO完成时,会收到一个OVERLAPPED指针,这就是调用异步IO函数传递的那个OVERLAPPED指针,我们可以写一个C++类从OVERLAPPED派生,这样就能添加更多的数据几个注意事项:
1:异步IO不会按照你的投递顺序来执行,驱动会选择他认为最快的方式来组合这些投递
2:错误处理,以文件IO为例,当我们投递一个异步ReadFile()时,设备驱动程序可能会以同步方式执行,例如如果设备驱动程序发现要读取的数据在文件缓冲里时,就不会投递这个异步设备IO,而是直接将数据复制进我们的缓冲区
如果IO是同步方式执行,ReadFile()和WriteFile()返回非零值,如果是异步或者出现错误,返回FALSE,调用GetLastError()获得错误码,错误码列举一部分
ERROR_IO_PENDING //异步IO投递成功
ERROR_INVALID_USER_BUFFER
ERROR_NOT_ENOUGH_MEMORY //每个设备驱动程序会在非分页缓冲池中维护一个固定大小的列表来管理
//待处理的IO请求,如果这个列表已满,异步IO就会失败,返两个错误中的一种,具体返回那个要看设备驱动程序
ERROR_NOT_ENOUGH_QUOTA //某些设备要求我们提供的数据缓存页面锁定,也就是不能被唤出内存的页面,
//但是系统对一个进程能够锁定的页面数量做了限制,如果超过这个数量,则会返回此错误
//例如如果指定了FILE_FLAG_NO_BUFFERING,就必须满足页面锁定要求
//可以调用SetProcessWorkingSetSize()来增加进程能锁定页面数量的配额
在异步IO完成之前,不能删除OVERLAPPED结构
取消设备中的异步IO请求
1:调用BOOL CancelIo(HANDLE hd);关闭调用线程添加的所有IO请求(完成端口除外)
2:关闭设备句柄,这将取消其所有IO请求
3:线程终止,会取消此线程所有IO请求(完成端口除外)
4:调用BOOL CancelIoEx(HANDLE hd,LPOVERLAPPED pOVerLapped);如果pOVerLapped为0,则取消此线程投递的所有IO请求,如果不为0,则取消相应的IO请求而不管是不是本线程投递的
1:触发设备内核对象HANDLE hd=CreateFile(...,FILE_FLAG_OVERLAPPED,...);
...//初始化其他数据
bool bReadDone=ReadFile(...);//这会将文件句柄设置为未触发状态
DWORD dwError=GetLastError();
if(bReadDone==false&&(dwError==ERROR_IO_PENDING))
{
WaitForSingleObject(hd,INFINITE);
...//查看OVERLAPPED结构中错误码和实际传输字节数
}
else
{
//此异步IO被同步执行,或者发生了其他错误,检查dwError具体值
}
这个方法不能投递多个异步IO,因为设备内核对象触发时,我们不能确定投递的那一个IO请求完成了
如果不用这种方式,为了略微提高性能,我们应该关闭操作系统触发设备内核对象,调用如下函数
BOOL SetFileCompletionNotificationModes(HADNLE hd,UCHAR uFlags);为uFlags传入FILE_SKIP_SET_EVENT_ON_HANDLE
2:触发事件内核对象
OVERLAPPED有一个hEvent,它用来标识一个事件内核对象,我们每投递一个异步IO,就为其添加一个事件内核对象,这样,当一个异步IO完成时,设备驱动会去检查这个hEvent的值,如果有值,则会SetEvent(), 我们就可以调用WaitForMultipleObjects()来等待这些事件内核对象,并通过返回值确定是那个异步IO完成了
可提醒IO
1:每个线程都有一个与其相关的队列,叫做异步过程调用(APC)
2:使用APC执行异步函数如下
CreateFileEx(...,LPOVERLAPPED_COMPLETION_ROUTINE pfn);
pfn别成为完成函数
(WINAPI *LPOVERLAPPED_COMPLETION_ROUTINE)(
__in DWORD dwErrorCode,
__in DWORD dwNumberOfBytesTransfered,
__inout LPOVERLAPPED lpOverlapped
);
3.系统将已经完成的异步IO投递到APC队列中的顺序是随机的
4:调用
SleepEx();
WaitForSingleObjectEx();
WaitForMultipleObjectsEx();
SignalObjectAndWait();
GetQueuedCompletionStatusEx();
MsgWaitForMultipleObjectsEx();
中的一个将线程置为可提醒状态(很多最后一个值需设置为TRUE)
只要APC队列中有一项,线程就不会被挂起,转而去执行完成函数,如果APC队列为空,线程被挂起,直到APC队列不为空或者等待的内核对象被触发或者超时或者一个互斥量被遗弃
这6个函数如果是因为APC返回,返回值为WAIT_IO_COMPLETION,GetLastError()也是此值
可提醒IO不会试图去触发OVERLAPPED的事件对象,所以我们可以据为己有
可提醒IO的缺点:
发出IO请求的线程必须对其进行处理,即使其他线程处于空闲状态,效率低下
采用APC通知一个线程优雅退出的方式
//手动添加APC
DWORD QueueUserAPC( //返回值实际是BOOL值,表示添加成功与否
__in PAPCFUNC pfnAPC, //回调函数,可以跨进程,那么此函数也应该在相应的地址空间
__in HANDLE hThread,
__in ULONG_PTR dwData //传个回调函数的一个参数而已
);
//回调函数原型
VOID (APIENTRY *PAPCFUNC)(
__in ULONG_PTR dwParam);
//采用APC通知一个线程优雅退出的方式
void ThreadFun()
{
...;
DWORD dw=WaitForSingleObjectEx(hd,INFINITE,TRUE);
if(dw==WAIT_OBJECT_0)
{
...;
}
else if(dw==WAIT_IO_COMPLETION)
{
QUIT;
}
}