生活的天平本不平衡,只有通过努力改变其偏向。

Windows驱动编程基础教程 第五部分

2008-05-24

Windows驱动编程基础教程(3.1-3.2)

3.1 使用OBJECT_ATTRIBUTES

一般的想法是,打开文件应该传入这个文件的路径。但是实际上这个函数并不直接接受一个字符串。使用者必须首先填写一个OBJECT_ATTRIBUTES 结构。在文档中并没有公开这个OBJECT_ATTRIBUTES结构。这个结构总是被InitializeObjectAttributes初始化。
下面专门说明InitializeObjectAttributes。

C++代码
  1. VOID InitializeObjectAttributes(
  2. OUT POBJECT_ATTRIBUTES InitializedAttributes,
  3. IN PUNICODE_STRING ObjectName,
  4. IN ULONG Attributes,
  5. IN HANDLE RootDirectory,
  6. IN PSECURITY_DESCRIPTOR SecurityDescriptor);

读者需要注意的以下的几点:InitializedAttributes是要初始化的OBJECT_ATTRIBUTES结构的指针。ObjectName则是对象名字字符串。也就是前文所描述的文件的路径(如果要打开的对象是一个文件的话)。
Attributes则只需要填写OBJ_CASE_INSENSITIVE| OBJ_KERNEL_HANDLE即可(如果读者是想要方便的简洁的打开一个文件的话)。OBJ_CASE_INSENSITIVE意味着名字字符串是 不区分大小写的。由于Windows的文件系统本来就不区分字母大小写,所以笔者并没有尝试过如果不设置这个标记会有什么后果。 OBJ_KERNEL_HANDLE表明打开的文件句柄一个“内核句柄”。内核文件句柄比应用层句柄使用更方便,可以不受线程和进程的限制。在任何线程中 都可以读写。同时打开内核文件句柄不需要顾及当前进程是否有权限访问该文件的问题(如果是有安全权限限制的文件系统)。如果不使用内核句柄,则有时不得不 填写后面的的SecurityDescriptor参数。
RootDirectory用于相对打开的情况。目前省略。请读者传入NULL即可。
SecurityDescriptor用于设置安全描述符。由于笔者总是打开内核句柄,所以很少设置这个参数。

3.2 打开和关闭文件

下面的函数用于打开一个文件:

C++代码
  1. NTSTATUS ZwCreateFile(
  2. OUT PHANDLE FileHandle,
  3. IN ACCESS_MASK DesiredAccess,
  4. IN POBJECT_ATTRIBUTES ObjectAttribute,
  5. OUT PIO_STATUS_BLOCK IoStatusBlock,
  6. IN PLARGE_INTEGER AllocationSize OPTIONAL,
  7. IN ULONG FileAttributes,
  8. IN ULONG ShareAccess,
  9. IN ULONG CreateDisposition,
  10. IN ULONG createOptions,
  11. IN PVOID EaBuffer OPTIONAL,
  12. IN ULONG EaLength);

这个函数的参数异常复杂。下面逐个的说明如下:
FileHandle:是一个句柄的指针。如果这个函数调用返回成成功(STATUS_SUCCESS),那就么打开的文件句柄就返回在这个地址内。
DesiredAccess: 申请的权限。如果打开写文件内容,请使用FILE_WRITE_DATA。如果需要读文件内容,请使用FILE_READ_DATA。如果需要删除文件或 者把文件改名,请使用DELETE。如果想设置文件属性,请使用FILE_WRITE_ATTRIBUTES。反之,读文件属性则使用 FILE_READ_ATTRIBUTES。这些条件可以用|(位或)来组合。有两个宏分别组合了常用的读权限和常用的写权限。分别为 GENERIC_READ和GENERIC_WRITE。此外还有一个宏代表全部权限,是GENERIC_ALL。此外,如果想同步的打开文件,请加上 SYNCHRONIZE。同步打开文件详见后面对CreateOptions的说明。
ObjectAttribute:对象描述。见前一小节。
IoStatusBlock也是一个结构。这个结构在内核开发中经常使用。它往往用于表示一个操作的结果。这个结构在文档中是公开的,如下:

C++代码
  1. typedef struct _IO_STATUS_BLOCK {
  2. union {
  3. NTSTATUS Status;
  4. PVOID Pointer;
  5. };
  6. ULONG_PTR Information;
  7. } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

实际编程中很少用到Pointer。一般的说,返回的结果在Status中。成功则为STATUS_SUCCESS。否则则是一个错误码。进一步的信息在 Information中。不同的情况下返回的Information的信息意义不同。针对ZwCreateFile调用的情况,Information 的返回值有以下几种可能:
?    FILE_CREATED:文件被成功的新建了。
?    FILE_OPENED:    文件被打开了。
?    FILE_OVERWRITTEN:文件被覆盖了。
?    FILE_SUPERSEDED:    文件被替代了。
?    FILE_EXISTS:文件已存在。(因而打开失败了)。
?    FILE_DOES_NOT_EXIST:文件不存在。(因而打开失败了)。
这些返回值和打开文件的意图有关(有时希望打开已存在的文件,有时则希望建立新的文件等等。这些意图在本小节稍后的内容中详细说明。
ZwCreateFile的下一个参数是AllocationSize。这个参数很少使用,请设置为NULL。    再接下来的一个参数为FileAttributes。这个参数控制新建立的文件的属性。一般的说,设置为FILE_ATTRIBUTE_NORMAL即 可。在实际编程中,笔者没有尝试过其他的值。
ShareAccess是一个非常容易被人误解的参数。实际上,这是在本代码打开这个文件的时候,允许别的代码同时打开这个文件所持有的权限。所以称为共 享访问。一共有三种共享标记可以设置:FILE_SHARE_READ、FILE_SHARE_WRITE、FILE_SHARE_DELETE。这三个 标记可以用|(位或)来组合。举例如下:如果本次打开只使用了FILE_SHARE_READ,那么这个文件在本次打开之后,关闭之前,别次打开试图以读 权限打开,则被允许,可以成功打开。如果别次打开试图以写权限打开,则一定失败。返回共享冲突。
同时,如果本次打开只只用了FILE_SHARE_READ,而之前这个文件已经被另一次打开用写权限打开着。那么本次打开一定失败,返回共享冲突。其中的逻辑关系貌似比较复杂,读者应耐心理解。
CreateDisposition参数说明了这次打开的意图。可能的选择如下(请注意这些选择不能组合):
?    FILE_CREATE:新建文件。如果文件已经存在,则这个请求失败。
?    FILE_OPEN:打开文件。如果文件不存在,则请求失败。
?    FILE_OPEN_IF:打开或新建。如果文件存在,则打开。如果不存在,则失败。
?    FILE_OVERWRITE:覆盖。如果文件存在,则打开并覆盖其内容。如果文件不存在,这个请求返回失败。
?    FILE_OVERWRITE_IF:新建或覆盖。如果要打开的文件已存在,则打开它,并覆盖其内存。如果不存在,则简单的新建新文件。
?    FILE_SUPERSEDE:新建或取代。如果要打开的文件已存在。则生成一个新文件替代之。如果不存在,则简单的生成新文件。
请联系上面的IoStatusBlock参数中的Information的说明。
最后一个重要的参数是CreateOptions。在惯常的编程中,笔者使用FILE_NON_DIRECTORY_FILE| FILE_SYNCHRONOUS_IO_NONALERT。此时文件被同步的打开。而且打开的是文件(而不是目录。创建目录请用FILE_ DIRECTORY_FILE)。所谓同步的打开的意义在于,以后每次操作文件的时候,比如写入文件,调用ZwWriteFile,在 ZwWriteFile返回时,文件写操作已经得到了完成。而不会有返回STATUS_PENDING(未决)的情况。在非同步文件的情况下,返回未决是 常见的。此时文件请求没有完成,使用者需要等待事件来等待请求的完成。当然,好处是使用者可以先去做别的事情。
要同步打开,前面的DesiredAccess必须含有SYNCHRONIZE。
此外还有一些其他的情况。比如不通过缓冲操作文件。希望每次读写文件都是直接往磁盘上操作的。此时CreateOptions中应该带标记 FILE_NO_INTERMEDIATE_BUFFERING。带了这个标记后,请注意操作文件每次读写都必须以磁盘扇区大小(最常见的是512字节) 对齐。否则会返回错误。
这个函数是如此的繁琐,以至于再多的文档也不如一个可以利用的例子。早期笔者调用这个函数往往因为参数设置不对而导致打开失败。非常渴望找到一个实际可以使用的参数的范例。下面举例如下:

C++代码
  1. // 要返回的文件句柄
  2. HANDLE file_handle = NULL;
  3. // 返回值
  4. NTSTATUS status;
  5. // 首先初始化含有文件路径的OBJECT_ATTRIBUTES
  6. OBJECT_ATTRIBUTES object_attributes;
  7. UNICODE_STRING ufile_name = RTL_CONST_STRING(L”\\??\\C:\\a.dat”);
  8. InitializeObjectAttributes(
  9. &object_attributes,
  10. &ufile_name,
  11. OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,
  12. NULL,
  13. NULL);
  14. // 以OPEN_IF方式打开文件。
  15. status = ZwCreateFile(
  16. &file_handle,
  17. GENERIC_READ | GENERIC_WRITE,
  18. &object_attributes,
  19. &io_status,
  20. NULL,
  21. FILE_ATTRIBUTE_NORMAL,
  22. FILE_SHARE_READ,
  23. FILE_OPEN_IF,
  24. FILE_NON_DIRECTORY_FILE |
  25. FILE_RANDOM_ACCESS |
  26. FILE_SYNCHRONOUS_IO_NONALERT,
  27. NULL,
  28. 0);

值得注意的是路径的写法。并不是像应用层一样直接写“C:\\a.dat”。而是写成了“\\??\\C:\\a.dat”。这是因为ZwCreateFile使用的是对象路径。“C:”是一个符号链接对象。符号链接对象一般都在“\\??\\”路径下。
这种文件句柄的关闭非常简单。调用ZwClose即可。内核句柄的关闭不需要和打开在同一进程中。示例如下:

C++代码
  1. ZwClose(file_handle);
作者:lonkil | 分类目录:编程开发 | 标签:

2 条评论

  1. lonkil 说道:

    都是缘份呀。

  2. Tr0j4n 说道:

    真是机缘巧合,在网上搜东西搜到你这里来了。突然想起很久没来过了的样子

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>