window内核对象
一、什么是内核对象
- 每个系统都有自己的内核对象,所谓的内核对象,本质上就是一个类的实例,比如你创建了一个类A,A a的方式创建一个对象,如果把这a对象关系到系统内部运行环境中,那么a对象也可以认为是内核对象。内核对象是系统开发者在系统运行时创建用来维持整个系统得以正常运行的实例,而这些对象一般是不开放给系统维护以为的人员,故称之内核对象。
- 其实平时开发过程中也用到过内核对象,只是封装起来了,你使用时候没有发现而已,比如打开一个文件,会使用到file的内核对象。
- 系统中常见的内核对象有:事件对象、文件对象、文件映射对象、作业对象、邮槽对象、互斥量、管道、进程、线程、定时器等
- 内核对象存的特性
内核对象存在于系统内部,这些对象有很多共性
- 使用计数:用来记录多少个进程、线程在使用这个对象,计数为0时销毁
- 安全性:不同用户具有不同的使用权限,可以设置不同的读写权限给客户(此时的客户为使用你代码的人)
二、进程内核对象句柄表
- 每个进程在创建时候,会记录当前进程使用了哪些内核对象,系统中该对象的使用计数+1,因为内核对象并不允许直接提供给用户,则在进程中以内核对象句柄表的方式提供给用户使用该内核对象。
- 如下图所示,每个进程有自己的句柄表,用户只能拿到自己进程的句柄,用来操作内核对象的相关属性,而这个句柄仅仅是一个long类型的数据,并非内核对象的地址,所以进程一种的句柄1,通过进程间通讯,进程二拿到这个句柄1,直接调用是无效的,因为进程二中句柄1可能绑定了其他的内核对象,也可能为空,会导致很严重的拒绝访问错误。
三、内核对象跨进程通讯
- 由上述可知,进程间通讯不能直接使用其他进程的句柄。
- 使用内核对象进行跨进程通讯的几种方式
- 使用内核对象句柄继承
- 为内核对象命名
- 复制内核对象句柄
- 终端服务命名空间
- 专有命名空间
- 第一种方式:使用内核对象句柄继承
这种方式需要是父子进程之间使用,当父进程创建子进程时,形成父子进程,同时会继承父进程的句柄表,可以获得父进程的内核对象的权限,当然如果父进程在创建子进程时对内核对象做了使用权限修改,则子进程可能无法获取到父进程的句柄表,即获取父进程的内核对象中的数据,达到通讯的目标。 - 第二种方式:为内核对象命名
由于内核对象的实例在系统中一直存在(只要计数不为0),那么另外的进程就有可能获取到这个内核对象里面的数据,使用命名的内核对象可以达到这种效果。
比如进程一创建了一个名字为“mutex”的内核对象(比如是互斥量),系统中他一直存在,那么进程二只需要使用系统提供的打开这种内核对象的api,并传入名称(openMutex(xxx,“mutex”,xxx)),即可获得该内核对象,并在进程二的内核对象句柄表中添加该信息,即可获取名为“mutex”的内核对象,从中获取数据,达到跨进程通讯 - 第三种方式:复制内核对象句柄
像上述所说,进程二不能直接拿进程一的句柄1来使用,而window为用户提供一个api:DuplicateHandle,这个函数可以帮助进程二可以使用进程一的句柄,但不是原来的句柄1
大致过程是:DuplicateHandle函数找到进程一种的句柄1所对应的内核对象地址,然后在进程二中新建一个句柄对象,可能是句柄5,然后将进程一的内核对象地址和句柄5一起写入进程二的句柄表,从而达到跨进程使用句柄,实现数据通讯。 - 第四种方式:终端服务命名空间
这种方式是专门为系统服务而设立的,上述的方式都是全局的命名空间,而系统为底层服务专门有一个命名空间,目的是对服务进行保护。
全局的命名对象,如第二种方式,也可以这样表示“Global\mutex”
终端服务的命名对象写法是这样表示:“Local\mutex”
在创建命名对象的时候,除了上述的命名参数不一样,其他参数都一样。 - 第五种方式:专有命名空间
系统提供一种可以自己专属命名的方式,类似于c++的命名空间的概念,自己创建一个专有的命名空间,目的是用于防止第三方攻击,如你使用mutex的单例模式进程,对方创建一个mutex,让你的程序误认为已经启动,达到攻击的目标。
四、总结
了解了内核对象这些特性,以及跨进程的使用方式,在实际开发过程中可以根据自己的情况选择一种合适的方式!