WINDOWS核心编程——设备IO

IO是程序与外部通信的主要方式,程序通过API可以与以下设备通信,windows将这些抽象为设备通过CreateFile函数打开设备,通过CloseHandle关闭设备。

WINDOWS核心编程——设备IO_第1张图片

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完成是通知应用程序的方式方法决定了程序的效率:

WINDOWS核心编程——设备IO_第2张图片
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
WINDOWS核心编程——设备IO_第3张图片
4.完成端口,我们通过创建一个完成端口并与设备相互关联,再创建一个线程池监控完成端口的状态。当IO完成之后设备会将一个完成记录加入到完成队列中,线程池监控函数得到这个完成记录并返回处理这个完成事件。同样的当线程终止时所有等待动作结束,当设备关闭时给等待一个信号。
WINDOWS核心编程——设备IO_第4张图片
这张图完整的解释了为什么完成端口这么牛逼,就在于他对线程池的调度(减少线程切换的代价),并接口设计的足够合理减少复杂度(可提醒IO的缺点)。常用接口如下:

//创建完成端口并与设备相管理
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结构地址
);

你可能感兴趣的:(Win笔记)