Handles
在用户空间为32bit的数字,内核空间为内核对象。
头文件声明在kernel/object/include/object/handle.h
实现对应kernel/object/handle.cc
结构
- 指向内核对象的引用
- 内核对象的权限
- 所属进程
在代码中体现为Handle类的三个成员变量
ktl::atomic<zx_koid_t> process_id_; // 原子性的id
fbl::RefPtr<Dispatcher> dispatcher_; // 内核对象指针
const zx_rights_t rights_; // 内核对象对应的权限
内核对象权限
每个handle都会指定所占用内核对象的权限,而这些权限都是已经定义好的宏,类型为uint32。
定义在zircon/system/public/zircon/rights.h
,一些例子
typedef uint32_t zx_rights_t;
#define ZX_RIGHT_NONE ((zx_rights_t)0u)
#define ZX_RIGHT_DUPLICATE ((zx_rights_t)1u << 0)
#define ZX_RIGHT_TRANSFER ((zx_rights_t)1u << 1)
#define ZX_RIGHT_READ ((zx_rights_t)1u << 2)
#define ZX_RIGHT_WRITE ((zx_rights_t)1u << 3)
#define ZX_RIGHT_EXECUTE ((zx_rights_t)1u << 4)
#define ZX_RIGHT_MAP ((zx_rights_t)1u << 5)
进程id
保持原子性(自旋锁),因为不同进程的线程可能会同时的访问进程id,虽然handle_table_lock_ 在不同进程中的锁是不共享的。
内核对象指针
将handle对应的内核对象通过RefPtr封装,维护引用计数,同时保持指针的特性。
源码分析
用户空间的handle数值是通过系统调用获得的, 比如zx_event_create可以创建一个event内核对象,同时会初始化一个handle对象指向该event对象,并返回一个zx_handle_t对应这个handle对象。
那么zx_handle_t 和handle对象是如何转换的是一个比较重要的问题。
一个process具有多个handle,所以对同一个内核对象可能具有不同的权限。handle在process中是通过链表存储的。
handle对象转换为zx_handle_t
内核空间的handle对象需要转换成zx_handle_t,系统调用时返回zx_handle_t,由于Handle是通过HandleTable管理的,具体的实现也在HandleTable中,声明部分
// Maps a |handle| to an integer which can be given to usermode as a
// handle value. Uses Handle->base_value() plus additional mixing.
zx_handle_t MapHandleToValue(const Handle* handle) const;
zx_handle_t MapHandleToValue(const HandleOwner& handle) const;
对应的实现为
zx_handle_t HandleTable::MapHandleToValue(const Handle* handle) const {
return map_handle_to_value(handle, random_value_);
}
zx_handle_t HandleTable::MapHandleToValue(const HandleOwner& handle) const {
return map_handle_to_value(handle.get(), random_value_);
}
关注两个点,首先是调用了子函数map_handle_to_value
,其次是random_value_
,其中random_value_
是在HandleTable初始化的时候使用prng生成的。
HandleTable::HandleTable(ProcessDispatcher* process) : process_(process) {
// Generate handle XOR mask with top bit and bottom two bits cleared
uint32_t secret;
auto prng = crypto::GlobalPRNG::GetInstance();
prng->Draw(&secret, sizeof(secret));
// Handle values must always have the low kHandleReservedBits set. Do not
// ever attempt to toggle these bits using the random_value_ xor mask.
random_value_ = secret << kHandleReservedBits; // 使用伪随机数生成secret,然后lsb置为0
}
具体的变化操作如下
static zx_handle_t map_handle_to_value(const Handle* handle, uint32_t mixer) {
// Ensure that the last two bits of the result is not zero, and make sure we
// don't lose any base_value bits when shifting.
constexpr uint32_t kBaseValueMustBeZeroMask =
(kHandleMustBeOneMask << ((sizeof(handle->base_value()) * 8) - kHandleReservedBits)); // 1100000....000 (30个0,一共32位)
DEBUG_ASSERT((mixer & kHandleMustBeOneMask) == 0); // mixer 最低2位必须为0
DEBUG_ASSERT((handle->base_value() & kBaseValueMustBeZeroMask) == 0);// base 最高2位必须为0
auto handle_id = (handle->base_value() << kHandleReservedBits) | kHandleMustBeOneMask;
// 左移2位,然后将最低2位置为1
return static_cast<zx_handle_t>(mixer ^ handle_id); // 和mixer 异或得到 用户态的handle数值
}
其实就是将handle的base_value_
的低30位左移两位,同时将最低两位置为1后,再和handletable的random_value_
异或得到用户态的数值。
获取handle内核对象
同样的实在handletable中实现
// Maps a handle value into a Handle as long we can verify that
// it belongs to this handle table. Use |skip_policy = true| for testing that
// a handle is valid without potentially triggering a job policy exception.
Handle* GetHandleLocked(zx_handle_t handle_value, bool skip_policy = false) TA_REQ_SHARED(lock_);
对应的实现为
Handle* HandleTable::GetHandleLocked(zx_handle_t handle_value, bool skip_policy) {
auto handle = map_value_to_handle(handle_value, random_value_);// 获取内核对象
if (handle && handle->process_id() == process_->get_koid()) // 检查handle所属进程与当前进程是否相同
return handle;
// job policy 用于处理异常 并返回空指针。
if (likely(!skip_policy)) {
// Handle lookup failed. We potentially generate an exception or kill the process,
// depending on the job policy. Note that we don't use the return value from
// EnforceBasicPolicy() here: ZX_POL_ACTION_ALLOW and ZX_POL_ACTION_DENY are equivalent for
// ZX_POL_BAD_HANDLE.
__UNUSED auto result = process_->EnforceBasicPolicy(ZX_POL_BAD_HANDLE);
}
return nullptr;
}
可以看到先调用map_value_to_handle
获取内核对象,然后校验handle对象所属的进程是否和当前进程想匹配。
跟进map_value_to_handle
,其实就是handle转zx_handle_t
的逆过程
static Handle* map_value_to_handle(zx_handle_t value, uint32_t mixer) {
// Validate that the "must be one" bits are actually one.
if ((value & kHandleMustBeOneMask) != kHandleMustBeOneMask) {
return nullptr;
}
uint32_t handle_id = (static_cast<uint32_t>(value) ^ mixer) >> kHandleReservedBits; // 这里的handle_id 其实就是原始的base_value
return Handle::FromU32(handle_id);
}
注意到hanlde_id
其实就是handle对象中的base_value_
,通过FromU32
获得handle对象,看一下FromU32
// Maps an integer obtained by Handle::base_value() back to a Handle.
static Handle* FromU32(uint32_t value);
对应的比较复杂,涉及到了GParena(一个内存管理类,类似于glibc的heap中的arena)
Handle* Handle::FromU32(uint32_t value) {
uint32_t index = HandleValueToIndex(value);// 获取索引,即在arena中的偏移
uintptr_t handle_addr = IndexToHandle(index); // 根据偏移得到handle对象的地址
if (unlikely(!gHandleTableArena.arena_.Committed(reinterpret_cast<void*>(handle_addr))))
return nullptr;
handle_addr = gHandleTableArena.arena_.Confine(handle_addr);
auto handle = reinterpret_cast<Handle*>(handle_addr);
return reinterpret_cast<Handle*>(
fbl::conditional_select_nospec_eq(handle->base_value(), value, handle_addr, 0));
}
这部分还需要先了解base_value的结构
所以上述获得index部分其实是通过掩码,获取base_value
的低17bits
uint32_t HandleValueToIndex(uint32_t value) { return value & kHandleIndexMask; }
而IndexToHandle
是利用index作为偏移和arena的基址,找到对应的位置的handle对象
uintptr_t Handle::IndexToHandle(uint32_t index) {
return reinterpret_cast<uintptr_t>(gHandleTableArena.arena_.Base()) + index * sizeof(Handle);
}
对应的逆操作为HandleToIndex
,即根据hanldle再arena中的地址计算偏移,作为index。
uint32_t HandleTableArena::HandleToIndex(Handle* handle) {
return static_cast<uint32_t>(handle - reinterpret_cast<Handle*>(arena_.Base()));
}
index number
部分是用于索引handle再arena中的地址,而generation number
则是用于更新base_value
,每次加1
// |index| is the literal index into the table. |old_value| is the
// |index mixed with the per-handle-lifetime state.
uint32_t NewHandleValue(uint32_t index, uint32_t old_value) {
DEBUG_ASSERT((index & ~kHandleIndexMask) == 0); // 校验index是否合法
uint32_t old_gen = 0;
if (old_value != 0) { // 上一次的base_value
// This slot has been used before.
DEBUG_ASSERT((old_value & kHandleIndexMask) == index);// 更新base_value,只是更新generation部分,index部分不变,这里进行了一个检查
old_gen = (old_value & kHandleGenerationMask) >> kHandleGenerationShift;// 提取generation 部分
}
uint32_t new_gen = (((old_gen + 1) << kHandleGenerationShift) & kHandleGenerationMask);// 加1 然后移位
return (index | new_gen); // 拼接成新的base_value
}
源代码中的注释说到,generation部分貌似和handle的生命周期有关。在getnewbasevalue
中调用
uint32_t HandleTableArena::GetNewBaseValue(void* addr) {
// Get the index of this slot within the arena.
uint32_t handle_index = HandleToIndex(reinterpret_cast<Handle*>(addr)); // 将addr出转换成Handle* 类型 然后计算偏移,作为handle_index
// Check the free memory for a stashed base_value. 其实就是将free的内存转换成 handle结构体,获取对应位置的base_value_ 这个base_value_ 是个随机值?
uint32_t v = reinterpret_cast<Handle*>(addr)->base_value_;
return NewHandleValue(handle_index, v);
}
handle对象的创建流程
创建
handle对象被封装成了HandleOwner,只能通过HandleOwner的Make和Dup,创建和复制handle对象。首先看一下Make,Dup的逻辑大同小异,handle的构造函数和赋值函数只能在HandleOwner中调用。
HandleOwner Handle::Make(fbl::RefPtr<Dispatcher> dispatcher, zx_rights_t rights) {
uint32_t base_value;
void* addr = gHandleTableArena.Alloc(dispatcher, "new", &base_value);//gHandleTableArena 负责分配内存,更新dispatcher的引用计数,并更新base_value
if (unlikely(!addr))
return nullptr;
kcounter_add(handle_count_made, 1);// 更新创建的handle 数目
kcounter_add(handle_count_live, 1);// 更新存活的handle 数目
return HandleOwner(new (addr) Handle(ktl::move(dispatcher), rights, base_value));// 封装为HandleOwner
}
主要还是分配内存,维护计数,封装为HandleOwner,这里关注一下,内存分配的部分
void* HandleTableArena::Alloc(const fbl::RefPtr<Dispatcher>& dispatcher, const char* what,
uint32_t* base_value) {
// Attempt to allocate a handle.
void* addr = arena_.Alloc(); //
size_t outstanding_handles = arena_.DiagnosticCount();
if (unlikely(addr == nullptr)) {
kcounter_add(handle_count_alloc_failed, 1);
printf("WARNING: Could not allocate %s handle (%zu outstanding)\n", what, outstanding_handles);
return nullptr;
}
// Emit a warning if too many handles have been created and we haven't recently logged
if (unlikely(outstanding_handles > kHighHandleCount) && handle_count_high_log_.Ready()) {
printf("WARNING: High handle count: %zu / %zu handles\n", outstanding_handles,
kHighHandleCount);
}
dispatcher->increment_handle_count();
// checking the process_id_ and dispatcher is really about trying to catch cases where this
// Handle might somehow already be in use.
DEBUG_ASSERT(reinterpret_cast<Handle*>(addr)->process_id_ == ZX_KOID_INVALID);
DEBUG_ASSERT(reinterpret_cast<Handle*>(addr)->dispatcher_ == nullptr);
*base_value = GetNewBaseValue(addr);
return addr;
}
可以看到其实内存分配部分,不仅仅简单的申请了一块内存,同时为dispatcher更新了引用计数,并获取了新的base_value
。
TearDown
handle对象需要销毁时,调用TearDown方法,此方法会将handle指向的内核对象reset,同时销毁进程id。但是会保留base_value
,以便下次使用(alloc时,再次分配到了这块内存,可以直接使用这里的base_value
void Handle::TearDown() {
uint32_t __UNUSED old_base_value = base_value();
DEBUG_ASSERT(process_id() == ZX_KOID_INVALID);
this->dispatcher_.reset();
this->~Handle();
// Validate that destruction did not change the stored base value.
DEBUG_ASSERT(base_value() == old_base_value);
}