初识Zircon句柄

badmonkey 2021年07月10日 168次浏览

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中是通过链表存储的。

process.png

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的结构

base_value.png

所以上述获得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);
}