内核对象是个比较难理解的概念,问题的根源就在于即使是《核心编程》书中也没有说清楚它的定义,只是不停地举例和描述它的性质,还有如何使用。

  盲人摸象,难见全貌。只能尽可能列举它的性质,注意使用了

  引用计数(书中的说法是使用计数)就是内核对象的一个很关键的性质。由于内核对象的拥有者是内核而不是进程,所以只能由内核来做撤销内核对象的操作。而通常一个内核对象不一定只被一个进程使用的,创建或者撤销内核对象,就要看引用计数了。引用计数在内核对象被创建的时候被置为1,被进程访问一次引用计数就递增1。当引用计数降为0,内核就撤销这个内核对象。(至于何时引用计数递减1,书中没有明确说明,不过有编程经验的你肯定知道是什么时候了)

  安全性也是内核对象的一个重要的性质,它用于描述内核对象的访问者。由于创建内核对象的时候几乎都会有一个参数,指向Security_Attributes结构体的指针,例如CreateFileMapping(定义不详)。如果这个指针是NULL值,那就是默认的安全性,只有管理对象的小组成员和创建者可以访问;不过,只要你初始化并操作lpSecurityDescriptor这个成员,就可以设置它的安全性了。这样,内核对象被访问的时候(例如OpenFileMapping),在返回一个有效的句柄之前会执行一次安全检查,如果不通过检查返回的就不是一个有效句柄,而是NULL了,它的LastError是5(ERROR_ACCESS_DENIED)。

  进程的内核对象句柄表,在进程被创建的时候被分配。当线程共有内核对象产生的时候,内核会在句柄表中找出一个空项,把内核对象的内存块指针写进去。

  如果一个线程中调用函数返回一个句柄,这个句柄可以也只可以被线程中的所有线程使用。这些句柄的值实际上是句柄在当前进程句柄表中的索引,但不是固定的,如在Win2k中返回的句柄是用于标识句柄表的该对象的字节数。如果给句柄表中传入一个无效值,GetLastError则会返回6(ERROR_INVALID_HANDLE)。

  如果调用函数创建内核对象失败了,那么句柄的值通常是0(NULL),也有些函数返回的是INVALID_HANDLE_VALUE。但是无论用什么方式创建内核对象,都要通过CloseHandle来结束对对象的操作。这个函数会先检查句柄表,确定传入的索引是否有效,并查看引用计数,如果是0则撤销这个对象。

  一个无效的句柄传给CloseHandle的话,GetLastError会返回ERROR_INVALID_HANDLE。如果进程正在被调试,则通知调试器,以确定这个错误。

  加入忘记调用CloseHandle,有可能会产生资源泄漏。因为进程终止的时候,系统会扫描它的句柄表中的无效项目,然后关闭这些对象句柄。这时句柄的引用计数就有机会降为0而被撤销了。

  当进程间有父子关系的时候,父进程才有机会使用一个或多个内核对象句柄,并且父进程还可以产生一个可以访问这个内核对象的子进程。步骤如下:

  • 父进程创建内核对象时,必须指明这个句柄可以被继承。(不是内核对象本身可以被继承)
  • 指定一个SECURITY_ATTRIBUTES结构并对它进行初始化,然后把结构的地址传给Create函数。
  • sa.bInheritHandle = TRUE;

  每个句柄表项中都有一个标志位,用以指明这个句柄是否有继承性。如果是bInheritHandle属性是TRUE,标志位被置1,否则置0。

   此后只要调用CreateProcess中的参数bInheritHandle是TRUE,那么被创建的进程就在创建空的句柄表的同时,遍历父进程的句柄表找到有继承属性的项目,并拷贝到新的句柄表中完全相同的位置、递增引用计数。这样即使父进程关闭了这个句柄,由于引用计数还没到0,也要等子进程终止的时候才能置零。这样子进程只要知道句柄的值就可以使用了。

  改变句柄的标志,可以使用SetHandleInformation,第一个参数是句柄,第二个参数就确定修改哪些标志了。和每个句柄相关的就是HANDLE_FLAG_INHERITHANDLE_FLAG_PROJECT_FROM_CLOSE了。第三个参数dwFlags,可以用于指明内核对象的继承标志:

  • 打开继承标志:SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
  • 关闭继承标志:SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, 0);

  使用SetHandleInformation(hObj, HANDLE_FLAG_PROJECT_FROM_CLOSE, HANDLE_FLAG_PROJECT_FROM_CLOSE),会告诉系统这个句柄不应该被关闭,如果关闭则会产生一个异常。只有一种特殊的情况,就是子进程又产生了子进程,而有继承属性的句柄也有可能会被传递到新的子进程。但是旧的子进程有可能在新的子进程生成之前关闭这个句柄,那么父进程就不能和新的子进程通信了,因为没有继承这个内核对象。这种情况下,告诉系统句柄不应该关闭才有意义

  跨进程共享内核对象的第二种方法是给对象命名。

  1. 进程A创建了一个名字为“JeffMutex”的互斥内核对象。
  2. 进程B也创建一个名字为“JeffMutex”的互斥内核对象,系统会有以下操作:
    1. 查看是否已经有同名的内核对象
      1. 如果同名,就检查同名内核对象的类型。
        1. 如果类型也相同,系统会查看调用者的访问权限。
          1. 如果有就找个空项目,初始化再指向现有的内核对象
          2. 没有权限则返回NULL。
        2. 如果类型不匹配,返回NULL。
      2. 不同名就创建新的内核对象。
    2. 没有就创建新的内核对象。

  进程B调用函数成功后,不是返回一个内核对象,而是返回一个和进程相关的句柄值。

  按名字共享的另一种方法是不使用Create*函数,而是Open*函数。原型相同。最后一个参数必须是0结尾的地址,不能传递NULL。如果不存在,GetLastError返回值是2(ERROR_FIEL_NOT_FOUND)。还得检查访问权限,如果有就把引用计数递增1。

  为了保证对象的唯一性,建议创建GUID用来当作对象的名字。这种方法也多用于检查你的应用程序有另一个进程正在运行。

  跨进程共享内核对象的最后一个方法是使用DuplicateHandle函数。简单地说,这个函数只是取出这个进程的句柄表中的一项,拷贝到另一个进程的句柄表中。

  DuplicateHandle的参数虽然多,但不复杂。既然是复制句柄,自然少不了源进程和源句柄,以及目标进程和目标句柄了,设计句柄,就得有它的屏蔽值与继承性,这里给了三个。前四个参数不难理解,只是后面的参数要注意:

  • dwOption参数可以是0,也可以是DUPLICATE_SAME_ACCESSDUPLICATE_CLOSE_SOURCE
  • 如果设定了DUPLICATE_SAME_ACCESS,则目标进程的句柄拥有相同的访问屏蔽,并让函数忽略dwDesiredAccess参数。
  • 如果设定DUPLICATE_CLOSE_SOURCE,则可以关闭源进程中的句柄。使用该标志时,内核对象的引用计数不会受到影响。

  最后书中的例子提到,使用DuplicateHandle函数的时候,dwDesiredAccess参数应该设置为只读(FILE_MAP_READ),这样就可以不影响源进程的句柄,提高健壮性。

  

DUPLICATE_CLOSE_SOURCE

回炉重造之重读Windows核心编程-003-内核对象的更多相关文章

  1. 回炉重造之重读Windows核心编程-006-线程

    线程也是有两部分组成的: 线程的内核对象,操作系统用来管理线程和统计线程信息的地方. 线程堆栈,用于维护现场在执行代码的时候用到的所有函数参数和局部变量. 进程是线程的容器,如果进程中有一个以上的线程 ...

  2. 回炉重造之重读Windows核心编程-004-进程

    进程是一个正在运行的程序的实例,由内核对象和地址空间组成.进程是不活泼的,执行地址空间中代码的是在它的环境中运行线程.每个线程都需要自己的一组CPU寄存器和堆栈. 为了让所有线程都能运行,操作系统就要 ...

  3. 回炉重造之重读Windows核心编程-002-字符集

    使用Unicode的优势: 便于在不同语言之间进行数据交换. 让你的exe或者dll文件支持所有的语言. 提高应用程序的执行效率. Windows2000是使用Unicode重新开发的,核心部分都需要 ...

  4. 回炉重造之重读Windows核心编程-001-错误处理

    Windows处理错误靠的是API的返回值,类型不止一种种: VOID,函数不可能失败,Windows API的返回值很少是这个情况. BOOL,如果函数失败,则返回值是0,否则返回是非零值.不要测试 ...

  5. 《windows核心编程系列》二十一谈谈基址重定位和模块绑定

    每个DLL和可执行文件都有一个首选基地址.它表示该模块被映射到进程地址空间时最佳的内存地址.在构建可执行文件时,默认情况下链接器会将它的首选基地址设为0x400000.对于DLL来说,链接器会将它的首 ...

  6. Asp.Net SignalR 使用记录 技术回炉重造-总纲 动态类型dynamic转换为特定类型T的方案 通过对象方法获取委托_C#反射获取委托_ .net core入门-跨域访问配置

    Asp.Net SignalR 使用记录   工作上遇到一个推送消息的功能的实现.本着面向百度编程的思想.网上百度了一大堆.主要的实现方式是原生的WebSocket,和SignalR,再次写一个关于A ...

  7. windows核心编程 - 线程同步机制

    线程同步机制 常用的线程同步机制有很多种,主要分为用户模式和内核对象两类:其中 用户模式包括:原子操作.关键代码段 内核对象包括:时间内核对象(Event).等待定时器内核对象(WaitableTim ...

  8. windows核心编程---第二章 字符和字符串处理

        使用vc编程时项目-->属性-->常规栏下我们可以设置项目字符集合,它可以是ANSI(多字节)字符集,也可以是unicode字符集.一般情况下说Unicode都是指UTF-16.也 ...

  9. 《windows核心编程系列》二谈谈ANSI和Unicode字符集 .

    http://blog.csdn.net/ithzhang/article/details/7916732转载请注明出处!! 第二章:字符和字符串处理 使用vc编程时项目-->属性-->常 ...

随机推荐

  1. spring cloud 与 docker 读书笔记 1

    Eureka Server 的高可用

  2. 使用属性创建区域 (Creating Areas with Attributes) | 使用区域 | 高级路由特性 | 精通ASP-NET-MVC-5-弗瑞曼

  3. JPA基本注解的使用

    一:JPA基本注解 使用: 使用: 使用: 查看表: 二:用table来生成主键 使用: allocationSize:每次增加多少 tablel:指定使用那张表 执行两次main方法后查看表: jp ...

  4. 龙芯 3A4000 Fedora28 安装笔记

    版权声明:原创文章,未经博主允许不得转载 3A4000用起来性能显然已经非常优秀,和朋友手上的3A3000相比有很大的提升(果然网上水分超多的什么测评看看呵呵就好).从零开始却用一半的核数和更低的制程 ...

  5. COCOAPI for windows error!

    refer this https://github.com/philferriere/cocoapi However, you may encounter a bug where you cannot ...

  6. linux--->用户管理和sudo权限

    linux 用户 配置文件 linux主要通过用户配置文件来查看和修改用户信息 etc/passwd 第一个字段:用户名 第二个字段:密码标志 (表明这个用户有密码,密码放在etc/shadow文件) ...

  7. qq机器人 python实现 自动回复

    我以前写的代码我现在贴在了下面,下面的连接是我自己的博客,有问题希望大家提出来,一起进步...我以前试过,没啥问题.可以实现聊天. https://realwuxiong.github.io/blog ...

  8. artTemplate--模板使用自定义函数(1)

    案例 因为公司业务需要频繁调用接口,后端返回的都是json树对象,需要有些特殊的方法做大量判断和数据处理,显然目前简单语法已经不能满足业务需要了,需要自己定制一些 方法来处理业务逻辑. 例如后台返回的 ...

  9. session学习总结【session原理、应用、与cookie区别】

    session原理 session也是一种记录浏览器状态的机制,但与cookie不同的是,session是保存在服务器中. 由于http是无状态协议,当服务器存储了多个用户的session数据时,如何 ...

  10. random模块学习笔记

    import random #生成随机浮点数(0到1,没有参数) rf1= random.random() #生成随机浮点数(指定区间) rf2=random.uniform(1,4) #浮点数保留指 ...