Tag Archives: Kernel

Zircon进程对象

概要

一个Zircon进程就是一个程序的实例:一个或者多个线程的指令执行以及一系列的资源。

描述

进程对象是以下资源的一个集合:

一般来说,进程是一系列关联代码的执行直到进程被强制终止或者进程主动退出。

进程属于作业,它可以在资源、权限和生命周期控制的角度将多个进程组合为一个应用程序。

生命周期

一个进程通过 sys_process_create() 系统调用来创建,这个调用没有参数;当主线程终止或者最后一个句柄被关闭时(⚠ 未实现),进程进入终止状态。

下一步, 主程序代码通过 sys_process_load() 加载进入进程,开始调用 sys_process_start()开始执行。

系统调用

  • job_create – 在父作业内创建一个新的作业
  • vmar_map – 在一个地址空间范围上映射内存
  • vmar_protect – 在一个地址空间范围上修改权限
  • vmar_unmap – 在一个地址空间范围上解除内存映射

Zircon内核对象

Zircon是基于对象的内核。用户模式下的代码基本上都是通过对象句柄与OS资源交互。句柄可以被认为是某个OS子系统与某个资源之间的活动会话。

Zircon动态地管理着如下资源:

  • 处理器时间
  • 内存和地址空间
  • 设备IO内存
  • 中断
  • 信号和等待

应用层内核对象

IPC

任务

信号

内存和地址空间

等待

驱动层内核对象

内核对象与LK层

许多内核对象封装了一个或者多个LK层结构体,比如线程对象封装了 thread_t,相反通道对就没有封装任何LK层对象。

内核对象生命周期

内核对象可以被重新计数。大多数的内核对象在系统调用时产生,被句柄持有计数等于1时处于活动状态,句柄绑定这个句柄值作为系统调用的输出。句柄对象一旦被附加到句柄表中,它就处于活动状态了。当句柄从关闭(调用sys_close())它们的句柄表中卸载时就会递减内核对象的计数。通常最后一个句柄被关闭时内核对象计数会等于0,此时会触发析构函数的运行。

当引用对象的新句柄被创建或者(某些内核代码)获取直接指针引用时,计数会递增。所以一个内核对象的生命周期可能会比创建它的进程的生命周期还要长。

调度器

内核对象是一个派生自Dispatcher 并实现了方法的C++类。所以你会看到线程对象的代码在 ThreadDispatcher中。有许多代码只关心通用意义上的内核对象,这种情况下你看到的名字就是 fbl::RefPtr<Dispatcher>

内核对象安全

原则上说,内核对象没有内在的安全概念,也不会做安全检查。一个单独的进程可能由于同一对象的不同权限拥有两个不同的句柄。

参考更多:

句柄

Zircon内核概念

前言

Zircon内核管理着大量不同类型的Objects。这些Objects可以通过系统调用(用C++实现了Dispatcher接口的类)来直接访问。这些Objects定义在 kernel/object下面,它们有些是自包含的高级Objects,有些是对LK低级别原语的封装。

系统调用

用户态代码与内核对象通过系统调用来交互,而且基本上都是通过句柄这个概念来实现。在用户态句柄用一个32位整型数来表示(zx_handle_t类型)。执行系统调用时,内核会检查句柄参数对应的操作是否在当前调用者进程的操作表中,同时在后面也会检查句柄类型是否正确、请求的操作是否有权限执行。

从访问角度看,系统调用主要分为三大类:

1、调用没有限制。这类调用只占很少一部分,比如 zx_time_get() 、zx_nanosleep() 可以被任意线程调用。

2、调用的第一个参数是一个句柄,它作为要施加操作的对象。这类调用有很多,比如zx_channel_write()zx_port_queue()

3、调用会创建新的Objects但并不作为句柄使用,比如zx_event_create() 、zx_channel_create()。对Objects的访问以及对它们的限制都由调用的进程作业所控制。

libzircon.so提供了所有的系统调用,它是一个kernel提供给用户态的“虚拟”共享库(或者叫做虚拟动态共享对象,简称vDSO)。这些系统调用被表示为zx_noun_verb() 、zx_noun_verb_direct-object()样式的C ELF ABI函数。

所有的系统调用被定义在syscalls.sysgen 文件中,并通过 sysgen工具写入到include、libzircon以及内核的libsyscalls文件中。

句柄 、权限

对象可能会有多个句柄的引用(在一个或者多个进程中)。

对大多数对象来说,当最后一个引用它的句柄关闭时,这个对象要么被废弃要么进入不可撤销的终止状态。

使用zx_channel_write()) 可以将句柄写入一个通道来实现从一个进程到另外一个进程的移动或者使用zx_process_start() 将一个句柄作为参数传递新进程的第一个线程。这些行为会受到句柄、句柄所引用对象的权限约束,同一对象的两个句柄也可能具有不同的权限。

zx_handle_duplicate() 和 zx_handle_replace() 系统调用可以获取传入对象的额外句柄,也可以削弱其权限。zx_handle_close() 系统调用会关闭一个句柄,如果这个句柄是引用对象的最后一个句柄,那么也会释放这个对象。

内核对象ID

每个在内核中的对象都有一个“内核对象id”(简称koid)。koid用一个64位的无符号整型来表示,它在整个运行时来唯一的标识这个对象,这意味着koid从来不会被重用。

koid中有两个特殊值:
ZX_KOID_INVALID-值为0,用来表示null。
ZX_KOID_KERNEL-值为1。

运行时:作业、进程、线程

线程代表了线程在其所属进程地址空间的执行(寄存器、栈等)。
进程属于作业,它定义许多资源的限制条件。
作业从属于父作业,所有的作业又都从属于内核启动后传递给userboot的第一个用户态进程的根作业。

进程内的线程如果要创建一个新的进程或者作业,必须通过作业句柄来实现。

程序加载由内核层之上的用户态设备、协议来提供。

更多参考:进程创建进程启动线程创建线程启动

消息传递: 套接字、通道

套接字和通道都是支持双向传递的IPC对象。创建一个套接字或者通道会返回两个连接每一末端对象的句柄。

套接字是面向流的,支持读/写一个或者多个字节,短写(套接字缓冲区满)和短读(请求到的数据多于套接字缓冲区)都有可能发生。

通道是面向数据包的,消息最大长度为64K字节(可以调整,通常会设置的更小),支持1024个(可以调整,通常会设置的更小)句柄附加到消息上。不管消息能不能适应大小,通道都不支持短写和短读。

当句柄写入到通道时它们就会从发送端进程中移除,相反含有句柄的消息从通道中读出时句柄会被追加到接收进程。在这两个事件之间,句柄会持续存在(前提是对象引用持续存在),如果句柄写入方向的通道关闭,那么消息就会被废弃,所有的句柄都会被关闭。

更多参考:

channel_createchannel_readchannel_writechannel_callsocket_createsocket_readsocket_write.

对象、信号

对象有32种信号(zx_signals_t类型,定义在ZX_SIGNAL文件中),信号代表当前对象的一系列的状态信息。比如通道、套接字有可读、可写状态,进程、线程有终止状态等等。

线程可能在一个或者多个对象上等待信号让它变为活动状态。

更多参考: signals

等待: 等待一个、等待多个、端口

线程可以使用 zx_object_wait_one() 在一个句柄上等待一个信号变为活动状态,也可以使用 zx_object_wait_many() 在多个句柄上等待信号。这两个系统调用都允许设置超时时间,即便信号没有被发送也可以返回。

如果一个线程在大量的句柄上等待,使用端口会更有效率。端口是一个对象,它被众多声明了信号的对象绑定。端口会接收一个关于发送信号信息的包。

更多参考: port_createport_queueport_waitport_cancel.

事件、事件对

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

事件对是一对可以互相发信号的事件组合。事件对一个有用的场景是当事件对的一方离开(所有的句柄都被关闭)时,事件对的另一方就被置为PEER_CLOSED信号。

更多参考: event_create, and eventpair_create.

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

虚拟内存对象代表了一组内存物理页或者内存物理页的操纵能力(创建、填充、请求)。

zx_vmar_map() 系统调用将VMO映射到进程的地址空间。zx_vmar_unmap() 系统调用将VMO从进程的地址空间中解映射。映射页的权限可以通过 zx_vmar_protect() 来调整。

VMO也可以通过 zx_vmo_read() 、zx_vmo_write() 来直接读取、写入,这样可以避免映射VMO到进程地址空间的耗时操作(比如创建VMO、写数据集到内部、传递给其他线程来使用)。

地址空间管理

虚拟内存地址区(VMARs)提供了管理进程地址空间的一层抽象,在进程创建时会传递给创建者一个根VMAR的句柄,这个句柄是整个地址空间VMAR的引用。整个地址空间可以通过 zx_vmar_map() 系统调用来切分,通过 zx_vmar_allocate() 来生成新的VMAR(子分区),这个分区可以是部分地址空间的组合。

更多参考: vmar_mapvmar_allocatevmar_protectvmar_unmapvmar_destroy,

互斥体

互斥体是与用户态原子操作一起使用的内核原语,用来实现高效的同步原语。比如Mutex仅在竞争情况下才会做系统调用。一般情况下标准库才对互斥体感兴趣。Zircon的libc、libc++提供了C11、C++、Pthread API版本的Mutex、条件变量。

更多参考: futex_waitfutex_wakefutex_requeue.

Zircon设备索引

Zircon设备索引是从bootloader传递过来的只读二进制数据结构,它包含了内核以及众多驱动的配置信息。

详情参见 zircon设备索引