第十章:同步设备I/O与异步设备I/O

 

由于第十章的笔记也比较多,现在先将前一部分的笔记贴出来.

1. 用来打开各种设备的函数

设备

用来打开设备的函数

文件

CreateFile(pszName为路径名或UNC路径名)

目录

CreateFile(pszName为路径名或UNC路径名).如果在调用CreateFile 的时候指定FILE_FLAG_BACKUP_SEMANTICS标志,那么Windows允许我们打开一个目录.打开目录使我们能够改变目录的属性(隐藏,正常...)和它的时间戳

逻辑磁盘驱动器

CreateFile(pszName为"\\.\x")windows允许我们打开一个逻辑磁盘驱动器,其中x是驱动器的盘符,打开驱动器是我们能够格式化驱动器或者检测驱动器媒介大小

物理磁盘驱动器

CreateFile(pszName为"\\.\PHYSICALDRIVEx").此时windows允许我们打开一个物理磁盘驱动器,其中x是物理驱动器号.打开物理驱动器使得我们能够直接访问硬盘分区表.打开物理驱动器有潜在的危险,错误的写入设备可能会导致操作系统的文件无法访问磁盘的内容

串口

CreateFile(pszName为"COMx")

并口

CreateFile(pszName为"LPTx")

邮件槽服务器

CreateMailslot(pszName为"\\.\mailslot\mailslotname"

邮件槽客户端

CreateFile(pszName为"\\serverName\mailsolt\mailsoltname"

命名管道服务器

CreateNamedPipe(pszName为"\\.\pipe\pipename")

命名管道客户端

CreateFlies(pszName为"\\.\servername\pipe\pipename")

匿名管道

CreatePipe用来打开服务器和客户端

套接字

Socket,accept或AcceptEx

控制台

CrateConsoleScreenBuffer或GetStdHandle

设置串口的波特率:

BOOL SetCommConfig(

HANDLE hCommDev,

LPCOMMCONFIG pCC,

DWORD dwSize);

在等待读取的时候,可以设置一个超时值.

BOOL SetMailslotInfo(

HANDLE hMailslot,

DWORD dwReadTimeout);

如果有一个设备句柄,那么可以调用GetFileType来查出设备的类型:

DWORD GetFileType(HANDLE hDevice);

描述

FILE_TYPE_UNKNOWN

指定的文件为未知类型

FILE_TYPE_DISK

指定的文件是一个磁盘

FILE_TYPE_CHAR

指定的文件是一个字符文件,一般来说是一个并口设备或控制台

FILE_TYPE_PIPE

指定的文件是一个命名管道或匿名管道

CreateFile详解:

HANDLE CreateFile(

PCTSTR pszName,//表示设备的类型,也表示该设备的某个实例

DWORD dwDesiredAccess,//想以何种方式来和设备进行数据传输,具体取值如下所示

DWORD dwShareMode,//用来指定设备共享特权,当我们仍然打开着一个设备,该参数可 //控制其他的CreateFile的调用,能够以何种方式来打开设备.

PSECURITY_ATTRIBUTES psa,//

DWORD dwCreationDisposition,

DWORD dwFlagsAndAttributes,

HANDLE hFileTemplate);//表示一个已打开的文件句柄

dwDesiredAccess的取值:

含义

0

我们不希望从设备读取数据或向设备写入数据.如果改变只想设备的配置(比如知识修改文件的时间戳),那么可传入0

GENERIC_READ

允许对设备进行只读访问

GENERIC_WRITE

允许对设备进行只写访问.

GENERIC_WRITE|

GENERIC_READ

允许设备进行读写操作.由于这个标志允许我们和设备之间自由地交换数据,因此最常用

dwShareMode取值:

含义

0

要求独占对设备的访问.如果设备已经打开,CreateFile调用会失败

FILE_SHARE_READ

如果有其他内核对象要使用该设备,我们要求它们不得修改设备的数据.如果设备已经以写入方式或独占方式打开,那么我们的CreateFile调用会失败.如果我们成功的打开了设备,那么后续的使用了GENERIC_WRITE访问标志的CreateFile调用会失败.

FILE_SHARE_WRITE

同上

FILE_SHARE_READ|

FILE_SHARE_WRITE

如果有其他内核对象要使用该设备,我们不关心它们会从设备读取数据还是会向设备写入数据.如果设备已经以独占方式打开,那么我们的CreateFile会调用失败.如果我们成功打开了设备,那么后续的要求独占读取访问,独占写入访问或者独占读访问都会失败

FILE_SHARE_DELETE

当对文件进行操作的时候,我们不关心文件是否被逻辑删除或被移动.在Windows内部,系统会将文件标记为待删除,然后当该文件所有已打开的句柄都被关闭的时候,再将其真正的删除.

dwCreationDisposition取值:

含义

CREATE_NEW

告诉CreateFile创建一个新文件,如果文件已经存在,则调用失败

CREATE_ALWAYS

告诉CreateFile无论同名文件存在与否都创建一个新文件.如果同名文件已经存在,则该函数会覆盖原来的文件

OPEN_EXISTING

打开一个已有的文件或设备,如果文件或设备不存在,那么调用将会是失败

OPEN_ALWAYS

打开一个已有文件,如果文件存在,函数就直接打开,如果不存在,那么函数会创建一个新文件

TRUNCATE_EXISTING

打开一个已有的文件并将文件的大小截断为0字节.如果文件不存在,则调用失败

如果想要使用CreateFile来打开文件之外的其他设备时,必须将OPEN_EXISTING传给dwCreationDisposition参数.

dwFlagsAndAttribute两个用途:

1. 允许我们设置一些标志来微调与设备之间的通讯;

2. 如果设备是一个文件,我们还能够设备文件的属性.

这些通讯标志中的大多数都是一些信号,用来告诉系统我们打算以何种方式来访问设备.

通讯标志:

1. 高速缓存标志:

FILE_FLAG_NO_BUFFERING:表示在访问文件的时候不要使用任何数据缓存.

但在使用时,必须遵循一定的规则:

◆ 在访问文件的时候,使用的偏移量必须正好是磁盘卷的扇区大小的整数倍(可以使用GetDiskFreeSpace函数来确定磁盘卷的扇区大小)

◆ 读取/写入文件的字节数必须正好是扇区大小的整数倍

◆ 必须确保缓存在进程地址空间中的起始地址正好是扇区大小的整数倍

FILE_FLAG_SEQUENTIAL_SCAN和FILE_FLAG_RANDOM_ACCESS只有我们允许系统对文件数据进行缓存的时候,这些标志才会被忽略.

如果指定前者,系统会认为我们会顺序的访问文件(一次读入大量的数据).如果我们不需要这样,可以指定后一标志.

FILE_FLAG_WRITE_THROUGH:禁止对文件写入操作进行缓存以减少数据丢失的可能.此时系统会将文件修改直接写到磁盘中.但在内部缓存会保存文件数据.

2. 其他标志:

FILE_FLAG_DELETE_ON_CLOSE常和FILE_ATTRIBUTE_TEMPORARY属性一起使用,表示应用程序创建一个临时的文件,向文件中写入数据,从文件中读取数据,最后关闭文件.当文件关闭时,系统自动删除该文件.

FILE_FLAG_BACKUP_SEMANTICS:为了确保试图打开文件或创建文件的进程具有所需的访问特权,系统通常会执行安全性检查.一般将此标志用于备份和恢复软件.

FILE_FLAG_POSIX_SEMANTICS:表明文件名区分大小写.

FILE_FLAG_OPEN_REPARSE_POINT告知系统忽略文件的重解析属性.不推荐使用该标志

FILE_FLAG_OPEN_NO_RECALL:告知系统不要将文件内容从脱机存储器恢复到联机存储器.

FILE_FLAG_OVERLAPPED:这个标志告诉系统我们想要以异步方式来访问设备.

dwFlagsAndAttribute标志:除非我们正在创建一个新文件,而且我们传给hFileTemplate的参数为NULL,否则系统将忽略该参数.

标志

含义

FILE_ATTRIBUTE_ARCHIVE

文件是一个存档文件.应用程序用这个标志来将文件标志为待备份或待删除.系统默认设置这个标志

FILE_ATTRIBUTE_ENCRYPTED

文件是经过加密的

FILE_ATTRIBUTE_HIDDEN

文件是隐藏的.它不会出现在通常的目录清单中

FILE_ATTRIBUTE_NORMAL

文件没有其他属性,只有单独使用的时候这个标志才有效

FILE_ATTRIBUTE_NOT_CONTENT_INDEXED

内容索引服务不会对文件进行索引

FILE_ATTRIBUTE_OFFLINE

文件虽然存在,但文件内容已经转移到脱机存储器中.这个标志对层级存储系统比较有用

FILE_ATTRIBUTE_READONLY

文件是只读的.应用程序可以读取文件,但不能写入或删除文件

FILE_ATTRIBUTE_SYSTEM

文件是操作系统的一部分,或专供系统调用

FILE_ATTRIBUTE_TEMPORARY

文件数据只会使用一段时间.为了将访问时间降至最低,文件系统会尽量将文件数据保存在内存中,而不是保存在磁盘中.

dwFileTemplate:如果标示的是一个文件句柄,那么CreateFile会完全忽略dwFlagsAndAttribute参数,并转而使用hFileTemplate使用的属性(此时必须是一个已用GENERIC_READ标志打开的文件).如果打开已有的文件,此参数将被忽略.

如果函数调用成功,会返回文件或设备句柄,如果失败,返回INVALID_HANDLE_VALUE.

2. 取得文件的大小:

BOOL GetFileSizeEx(

HANDLE hFile,//句柄

PLARGE_INTEGER pliFileSize);//返回值

结构定义如下:(其实这个结构我们在前面使用可等待计时器内核对象的时候有使用过)

//有符号

typedef union _LARGE_INTEGER

{

struct  

{

DWORD LowPart;

LONG  HighPart;

};

LONGLONG QuadPart;

}LARGE_INTEGER,*PLARGE_INTEGER;

//无符号

typedef union _ULARGE_INTEGER

{

struct

{

DWORD LowPart;

DWORD HighPart;

};

ULONGLONG QuadPart;

}ULARGE_INTEGER,*PULARGE_INTEGER;

另外一个获取文件的物理大小的函数如下:

DWORD GetCompressedFileSize(

PCTSTR pszFileName,//

PDWORD pdwFileSizeHigh);

两个函数的区别:前者是获取逻辑大小.后者是返回物理大小(实际占用物理大小).

3. 常用函数讲解:

◆ 读取文件:

BOOL ReadFile(

HANDLE hFile,//文件句柄

LPVOID lpBuffer,//缓冲区指针

DWORD nNumberOfBytesToRead,//需要读取多少个字节

LPDWORD lpNumberOfBytesRead,//实际使用多少个字节

LPOVERLAPPED lpOverlapped);//一般设NULL

这个函数如果将lpNumberOfBytesRead设为NULL,将会产生错误.使用方法:

HANDLE hFile = CreateFile( strPath,

GENERIC_READ,

0,

NULL,

OPEN_EXISTING,

FILE_ATTRIBUTE_READONLY,

NULL );

if ( hFile == INVALID_HANDLE_VALUE )

{

MessageBox("文件打开失败");

}

else

{

char ch[10000];

DWORD dwNumbyte;

memset( ch,0,100 );

ReadFile( hFile,ch,9999,&dwNumbyte,NULL );

ch[dwNumbyte - 1] = '\0';

//此时ch的内容即为读取到的内容

}

◆ 随机访问文件(改变文件内核对象相关联的文件指针).

BOOL SetFilePointerEx(

HANDLE hFile,

LARGE_INTEGER  liDistanceToMove,//需要把指针移动多少个字节

PLARGE_INTEGER  pliNewFilePointer,//返回新的指针值

DWORD dwMoveMethod);//如何解释pliNewFilePointer.具体取值如下

含义

FILE_BEGIN

文件对象的文件指针将被设为liDistanceToMove参数指定的值.此时liDistanceToMove在这里解释为一个无符号64位值

FILE_CURRENT

文件对象的文件指针将与liDistanceToMove相加.如果想让指针向后移动,可以将liDistanceToMove解释为有符号的64位值

FILE_END

文件对象的文件指针被设为文件的逻辑大小加上liDistanceToMove参数.如果想让指针向后移动,可以将liDistanceToMove解释为有符号的64位值

值得注意的一些事实:

● 将文件指针的值设为超过文件当前的大小事正当的操作.除非是在该位置向文件写入数据或者调用SetEndOfFile,否则这样做不会增加文件在磁盘上的实际大小.

● 如果SetFilePointerEx操作的文件是用FIlE_FLAG_NO_BUFFERING标志打开的,那么文件指针只能被设置为扇区大小的整数倍.

● Windows没有提供一个GetFilePointerEx函数,我们可以使用下面类似的代码来获取当前指针的值:

LARGE_INTEGER liCurrentPosition = {0};

SetFilePointerEx( hFile,liCurrentPosition,&liCurrentPosition,FILE_CURRENT);

◆ 设置文件尾

BOOL SetEndOfFile(

HANDlE hFile);

通常,在关闭文件的时候,系统会负责设置文件尾.该函数会根据文件对象的文件指针当前所在的位置来截断文件的大小或增加文件的大小.

//下面代码就是将文件设为1024字节大小

HANDLE hFile = CreateFile(...);

LARGE_INTEGER liDistanceToMove;

liDistanceToMove.QuadPart = 1024;

SetFilePointerEx( hFile,liDistanceToMove,NULL,FILE_BEGIN );

SetEndOfFile( hFile );

4. 常见的几个函数: 

■ 写函数:

如文件打开时制定了FILE_FLAG_OVERLAPPED,那么必须用这个参数引用一个特殊的结构.那个结构定义了一个上次异步读取操作.否则应该将这个参数设置为NULL.

BOOL WriteFile(

HANDLE hFile,

CONST VOID* pvBuffer,//缓存

DWORD nNumBytesToWrite,//向设备写入多少个字节

PDWORD pdwNumBytes, //实际写入数据的字节数量.

OVERLAPPED* pOverlapped);

两个函数成功返回TRUE,否则返回FALSE.两者必须要有对应的权限.

同样,如果我们在调用该函数的时候如果给pdwNumBytes传NULL,那么将会产生异常.

■ 强制刷新数据,使得数据将缓存写入到设备.

BOOL FlushFileBuffers(HANDLE hFile);

调用成功返回TRUE,否则返回FLASE.

5. 用来进行同步I/O的函数很容易,但他们会阻塞来自同一个线程(即发出I/O请求的线程)地任何操作.

Vista提供了一些较大的特性:如果控制台是因为在同步I/O时而停止响应,那么用户现在可以按Ctrl+C来拿回控制权,继而继续使用.另外,新的Vista为打开和保存文件对话框提供了一个取消按钮,如何程序在一段时间没有响应,那么我们可以单击取消按钮.

Vista提供了一个函数允许我们将一个给定线程尚未完成的同步I/O请求取消:

BOOL CancelSynchronousIO(HANDLE hThread);//参数表示因等待I/O而被挂起的线程

这个句柄必须使用THREAD_TERMINATE访问权限创建的.如果指定的线程由于等待同步I/O操作完成而被挂起,那么CancelSynchronousIO会将被挂起的线程唤醒,线程试图执行的操作也将会失败.(GetLastError返回ERROR_OPERATION_ABORTED),另外会返回TRUE给调用者.

如果在调用CancelSynchronousIO的时候,线程并不是因为要等待设备响应而被挂起,那么函数将返回FLASE(GetLastError返回ERROR_NOT_FOUND).\

注意:以上都是建立在驱动程序支持取消的情况.

6. 异步操作:执行异步操作时,我们需要向CreateFile函数的dwFlagsAndAttribute参数传递FILE_FLAG_OVERLAPPED标志来打开设备,这个标志也是告诉我们想要以异步的方式来访问设备.(这是在ReadFile和WriteFile两个函数的pdwNumByte传NULL).

OVERLAPPED结构:

typedef struct _OVERLAPPED 

{

DWORD Internal; //[out] error code

DWORD InternalHigh; //[out] Number of bytes transferred

DWORD Offset; //[in] Low 32-bit file offset

DWORD OffsetHigh; //[in] High 32-bit file offset

HANDLE hEvent; //[in] Event handle or data

}OVERLAPPED,*LPOVERLAPPED;

参数详解:

Offset和OffsetHigh:两者构成一个64位的偏移量,他们表示当访问文件的时候应该从哪里开始进行I/O操作.需要注意的是异步操作将会忽略文件指针.非文件设备会忽略Offset和OffsetHigh--我们将这两个成员都初始化为0.否则I/O请求就会失败.

hEvent:在使用I/O完成端口)会用到这个成员.当使用可提醒I/O通知函数时,我们可以根据自己的需要来使用这个成员.

Internal:保存已处理的I/O请求的错误码.一旦我们发出一个异步I/O请求,设备驱动程序会立即将internal设为STAUTS_PENDING,表示没有错误.因为操作尚未开始.WinBase.h中定义HasOverlappedIOCompleted允许我们检查一个异步I/O操作是否已经完成.如果还是处于等待状态,那么返回FALSE,否则返回TRUE.

#define HasOverlappedIoCompleted(pOverlapped) \

((pOverlapped)->Internal) != STATUS_PENING)

InternalHigh:当异步I/O完成的时候,这个成员用来保存已传输的字节数.

异步设备I/O注意事项:

■ 设备驱动程序不必以先入先出的方式来处理队列中的I/O请求.

■ 能够以正确的方式来检查错误

如果GetLastError返回的是ERROR_IO_PENDING,表明I/O请求已经被成功的加入了队列,会在晚些时候完成.

返回的值为ERROR_INVALID_USER_BUFFER或ERROR_NOT_ENOUGH_MEMORY表明非分页缓冲池已满.

返回值为ERROR_NOT_ENOUGH_QUOTA 某些设备要求将我们的数据缓存所在的存储也页面锁定,这样当I/O在等待处理的时候,数据不会被唤出内存.当使用了FIILE_FLAG_NO_BUFFERING时,如果此时锁定失败,则返回ERROR_NOT_ENOUGH_QUOTA 。

■ 在异步I/O请求完成之前,一定不能移动或者销毁在发出I/O请求时所使用的数据缓存和OVERLAPPED结构的.

7. 有时women需要设备驱动程序对一个已经加入队列的设备I/O请求进行处理之前将其取消.windows提供了一下方法来达到这一目的.

■ 我们可以调用CancelIO来取消由给定句柄所标示的线程添加到队列中的所有I/O请求(除非该句柄有与之相关联的I/O完成端口):

BOOL CancelIO(HANDLE hFile);

■ 我们可以关闭设备句柄,来取消已经添加到队列中的所有I/O请求,而不管他是用那个线程添加的.

■ 当线程终止的时候,系统会自动取消该线程发出的所有I/O请求,但如果请求被发往的设备句柄具有与之关联的I/O完成端口,那么他们不在被取消之列.

■ 如果需要将发往给文件句柄的指定I/O请求取消,那么我们可以调用CancelIOEx:

BOOL CancelIoEx(HANDLE hFile,LPOVERLAPPED pOverlapped);

是用CancelIoEx,我们能够将调用线程之外的其他线程发出的待处理的I/O请求取消.这个函数会将hFile设备的待处理的I/O请求中所有与pOverlapped参数相关的请求都标记为取消.(换句话说就是他会取消指定的I/O请求).如果将此值传递NULL,那么将取消所有待处理的和hFile相关的所有I/O请求.

被取消的I/O请求会返回错误码ERROR_OPERATION_ABORTED.

你可能感兴趣的:(windows,struct,File,null,Integer,磁盘)