Fuchsia系列博客(三) Zircon内核核心概念

  • 翻译自(不是严格翻译):

fuchsia.googlesource.com

介绍

Zircon内核负责管理大量不同类型的对象.

那些实现了Dispather介面的对象可以通过系统调用直接访问, 这些对象在kernal/object目录下, 包括许多自包含的高级对象, 和一些对lk原语的封装.

系统调用(System Call)

用户空间的代码通过系统调用与内核对象交互, 甚至可以说, 绝大多数是通过Handles(句柄).(译者按: 下文中, Handle一词一律不翻译.)Handle实际上是一个32bit 的整数(zx_handle_t类型).

在系统调用被实际执行前, 内核将进行三项检查.

  1. 检查Handle参数是否指向调用process进程的Handle表中存在的实际句柄.
  2. 检查Handle的类型是否正确. (例如:将Thread Handle传给需要Event Handle的系统调用会导致错误.)
  3. 检查Handle是否具有所请求的操作许可权.

从访问限制的角度看, 系统调用分为三大类:

  1. 无限制系统调用. 可以被任何thread(线程)调用, 极少数的系统调用属于这一类, 例如:zx_clock_get()zx_nanosleep().
  2. 消费一个Handle的系统调用. Handle作为第一个参数, 代表系统调用作用的对象. 绝大多数的系统调用属于该类, 例如:zx_channel_write()zx_port_queue().
  3. 无需Handle并创造新对象的系统调用. 他们的调用以及访问限制由包含该process(进程)的Job(作业)控制.例如:zx_event_create()zx_channel_create().

系统调用由libzircon.so提供, libzircon.so是一个由内核提供的"虚拟"共享库, 也被称之为virtual Dynamic Shared Object 或者vDSO. 系统调用都是C ELF ABI形式的函数: zx_名词_动词或者zx_名词_动词_直接对象.

系统调用由syscall.abigen定义, 并且由abigen tool处理头文件和libzircon中与内核libsyscalls连接的胶水代码.

Handles(句柄) 和 Rights(许可权)

  1. 可以有很多分布在一个或多个进程(process)中的Handle指向同一个对象.
  2. 对于绝大多数对象来说, 最后一个指向他的Handle被关闭, 该对象也随之消亡, 抑或进入一种无法撤销的最终状态.
  3. 有两种方式把句柄在进程之间转移, 第一种是调用zx_channel_write()把Handle写入Channel(通道), 另一种方法是调用zx_process_start(), 把这个Handle传递给新进程的第一个线程.
  4. Rights负责管理相关Handle和Object可能进行的任何行为, 指向同一对象的不同Handle完全可能用有不同的许可权.
  5. zx_handle_duplicate()zx_handle_replace()系统调用可以产生指向该对象的新的Handle, 并且可以选择性减少新Handle的许可权.
  6. zx_handle_close()负责关闭Handle, 如上所述, 该方法也将销毁对象, 如果关闭Handle后再没有Handle持有该对象. zx_handle_close_many()与其相似, 只是关闭一个Handle数组中的所有Handle.

内核对象ID (Kernel Object IDs)

每个对象都有个内核对象ID, 简称koid. 这是一个64位整数, 是对象的唯一标识符, 并且在系统运行的周期内是唯一的. 这意味著"koid"永远不会被重用.(译者按: 真的不会被用完吗? 0.0)

有两个特殊的koid值: ZX_KOID_INVALID: 值是0, 用来做null. ZX_KOID_KERNEL: 内核自己的唯一koid. 内核只用63个位(真的是很多了)生成koid. 用户可以通过设置最高有效位, 分配剩余的koid. 内核分配koid的顺序没有被指定, 并且可能发生变化. 人工分配的koid提供了更多可能:例如辨别人工对象, 跟踪虚拟线程, 这些都可以被各种工具使用. 而对于每个程序如何分配人工koid, 本文档不做任何限制和规则约束.

运行中的代码: 作业(Jobs), 进程(Processes)和线程(Threads)

线程代表地址空间中实际执行的线程(CPU 寄存器, 栈等), 线程的地址空间被他们所在的进程拥有.

进程又由作业拥有, 作业定义了各种各样的资源限制. 作业又被它的父作业拥有, 层层传递, 直到根作业(Root Job). 根作业在系统启动时由内核创建, 进而传递给第一个用户空间进程--userboot. 没有作业的Handle, 进程中的线程不可能创建另一个进程或者另一个作业. 程序如何载入由内核空间上层的用户空间设施和协议规定. 请参阅:zx_process_create(), zx_process_start(), zx_thread_create()zx_thread_start().

消息传递: 套接字(Sockets)和通道(Channels)

套接字和通道都是用于双向通信的IPC对象, 创建套接字或者通道将返回两个对象, 一边一个.

套接字是面向流的编程模型, 允许短读和短写, 数据以一个或多个位元组作为单元传输. 通道是面相数据报的编程模型, 拥有最大长度限制ZX_CHANNEL_MAX_MSG_BYTES, 还拥有消息句柄数量限制, 即ZX_CHANNEL_MAX_MSG_HANDLES. 不支持短写和短读 -- 严格判定每个消息是否合适. 当Handle被写入通道时, 他们从发送端进程移除. 当Handle从通道读取时, 他们被加入到接收端进程. 在传输过程中, Handle并未消失, 这意味著它指向的对象仍旧存在. 然而, 如果通道的终点被关闭, 传输中的消息将会被丢弃, 所有指向该消息的句柄都会被关闭.

请参阅: zx_channel_create(), zx_channel_read(), zx_channel_write(), zx_channel_call(), zx_socket_create(), zx_socket_read()zx_socket_write().

对象(Objects)和信号(Signals)

对象有至多32种信号(zx_signals_t类型, 由ZX_SIGNAL定义), 这些信号代表对象当前状态的信息. 例如: 通道(Channel)和套接字(Socket), 可能是READABLE或者WRITABLE的. 进程(Process)和线程(Thread)可以是TERMINATED, 等等...

线程可以等待一个或多个对象上的信号来激活自己. 参见Signals获取更多信息.

等待:等待一个, 等待许多, 等待埠

线程可以使用zx_object_wait_one()等待一个信号Handle, 或者用zx_object_wait_many()等待一组信号Handle.

这两种方法都允许超时设置, 一旦超时, 即使没有响应信号, 方法也会返回.

由于timer slcak(计时器松弛)的存在, 超时设置将会有误差, 并不一定在设置的时间返回. 详情请见timer slack.

如果一个线程想要等待大量的Handle, 那不如使用埠(Port). 埠是一个对象, 可以被其他对象绑定, 这样, 当其他对象上的信号到达时, 埠就会收到与这些信号的信息相关的数据包.

请参阅: zx_port_create(), zx_port_queue(), zx_port_wait()zx_port_cancel().

事件 (Events), 事件对 (Event Pairs)

事件是最简单的对象,除了其活动信号集合之外没有其他状态。

事件对是可以相互发信号的一对事件之一。事件对的一个有用属性是当一侧消失(所有句柄都已关闭)时,PEER_CLOSED信号在另一侧被断言。(译者按: 量子纠缠?)

请参阅: zx_event_create()zx_eventpair_create().

共享内存: 虚拟内存对象(VMO)

虚拟内存对象代表一组物理内存页面,或"潜在"的页面(可以按需创建/填充的页面).

虚拟内存对象可以通过zx_vmar_map()来映射到进程的地址空间, 用zx_vmar_unmap()取消该映射. 被映射页面的许可权可由zx_vmar_protect()来调整.

VMO也可以通过zx_vmo_read()zx_vmo_write()直接读取和写入. 因此,对于一次性操作, 如"创建一个VMO, 将数据集写入其中, 并将其交给另一个进程使用", 可以避免将它们映射到地址空间的成本。

地址空间管理

虚拟内存地址区域(VMAR)提供了管理进程地址空间的抽象。在进程创建时,将向进程创建者提供root VMAR的句柄。该句柄指的是跨越整个地址空间的VMAR。这个空间可以通过zx_vmar_map()zx_vmar_allocate()介面进行分割. zx_vmar_allocate()可以用于生成新的VMAR(称为subregionschildren), 这可以将地址空间的某些部分组合在一起。

请参阅: zx_vmar_map(), zx_vmar_allocate(), zx_vmar_protect() ,zx_vmar_unmap()zx_vmar_destroy().

Futexes

Futexes是与用户空间原子操作一起使用的内核原语, 用于实现高效的同步原语 -- 例如, 竞争情况下的互斥锁实现, 只需要一个系统调用. 通常只有标准库的实现者对他们感兴趣. Zirconlibclibc++为互斥体, 条件变数等以Futexes的形式提供了C11, C ++pthread的API.

请参阅: zx_futex_wait(), zx_futex_wake()zx_futex_requeue().

推荐阅读:

相关文章