基础知识
句柄是允许用户模式程序引用内核对象的内核结构。句柄可以看作是与特定内核对象的会话或连接。
通常情况下,多个进程通过不同的句柄并发访问同一对象。不过,单个句柄只能绑定到单个进程或绑定到内核。
当它绑定到内核时,我们称其为“传输中”。
在用户模式下,句柄只是某个系统调用返回的一个特定数字。只有未在传输中的句柄对用户模式可见。
表示句柄的整数仅对该进程有意义。另一个进程中的相同编号可能不会映射到任何句柄,也可能映射到指向完全不同的内核对象的句柄。
句柄的整数值是任意 32 位数字,但与 ZX_HANDLE_INVALID 对应的值将始终为 0。除此之外,有效句柄的整数值将始终具有句柄集的两个最低有效位。可以使用 ZX_HANDLE_FIXED_BITS_MASK 访问表示这些位的掩码
在内核模式下,句柄是包含以下三个逻辑字段的 C++ 对象:
- 对内核对象的引用
- 内核对象的相关权利
- 它绑定到的进程(或者它已绑定到内核的进程)
权限指定允许对内核对象执行哪些操作。单个进程可能会为同一内核对象提供两个不同句柄,但该内核对象具有不同的权限。
图 1. 用户进程开始创建标识名。
图 2. 用户进程通过系统调用创建内核对象(例如事件),并保留对该对象的整数引用。
图 3. 句柄创建时拥有一组基本权限以及适用于内核对象类型的所有其他权限。
图 4. 标识名可以复制。在此过程中,权利可能会被撤消。
使用标识名
有许多系统调用会创建新内核对象并向该对象返回句柄。例如:
这些调用会创建内核对象和指向该对象的第一个句柄。该句柄会绑定到发出系统调用的进程,相关权限是该类型内核对象的默认权限。
只有一个系统调用可以复制句柄,该句柄指向同一内核对象并绑定到发出系统调用的同一进程:
有一个系统调用会创建一个等效的句柄(可能具有较少的权限),会使原始句柄失效:
有一个系统调用只会销毁句柄:
有一个系统调用接受绑定到调用进程的句柄,并将其绑定到内核(将此句柄置于传输中):
有两个系统调用,它们会接受传输中的句柄并将其绑定到调用进程:
上述通道和套接字系统调用用于将句柄从一个进程转移到另一个进程。例如,您可以通过一个通道将两个进程连接起来。如需传输句柄,源进程会调用 zx_channel_write
或 zx_channel_call
,然后目标进程会在同一通道上调用 zx_channel_read
。
最后,有一个系统调用可为新进程提供其引导句柄,即它可以用于请求其他句柄的句柄:
引导句柄可以是任何可传输的内核对象,但最合理的情况是它指向通道的一端,以便此初始通道可用于将更多的句柄发送到新进程中。
垃圾回收
如果句柄有效,则保证它指向的内核对象有效。这样可以确保这一点,因为内核对象会被引用计数,并且每个句柄都存储有对其内核对象的引用。
反之则不成立。句柄被销毁并不意味着其对应的内核对象也会被销毁。可能会有其他句柄指向该对象,或者内核本身可能持有对内核对象的引用。
当释放对内核对象的最后一次引用时,内核对象会被销毁,或者内核会将该对象标记为垃圾回收;稍后,当对象上当前的一组待处理操作完成时,该对象会被销毁。
如需了解详情,请参阅内核对象生命周期页面。
特殊情况
当句柄在传输中并且其写入的通道或套接字被销毁时,该句柄会关闭。
调试会话(和调试程序)可能具有特殊的系统调用来获取句柄。
无效的句柄和句柄重复使用
传递给除 zx_object_get_info
以下值以外的任何系统调用都会出错:
- 与闭合的句柄相对应的句柄值
- ZX_HANDLE_INVALID 值(
zx_handle_close
系统调用除外)
内核可以随意为新创建的对象重复使用闭合句柄的整数值。因此,请务必遵循适当的处理程序健全度:
不要让一个线程关闭给定句柄,而另一个线程以花哨的方式使用相同的句柄。即使第二个线程也会将其关闭。
不要忽略 ZX_ERR_BAD_HANDLE 返回代码。它们通常表示代码存在逻辑错误。
将 ZX_POL_BAD_HANDLE 作业政策与 ZX_POL_ACTION_EXCEPTION 搭配使用,可以自动检测无效句柄使用情况,以便在此类作业对象下的进程尝试上述任何无效情况时生成异常。