XPC论文总结

XPC: Architectural Support for Secure and Efficient Cross Process Call 论文总结

摘要

1.微内核有很多有趣的特性,例如安全性、容错、模块化、可定制性。
2.IPC是影响操作系统性能的主要因素。
3.IPC在例如Android Linux等单片内核中也发挥着重要作用,移动应用经常通过IPC与大量用户级服务进行通信。
4.以前的软件优化通常无法绕过负责域切换和消息复制/重映射的内核。
5.硬件解决方案(如标记内存或功能)为了隔离而替换页表,但通常需要对现有软件堆栈进行大量修改,以适应新的硬件原语。
6.本文提出了一种基于硬件辅助的操作系统原语XPC (Cross Process Call),用于实现快速、安全的同步IPC。XPC支持IPC调用方和被调用方之间的直接切换,而不需要陷进内核,并且支持通过调用链跨多个进程传递消息,而不需要复制。
7.该原语与传统的基于地址空间的隔离机制兼容,可以很容易地集成到现有的微内核和单片内核中。
8.本文利用FPGA板实现了一个基于Rocket RISC-V核的XPC原型,并移植了两个微核实现seL4和Zircon,以及一个单片内核实现Android Binder进行评估。

一.介绍

1.微核是将特权模式下的功能最小化,并将大多数功能(包括分页、文件系统和设备驱动程序)放在隔离的用户模式域中,以实现细粒度的隔离、更好的可扩展性、安全性和容错。

2.当前基于微核的操作系统的实现仍然面临一个安全性和性能之间的权衡:更细粒度的隔离通常会导致更好的安全性和容错,而且更多的ipc(进程间通信),这被称为微核的阿基里斯之踵。

3.基于单核内核的操作系统还存在IPC的长延迟问题,例如Android构建在单内核Linux上,并为移动应用程序提供很多用户级服务,这些服务会导致很高的开销。Android在Linux内核中引入了Binder和匿名共享内存来缓解这个问题,但是延迟仍然很高。

4.IPC的大部分周期会花费在两个任务上:

  1. 域切换
  2. 消息复制

由于调用者和被调用者都处于用户模式,因此它们必须进入内核以切换地址空间,这包括上下文保存/恢复、功能检查和许多其他IPC逻辑。

5.通过共享内存发送消息可以减少复制的数量,但如果调用者和被调用者同时拥有共享内存,则也可能导致TOCTTOU攻击(从检查到使用的时间)。采用页面重新映射进行所有权转移可以缓解上述安全问题,但重新映射操作仍然需要内核的参与。同时,重新映射也可能导致昂贵的TLB击落。

6.以前的优化ipc方法,有通过软件和硬件的,对于大多数软件解决方法,陷入内核的开销无法避免,消息传递将导致多次复制或TLB关闭。硬件解决方法,如codom,利用标记内存而不是页表来实现隔离,它们采用单地址空间,减少了域交换和消息传递的开销。这些新的硬件解决方案通常需要对针对多个地址空间设计的现有内核实现进行大量修改。

7.本文提出了一种新的硬件辅助操作系统原语XPC (cross process call),以安全地提高IPC的性能。设计有四个目标:

  1. 直接切换,不陷入到内核。
  2. 用于消息传递的安全零复制
  3. 易于与现有的内核集成
  4. 最小的硬件修改

我们的新原语包含三个部分:
第一个是新的硬件感知抽象x-entry,每个x-entry都有自己的ID,并使用一个新功能xcall-cap进行访问控制。该功能由内核管理以提高灵活性,由硬件检查以提高效率。
第二种是一组新的指令,包括xcall和xret,它们允许用户级代码在不涉及内核的情况下直接在进程间切换。
第三种是一种新的地址空间映射机制,名为relayseg(“中继内存段”的缩写),用于在调用者和被调用者之间零复制消息传递。(映射由一个新的寄存器完成,该寄存器指定消息的虚拟地址和物理地址的基础和范围,这种机制通过确保在任何时候只有一个消息所有者来支持消息的所有权转移,这可以防止TOCTTOU攻击,并且在域切换后不需要TLB刷新。中继seg也可以通过调用链传递,也就是切换,以进一步减少复制的数量。)

8.XPC选择在不同的地址空间中保持同步IPC的语义,这使得他很容易被现有的OS内核采用,同时,xpc克服了传统同步ipc的两个限制:

  1. 数据传输吞吐量相对较低
  2. 对于多线程应用程序不容易使用模型

XPC通过中继seg机制提高吞吐量,并通过迁移线程模型提供易于使用的多线程编程接口。

9.本文的主要贡献:

  1. IPC性能开销的详细分析以及与现有优化的比较
  2. 一个新的ipc原语,在调用链上支持不陷入内核和消息的零复制
  3. 在FPGA上实现低硬件成本的XPC和Gem5,与两个现实世界的微内核和Android绑定的集成
  4. 在真实平台上用微基准和应用程序进行评估。p

二.动机

2.1 IPC性能仍然很关键

在这里插入图片描述
对于短消息IPC,主要开销是域切换,但是随着消息的增长,花费的时间主要是数据传输。
图a显示了ipc占用了时间的比例,图b显示了随着消息长度的增加,数据传输占ipc总开销的比例。
因此可以看出ipc的性能还有待提升。

2.2 解构ipc

分解ipc的每一步流程,并分析每一步的时间成本。

sel4有两个ipc路径,分别是快速路径慢速路径,其中慢速路径是允许调度和中断的,因此会有更多的延迟。我们将关注快速路径,啥时候用慢速稍后介绍。

1)陷入和恢复

调用方通过系统调用来启动一个ipc,从而陷入到内核,内核首先保存调用方的上下文,然后切换到内核自己的上下文,在内核处理完ipc的代码后,会恢复被调用者的上下文,并切换到用户空间。这两个阶段花费300周期,说明域切换有很大的开销。

现有的系统内核都是假定调用双方互相是不信任的,因此每次都是通过保存上下文以实现隔离的,但是调用方和被调用方有不同的信任假设,因此由调用方和被调用方管理自己的上下文会更加的灵活和高效。

2)IPC逻辑

在IPC逻辑部分,主要任务是检查。seL4使用功能来管理所有内核资源,包括IPC。内核首先获取调用者的能力并检查其有效性(例如,是否有发送权限)。然后它检查下列条件是否满足,以决定是否采用慢速路径:

  • 调用方和被调用方有不同的优先级
  • 调用方和被调用方不在同一个核心上
  • 消息的大小大于寄存器(32字节)和小于120字节(IPC缓冲区大小)。

我们发现这些检查逻辑更适合在硬件上实现,可以并行完成,以隐藏延迟。它启发我们将逻辑划分为控制平面和数据平面,前者通过软件来实现更大的灵活性,后者则通过硬件来实现更大的效率。

3)进程切换

在运行IPC逻辑之后,内核达到了不返回点,并将上下文切换到被调用方。在这一部分中,内核操作调度队列推出被调用线程,阻塞调用线程。为了使调用线程具有应答能力,将reply_cap添加到被调用线程中,最后内核传输IPC消息,并切换到被调用方的地址空间。

进程切换引入了几种内存访问,例如用户上下文,功能和调度队列,这些内存访问可能会触发缓存和TLB丢失,从而影响IPC性能。

4)消息传输

在sel4中有三种方式传输消息:

  • 如果消息足够小,可以放入寄存器,那么他将在进程切换时被复制。
  • 对于中等大小的消息(小与IPC缓冲区大小,大于寄存器大小),sel4将切换到慢路径上去复制消息。
  • 对于长时间的消息传输,sel4使用用户空间中的共享内存来减少数据复制。

然而,共享内存很难实现高效、安全的消息传输。虽然在共享内存中就地更新可以实现调用者和被调用者之间的零复制,但它不安全。

5)观察结果

1.一个不依赖于内核的快速IPC是必要的
2.在支持切换的同时,传递消息的安全且零复制机制对性能至关重要。

3.设计

XPC是由新的硬件组件(xpc引擎)和软件支持(OS内核和用户库)组成。
XPC引擎提供了基本功能,包括功能检查,上下文切换和轻量级但高效的消息传递机制。
操作系统内核充当控制平面,通过配置XPC引擎来管理IPC。

3.1 设计概述

本节描述XPC引擎提供的两个硬件原语和编程模型,硬件更改汇总在图中。

1)用户级的跨进程调用
XPC引擎为这个原语提供了两个新的抽象x-entry和xcall-cap。
x-entry与可以被其他进程调用的程序绑定,一个进程可以创建多个x条目。
所有的x条目被存储在一个名为x-entry-table的表中,这是一个全局内存区域由寄存器x-entry-table-reg指向。每个x条目有一个ID,该ID是它在x-entry-table中的索引。一个新的寄存器x-entry-table-size控制x-entry-table的大小,并使表具有可伸缩性,调用者需要一个xcall-cap来调用x-entry。

xcall-cap是“XPC调用能力”的缩写,它记录了每个x-entry的IPC能力,IPC调用和返回提供了两个新的指令,xcall #reg和xret,其中#reg记录了操作系统内核提供的x-entry索引。

2)轻量级消息传输
XPC引擎提供了一种轻量级的机制,名为relay-seg,用于消息传输。relay-seg是一个由连续物理内存支持的内存区域,relay-seg的转换是由新的寄存器seg-reg完成,而不是页表。
seg-reg可以从调用方传递给被调用方,因此被调用方可以直接访问seg-reg中指定的虚拟地址范围内的数据,OS内核将确保relay-seg的映射不会被页表的任何映射所重叠,因此,不需要在这个区域击落TLB。

3)XPC编程模型
XPC的编程模型既兼容基于能力的权限检查,也兼容基于页表的隔离。图中显示了一个示例代码片段。服务器首先通过传递过程处理程序一个处理程序线程,和一个最大上下文号(表示同时调用的最大数量)来注册一个x条目。
处理程序线程用于为客户机线程提供运行时状态,并且可以由多个x条目共享,之后服务器完成注册,准备好服务IPC请求。
客户端获得服务器的ID以及IPC能力,通常从它的父进程或名称服务器获得。
IPC通过xcall #reg执行,消息可以通过通用寄存器和relay-seg传输。

void xpc_handler(void* arg) 
{ ... /* handler logic */ 
xpc_return ();
}
void server() {
... /* Register an XPC Entry */ 
xpc_handler_thread = create_thread ();
max_xpc_context = 4;
}
void
client () {
/*get server's entry ID and capability from parent process */
xpc_ID = xpc_register_entry( entry_handler ,xpc_handler_thread ,max_xpc_context );
server_ID = acquire_server_ID("servername"); 
xpc_arg = alloc_relay_mem(size);
... /* fill relay -seg with argument */
xpc_call(server_ID , xpc_arg); 
}

3.2 XPC引擎

xcall:xcall #reg指令调用由寄存器指定的ID的x条目,XPC引擎表现在四个任务上:

  1. 首先通过读取xcall-cap位图中的#reg位来检查调用者的xcall-cap
  2. 然后引擎从x-entry-table加载目标x-entry,并检查entry的有效位。
  3. 一个链接记录被推入链路堆栈。在这里,我们使用术语链接记录来指示返回所需的信息,这些信息将存储在每线程堆栈(称为链接堆栈)中。
  4. 然后,处理器加载新的页表指针(如果需要,刷新TLB),并将PC设置为过程的入口地址。

引擎将把调用者的xcall-cap-reg放到寄存器中(例如,RISC-V中的t0),以帮助被调用者识别调用者。进程中发生的任何异常都将报告给内核。

xret:xret指令从链路堆栈弹出一条链接记录,并返回到前一个进程。CPU首先检查弹出的链接记录的有效位。然后它根据链接记录恢复到调用者。

xcall-cap:xcall-cap将在xcall期间被检查。出于性能的考虑,我们使用一个位图来表示xcall。每个带索引i的位表示线程是否能够调用ID i对应的x条目。位图存储在每个线程的内存区域中,由xcall-cap-reg寄存器指向,它将由内核维护,并在xcall期间由硬件检查。

链接堆栈:链接堆栈用于记录调用信息(链接记录),这是一个由寄存器链接reg指向的每线程内存区域,并且只能由内核访问。在我们当前的设计中,一个链接记录包括页表指针,返回地址,xcall-cap-reg, seg-list-reg,中继段和一个有效位。
在推出链接记录时,XPC引擎准备进行切换,并可以惰性保存链接记录。因此,我们可以使用一种非阻塞的方法来优化链路堆栈,以隐藏写堆栈的延迟

XPC引擎缓存:我们添加了一个专用缓存来优化XPC引擎的内存访问,以获取x-entry和功能。对于这个设计决策,我们有两个观察结果:IPC具有高时间局部性(用于单个线程);IPC是可以预测的。基于这两个观察结果,我们为XPC引擎使用了一个软件可管理的缓存来存储x条目。支持预取,以便用户应用程序可以提前将x条目加载到缓存中。

3.3 中继段

relay-seg:seg-reg寄存器被引入作为TLB模块的扩展,用于映射一个relay-seg,relay-seg包含四个字段:虚拟基地址、物理基地址、长度和权限,虚拟区域(从VA_BASE到VA_BASE + LEN)直接映射到物理区域(从PA_BASE到PA_BASE + LEN)。在地址转换期间,seg-reg比页表有更高的优先级。
图中显示了relay-seg的寄存器和操作,用户应用程序不能直接更改seg-reg的映射,而是可以通过一个新的寄存器seg-mask来缩小当前relay-seg的范围,并将新的范围传递给被调用方
当只有消息的一部分应该传递给被调用方,特别是沿着调用链传递时,这是非常有用的。
在xcall期间,seg-reg和seg-mask都保存在链接记录中,并且seg-reg被更新为seg-reg和seg-mask的交集。在此之后,被调用方可以像调用方一样访问relay-seg。
在这里插入图片描述
多relay-seg:一个服务器可以创建多个中继seg,它将存储在由OS内核管理的名为seg-list的每个进程内存区域中,该区域由一个新的寄存器seg-list-reg指向。
如果一个进程需要对另一个中继seg执行调用,它可以使用一个新的指令swapseg #reg来原子地交换当前的seg-reg(在它的seg-list中由#reg索引的seg-reg)。通过与无效条目交换,线程可以使seg-reg失效。

relay-seg的所有权:为了防御TOCTTOU攻击,内核将确保每个relay-seg一次只能在一个CPU核心上活动。换句话说,一个活动的relay-seg只能被一个线程拥有,并且所有权将沿着它的调用链转移,这样两个cpu就不能同时操作一个中继seg。

返回一个relay-seg:在xret期间,被调用方的seg-reg必须与被调用时相同。XPC引擎将通过使用保存在链接记录中的seg-reg和seg-mask检查当前seg-reg来确保这一点。检查是必要的;否则,恶意被调用方可能会将调用方的relay-seg交换到它的seg-list,并返回一个不同的seg。如果检查失败,将引发一个异常,内核将处理它。

4.实现

我们从与RocketChip RISC-V内核的集成、对微内核的支持、对单片内核的支持以及用户级消息切换机制四个方面描述了总体设计的具体实现。

4.1 融入RocketChip

这里介绍RocketChip中的RTL原型实现,以及OS内核如何管理XPC引擎。
XPC引擎:XPC引擎是作为RocketChip核心的一个单元实现的
在这里插入图片描述
表中显示了关于新寄存器和指令的详细信息。新的寄存器被实现为CSRs(控制和状态寄存器),可以通过csrr (CSR read)和csrw (CSR write)指令访问。
三条新指令在执行阶段被发送到xpc engine,XPC引擎检查IPC的有效性,并将被调用方信息返回给管道。添加了5个新的异常,包括无效的x-entry、无效的xcall-cap、无效的链接、无效的seg-mask和swapseg错误。

XPC管理:内核管理4个XPC对象:1)全局x-entry表,2)per_thread链接堆栈,3)per_thread xcall功能位图和4)per_address_space中继段列表。在系统引导期间,内核为x-entry表分配内存并设置表的大小(在我们当前的实现中为1024个条目)。
在创建线程时,它为线程的链接堆栈分配8KB内存,为功能位图分配128B内存,为seg-list分配4KB页面。在上下文切换期间,内核保存并恢复per_thread对象。

4.2 支持微核

能力:IPC的能力已经被微内核广泛使用,为了在线程之间传输xcall-cap,我们的实现引入了一个软件功能,grant-cap,它允许一个线程将一个功能(xcall和grant)授予另一个线程。
内核将维护并执行每个线程的授权能力列表。当一个线程创建一个x条目时,它将拥有新x条目的grant-cap,并可以将xcall-cap授予其他线程。
分裂线程状态:(这个部分不理解)用户模式下的域切换可能会导致内核的错误行为,因为内核不知道当前正在运行的线程。为了解决这个问题,我们借鉴了迁移线程的思想,将内核维护的线程状态分为两部分:调度状态和运行时状态。调度状态包含所有与调度相关的信息,包括内核堆栈、先验性、时间片等。运行时状态包含当前地址空间和功能,内核使用它们来服务于此线程。每个线程都有一个调度状态绑定,但是在运行时可能有不同的运行时状态。在我们的实现中,我们使用xcall-cap-reg来确定运行时状态。一旦一个线程捕获到内核,内核将使用xcall-cap-reg的值来查找当前运行时状态,因为这个寄存器是每个线程的,并且在xcall期间将由硬件更新。

预调用C栈:XPC的线程模型支持服务器的一个x条目,由多个客户机同时调用。在XPC中,我们的库提供了每次调用的XPC上下文,其中包括一个执行堆栈(称为C-Stack)和本地数据,以支持同时的跨过程调用。
当创建一个x条目时,服务器会为它指定一个最大数量的XPC上下文。XPC库将提前创建这些上下文,并为每个x条目添加一个蹦床trampoline将选择一个空闲的XPC上下文,切换到相应的C-Stack并在调用之前恢复本地数据,并在返回之前释放资源。如果没有空闲上下文可用,trampoline要么返回错误,要么等待空闲上下文。

应用程序终止:我们的实现还考虑到调用链上的任何过程都可能异常终止,这可能会影响整个链。例如,考虑一个调用链:A→B→C,其中B由于某些异常被内核杀死。当C调用xret时,它可能返回到错误的进程。在这种情况下,我们需要一种方法来触发异常,并让内核来处理它。
在我们的实现中,当一个进程终止时,内核将扫描所有的链接堆栈并将所有进程的链接记录(通过比较页表指针)标记为无效。因此,在前面的例子中,一旦C返回,硬件将触发一个异常,内核将弹出B的链接记录并返回一个超时错误给A。
我们还引入了一个优化来减少链路堆栈扫描的频率:当B被杀死时,内核将清零B的页表(顶层页),而不需要扫描。因此,当C返回到B时,一个页面错误将被触发,内核将有机会处理它。内核也会撤销B的资源。

4.3 支持Android Binder

Android Binder是Android为进程间通信引入的一个重要扩展。它在Android组件中得到了广泛应用,包括窗口管理器、活动管理器、输入法管理器等。

Android绑定器由若干层组成,包括Linux Binder驱动程序、Android Binder框架(例如,c++中间件)和API(例如,Android接口定义语言)。

我们的修改集中在驱动程序和框架上,但保持API不被修改以满足现有的应用程序。

Binder使用Binder事务表示跨进程方法调用,并使用内核“双重副本”传输数据(命名为事务缓冲区)。此外,Android还引入了ashmem(匿名共享内存)来提高进程间批量内存传输的性能。

Binder事务:客户端和服务器之间Binder事务的处理过程包括以下步骤:

  1. 客户端准备一个方法代码,表示要调用的远程方法,并打包数据。
  2. 客户端Binder对象调用transact()。这个调用将通过Linux Binder驱动程序传递,它从用户空间事务缓冲区复制数据(通过copy_- from_user),切换到服务器端,并从内核复制数据(通过copy_to_user)。(两个内存复制和两个域切换。)
  3. 服务器端的Binder框架接收请求,并通过调用onTransact()方法来处理这个调用,该方法是服务器预先准备好的。
  4. 服务器通过Linux Binder驱动程序回复请求。

在这里插入图片描述

如图所示,我们使用XPC优化上述流程。首先,我们保持由Android绑定器框架提供的IPC接口(如transact()和onTransact())不变,以支持现有的应用程序。
此外,我们扩展绑定器驱动程序来管理xcall-cap功能(例如,set_xcap和clear_xcap接口)和x-entry表(例如,add_x-entry和remove_x- entry接口)。

当一个服务器进程通过Binder接口(例如addService)注册一个服务时,修改后的框架将向Linux绑定驱动程序发出ioctl命令来添加一个x条目。类似地,当客户端进程通过API(例如getService)请求服务时,框架将发出set-xcap。

最后,与调用ioctl不同,框架将使用xcall和xret进行远程方法调用,并使用中继段实现数据传输的包。此外,Linux内核也应该为线程维护XPC寄存器。通过XPC的优化,消除了域切换和内存复制。

Ashmem:匿名共享内存(Ashmem)子系统为用户空间提供基于文件的共享内存接口。它的工作原理类似于匿名内存,但是进程可以通过共享文件描述符与另一个进程共享映射。可以通过mmap或文件I/O访问共享内存。在Android Binder中,进程可以通过Binder驱动共享ashmem的文件描述符。

与传统的共享内存方法一样,ashmem也需要额外的复制来避免TOCTTOU攻击。本文利用XPC中的relay-seg实现了ashmem。

  • ashmem分配:Binder框架通过从Binder驱动分配一个relay-seg来分配一块ashmem。
  • ashmem映射:内存映射操作将为segment分配一个虚拟地址,并设置relay-seg寄存器。
  • ashmem传输:ashmem可以在xcall期间通过传递框架中的seg-reg寄存器在进程之间传输。

使用中继段,框架可以避免在服务器端进行额外的复制,因为映射的所有权已经转移。然而,一个限制是,在原型实现中,一次只有一个有效的中继段。因此,当应用程序需要同时访问多个ashmems时,我们依靠页面错误(隐式)/swapseg(显式)来切换有效中继段。

4.4 沿呼叫链切换

relay-seg机制允许一段数据沿着调用链传递,不同的情况下,流程可能有不同的切换用法。假设一个调用链:A→B→C,其中a沿着链传递一个消息M。这里我们需要克服三个挑战来支持零拷贝切换。
第一,B可以将数据附加到M,这样的附加项可能会超出relay-seg的边界。
第二,C接口可能只接受小块数据,例如文件系统服务器将数据分割成块大小,然后逐个发送到磁盘服务器。
第三,当C执行时,B可能会异常终止,这需要撤销它的relay-segs。

消息大小协商:消息大小协商用于处理第一个挑战。它允许调用链中的所有进程协商合适的消息大小,以便客户端可以保留一些空间用于追加消息。
协商是递归执行的。给定一个调用链:A→B→[C|D],其中B可以调用C或D。Sself (B)表示B将要添加的大小,Sall (B)表示B和它所有可能的调用将添加的大小。
因此,Sall (B) = Sself (B) + Max©, Sall (D))。当A第一次调用B时,它向B请求Sall (B),这样A就可以为整个链保留足够的空间。服务器也可以有它们的大小协商的实现。

消息收缩:消息收缩允许调用者在seg-mask的帮助下将消息的一部分传递给它的被调用者。

段撤销:段撤销是在进程终止时由内核执行的。内核将扫描进程的seg-list,将调用方的relay-segs返回给调用方,并撤销其他relay-segs。

5.评价

  • XPC如何提高IPC性能?(&5.2)
  • 操作系统如何从XPC中获益?(&5.3)
  • 应用程序如何从XPC中获益?(&5.4)
  • Android Binder如何从XPC中获益?(&5.5)
  • XPC在其他架构上可移植性如何?(&5.6)
  • XPC的硬件资源成本是多少?(&5.7)

5.1 方法

我们基于两个开源的RISC-V实现XPC引擎:siFive Freedom U500和lowRISC。我们已经移植了两个最先进的微内核,在siFive Free上的seL43 Freedom U500和在lowRISC上的Zircon[2],并在两个系统中添加了XPC支持。
此外,我们在Linux 4.15中将Android Binder框架libBinder移植到Freedom U500(通过将同步汇编代码修改为RISC-V),并在XPC中优化绑定器中的同步IPC。我们评估了Zircon、seL4、Android Binder、Zircon- xpc、seL4- xpc和Binder- xpc 6个系统的性能。
除了FPGA硬件,我们还将XPC移植到ARMv8的GEM5模拟器上,以验证XPC的通用性。

5.2 微基准测试

优化和分解:我们使用以下五种不同的优化方法来测量IPC的延迟,并显示性能收益的分解。

  • Full-Ctx:保存和恢复完整的上下文。
  • Partial-Ctx:保存和恢复部分上下文。
  • +Tagged TLB:启用以前的优化并采用标记TLB以减少TLB缺失。
  • Noneblock Link Stack:启用以前的优化和采用非块链接堆栈。
  • +XPC Engine Cache:启用以前的优化并采用XPC引擎缓存。

在这里插入图片描述

图中显示了使用不同方法的一个IPC调用周期。
Full-Ctx方法中,由于RocketChip还不支持带标记的TLB,它将导致大约40个周期的TLB刷新/丢失。蹦床代码需要76个周期来保存和恢复通用寄存器。xcall逻辑大约需要34个周期。
Partial-Cxt优化只考虑必要的寄存器(如堆栈点寄存器和返回地址寄存器),并将蹦床代码减少到15个周期。
采用Tagged-TLB可以减少TLB缺失。
Nonblock LinkStack隐藏了推送链接记录的延迟,可减少16周期的延迟。
Engine Cache使用预取来进一步减少12个周期的延迟。
通过这些优化,一个xcall可以实现6个周期,一个IPC只花费21个周期。
在接下来的评估中,XPC将使用Full-Ctx和Nonblocking Link Stack优化以确保比较的公平性。

在这里插入图片描述

单向调用:我们还评估了单向调用的性能,客户机用不同的消息大小调用服务器,我们计算从客户机调用开始,到服务器获取请求的周期。
如图所示sel4-xpc在sel4的快速路径上有5-37x的加速。一个原因是sel4-XPC用relay-seg来传递消息,而在sel4中内核复制仅在小于120字节时使用,并且它使用共享内存来传输大消息。随着消息的增大,加速更多的来自relay-seg,sel4只在消息大小为中等时使用慢路径
由于消除了调度和内核的参与,在消息较小的情况下,Zircon-XPC可以获得60x的加速,Zircon使用内核双重复制来传输消息,并且没有优化IPC路径中的调度,这使得它比seL4慢得多。

多核IPC:跨核IPC的性能从81x提高到141x,由于XPC采用了迁移线程IPC模型,服务器的代码可以在客户机的上下中运行,XPC还提供了更好的TLB(在中继段中)和缓存局部性。此外,客户机可以通过在不同核上创建多个工作线程并将服务器拉到这些核上运行,从而轻松地扩展自身。

在这里插入图片描述

其他指令:我们测量xcall,xret,swapseg指令的周期。如上表所示,除了上述的xcall外,xret需要23个周期,swapseg需要11个周期。这三条指令的成本都很小,主要来自内存操作。微内核可以基于这些原语实现高效的IPC。

5.3 操作系统服务

为了说明IPC如何影响微内核的性能,我们评估了两个OS服务:文件系统和网络子系统的性能。

在这里插入图片描述

文件系统:一个文件系统通常包括两个服务器,一个文件系统服务器和一个块设备服务器。
我们测试文件读/写操作的吞吐量,结果如图a,b所示,Zircon使用内核双重复制,sel4使用共享内存,我们实现了seL4-one-copy版本,它需要一次复制来满足接口(有TOCTTOU问题),以及seL4-two-copy版本,它需要两次复制,提供了更高的安全性保证。XPC优化的系统可以实现零复制,没有TOCTTOU问题。与Zircon/seL4相比,XPC读操作的平均加速速度为7.8倍/3.8倍,写操作的平均加速速度为13.2倍/3.0倍。
改进主要来自更快的切换和对XPC的零复制,特别是对于写操作,这将导致在文件系统服务器和块设备服务器之间进行许多ipc和数据传输。

在这里插入图片描述

网络:微核系统通常有两个网络服务器,一个网络堆栈服务器(包括所有的网络协议)和一个网络设备服务器
评估了不同缓冲区大小下TCP连接的吞吐量,如图所示。Zircon-XPC的平均速度是Zircon的6倍,对于较小的缓冲区,Zircon-XPC可以实现高达8倍的加速,并且随着缓冲区大小的增加,加速次数会减少。这是因为lwIP为批处理缓冲客户端消息,因此增加缓冲区大小将减少IPC的数量,由于其较高的IPC延迟,从而提高原始锆石的性能。

5.4 应用程序

为了展示XPC如何提高实际应用程序的性能,我们评估数据库和web服务器的性能。
Sqlite3:Sqlite是一个广泛使用的关系数据库,在不同的工作负载下测量吞吐量,每个工作负载在一个包含1,000条记录的表上执行。
在这里插入图片描述
结果如图8(a)和(b)所示。XPC在seL4上的平均加速率为60%,在锆石上的平均加速率为108%。YCSB-A和YCSB-F获得了最大的改进,因为它们有很多写入/更新操作,这些操作将触发频繁的文件访问。YSCB-C几乎没有什么改进,因为它是只读工作负载,而Sqlite3有一个内存缓存,可以很好地处理读请求。

Web Server:这个测试涉及三个服务器,一个HTTP服务器,从lwIP移植而来,它接受一个请求,然后重新转换一个静态HTML文件;AES加密服务器,用128位密钥对网络流量进行加密;一个内存文件缓存服务器,用于在两种模式下缓存HTML文件。HTTP服务器配置了启用加密模式和禁用加密模式。客户端不断地向web服务器发送HTTP请求。对吞吐量进行测量,结果如图©所示。
在这里插入图片描述
XPC在进行加密时大约有10倍的加速,在未进行加密时大约有12倍的加速。最大的好处来自切换优化。因为在多服务器的情况下,消息将被传输多次。使用切换可以有效地减少这些IPC中的内存复制次数。

5.5 Android Binder

在这里插入图片描述

Binder:我们通过模拟窗口管理器和界面之间的通信来评估Binder,在这种情况下,界面排序器将界面数据通过绑定器传递给windows管理器,然后windows管理器需要读取界面数据并绘制相关的界面。
我们考虑了两种绑定工具:通过绑定缓冲区传递数据和通过ashmem传递数据,并评估了通信的延迟。结果如图所示,其中延迟时间包括数据准备(客户机)、远程方法调用和数据传输(框架),处理界面内容(服务器)和应答(框架)。
图(a)显示了使用缓冲区进行通信的结果。Android Binder对于2KB数据的延迟为378.4us,对于16KB数据的延迟为878.0us(100次运行的平均值),而Binder-XPC对于2KB数据的延迟为8.2us (46.2x改进),对于16KB数据的延迟为29.0us (30.2x改进)。值得注意的是,在Android中缓冲区的大小是受限制的(例如,小于1MB)。
在Binder中使用ashmem进行数据传输的结果如图(b)所示。Android Binder的延迟从0.5ms (4KB表面数据大小)到233.2ms (32MB表面数据大小),而Binder- xpc为4KB数据达到9.3 ms (54.2x改进)和81.8ms (2.8x改进)。

Ashmem:当我们只采用relay-segment来优化ashmem时(图中的ashmem-xpc),我们使用相同的情况来评估延迟,如图b所示,ashmem-xpc在4KB数据上达到0.3ms(1.6倍改进),在32MB数据上达到82.0ms(2.8倍改进)。改进主要来自于零复制消息的传输。

讨论:总的来说XPC可以有效的优化Android Binder和Ashmem的性能

5.6 普遍性

在这里插入图片描述

XPC是一种支持不同体系结构的通用设计,除了在FPGA上实现RISC-V外,我们还在ARM平台上使用周期精确的GEM5模拟器实现它。该实现基于ARM HPI(高性能顺序)模型。表4中列出的仿真参数模拟了现代有序的ARMv8-A实现。
我们使用microOps来实现XPC引擎的功能。通过仔细选择顺序,我们可以避免xcall和xret指令中的投机问题。我们将端点表的项设为512,能力位图的长度设为512位,调用堆栈设为512个项。这些区域上的任何正常加载/存储指令都将触发异常。实现不包含任何优化,如非阻塞链接堆栈和引擎缓存。

在这里插入图片描述

我们选择seL4的IPC作为基线。由于seL4不支持GEM5,我们使用名为Panda的记录和回放工具转储seL4的fastpath_- call和fastpath_reply_recv的指令跟踪。我们直接在GEM5中运行跟踪。结果如表5所示,其中只考虑seL4中的IPC逻辑部分和XPC中的xcall/xret。
基线和XPC都比真正的硬件有更好的性能。其中一个原因是GEM5不会模拟TLB冲洗成本(在ARM中)4。我们在Hikey-960电路板(ARMv8)中对TTBR0进行指令障碍(isb指令)和数据障碍(dsb指令)更新的成本进行了评估,成本约为58个周期。
使用XPC, IPC逻辑部分得到了改进:当消息传输大小很小且缓存是热的时,IPC逻辑部分从66 (+58 TLB开销)周期到7 (+58 TLB开销)周期。GEM5的实现证实了XPC设计的通用性。

5.7 硬件成本

在这里插入图片描述
我们使用Vivado[10]工具生成硬件时,我们可以从中得到FPGA中的资源利用率报告。硬件成本报告如表6所示(不包括引擎缓存)。总体硬件成本很小(LUT为1.99%,RAM为0.00%)。通过进一步调查资源成本,我们发现XPC中的CSRFile使用了比基线(用于处理7个新寄存器)更多的372个lut和273个FFs,而XPC引擎使用422个lut、462个FFs和1个DSP48块。
当然,它的利用还可以进一步优化,比如在RocketChip中使用Verilog代替Chisel。较低的硬件成本使XPC可以应用于现有的处理器(例如Intel x86和ARM)。

6.讨论

6.1 安全分析

XPC身份验证和标识:在没有相应的xcall-cap的情况下,调用者不能直接发出xcall ID来调用XPC。它可以从带有相应赋予授权的服务器请求xcall-cap,就像L4中的名称服务器一样。被调用方可以通过其xcall-cap-reg来识别调用方,该reg将被XPC引擎放入一个通用寄存器中,并且不能被伪造。

防御TOCTTOU攻击:TOCTTOU攻击的发生是由于消息的所有权转移不足。在XPC中,消息由一个relay-seg传递,它一次只属于一个线程。同时,内核将确保relay-seg不会与任何其他映射的内存范围重叠。因此,每个所有者都可以在一个relay-seg中专有地访问数据,这可以从本质上防御TOCTTOU攻击。

故障隔离:在xcall期间,被调用方崩溃不会影响调用方的执行,反之亦然,如§4.2和§4.4所述。如果被调用方挂起很长时间,调用方线程也可能挂起。在这种情况下,XPC可以提供一种超时机制来强制控制流返回给调用者。然而,在实践中,超时的阈值通常被设置为0或无限,这使得超时机制不太有用。

防御DoS攻击:恶意进程可能试图通过消耗远远超过其需要的硬件资源来发起DoS攻击。
一种可能的攻击是创建大量中继seg,这需要许多连续的物理内存范围,这可能触发外部碎片。在XPC中,一个relay-seg将使用进程的私有地址空间,这将不会影响其他进程或内核。
另一种可能是,恶意调用者可能由于过度调用被调用者而耗尽被调用者的可用上下文。我们可以使用信用系统[3,13]来克服这个问题。被调用方在为其分配XPC上下文之前,将首先检查调用方是否有足够的信用。

定时攻击:XPC引擎缓存可能是计时攻击的来源,但是非常困难,因为条目的数量很少(在本文中只有一个)。此外,可以通过在引擎缓存中添加标记(如tag - tlb)来缓解这个问题。由于每个缓存条目对于一个线程(带有标记)都是私有的,因此可以减轻计时攻击。

6.2 进一步优化

可伸缩的xcall-cap:xcall-cap在我们的原型中被实现为位图,它是有效的,但是存在可扩展性的问题,另一种方法是使用radix树,它具有更好的可伸缩性,但会增加内存占用并影响IPC性能。

中继页表:relay-seg机制有一个限制,它只能支持连续内存,这个问题可以通过扩展段设计来支持页表设计来解决。

7.相关工作

减少域交换的延迟和消息传输以优化IPC的研究有很长一段时间。在本节中,我们将回顾以前的IPC优化,并在表7中进行比较。

7.1 在域切换上的优化

软件优化:一个广泛采用的优化是使用调用方的线程在被调用方的地址空间中运行被调用方的代码,例如迁移线程模型,这种优化消除了调度延迟和IPC逻辑开销。
L4使用一个类似的技术称为直接进程交换,它支持调用者和被调用者之间的地址空间交换,内核开销很小,还采用惰性调度来避免频繁的运行队列操作。

硬件优化:新的硬件扩展也被提出,以提高跨域调用的性能,如Opal , CHERI和codom。在这里,域不是一个被地址空间隔离的进程,而是一个新的抽象的执行对象(例如,一段代码),它有自己的ID(例如,域ID)。在一个域切换期间,标识将被显式地改变(例如,保存在寄存器中的ID)或隐式地改变(例如,程序计数器隐含的ID)。切换可以直接在非特权级别完成,而不会困住内核,这对于软件优化来说是一个巨大的优势。同时,多个域可以共享一个地址空间,进一步减少域切换后TLB丢失的开销。然而,这些系统通常需要对现有的基于地址空间隔离机制的微内核进行不小的改动。为了实现更好的兼容性,像CHERI这样的系统采用了一种混合方法,同时使用功能和地址空间,但是在地址空间之间进行切换仍然需要内核的参与。跨界[44]和SkyBridge[50]利用了硬件虚拟化特性VMFUNC,它允许虚拟机直接切换其EPT(扩展页表),而不需要捕获到管理程序。但是,该特性仅适用于虚拟化环境。

7.2 在数据传输上的优化

在这里插入图片描述

软件优化:对于长消息传递,一种简单,安全但不高效的方法是采用“双重拷贝”(调用方→内核→被调用方),有些系统,例如LRPC,利用用户级内存共享来传输消息,并将复制时间从两个人减少到一个人(调用者→共享缓冲区),如图(b)所示。然而,这种设计可能会影响安全性,因为恶意调用者可能在被调用者运行时随时更改消息,例如,在被调用者检查消息的有效性之后,这将导致TOCTTOU攻击。一种解决方案是被调用方将消息复制到它自己的空间中,但这将消耗单拷贝的好处。

另一个解决方案是通过重新映射来更改共享内存的所有权,如图c所示。但是,内存重新映射需要内核的参与,并导致TLB失效。同时,由于这样的内存通常在两个进程之间共享,如果需要通过调用链上的多个进程传递消息,则必须将其从一个共享内存复制到另一个共享内存。

L4应用临时映射来实现消息的直接传输。内核首先会找到被调用方的一个未映射的地址空间,并将它临时映射到调用方的通信窗口中,该窗口位于调用方的地址空间中,但只能被内核访问。这样,就得到了一个副本(呼叫者→通信窗口)。同时,由于调用方无法访问通信窗口,因此在发送消息后无法更改消息。然而,调用者仍然需要内核进行复制和重新映射,这将导致不可忽略的开销。

硬件优化:许多硬件辅助系统利用域间有效的消息传输能力。
codom使用一种混合的内存授权机制,结合了权限列表(域粒度)和功能寄存器(字节粒度)。通过传递一个功能寄存器,内存区域可以从调用者传递到被调用者并转发。然而,该区域的所有者可以随时访问该区域,这使得系统仍然容易受到TOCTTOU攻击。

CHERI使用硬件能力指针(解出内存范围的下界和上界)进行内存传输。尽管设计中考虑了一些元数据(例如,文件描述符)的TOCTTOU问题,但它仍然会遭受数据的TOCTTOU攻击。同时,这些系统被设计为单地址空间,并使用标记内存进行隔离。

Opal和MMP提出了新的硬件设计,使消息传输更加有效。他们使用PLB(保护备用缓冲区)来解耦地址空间转换的权限,以实现字节粒度共享。然而,如果没有额外的数据拷贝,它们既不能减轻TOCTTOU攻击,也不能支持沿调用链的长消息传递。

M3利用了一个新的硬件组件,DTU(数据传输单元),用于消息传输。DTU类似于DMA,它可以在消息量大的情况下实现高吞吐量,并允许高效的跨核数据传输。

然而,众所周知,DMA并不适合中小型数据[51],因为DMA通道初始化的开销不能很好地平摊。HAQu[41]利用基于队列的硬件加速器来优化跨核通信,而XPC可以支持更通用的消息格式。

表7总结了不同IPC优化的系统的特征。如所示,硬件方法可以实现更好的性能,如通过消除总线捕获来实现更快的域切换。然而,这些方法通常需要对硬件(例如CHERI增加了33个新结构)和软件(例如,不支持现有的基于地址空间隔离的微内核)进行重大更改。
在这里插入图片描述

7.3 其它相关工作

对OS性能的架构支持:XPC继续在社区中开展对OS性能的架构支持工作。具体地说,XPC提供了一个硬件辅助原语,它显著地提高了IPC的性能。这与其他最近的架构支持相结合,将显著提高操作系统在各种工作负载下的性能。

IPC共享内存:许多操作系统采用共享内存的思想来进行消息传递[20,25,44]。Fbufs[25]使用内存映射和共享内存实现保护域之间的有效数据传输。在要求性能的同时,它们容易受到TOCTTOU攻击,不能支持一般的切换。

异步IPC:异步IPC作为同步IPC的补充,已被广泛研究。FlexSC提出异步系统调用,用于批处理和减少用户和内核之间的模式切换。Barrelfish使用异步IPC实现核之间的非阻塞消息交换。尽管异步IPC由于其批处理策略可以带来良好的吞吐量,但它通常不能实现低延迟。

全局地址空间系统:全局虚拟地址系统将所有域放到一个地址空间。在相同地址空间内强制不同域之间的隔离通常需要隔离机制,而不是分页机制。例如,Singularity依靠软件验证和语言支持来加强系统中程序的隔离性和行为正确性。XPC具有更好的兼容性,可以很容易地被现有的微内核所采用。

8.总结

本文介绍了XPC,一个新的架构扩展来支持快速和安全的IPC。扩展兼容传统的地址空间隔离,可以很容易地与现有的操作系统内核集成。我们的评估表明,XPC能够显著地改善现代微内核和Android Binder的各种工作负载的性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值