3.1 什么是内核对象

来源:互联网 发布:知行劳务 编辑:程序博客网 时间:2024/06/07 11:23

第3章 内核对象

在介绍Windows API 的时候,首先要讲述内核对象以及它们的句柄。本章将要介绍一些比较抽象的概念,在此并不讨论某个特定内核对象的特性,相 反只是介绍适用于所有内核对象的特性。

首先介绍一个比较具体的问题,准确地理解内核对象对于想要成为一名Wi n d o w s 软件开发能手的人来说是至关重要的。内核对象可以供系统和 应用程序使用来管理各种各样的资源,比如进程、线程和文件等。本章讲述的概念也会出现在本书的其他各章之中。但是,在你开始使用实际的函数 来操作内核对象之前,是无法深刻理解本章讲述的部分内容的。因此当阅读本书的其他章节时,可能需要经常回过来参考本章的内容。


3.1 什么是内核对象

作为一个Wi n d o w s 软件开发人员,你经常需要创建、打开和操作各种内核对象。系统要创建和操作若干类型的内核对象,比如存取符号对象、 事件对象、文件对象、文件映射对象、I / O 完成端口对象、作业对象、信箱对象、互斥对象、管道对象、进程对象、信标对象、线程对象和等待计 时器对象等。这些对象都是通过调用函数来创建的。例如,C r e a t e F i l e M a p p i n g 函数可使系统能够创建一个文件映射对象。每个内 核对象只是内核分配的一个内存块,并且只能由该内核访问。该内存块是一种数据结构,它的成员负责维护该对象的各种信息。有些数据成员(如安全性描述符、使用计数等)在所有对象类型中是相同的,但大多数数据成员属于特定的对象类型。例如,进程对象有一个进程I D 、一个基 本优先级和一个退出代码,而文件对象则拥有一个字节位移、一个共享模式和一个打开模式。

由于内核对象的数据结构只能被内核访问,因此应用程序无法在内存中找到这些数据结构并直接改变它们的内容。M i c r o s o f t 规定了这个限 制条件,目的是为了确保内核对象结构保持状态的一致。这个限制也使M i c r o s o f t 能够在不破坏任何应用程序的情况下在这些结构中添加、 删除和修改数据成员。

如果我们不能直接改变这些数据结构,那么我们的应用程序如何才能操作这些内核对象呢?解决办法是,Wi n d o w s 提供了一组函数,以便用定 义得很好的方法来对这些结构进行操作。这些内核对象始终都可以通过这些函数进行访问。当调用一个用于创建内核对象的函数时,该函数就返回一 个用于标识该对象的句柄。该句柄可以被视为一个不透明值,你的进程中的任何线程都可以使用这个值。将这个句柄传递给Wi n d o w s 的各个函 数,这样,系统就能知道你想操作哪个内核对象。本章后面还要详细讲述这些句柄的特性。

为了使操作系统变得更加健壮,这些句柄值是与进程密切相关的。因此,如果将该句柄值传递给另一个进程中的一个线程(使用某种形式的进程间的 通信)那么这另一个进程使用你的进程的句柄值所作的调用就会失败。在3 . 3 节“跨越进程边界共享内核对象”中,将要介绍3 种机制,使多个进 程能够成功地共享单个内核对象。

3.1.1 内核对象的使用计数

内核对象由内核所拥有,而不是由进程所拥有。换句话说,如果你的进程调用了一个创建内核对象的函数,然后你的进程终止运行,那么内核对象不 一定被撤消。在大多数情况下,对象将被撤消,但是如果另一个进程正在使用你的进程创建的内核对象,那么该内核知道,在另一个进程停止使用该 对象前不要撤消该对象,必须记住的是,内核对象的存在时间可以比创建该对象的进程长。

内核知道有多少进程正在使用某个内核对象,因为每个对象包含一个使用计数。使用计数是所有内核对象类型常用的数据成员之一。当一个对象刚刚 创建时,它的使用计数被置为1 。然后,当另一个进程访问一个现有的内核对象时,使用计数就递增1 。当进程终止运行时,内核就自动确定该进程 仍然打开的所有内核对象的使用计数。如果内核对象的使用计数降为0 ,内核就撤消该对象。这样可以确保在没有进程引用该对象时系统中不保留任 何内核对象。

3.1.2 安全性

内核对象能够得到安全描述符的保护。安全描述符用于描述谁创建了该对象,谁能够访问或使用该对象,谁无权访问该对象。安全描述符通常在编写 服务器应用程序时使用,如果你编写客户机端的应用程序,那么可以忽略内核对象的这个特性。

Windows 98 根据原来的设计,Windows 98 并不用作服务器端的操作系统。为此,M i c r o s o f t 公司没有在Windows 98 中配备安全特性。不 过,如果你现在为Windows 98设计软件,在实现你的应用程序时仍然应该了解有关的安全问题,并且使用相应的访问信息,以确保它能在Windows 2000上正确地运行.

用于创建内核对象的函数几乎都有一个指向S E C U R I T Y _ AT T R I B U T E S 结构的指针作为其参数,下面显示了C r e a t e F i l e M a p p i n g 函数的指针:

HANDLE CreateFileMapping(   HANDLE hFile.   PSECURITY_ATTRIBUTES psa,   DWORD flProtect,   DWORD dwMaximumSizeHigh,   DWORD dwMaximuniSizeLow,   PCTSTR pszNarne);
大多数应用程序只是为该参数传递N U L L ,这样就可以创建带有默认安全性的内核对象。默认安全性意味着对象的管理小组的任何成员和对象的创 建者都拥有对该对象的全部访问权,而其他所有人均无权访问该对象。但是,可以指定一个S E C U R I T Y _ AT T R I B U T E S 结构,对它进 行初始化,并为该参数传递该结构的地址。S E C U R I T Y _ AT T R I B U T E S 结构类似下面的样子:尽管该结构称为S E C U R I T Y _ AT T R I B U T E S ,但是它包含的与安全性有关的成员实际上只有一个,即l p S e c u r i t y D e s c r i p t o r 。如果你想要限制人们对你 创建的内核对象的访问,必须创建一个安全性描述符,然后像下面这样对S E C U R I T Y _ AT T R I B U T E S 结构进行初始化:
typedef struct _SECURITY_ATTRIBUTES{   DWORD nLength,   LPVOID lpSecurityDescriptor;   BOOL bInherttHandle;} SECURITY_ATTRIBUTES;
尽管该结构称为S E C U R I T Y _ AT T R I B U T E S ,但是它包含的与安全性有关的成员实际上只有一个,即l p S e c u r i t y D e s c r i p t o r 。如果你想要限制人们对你创建的内核对象的访问,必须创建一个安全性描述符,然后像下面这样对S E C U R I T Y _ AT T R I B U T E S 结构进行初始化:
   SECURITY_ATTRIBUTES sa;   sa.nLength = sizeof(sa);       //Used for versioning   sa.lpSecuntyDescriptor = pSD,  //Address of an initialized SD   sa.bInheritHandle = FALSE;     //Discussed later   HANDLE hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE,       &sa, PAGE_REAOWRITE, 0, 1024, "MyFileMapping");
由于b I n h e r i t H a n d l e 这个成员与安全性毫无关系,因此准备推迟到本章后面部分继承性一节中再介绍b I n h e r i t H a n d l e 这个成员。

当你想要获得对相应的一个内核对象的访问权(而不是创建一个新对象)时,必须设定要对该对象执行什么操作。例如,如果想要访问一个现有的文 件映射内核对象,以便读取它的数据,那么应该调用下面这个O p e n f i l e M a p p i n g 函数:

HANDLE hFileMapping = OpenFileMapping(FILE_MAP_READ,   FALSE, "MyFileMapping");
通过将F I L E _ M A P _ R E A D 作为第一个参数传递给O p e n F i l e M a p p i n g ,指明打算在获得对该文件映象的访问权后读取该文件 ,O p e n F i l e M a p p i n g 函数在返回一个有效的句柄值之前,首先执行一次安全检查。如果(已登录用户)被允许访问现有的文件映射内 核对象,O p e n F i l eM a p p i n g 就返回一个有效的句柄。但是,如果被拒绝访问该对象,O p e n F i l e M a p p i n g 将返回N U L L ,而调用G e t L a s t E r r o r 函数则返回5 (E R R O R _ A C C E S S _ D E N I E D ),同样,大多数应用程序并不使用该安全性,因此 将不进一步讨论这个问题。

Windows 98 虽然许多应用程序不需要考虑安全性问题,但是Wi n d o w s 的许多函数要求传递必要的安全访问信息。为Windows 98 设计的若干应 用程序在Windows 2000 上无法正确地运行,因为在实现这些应用程序时没有对安全问题给于足够的考虑。

例如,假设一个应用程序在开始运行时要从注册表的子关键字中读取一些数据。为了正确地进行这项操作,你的代码应该调用R e g O p e n K e y E x ,传递K E Y_Q U E RY_VA L U E ,以便获得必要的访问权。

但是,许多应用程序原先是为Windows 98 开发的,当时没有考虑到运行Wi n d o w s2 0 0 0 的需要。由于Windows 98 没有解决注册表的安全问题 ,因此软件开发人员常常要调用R e g O p e n K e y E x 函数,传递K E Y _ A l l _ A C C E S S ,作为必要的访问权。开发人员这样做的原因 是,它是一种比较简单的解决方案,意味着开发人员不必考虑究竟需要什么访问权。问题是注册表的子关键字可以被用户读取,但是不能写入。

因此,当该应用程序现在放在Windows 2000 上运行时,用K E Y _ A L L _ A C C E S S 调用R e g O p e n K e y E x 就会失败,而且,没有相 应的错误检查方法,应用程序的运行就会产生不可预料的结果。

如果开发人员想到安全问题,把K E Y _ A L L _ A C C E S S 改为K E Y _ Q U E RY _ VA L U E ,则该产品可适用于两种操作系统平台。

开发人员的最大错误之一就是忽略安全访问标志。使用正确的标志会使最初为Windows 98 设计的应用程序更易于向Windows 2000 转换。

除了内核对象外,你的应用程序也可以使用其他类型的对象,如菜单、窗口、鼠标光标、刷子和字体等。这些对象属于用户对象或图形设备接口(G D I )对象,而不是内核对象。当初次着手为Wi n d o w s 编程时,如果想要将用户对象或G D I 对象与内核对象区分开来,你一定会感到不知所 措。比如,图标究竟是用户对象还是内核对象呢?若要确定一个对象是否属于内核对象,最容易的方法是观察创建该对象所用的函数。创建内核对象 的所有函数几乎都有一个参数,你可以用来设定安全属性的信息,这与前面讲到的C r e a t e F i l e M a p p i n g 函数是相同的。

用于创建用户对象或G D I 对象的函数都没有P S E C U R I T Y _ AT T R I B U T E S 参数。例如,让我们来看一看下面这个C r e a t e I c o n 函数:

HICON CreateIcon(   HINSTANCE hinst.   int nWidth,   int nHeight,   BYTE cPlanes,   BYTE cBitsPixel,   CONST BYTE *pbANDbits,   CONST BYTE *pbXORbits);

0 0
原创粉丝点击