虚拟机
语言实现角度很多 runtime 系统有意思的话题已经在之前的章节提到了,为了开展虚拟机的阐述,这里回顾一下
- 垃圾收集(8.3)。章节介绍所述,跟踪垃圾收集器必须发现程序中所有的根结点,确定引用的类型以及每个分配块。compacting 收集器必须能够修改程序的指针。generatinoal 收集器必须可以访问引用的主程序通过写屏障建立的新老指针。像 Java 的收集器必须可以正确的调用 finalize 方法。在支持并发或者增量的收集器实现中,主处理程序和收集器必须就某种锁定协议达成共识,以保持堆的一致性。
- 参数变量的数量(9.3.3)。有些语言允许开发人员声明不定数量的参数。在 C 中,
va_arg(my_args, arg_type)
的调用必须返回列表my_args
中的下一项。为了找到下一项,va_arg
必须知道哪个参数被传递到了哪个寄存器,哪个参数被传递到了栈上(以及对齐,padding和offset信息)。如果va_arg
的代码是内嵌生成的,compiler 必须知道所有信息。如果代码在库 routines 中,routines 必须是 compiler-specific,因此需要一个小型 runtime 系统。- 异常处理(9.4)。异常传导要求我们 unwind 栈当控制流要脱离当前 subroutine。compiler 可以通过逐帧展开当前栈。另外,一个通用的用来展开栈信息的 rontine 通常也是 runtime 系统提供。同样的,可以通过 compiler 生成的代码来找到该程序中给定点最接近的异常处理,或者通过通用的 runtime 检查程序编译时生成的 pc 处理表。后一种方法可以避免 runtime 进入离开受保护区开销(try block)
- 事件处理(9.6)。事件通常在单线程程序调用 subroutine,或者在并发程序中单独线程的 callback。依赖实现策略,它们可能要利用 compiler 调用 subroutine 的约定。也需要在主程序和事件处理之间进行同步,来保护共享数据结构的一致性。一个真正的异步调用--可能发生在主程序的任何时间的任何地方--可能需要保存整个寄存器的状态。只能发生在“安全点”的调用只需要存储很少的状态。不管哪种情况,对于不在词法嵌套最外层的 handler 的调用需要解释成为了闭包来建立正确的执行环境。
- coroutine 和线程实现(9.5 和 13.2.4)。创建协程或者线程的代码必须分配并初始化栈,建立执行环境,执行处理未来异常所需的设置,并调用制定的 routine。像是 transfer, yield, reschedule, sleep_on (以及任何基于调度程序的同步机制)这些 routine也必须同样知道并发实现的大量细节。
- 远程过程调用(C-13.5.4)。远程过程调用(RPC)合并了事件和线程的概念:从服务器的角度,一次 RPC 是由单独线程响应客户端发起的一个事件。无论内建到语言中或者由 compiler stub,都需要知道调用约定,并发,和存储管理的 runtime 系统(分发)。
- 事务内存(13.4.4)。实现事务内存的软件必须缓存更新和读取,判断是否与其他事务存在冲突,并在执行任何可能造成不一致的操作前确认内存状态。还必须可以回滚一次事务的更新操作,或者提交完整事务。这些操作通常需要在事务开始和结束调用库,然后在中间执行读写操作。除此之外,这些调用还需要理解内存的对象形式,对象和事务的 metadata,以及事务冲突的仲裁策略。
- 动态链接(C-15.7)。任何分离编译的系统,compiler 生成 linker 需要的符号表信息用来找到外部引用。在全动态链接系统中国呢,外部引用会暂时在 linker 中以指针方式存在,必须有一个 runtime 系统。当程序试图调用一个没有链接的 routine 时,会唤起 linker,动态找到引用。具体而言,linker 查找符号表,然后以该语言约定的方式发起调用。
TL;