介绍
内核管理许多不同类型的对象。他们可以通过系统的调度器调用直接访问。这些是在kernel/object中实现的。许多是自独立的高级对象。有些包装了较低层的lk 。
系统调用
用户空间通过系统调用(几乎全部通过Handles)与内核对象进行交互。在用户空间中,句柄表示为32位整数(类型 zx_handle_t)。当执行系统调用时,内核会检查句柄参数是否引用了调用进程的句柄表中存在的已存在的句柄。内核进一步检查该句柄的类型正确(当线程句柄传递给syscall的事件句柄时会导致错误),并且该句柄具有所请求操作的必需权限。
从访问的角度来看,系统调用可分为三大类:
- 没有限制调用只有很少几个,例如
zx_clock_get_monotonic()
andzx_nanosleep()
可以由任何线程调用。 - 大多数调用都是以Handle作为第一个参数的,例如
zx_channel_write()
和zx_port_queue()
- 创建新对象的调用但不使用Handle,例如
zx_event_create()
和zx_channel_create()
。对它们的访问(以及对它们的限制)由包含调用进程的Job控制。
系统调用由 libzircon.so 提供,libzircon.so 是Zircon内核提供给用户空间的“虚拟”共享库,详情到 virtual Dynamic Shared Object or vDSO。它们是zx_noun_verb()
或者 zx_noun_verb_direct-object()
的C ELF ABI 的函数
系统调用定义在 //zircon/vdso 是 FIDL 的自定义形式。这些定义首先由fidlc处理,然后由kazoo处理,kazoo从fidlc获取IR表示,最后输出的形式是 VDSO,内核等。
句柄和权限
对象可能有多个句柄(在一个或多个进程中)引用它们。
对于几乎所有对象,当最后一个句柄引用的对象是关闭时,该对象要么被销毁,要么处于可能无法撤消的最终状态。
通过将句柄写入Channel(zx_channel_write()
),或使用 zx_process_start()
将句柄作为新进程中第一个线程的参数传递,可以将句柄从一个进程传递到另一个进程。
对句柄或其引用的对象可能采取的操作受与该句柄关联的权限关联。引用同一对象的两个句柄可能具有不同的权限。
可以使用zx_handle_duplicate()
和zx_handle_replace()
系统调用来获取相关的句柄,并传递到同个对象的作为引用,这时可选择性的降低权限。如果该句柄是所引用的对象最后一个,通过zx_handle_close()
的调用使关闭并释放它所引用的对象。 zx_handle_close_many()
该方法可以关闭一组句柄。
内核对象ID
每个对象在内核中都简短的成为 "kernel object id" 或者 "koid"。它是一个64位无符号整数,用于标识对象,并且在运行系统的生命周期内是唯一的。特别是,这意味着 koid 永远不会重用。
有两个特殊的koid值:
ZX_KOID_INVALID 的值为零,用作"null"标记。
ZX_KOID_KERNEL 只有一个内核,并且有自己的 koid。
内核生成的koid仅使用63位(足够多了)。通过设置最高有效位,可以为人为分配的koid留出空间。内核生成的 koid 分配的顺序是不确定的,并且可能会发生变化。
人造 koids 是为了支持如识别人造对象、虚拟线程的跟踪,以供工具使用。每个程序如何分配人造 koids 在本文档中没有附加任何的规则和约定。
运行时:Jobs、进程和线程
线程代表执行线程(CPU 寄存器、堆栈等)存在所属进程的地址空间内。进程属于Job,它定义了不同资源的限制。Job 又属于上一层的父Jobs,一直到根Job,该内核在启动的时候就传递到 userboot
, 即开始执行的第一个用户空间进程
如果没有Job 句柄,它不可能由一个进程内的线程去创建另一个进程或Job
程序加载 是由内核层上的用户空间设施和协议所提供
请参阅:zx_process_create()
, zx_process_start()
, zx_thread_create()
和zx_thread_start()
消息传递:套接字(Sockets)和通道(Channels)
套接字和通道都是双向和双端的IPC对象。创建一个套接字或一个通道将返回两个句柄,一个指向对象的每个端点。
套接字是面向流的,数据可以以一个或多个字节为单位写入或读出。可以进行短写(如果Socket的缓冲区已满)和短读(如果请求的数据多于缓冲区)。
通道是面向数据包的,并且最大消息大小由ZX_CHANNEL_MAX_MSG_BYTES 给出, ZX_CHANNEL_MAX_MSG_HANDLES 是通道获取最大的消息句柄。它们不支持短读或写。
将句柄写入通道后,会将其从发送过程中删除。从通道读取带有句柄的消息时,句柄将添加到接收进程中。在这两个事件之间,句柄将继续存在(确保它们所引用的对象继续存在),除非关闭了已写入它们的通道的末尾-在那一刻,传递给该端点的消息将被丢弃,并且它们包含的所有句柄都是关闭的。
参考: zx_channel_create()
, zx_channel_read()
, zx_channel_write()
, zx_channel_call()
, zx_socket_create()
, zx_socket_read()
, zx_socket_write()
.
对象和信号
对象最多可以具有32个信号(由zx_signalst类型和ZX SIGNAL 定义表示),这些信号代表有关其当前状态的一条信息。例如,通道和套接字可以是READABLE或WRITABLE。进程或线程可能被终止。等等。
线程可能会等待当一个或多个对象激活的时候
有关更多信息,请参见信号。
等待:等待一个,等待多个和端口
线程可以使用zx_object_wait_one()
等待信号在单个句柄上处于活动状态或 zx_object_wait_many()
等待多个句柄上的信号。这两个调用都允许超时,即使没有信号待处理,它们也会在超时后返回。
超时可能会由于计时器松弛而偏离指定的期限。有关更多信息,请参见timer slack。
如果线程要等待大量的句柄,则使用端口更为有效,该端口是其他对象可能绑定的对象,因此当在其上声明信号时,端口会接收包含信息的数据包关于待处理的信号。
参见:zx_port_create()
, zx_port_queue()
,zx_port_wait()
, 和 zx_port_cancel()
。
事件,事件对
事件是最简单的对象,除了其活动信号的集合外没有其他状态。
事件对是信号相互的事件中的一对。事件对的一个有用特性是,当事件中消失时(全部的句柄已经关闭),另外一个边就会出现 PEER_CLOSED (同时关闭) 信号
详情请参阅: zx_event_create()
, and zx_eventpair_create()
。
共享内存:虚拟内存对象(VMOs)
虚拟内存对象代表内存中物理页的集合,或 “潜在”页(按需创建)。
它可以通过zx_vmar_map()
映射到进程的地址空间,通过zx_vmar_protect()
可调整映射页面的权限。
VMOs 可以通过zx_vmo_read()
和 zx_vmo_write()
来直接读取和写入。因此,对于“创建 VMO,将数据集写入 和 处理它到另外一个进程使用” 这样的一次性操作,可避免应色号到内存空间的开销。
地址空间管理
虚拟内存地址区域(VMARs)抽象出了管理进程地址空间。在进程创建时,会将根 VMAR 提供给进程创建者。该句柄会指向跨越整个地址空间的 VMAR。该地址空间可以通过 zx_vmar_map()
和 zx_vmar_allocate()
接口划分, zx_vmar_allocate()
常用于创建新的 VMARs (调用子区域),地址空间的部分组合在一起。
详情参见: zx_vmar_map()
, zx_vmar_allocate()
, zx_vmar_protect()
, zx_vmar_unmap()
, 和 zx_vmar_destroy()
Futexes
Futures 是与用户空间原子操作一起使用的内核基元,用于实现高效的同步基元。 例如,互斥锁,在竞争情况下仅需要进行 syscall。通过它仅对实现标准库的实现者有意义。Zircon’s libc 和 libc++ 提供了针对 Futexes互斥锁,条件变量等的C11,C++ 和 pthread APIs
Futex是与用户空间原子操作一起使用的内核原语,用于实现高效的同步原语-例如Mutex,在竞争情况下仅需要进行syscall。通常,它们仅对标准库的实现者有意义。 Zircon的libc和libc ++提供了针对Futex的互斥体,条件变量等的C11,C ++和pthread API。
详情参见: zx_futex_wait()
, zx_futex_wake()
, and zx_futex_requeue()
.
- 中文文档地址:https://fuchsia-china.com/docs/zh-hans/concepts/kernel/concepts/
- 原始英文文档:https://fuchsia.googlesource.com/fuchsia/+/master/docs/concepts/kernel/concepts.md
- 翻译者:Dongchan 校稿者:N0B8D1
Comments