第十章 同步设备I/O与异步设备I/O
本章内容
10.1 打开和关闭设备
10.2 使用文件设备
10.3 执行同步设备I/O
10.4 异步设备I/O基础
10.5 接收I/O请求完成通知
希望线程在处理IO时不要被挂起(非阻塞),线程和他真正执行的IO操作进行通信。使用IO完成端口(I/O Comopletion port)
使用IO完成端口可以使得线程在读取设备和写入设备时候不比等待设备的响应,从而显著提高吞吐量。
(Windows程序员有必要完全理解IO完成端口的工作方式,它是一种有无数用途的绝佳的线程间通信机制)
10.1 打开和关闭设备
设备定义为能与之通信的任何东西。(类似linux系统的File)
Windows允许我们以相同的方式来从设备读取数据和向设备写入数据,而不必关心他是何种类型的设备。
当然各种设备之间还是存在差异。
例如对于串口来说,设置波特率是合理的,但在使用命名管道来进行跨网络传输,波特率就没有意义。
文件是最常用的设备之一。
以上函数都会返回一个设备的句柄,可以将句柄传递给许多函数来与设备进行通信。例如可以调用SetCommmConfig来设置波特率
WINBASEAPI
BOOL
WINAPI
SetCommConfig(
_In_ HANDLE hCommDev,
_In_reads_bytes_(dwSize) LPCOMMCONFIG lpCC,
_In_ DWORD dwSize
);
在读取数据的时候,可以调用SetMailslotInfo来设置一个超时值
WINBASEAPI
BOOL
WINAPI
SetMailslotInfo(
_In_ HANDLE hMailslot,
_In_ DWORD lReadTimeout
);
当完成对设备的操作以后,必须将其关闭,调用CloseHandle
如果是套接字,调用closesocket
如果有一个设备句柄,可以调用GetFileType来查出设备类型。
WINBASEAPI
DWORD
WINAPI
GetFileType(
_In_ HANDLE hFile
);
GetFileType函数的返回值。
细看CreateFile函数
可以用来打开和创建磁盘文件,也可以打开许多其他设备。
WINBASEAPI
HANDLE
WINAPI
CreateFileW(
_In_ LPCWSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
);
pszName 表明设备的类型,也可以是某个设备的实例。
dwDesireAccess用来指定想以何种方式来和设备进行数据传输。常用的如下
如果是文件设备,还可以传入FILE_READ_ATTRIBUTES之类的访问标志
参数dwShareMode用来指定设备的共享特权(device-sharing privilege)
参数psa指向一个SECURITY_ATTRIBUTES结构,可以用来指定安全信息以及我们是否希望CreateFile返回的句柄能被继承。
参数dwCreationDisposition对文件的含义更重大。
如果是打开非文件类型的设备,必须传递OPEN_EXISTING
dwFlagsAndAttributes参数有两个用途。
1)允许我们设置一些标志来微调与设备之间的通信
2)如果设备是一个文件,还可以设置文件的属性。这些通信标志中大多数都是一些信号,用来高速系统我们打算一何种方式访问设备。
1. CreateFile的高速缓存标志
FILE_FLAG_NO_BUFFERING 表明在访问文件时不要使用任何数据缓存。(我们会自己对数据进行缓存)
1)在访问文件的时候,使用的偏移量必须正好是磁盘卷的扇区大小的整数倍(可以用GetDiskFreeSpace函数来确定磁盘卷的扇区大小)
2)读取/写入文件的字节数必须正好是扇区大小的整数倍
3)必须确保缓存在进程地址空间中的其实地址正好是扇区大小的整数倍。
FILE_FLAG_SEQUENTIAL_SCAN 和 FILE_FLAG_RANDOM_ACCESS, 只有当我们允许堆文件数据进行缓存,这两个标志才有用。
如果指定了FILE_FLAG_NO_BUFFERING,这两个会被忽略。
FILE_FLAG_SEQUENTIAL_SCAN 系统假定我们堆文件进行顺序读取,系统会超额读取(缓存)
FILE_FLAG_RANDOM_ACCESS 告诉系统不要提前读取文件数据。
系统的高速缓存管理器内部会维护一些数据结构,文件越大所需的数据结构越多。
如果打开一个超大的文件,可能会失败。
因此读取大文件应该使用FILE_FLAG_NO_BUFFERING标志
FILE_FLAG_WRITE_THROUGH 禁止对写入数据进行高速缓存以减少数据丢失的可能性。所有对文件的修改会直接写入磁盘。(当然系统内部仍然会高速缓存文件,方便高速读取)
2. CreateFile的其他标志
FILE_FLAG_DELETE_ON_CLOSE
在文件的所有句柄都被关闭后,删除该文件。通常和FILE_ATTRIBUTE_TEMPORARY属性一起使用。
FILE_FLAG_BACKUP_SEMANTICS
用于备份和恢复软件。 系统会检查调用者的存取令牌(access token)是否具备堆文件和目录进行备份恢复的特权。如果调用者具备相应的特权,那么系统会运行其打开文件。
改标志也支持打开一个目录的句柄
FILE_FLAG_POSIX_SEMANTCS
表明创建或打开文件的时候,以区分大小写的方式来查找文件名。(因为POSIX子系统要求在查找文件名的时候区分大小写)
FILE_FLAG_OPEN_REPARSE_POINT
忽略文件的重解析属性(eparse attribute ,一般不使用此标志)
FILE_FLAG_OPEN_NO_RECALL
高速系统不要将文件内容从脱机存储器(offline storage 比如磁带)恢复到联机存储器(online storage 比如硬盘)
当文件很长一段时间没被访问可以将文件内容专一到脱机存储器,从而腾出硬盘空间。该标志高速系统不要恢复数据,这会导致系统对脱机存储媒介进行IO操作
FILE_FLAG_OVERLAPPED
告诉系统以异步方式来访问设备(默认是同步IO)
3. 文件属性标志
例如对于临时文件,可以使用FILE_ATTRIBUTE_TEMPORARY和前面的FILE_FLAG_DELETE_ON_CLOSE组合使用。
最后一个参数 hFileTemplate 可以表示一个已经打开的文件句柄也可以传入NULL
如果hFileTemplate是一个文件句柄,那么CreateFile忽略dwFlagsAndAttributes的参数使用hFileTemplate所标识的文件属性。
hFileTemplate必须是一个已经用GENERIC_READ标志打开的文件。
如果CreateFile成功会返回文件或设备句柄,否则返回INVALID_HANDLE_VALUE
10.2 使用文件设备
10.2.1 取得文件的大小
WINBASEAPI
BOOL
WINAPI
GetFileSizeEx(
_In_ HANDLE hFile,
_Out_ PLARGE_INTEGER lpFileSize
);
hFile 一个已经打开的文件句柄,
lpFileSize是一个LARGE_INTEGER联合类型的地址。
typedef union _LARGE_INTEGER {
struct {
DWORD LowPart;
LONG HighPart;
} DUMMYSTRUCTNAME;
struct {
DWORD LowPart;
LONG HighPart;
} u;
#endif //MIDL_PASS
LONGLONG QuadPart;
} LARGE_INTEGER;
还有一个ULARGE_INTEGER用了表示64位无符号值。
typedef union _ULARGE_INTEGER {
struct {
DWORD LowPart;
DWORD HighPart;
} DUMMYSTRUCTNAME;
struct {
DWORD LowPart;
DWORD HighPart;
} u;
#endif //MIDL_PASS
ULONGLONG QuadPart;
} ULARGE_INTEGER;
可以用来取得文件大小的另外一个非常有用的函数是GetCompressedFileSize
WINBASEAPI
DWORD
WINAPI
GetCompressedFileSizeW(
_In_ LPCWSTR lpFileName,
_Out_opt_ LPDWORD lpFileSizeHigh
);
返回物理大小。
ULARGE_INTEGER ulFileSize;
ulFileSize.LowPart = GetCompressedFileSize(TEXT("SomeFile.dat"),
&ulFileSize.HighPart);
GetFileSizeEx返回逻辑大小
10.2.2 设置文件指针的位置
BYTE pb[10];
DWORD dwNumBytes;
HANDLE hFile = CreateFile(TEXT("MyFile.dat"), ...); // Pointer set to 0
ReadFile(hFile, pb, 10, &dwNumBytes, NULL); // Reads bytes 0 - 9
ReadFile(hFile, pb, 10, &dwNumBytes, NULL); // Read bytes 10 - 19
每个内核对象都维护自己的文件指针,用不同内核对象打开同一个文件,文件指针也是独立的。
如果两个内核对象句柄是复制的,其实内部指向的是同一个内核对象。那么他们将共用同一个文件指针。
BYTE p