《Windows核心编程》学习——内核对象

内核对象
Windows核心编程》:
每个内核对象都只是一个内存块,它由操作系统内核分配,并只能由操作系统内核访问。该内存块是一个数据结构,其成员维护着与对象相关的信息。

常见的内核对象有:访问令牌对象,事件对象,文件对象,文件映射对象,I/O完成端口对象,作业对象,邮件槽对象、互斥量对象、管道对象、进程对象、信号量对象、线程对象、可等待的计时器对象以及线程池工厂对象等。
我的理解:
简单说来,内核对象就是被系统所管理的对象。它与那些由进程所管理的用户对象相对应,借助于系统的管理,实现了用户对象所不能实现的机制。例如通过进程内核对象,可以在程序中实现关闭指定进程的功能,如果不是因为这个进程对象是由系统内核来管理的话,在程序中就没办法知道现在系统里是不是运行着一个指定的进程,更别谈得到进程的句柄来关闭它了。像是内核对象所实现的这种跨进程管理的机制,是用户对象所不能实现的。这也正是内核对象的作用所在。
关于内核对象,最关键的便是要记住它被系统所有而不是被进程所有。


内核对象句柄
Windows核心编程》:
既然内核对象被系统所有,不能直接地去更改这个结构,应用程序应该如何操纵这些内核对象呢?答案是利用Windows提供的一组函数,这组函数会以最恰当的方式来操纵这些数据结构。我们始终可以用这些函数来访问这些内核对象。调用一个会创建内核对象的函数后,函数会返回一个句柄(HANDLE),它标识了所创建的内核对象。为了让操作系统知道我们要对哪个内核对象进行操作,我们需要将这个句柄传给各种Windows函数。
为了增强操作系统的可靠性,这些句柄值是与进程相关的。所以,如果想要把句柄值传递给另外一个进程的线程(通过某种进程间通信方式),那么另一个进程用我们的句柄值来发出调用时,就可能失败。
我的理解:
内核对象由系统所管理,进程中不能随意修改它,只能调用系统函数来对其做有限制的操作,创建或者打开内核对象之后可以得到一个句柄,这个句柄可以看成是内核对象的引用,对内核对象的操作需要借助于句柄来完成。
需要注意的是内核对象归系统管理,但内核对象句柄却是属于进程的。之所以这样做的原因,会在讲解跨进程内核对象共享时详细解释。


使用计数
Windows核心编程》:
内核对象的所有者是操作系统内核,而非进程。换言之,如果我们的进程调用一个函数来创建了一个内核对象,然后进程终止运行,则内核对象不一定会销毁。大多数情况下,这个内核对象是会销毁的,但假如另一个进程正在使用我们的进程创建的内核对象,那么在其它进程停止使用它之前,它是不会销毁的。总之,内核对象的生命期可能长于创建它的那个进程。
操作系统内核知道当前有多少个进程正在使用一个特定的内核对象,因为每个对象都包含一个使用计数(usagecount)。使用计数是所有内核对象类型都有的一个数据成员。初次创建一个对象的时候,其使用计数被设定为1.另一个进程获得对现有内核对象的访问后,使用计数就会递增。进程终止运行后,操作系统内核将自动递减此进程仍然打开的所有内核对象的使用计数。一旦对象的使用计数变成0,操作系统内核就会销毁该对象。这样一来,就可以保证系统中不存在没有被任何进程引用的内核对象。
我的理解:
这里注意一个特殊情况,调用CreateProcess()CreateThread()函数之后,产生的内核对象的使用计数是2而不是1,据说这是因为这两个函数在创建进程和线程内核对象之后还需要再打开一次,所以使用计数递增为2,这里内部的运作原理还不理解。经过ProcessExplore实际检测之后发现的确存在这样的情况,而CreateFile()创建的内核对象使用计数只有1,如果调用CloseHandle()函数的话使用计数降为0,文件内核对象被销毁。(此处存疑待考)


进程内核对象句柄表
Windows核心编程》:
一个进程初始化的时候,系统将为它分配一个句柄表(handletable)。这个句柄表仅供内核对象使用,不适用于用户对象或GDI对象。
一个进程首次初始化的时候,其句柄表为空。当进程内的一个线程调用一个会创建内核对象的函数时(比如CreateFile),内核将为这个对象分配并初始化一个内存块(也就是完成内核对象本身的创建和初始化过程)。然后,内核扫描进程的句柄表,查找一个空白的句柄表,查找一个空白的记录项,并对其进行初始化(也就是根据内核对象产生一个与进程相关的内核对象句柄)
调用一个函数时,如果它接受一个内核对象句柄作为一个参数,就必须把CreateXXXX函数返回的值传给它。在内部,这个函数会查找进程的句柄表,根据句柄表的映射关系找到句柄对应的内核对象的实际内存地址,然后以一种恰当的方式来操纵对象的数据结构。
我的理解:
前面曾经说过,内核对象的所有者是操作系统,但内核对象句柄的所有者是进程,每个进程都维护一个句柄表来记录进程中所包含的内核对象句柄,可以根据句柄来找到实际的内核对象。
如果一个WindowsAPI以内核对象句柄作为参数,那么它可以根据进程的句柄表来找到这个句柄对应的内核对象,之后用恰当的方式对内核对象进行操作。
每当在一个内核对象被关联到一个进程中时,相应的内核对象句柄就在句柄表中被创建,使用CloseHandle()函数来递减内核对象使用计数的时候,也会首先把内核对象句柄从句柄表中清除。这就是为什么说使用CloseHandle()可以使创建的进程与当前进程脱离关系的原因——句柄表中不再记录有进程的句柄,那么当前进程就无法通过句柄来对创建出来的进程产生影响,可以说是剪短了脐带
另外,只要调用一次CloseHandle()就会把句柄从句柄表中清除,所以尝试两次CloseHandle()来销毁一个刚刚创建的进程或线程内核对象是不会起作用的。


内核对象的跨进程共享
Windows核心编程》:
很多时候,在不同进程中运行的线程需要共享内核对象,下面罗列了一些理由:
1.
利用文件映射对象,可以在同一台机器上运行的两个不同进程间共享数据块
2.
借助邮件槽和命名管道,在网络中的不同计算机上运行的进程可以相互发送数据块
3.
互斥量、信号量和事件量允许不同进程中的线程同步执行。例如,一个应用程序可能需要在完成某个任务后向另一个应用程序发出通知。
由于内核对象的句柄是与每一个进城相关的,所以执行这些任务并不轻松。不过,Microsoft也有充分的理由需要将句柄设计成与进程相关(process-relative)”的。其中最重要的原因是健壮性(可靠性)如果把内核对象句柄设计成相对于整个系统,或者说把它们设计成系统级句柄,一个进程就可以很容易获得到另一个进程正在使用的一个对象的句柄,从而对该进程造成严重破坏。之所以将句柄设计成与进程相关的,或者说把它们设计成进程级的句柄,另一个原因是安全性。内核对象是受安全保护的,进程在试图操纵一个对象之前,必须先申请操纵它的权限。对象的创建者为了阻止一个未经许可的用户自己的对象,只需拒绝该用户访问它。
有三种不同的机制来允许进程共享内核对象:使用对象句柄继承;为对象命名;复制对象句柄。
我的理解:
这里解释了之前在学习内核对象句柄时的疑问,也就是为什么内核对象句柄要被设计成进程级而不是系统级。如果内核对象句柄被设计成系统级的话,只需要得到另一个进程正在使用的一个对象的句柄,就可以对这个进程造成破坏。就像把宝箱的钥匙放到了箱子旁边,只要看到箱子的人都可以把箱子的宝物拿走。而如果被设计成进程级的话,这把钥匙被保存在宝箱的主人那里,而且这钥匙还必须由主人亲自使用才能打开,别人即使拿到了钥匙,也是无计可施。
对于三种共享内核对象的机制,属于方法而非概念范畴,在此不做详细讲解,用到的时候可参见《Windows核心编程》。

 

总结

1.   理解了内核对象的实质意义——由系统维护而非属于进程。

2.   内核对象的生命期可以长于创建它的进程。

3.   访问内核对象需要通过内核对象句柄来进行,内核对象句柄属于进程并与进程相关,另一个进程即使得到句柄也不能正确操作内核对象。

4.   Windows提供了限定的几种机制来提供内核对象的跨进程共享。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值