翻译《The Old New Thing》- Kernel handles are not reference-counted

Kernel handles are not reference-counted - The Old New Thing (microsoft.com)icon-default.png?t=N7T8https://devblogs.microsoft.com/oldnewthing/20070829-00/?p=25363

Raymond Chen 2007年08月29日


内核句柄不是引用计数的

简要

内核句柄不使用引用计数管理。复制句柄会增加内核对象的引用计数,每个复制的句柄都需要独立关闭,以减少引用计数。关闭句柄时,它将不再可用。

 

正文

这里有一个过去浮出水面的问题:

在我的代码中,我有多个对象想要通过 DeviceIoControl 与同一个句柄通信。 每次我创建一个对象,我都使用 DuplicateHandle 来增加句柄的引用计数。 这样,当每个对象调用 CloseHandle 时,只有最后一个真正关闭句柄。 然而,当我运行代码时,我发现一旦第一个对象调用了 CloseHandle,句柄就不再有效,其他人也不能使用它了。 我需要向 CreateFile 传递什么标志才能让它工作?

 

换句话说,代码大概是这样的:

// h 是我们想要与新 CFred 对象共享的句柄
CFred *MakeFred(HANDLE h)
{
    // "复制句柄以增加引用计数"
    // 此代码是错误的 - 见讨论
    // 为了说明方便,去掉了所有错误检查
    HANDLE hDup;
    DuplicateHandle(GetCurrentProcess(), h,
                   GetCurrentProcess(), &hDup,
                   0, FALSE, DUPLICATE_SAME_ACCESS);
    return new CFred(h);
}

        内核句柄不是引用计数的。 当你调用 CloseHandle 时,句柄就关闭了,故事结束。

        从原始问题陈述中,我们知道 CFred 对象在销毁时关闭句柄。 就为了论证,假设调用者是这样操作的:

CFred *pfred1 = MakeFred(h);
CFred *pfred2 = MakeFred(h);
delete pfred1;
delete pfred2;

        当你运行这段代码片段时,实际发生了什么?

        第一次我们调用 MakeFred 时,我们获取原始句柄 h 并复制它,但我们把原始句柄交给了 CFred 构造函数,并且泄漏了 hDup! 原始发帖者假设复制句柄只是增加了句柄的假想引用计数,以至于 h == hDup。 (这也会让原始发帖者想知道我们为什么还要有 lpTargetHandle 参数。)

        当 pfred1 被删除时,它关闭了它的句柄,即 h。 这关闭了 h 句柄并使其无效,并可用于其他 CreateFile 或创建句柄的操作进行回收。

        当 pfred2 被删除时,它也关闭了它的句柄,仍然是 h。 这现在是关闭一个已经关闭的句柄,这是一个错误。 如果我们在调用 pfred2 的方法时使用了句柄,那么由于句柄不再有效,它也会从这些操作中获得失败。 (好吧,如果我们幸运的话,我们会得到一个失败。 如果我们不走运,句柄已经被回收,我们最终在别人的句柄上执行了 DeviceIoControl!)

        与此同时,调用代码的 h 副本也是坏的, 因为 pfred1 在被删除时关闭了它。

        我们真正想做的是复制句柄并将复制的句柄传递给每个对象。 DuplicateHandle 函数创建一个新的句柄,该句柄引用与原始句柄相同的对象。 那个新句柄可以关闭,而不影响原始句柄。

// h 是我们想要与新 CFred 对象共享的句柄
CFred *MakeFred(HANDLE h)
{
    // 创建另一个引用与 "h" 相同对象的句柄
    // 为了说明方便,去掉了所有错误检查
    HANDLE hDup;
    DuplicateHandle(GetCurrentProcess(), h,
                   GetCurrentProcess(), &hDup,
                   0, FALSE, DUPLICATE_SAME_ACCESS);
    return new CFred(hDup);
}

        修正就是蓝色高亮的那一个词。 我们给 CFred 对象复制的句柄。 这样,它就有自己的句柄,可以随时自由关闭,而且不会影响其他人的句柄。

        你可以将 DuplicateHandle 想象成内核对象的 AddRef。 每次你复制一个句柄,内核对象的引用计数就增加一,你获得了一个新的引用(新句柄)。 每次你关闭一个句柄,内核对象的引用计数就减少一。

        总结,句柄不是一个引用计数对象。 当你关闭一个句柄时,它就无效了。 当你复制一个句柄时,除了有义务关闭原来的句柄外,你还有义务关闭复制的句柄。复制的句柄引用与原始句柄相同的对象, 并且是底层对象是引用计数的。 (请注意,内核对象可以有来自非句柄的引用。例如,正在执行的线程会维护对底层线程对象的引用。关闭线程的最后一个句柄不会破坏线程对象,因为只要线程还在运行,它就会保留对自身的引用)

  • 29
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0x0007

可不可奖励我吃只毛嘴鸡 馋😋

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值