第十章:同步设备IO与异步设备IO

 

1:概念

1.1:设备是能与之通信的任何东西,如文件,套接字等

2:CreateFile()

[cpp] view plain copy print ?
  1. HANDLE CreateFile(  
  2.   LPCTSTR lpFileName,  
  3.   DWORD dwDesiredAccess,  
  4.   DWORD dwShareMode,  
  5.   LPSECURITY_ATTRIBUTES lpSecurityAttributes,  
  6.   DWORD dwCreationDisposition,  
  7.   DWORD dwFlagsAndAttributes,  
  8.   HANDLE hTemplateFile  
  9. );  
HANDLE CreateFile( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile );

2.1:dwDesiredAccess可以是0,代表我们不希望从设备中读取或写入数据,只是想改变它的配置,如时间戳,该值还可以是GENERIC_READ等

2.2:dwShareMode的值有以下含义

0:想独占打开设备

FILE_SHARE_READ:希望后续只能以0或者GENERIC_READ打开设备,我们在打开设备时,如果文件已经被非0或者GENERIC_READ打开,则自己的调用会失败,下面几个参数同理

FILE_SHARE_WRITE;FILE_SHARE_READ|FILE_SHARE_WRITE;

FILE_SHARE_DELETE:表示我们不关心文件是否被逻辑删除;在Windows内部,系统会先将文件标记为删除,然后当所有关于此文件的句柄关闭后,才真正删除此文件

2.3:文件名最大长度MAX_PATH

2.4:dwCreationDisposition是CREATE_NEW,OPEN_ALWAYS等参数,其中TRUNCATE_EXISTING表示打开一个已有文件并将其大小切断为0字节,如果没有则失败

 

2.5:dwFlagsAndAttributes可以微调和设备通信的参数,下面详细解释一些参数

[高速缓存标志]

2.5.1:FILE_FLAG_NO_BUFFERING

如果没有指定此标志,访问文件会产生一个高速缓存管理器,当我们读取一个字节时,高速缓存管理器可能会读取更多数据,当我们下次读取数据时,如果数据在高速缓存管理器中,则将直接被复制到我们提供的缓冲区中

如果启用了此标志,高速缓存管理器不会缓存任何数据,这可以提高性能和内存使用率,但由于文件系统设备驱动程序会将文件数据直接写入我们提供的缓存中,我们必须遵循以下规则:

1:访问文件时,使用的偏移量=磁盘卷的扇区大小的整数倍;磁盘卷的扇区大小可以通过GetDiskFreeSpace()获得

2:读取写入的字节数=扇区大小的整数倍

3:缓存在进程地址空间的起始地址=扇区大小的整数倍

2.5.1:如果启用了文件缓存,高速缓存管理器在打开文件时会添加一些文件相关的数据结构,如果文件特别大,这些数据结构也越多,这可能导致无法分配这些数据结构,所以,在打开特别大的文件时,要启用FILE_FLAG_NO_BUFFERING标志

2.5.2:当允许系统对文件系统进行缓存时,这两个标志才有用

1:FILE_FLAG_SEQUENTIAL_SCAN,系统会认为我们会顺序读取文件,高速缓存管理器会提前读取下面一些数据,如果设置了文件指针,这些缓存的数据就被浪费了

2:FILE_FLAG_RANDOM_ACCESS:告诉系统不要提前读取数据,如果我们要频繁设置文件指针,应该启用此标志

2.5.3:FILE_FLAG_WRITE_THROUGH

此标志将写入的数据直接写入文件而不是缓存一些一次性写入

但即使设置了此标志,缓存中任然会保留写入的数据,当下一次读取这些数据时,会直接读取缓存的数据而不必访问磁盘

[其他标志]

2.5.1:FILE_FLAG_NO_CLSOE:当文件所有句柄被关闭时,删除此文件,如果配合FILE_ATTRIBUTE_TEMPORARY属性一起使用,则表示创建一个临时文件,使用完毕后删除它

2.5.2:FILE_FLAG_OVERLAPPED:异步方式打开

[文件属性标志]

如果不是新创建一个文件,而是打开一个已有文件,系统会忽略文件属性标志

FILE_ATTRIBUTE_TEMPORARY:创建的文件时临时文件,系统会尽量把此文件中的数据放在内存中,当配合FILE_FLAG_NO_CLSOE时,系统不会将文件写回磁盘再删除它,而是直接删除

 

2.6:hTemplateFile是一个文件句柄,如果不为NULL,则会忽略dwFlagsAndAttributes的值,并使用hTemplateFile所指文件使用的标志

hTemplateFile所指的文件必须已经以GENERIC_READ打开

如果CreateFile()是打开一个已经存在的文件,会忽略此标志

2.7:CreateFile()失败返回INVALID_HANDLE_VALUE而不是NULL

3:获得文件大小

[cpp] view plain copy print ?
  1. //获得逻辑文件大小   
  2. LARGE_INTEGER li;  
  3. GetFileSizeEx(hd,&li); //GetFileSize()不能获得文件大小   
  4.   
  5. //获得物理文件大小   
  6. LARGE_INTEGER li;  
  7. li.LowPart=GetCompressedFileSize("1.txt",li.HighPart);  
  8. //此函数文件大小低32位是返回值,高32为未第二个参数  
//获得逻辑文件大小 LARGE_INTEGER li; GetFileSizeEx(hd,&li); //GetFileSize()不能获得文件大小 //获得物理文件大小 LARGE_INTEGER li; li.LowPart=GetCompressedFileSize("1.txt",li.HighPart); //此函数文件大小低32位是返回值,高32为未第二个参数

逻辑文件大小是未经过压缩的文件大小,物理文件大小是磁盘实际占用的文件大小

4:文件位置指针
4.1:CreateFile()会创建一个文件内核对象,其中有一个文件指针

[cpp] view plain copy print ?
  1. DWORD SetFilePointer(  
  2.   HANDLE hFile,  
  3.   LONG lDistanceToMove,         //低32位移动距离,可以为负值   
  4.   PLONG lpDistanceToMoveHigh,   //高32位移动距离,可以为NULL   
  5.   DWORD dwMoveMethod            //FILE_BEGIN,FILE_CURRENT,FILE_END   
  6. );  
  7.   
  8. BOOL SetFilePointerEx(  
  9.                       HANDLE hFile,  
  10.                       LARGE_INTEGER liDistanceToMove,   //移动距离   
  11.                       PLARGE_INTEGER lpNewFilePointer,  //当前文件指针位置,如果移动距离为0,就可以查询当前指针位置   
  12.                       DWORD dwMoveMethod                  
  13.                       );  
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 );

4.2:SetFilePointer的几点说明:

1:将文件指针设置超过文件大小是可行的,并且可以在该位置写入数据,这样文件大小将增大,还可以通过调用SetEndOfFile()增大或者切断文件

2:如果文件是被FILE_FLAG_NO_BUFFERING打开的,则文件指针只能被设置为扇区大小的整数倍

4.3:SetEndOfFile()可以切断或增大文件大小

5:同步异步IO基本函数

[cpp] view plain copy print ?
  1. BOOL ReadFile(  
  2.     HANDLE hFile,  
  3.     LPVOID lpBuffer,  
  4.     DWORD nNumberOfBytesToRead,  
  5.     LPDWORD lpNumberOfBytesRead,  //异步IO情况下此值没有意义,应传NULL   
  6.     LPOVERLAPPED lpOverlapped  
  7.     );  
  8. BOOL WriteFile(  
  9.     HANDLE hFile,  
  10.     LPCVOID lpBuffer,  
  11.     DWORD nNumberOfBytesToWrite,  
  12.     LPDWORD lpNumberOfBytesWritten,    //异步IO情况下此值没有意义,应传NULL   
  13.     LPOVERLAPPED lpOverlapped  
  14.     );  
  15. BOOL FlushFileBuffers(  
  16.     HANDLE hFile  
  17.     );  
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 );

Windows Vista提供了一些函数能够取消同步IO,具体参考书上
异步IO在CreateFile()中要制定FILE_FLAG_OVERLAPPED

6:异步IO

[cpp] view plain copy print ?
  1. typedef struct _OVERLAPPED   
  2. {    
  3.     ULONG_PTR Internal;         //错误码,一旦发出一个异步IO请求,此值被初始化为STATUS_PENDING,如果IO完成,则变为其他值   
  4.                                 //我们可以通过宏HasOverlappedIoCompleted(pOverlapped)检查IO是否完成,这个宏本质就是返回   
  5.                                 //return Internal!=STATUS_PENDING   
  6.     ULONG_PTR InternalHigh;     //IO请求完成时,保存已传输的字节数   
  7.     DWORD Offset;               //下面解释   
  8.     DWORD OffsetHigh;    
  9.     HANDLE hEvent;              //以后解释   
  10. }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;

4.5.1:采用异步IO打开文件设备,文件指针是没有意义的,因为有多个异步操作同时进行,我们无法对文件指针进行同步,所以Offset和OffsetHigh指定从文件那个地方开始执行异步IO操作

如果不是文件设备,必须\将Offset和OffsetHigh设为0

4.5.2:异步IO完成时,会收到一个OVERLAPPED指针,这就是调用异步IO函数传递的那个OVERLAPPED指针,我们可以写一个C++类从OVERLAPPED派生,这样就能添加更多的数据

4.5.3:几个注意事项:

1:异步IO不会按照你的投递顺序来执行,驱动会选择他认为最快的方式来组合这些投递

2:错误处理,以文件IO为例,当我们投递一个异步ReadFile()时,设备驱动程序可能会以同步方式执行,例如如果设备驱动程序发现要读取的数据在文件缓冲里时,就不会投递这个异步设备IO,而是直接将数据复制进我们的缓冲区

如果IO是同步方式执行,ReadFile()和WriteFile()返回非零值,如果是异步或者出现错误,返回FALSE,调用GetLastError()获得错误码,错误码列举一部分:

[cpp] view plain copy print ?
  1. ERROR_IO_PENDING                //异步IO投递成功   
  2.   
  3. ERROR_INVALID_USER_BUFFER         
  4. ERROR_NOT_ENOUGH_MEMORY         //每个设备驱动程序会在非分页缓冲池中维护一个固定大小的列表来管理   
  5. //待处理的IO请求,如果这个列表已满,异步IO就会失败,返两个错误中的一种,具体返回那个要看设备驱动程序   
  6.   
  7. ERROR_NOT_ENOUGH_QUOTA          //某些设备要求我们提供的数据缓存页面锁定,也就是不能被唤出内存的页面,   
  8. //但是系统对一个进程能够锁定的页面数量做了限制,如果超过这个数量,则会返回此错误   
  9. //例如如果指定了FILE_FLAG_NO_BUFFERING,就必须满足页面锁定要求   
  10. //可以调用SetProcessWorkingSetSize()来增加进程能锁定页面数量的配额  
ERROR_IO_PENDING //异步IO投递成功 ERROR_INVALID_USER_BUFFER ERROR_NOT_ENOUGH_MEMORY //每个设备驱动程序会在非分页缓冲池中维护一个固定大小的列表来管理 //待处理的IO请求,如果这个列表已满,异步IO就会失败,返两个错误中的一种,具体返回那个要看设备驱动程序 ERROR_NOT_ENOUGH_QUOTA //某些设备要求我们提供的数据缓存页面锁定,也就是不能被唤出内存的页面, //但是系统对一个进程能够锁定的页面数量做了限制,如果超过这个数量,则会返回此错误 //例如如果指定了FILE_FLAG_NO_BUFFERING,就必须满足页面锁定要求 //可以调用SetProcessWorkingSetSize()来增加进程能锁定页面数量的配额

3:在异步IO完成之前,不能删除OVERLAPPED结构

7:取消设备中的异步IO请求

1:4种取消方法

1.1:调用BOOL CancelIo(HANDLE hd);关闭调用线程添加的所有IO请求(完成端口除外)

1.2:关闭设备句柄,这将取消其所有IO请求

1.3:线程终止,会取消此线程所有IO请求(完成端口除外)

1.4:调用BOOL CancelIoEx(HANDLE hd,LPOVERLAPPED pOVerLapped);如果pOVerLapped为0,则取消此线程投递的所有IO请求,如果不为0,则取消相应的IO请求而不管是不是本线程投递的

2:被取消的IO请求会返回错误码ERROR_OPERATION_ABORTED

8:接受IO请求完成通知

1:方法1:触发设备内核对象

[cpp] view plain copy print ?
  1. HANDLE hd=CreateFile(...,FILE_FLAG_OVERLAPPED,...);  
  2. ...//初始化其他数据   
  3. bool  bReadDone=ReadFile(...);//这会将文件句柄设置为未触发状态   
  4. DWORD dwError=GetLastError();  
  5.   
  6. if(bReadDone==false&&(dwError==ERROR_IO_PENDING))  
  7. {  
  8.     WaitForSingleObject(hd,INFINITE);  
  9.     ...//查看OVERLAPPED结构中错误码和实际传输字节数   
  10. }  
  11. else  
  12. {  
  13.     //此异步IO被同步执行,或者发生了其他错误,检查dwError具体值   
  14. }  
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:方法2:触发事件内核对象

OVERLAPPED有一个hEvent,它用来标识一个事件内核对象,我们每投递一个异步IO,就为其添加一个事件内核对象,这样,当一个异步IO完成时,设备驱动会去检查这个hEvent的值,如果有值,则会SetEvent(), 我们就可以调用WaitForMultipleObjects()来等待这些事件内核对象,并通过返回值确定是那个异步IO完成了

9:可提醒IO

1:每个线程都有一个与其相关的队列,叫做异步过程调用(APC)

2:使用APC执行异步函数如下

CreateFileEx(...,LPOVERLAPPED_COMPLETION_ROUTINE pfn);

pfn别成为完成函数

[cpp] view plain copy print ?
  1. (WINAPI *LPOVERLAPPED_COMPLETION_ROUTINE)(  
  2.     __in    DWORD dwErrorCode,  
  3.     __in    DWORD dwNumberOfBytesTransfered,  
  4.     __inout LPOVERLAPPED lpOverlapped  
  5.     );  
(WINAPI *LPOVERLAPPED_COMPLETION_ROUTINE)( __in DWORD dwErrorCode, __in DWORD dwNumberOfBytesTransfered, __inout LPOVERLAPPED lpOverlapped );

3:系统将已经完成的异步IO投递到APC队列中的顺序是随机的

4:调用

[cpp] view plain copy print ?
  1. SleepEx();  
  2. WaitForSingleObjectEx();  
  3. WaitForMultipleObjectsEx();  
  4. SignalObjectAndWait();  
  5. GetQueuedCompletionStatusEx();  
  6. MsgWaitForMultipleObjectsEx();  
SleepEx(); WaitForSingleObjectEx(); WaitForMultipleObjectsEx(); SignalObjectAndWait(); GetQueuedCompletionStatusEx(); MsgWaitForMultipleObjectsEx();

中的一个将线程置为可提醒状态(很多最后一个值需设置为TRUE)

只要APC队列中有一项,线程就不会被挂起,转而去执行完成函数,如果APC队列为空,线程被挂起,直到APC队列不为空或者等待的内核对象被触发或者超时或者一个互斥量被遗弃

这6个函数如果是因为APC返回,返回值为WAIT_IO_COMPLETION,GetLastError()也是此值

可提醒IO不会试图去触发OVERLAPPED的事件对象,所以我们可以据为己有

5:可提醒IO的缺点:

发出IO请求的线程必须对其进行处理,即使其他线程处于空闲状态,效率低下

6:采用APC通知一个线程优雅退出的方式

[cpp] view plain copy print ?
  1. //手动添加APC   
  2. DWORD QueueUserAPC(     //返回值实际是BOOL值,表示添加成功与否   
  3. __in PAPCFUNC pfnAPC,   //回调函数,可以跨进程,那么此函数也应该在相应的地址空间   
  4. __in HANDLE hThread,  
  5. __in ULONG_PTR dwData   //传个回调函数的一个参数而已   
  6. );  
  7. //回调函数原型   
  8. VOID (APIENTRY *PAPCFUNC)(  
  9.     __in ULONG_PTR dwParam);  
  10.   
  11. //采用APC通知一个线程优雅退出的方式   
  12. void ThreadFun()  
  13. {  
  14.     ...;  
  15.     DWORD dw=WaitForSingleObjectEx(hd,INFINITE,TRUE);  
  16.     if(dw==WAIT_OBJECT_0)  
  17.     {  
  18.         ...;  
  19.     }  
  20.     else if(dw==WAIT_IO_COMPLETION)  
  21.     {  
  22.         QUIT;  
  23.     }  
  24. }  

你可能感兴趣的:(数据结构,IO,File,null,Integer,winapi)