一种面向微内核操作系统的权能机制设计

2023-11-10 15:11张艺川邹仕洪
小型微型计算机系统 2023年11期
关键词:共享内存权能内核

姜 博,张艺川,易 力,王 雷,姜 哲,邹仕洪,3

1(北京航空航天大学 计算机学院 软件开发环境国家重点实验室,北京 100191)

2(元心信息科技集团有限公司,北京 100013)

3(北京邮电大学 网络空间安全学院,北京 100876)

1 引 言

在微内核架构中,权能(Capability)是对进程所拥有的资源的一种描述,是一种无法伪造的权威令牌[1].权能的概念最早在1966年被提出,用来解决多用户系统中的权限问题[2].在实际应用中,访问控制列表(Access Control List)是一种广泛使用的操作系统权限管理机制.Linux、Windows等经典操作系统与INTEGRITY[3]、PikeOS[4]等商用操作系统均使用该机制来实现权限控制,但该机制不能解决如混淆代理人(Confused deputy)[5]的权限泄露问题.相比于访问控制列表,基于权能的系统在权限的划分上粒度更细,每个进程在每个资源上拥有的权限都会通过权能详细列出;此外,基于权能的系统在分配权能时遵循最小特权原则[6],从而使其得以解决上述问题.

一个标准的权能结构一般由两个部分组成:该权能描述的资源的相关信息(如地址、类型等)和持有该权能的进程所获得的对该资源的权限集合[7].为了保证每一个权能结构唯一不变地确定一个资源对象,该结构中的指向资源的指针被设计为自创建之后便无法修改.因此,在使用权能机制的操作系统中,进程在对资源执行操作前必须首先获得与其绑定的权能,并通过该权能提供的调用完成对资源的操作.通过这种方式,进程可以访问的资源被限制在其权能空间中,不会超出其完成任务所必需的资源范围,从而实现了权限细粒度化与特权最小化,响应了用户对系统安全性的需求[8,9].

MOS是一个运行在ARMv8上的微内核操作系统,它是北京航空航天大学操作系统教学科研团队设计完成的,用于支撑北航操作系统实验教学和实时领域的科研工作.MOS内核中提供了内存管理、进程调度、中断处理、进程间通信以及一些基础的系统调用,但是它仍然缺乏完善的权限管理机制.本工作针对此问题,首先对MOS系统中的内核对象进行分类处理,统计各个对象的类型,进而定义出各类型的对象所对应的权能类型;之后在内核中添加了权能的访问权限体系与访问控制功能,并实现了权能的创建、构造、复制、移动、删除这五种基本操作;此外还在用户态设计了一套进程间通信接口,并实现了一个进程服务器,以此为其他用户态进程提供权能授予和撤销的相关接口;最后基于实现好的权能机制,为用户态应用程序提供了动态内存管理、内存共享、消息队列等一系列标准C库函数接口.

2 相关工作

本章将介绍操作系统中典型的权能系统设计,以及权能系统在物联网、区块链等方向上的相关工作..

seL4[10]操作系统是L4微内核操作系统家族的一员,也是第一个经过了形式化验证,从数学的角度验证了其安全性的操作系统,其在车载系统[11]、航空电子系统[12]、嵌入式[13]等方向有着良好的应用前景.微内核操作系统最初被提出,是为了解决宏内核操作系统的可靠性与稳定性问题:宏内核操作系统将驱动等服务进程全部包括在内核中,使得系统内核容易崩溃;而在微内核系统中系统内核不会受到服务进程出错的影响,从而在可靠性与稳定性上优于宏内核[14].但在早期的微内核(如Mach和MINIX)中,进程间通信机制远远慢于宏内核中的系统调用机制;当时,Unix系统中一次系统调用大约需要10μs,而微内核中的进程间通信则需要100μs[15].不过,随着系统技术的进一步发展,通信开销的问题在L4等微内核系统中得到了解决,而如何提升安全性成为了微内核的新的命题.作为第一个被形式化验证的操作系统,seL4使用了权能机制来实现其安全性.在seL4中,权能被分为通信节点、虚拟地址空间、物理页帧等多种类型,每个用户进程拥有其独立的权能空间,规定了该进程能够访问的资源集合;seL4还为各应用程序提供了一套包括权能复制、移动、删除等操作的权能操作接口[16].此外,seL4中所有的权能通过一个叫做权能派生树(Capability Derivation Tree)的结构来进行追踪:在启动时,系统中仅存在一大块无类型的权能;随着权能的不断派发,最开始的无类型权能被不断的切分、派生,并最终转型为应用中的各类权能.

Barrelfish是一个面向异构与多核体系的操作系统,由微软剑桥研究院与苏黎世理工大学共同提出[17].Barrelfish同样采用了权能结构来保障操作系统的安全性.在Barrelfish中,权能的类型包括虚拟内存、未被映射的物理内存、内核中的数据控制块等;与seL4不同,Barrelfish规定了一套树状的转型规则,满足该规则的两类权能之间可以相互转换.在权能的组织上,Barrelfish与seL4类似,采用了其将权能集合作为树节点的机制,并在每个进程中保存其权能对应的树的根节点地址.在权能的查找上,Barrelfish使用了两级查找机制:树节点中存放权能表的表项地址,表项中存放所对应的权能地址.在查找进程是否拥有对应权能时,操作系统需要从根节点向下查找,直至找到对应权能或确定查找失败为止.

RedLeaf[18]是使用Rust语言编写的、为了研究操作系统构成受语言特性的影响而搭建的操作系统.该操作系统使用Rust本身的语言特性而非物理地址分配实现了进程之间的隔离;基于这种隔离方式,RedLeaf抽象出了域(domain)这一概念,并在域之间实现了信息隔离、错误隔离等安全措施.在Rust语言中,特征(trait)是对类型需要实现的一系列方法的描述.为了实现域之间的交互,RedLeaf将特征作为对域所提供接口的描述,并通过在不同域之间传递特征来实现域接口的暴露;而为了保证域之间交互的安全性,RedLeaf引入了权能系统,并使用权能包裹在域之间传递的特征或其他对象,以权能的传递替代特征的传递.通过这种权能传递的方式,RedLeaf可以跳过内核的监管并进一步提高跨域调用的效率,实现用户进程直接调用驱动等高效操作.

除了在通用的操作系统中管理权限,权能系统还可以解决物联网场景中的安全问题.在物联网场景中,权限管理系统需要将不同架构、不同操作系统、不同职能的众多物理设备组织起来,并对多种多样的调用进行鉴权,传统方法难以胜任;而权能系统的细粒度使其在物联网领域具有较大的优势.因此,近年来有一些新型物联网权限管理系统被提出[19,20],这些系统以权能的方式对不同物联网设备的各类操作进行详细地管理,在权限划分粒度、执行效率上要优于传统的权限管理系统.

在分布式系统中,权能系统还可以与区块链技术相结合,进一步提升其安全性.传统的访问控制系统一般为集中式,在分布式系统中遇到单点故障、数据泄露等问题;而区块链由于天然具有分布的性质,可以解决集中式系统的问题,但其不能存储大规模的数据.为了解决分布式系统中访问控制的问题,一些工作致力于使用在系统中同时使用区块链与权能[21,22],以充分利用权能系统的细粒度特性与区块链的分布性、不可篡改性,从而在分布式系统中构建高效的访问控制系统.

此外,通过与RISC指令集的融合,权能系统也可以通过与传统的页表结构相结合,在硬件层面做到字节级别的内存保护,并实现错误隔离[23].由于这种权能体系结构是通过扩展指令集、增加协处理器等硬件层面的措施来完成的,其在提供全面、可量化的访存保护的同时对大部分已有程序表现出良好的兼容性,并已出现众多衍生工作[24,25].

由于系统结构的差异性,seL4与Barrelfish的权能设计方案较为重量级,RedLeaf对Rust语言特性高度依赖,使得已有的设计很难直接应用在MOS微内核操作系统上.本工作将借鉴上述操作系统权能设计的思想,针对MOS微内核操作系统设计轻量级的权能机制.

3 MOS系统的权能机制设计

支持权能机制的MOS架构如图1所示.为了在MOS中建立权能机制,本工作首先设计出MOS中可能出现的各种权能类型,并构建了权能基本操作与权能访问控制两个模块.之后,为了将权能机制投入到实际应用中,本工作还基于权能机制重构了动态内存管理、共享内存等Libc库模块,并在用户态设立了进程服务器与对应的IPC接口来满足用户程序对权能相关服务的需求.

图1 包含权能机制的MOS架构图Fig.1 MOS architecture with capability support

3.1 权能类型和基本操作的设计

在权能系统的建设过程中,本工作首先对MOS操作系统所提供的内核资源进行了分类,并为每一种内核资源给出了与之相对应的权能类型;之后,本工作设计并提供了创建、铸造、拷贝、移动、删除5种调用作为权能的基本操作,并将MOS提供的调用转化为对权能的基础操作的调用,从而完成在权能系统下内核资源的访问;最后,本工作在MOS的内核中加入了权能的寻址算法,并且在执行操作前隐式地对权能所携带的权限位进行检验,从而实现了MOS上的权能访问控制功能,同时又使得权能系统在内核中的复杂逻辑对应用程序而言完全透明.

3.1.1 权能的类型设计与结构组织

为了在MOS中实现多种类型的权能,本工作将权能结构定义为如图2所示的cte结构体:表示权能类型的type字段,表示该权能所对应权限的rights字段,以及根据权能类型不同而实现不同的权能关联对象信息的capability_u字段.

图2 权能结构示意图Fig.2 Structure of capability

本工作中设计的所有权能类型如表1所示.对于每一种权能类型,其对应的权能实现中都记录了其描述的对象的地址、与之关联的其他权能等信息,为权能的相关操作做好准备.此外,在所有权能中,CNode类型的权能较为特殊;这类权能与其对应的内核CNode资源一同完成系统中权能存储结构的组织.而根据其对应的内核资源在存储结构中的位置,CNode类型的权能又可以被分为L1CNode、L2CNode两种.与Barrelfish的实现类似,MOS中的每个进程使用一棵深度为2的CNode树来描述其拥有的权能;L1CNode,L2CNode分别对应该树的根节点与次级节点.具体实现上,每个进程的进程控制块中都会存放其内核中对应的L1CNode的地址,代表该进程的权能空间(Capability Space);内核中,每个CNode对象通过提供多个可以存放权能的槽(slot)来完成对权能的组织,L1CNode的槽中存放L2CNode类型的权能,L2CNode的槽中才存放进程的具体权能.需要注意的是,每个L2CNode中只能存放同种类型的权能;L2CNode允许存放的权能类型由其对应的槽的类型所决定.

表1 MOS中的权能类型Table 1 Capability types in MOS

3.1.2 权能基本操作的设计

作为一种无法伪造但可以交换的令牌,权能的任何操作必然需要内核的参与,否则无法保证其不可篡改性;除此之外,内核还必须为应用程序提供相对应的接口,使得应用程序之间可以进行权能的交换;最后,内核中还必须提供一组给予、收回权能的接口,来满足安全系统的基本要求[26].据此,本工作共定义了以下几种与权能相关的基本操作:

1)权能的创建

权能的创建算法接受要创建的权能类型T、权能的权限r,以及该权能存放的L2CNode的地址S.在创建之前,算法首先检查地址S是否合法;在确认合法之后,算法会在S对应的L2CNode中分配一个空位存放新创建的权能,并初始化其权限、类型等信息,最后返回该权能的地址;若L2CNode中无法再分配空位,则直接返回.其具体步骤在算法1中给出.

算法1.权能创建算法

输入:权能类型T,权能存放的L2CNode的地址 S,权能权限r

输出:创建好的权能地址;若创建失败输出为空

1.if(S == NULL)∨(S.cap.u.type != L2CNode)then

2.returnNULL

3.endif

4. slot=alloc_slot(S)

5.ifslot == NULLthen

6.returnNULL

7.endif

8. struct cte* cap =(struct cte *)slot

9. 根据类型T初始化cap指向的对象信息

10. 将权能cap的权限初始化为r

11.returncap

2)权能的铸造

权能的铸造是指基于一个已有权能创建一个新的权能,且该新权能的权限范围不超出原权能.本工作中的铸造算法需要源权能空间地址S,原权能地址src_addr,目标权能空间D,目标权能地址dest_addr以及目标权能的权限r作为输入.算法首先检查输入的参数,在S或D为空的情况下直接返回;之后,算法通过寻址找到源与目标权能地址所对应的源槽地址src_slot与目标槽地址dest_slot,并检查其是否合法;此外,算法还检查了目标权能的权限是否超出了原权能的权限.在检查完毕之后,算法最后将源权能拷贝至dest_slot处,并设置其权限为r.其具体流程如算法2所示.

算法2.权能铸造算法

输入:源权能空间地址S,源权能地址 src_addr,目的权能空间地址D,目的权能地址 dest_addr,目的权能权限r

输出:无

1.if(S == NULL)∨(D == NULL)then

2.return

3.endif

4. src_slot=mos_cap_resolve_addr(S,src_addr)

5. dest_slot=mos_cap_resolve_addr(D,dest_addr)

6.if(src_slot == NULL)∨(dest_slot != NULL)∨(r不是src_slot 中权能权限的子集)then

7.return

8.endif

9. 将src_slot中的权能拷贝至dest_slot

10. 将dest_slot中的权能权限设置为r

3)权能的复制

权能复制操作与权能的铸造操作过程类似.权能复制函数以源权能空间地址S、源权能地址src_addr,目标权能空间D、目标权能地址dest_addr为输入;该算法首先检查S与D的合法性,再通过寻址算法找到权能地址所对应的源槽地址src_slot与目标槽地址dest_slot,同时检查其是否合法.满足以上条件后,算法将权能从src_slot拷贝至dest_slot,并保证拷贝前后的权能权限一致.

4)权能的移动

权能的移动过程需要将权能从一个槽移动到另一个槽中,并且保证移动后源槽中的权能被删除.该算法的具体流程如与复制算法基本一致,但在完成拷贝之后需要将原位置的权能彻底删除.

5)权能的删除

权能删除算法的详细过程如算法3所示.该算法需要接受权能空间地址base与其中待删除权能的地址addr作为输入.算法首先对base地址进行检验,确认其合法后在其中查找权能地址addr所对应的槽地址.若成功查找到,则算法删除槽中存放的权能;否则传入的参数不合法,直接返回.

算法3.权能删除算法

输入:权能空间地址base,权能地址 addr.

输出:无

1.ifbase == NULL then

2.return

3.endif

4. slot=mos_cap_resolve_addr(base,addr)

5.ifslot != NULLthen

6. 删除slot中存放的权能

7.endif

3.1.3 权限的设计和访问控制

MOS中,权能所持有的权限共分为5类;这些权限又可根据是否与对象有关分为两种.其具体分类如表2所示.需要注意的是,其中的可复制权限表示可以对某个权能进行复制,而可深拷贝权限则表示可在复制权能的同时复制一个对象的副本.

表2 MOS中的权限类型Table 2 Authorities in MOS

根据权能的类型不同,权能可持有的权限类型也不同.在MOS中,只有Endpoint,Rgn,MQ 这3种权能能够持有上述的权限;其具体对应关系如表3所示.

表3 权能与权限的对应关系Table 3 Relationship between capability and authorities

为了实现访问控制功能,应用程序只有在向内核申请资源时才可设置对应权能的权限;任何通过复制、铸造等方式从其他进程处得到的权限都不会超出原始范围.此外,应用程序若需要访问内核中的资源,则必须将之前获得的相关权能一并传入内核,使得内核得以通过权限位校验来确认是否允许该进程访问相应资源.在这种情景下,由于进程不能未经允许获取其他进程资源的权能,从而无法访问其他进程的资源,保证了系统的安全性.

3.2 基于权能的Libc库设计

在实现了基础的权能操作之后,为了让应用程序的所有操作经过权能系统的检验,MOS中提供给用户进程的接口需要以权能调用的方式重新编写.为了统一各类调用形式,本工作中所有的权能调用的形式为,其中CapType为权能类型,InvocLabel为该调用的调用标识,后面的部分为该调用所需的其他参数.

本工作主要为用户提供了3类经过重构的接口:动态内存管理、共享内存和消息队列.

3.2.1 动态内存管理的设计

动态内存是随进程在运行中向系统发出申请、释放等请求而动态变化的内存区域.本工作中提供了4个有关动态内存管理的接口:malloc、realloc、calloc和free函数.

1)malloc

malloc函数是从系统中申请动态内存的关键函数;其从用户进程处接受动态申请的内存大小为参数,并向操作系统申请对应大小的内存,并在成功时返回申请到的内存的起始地址.

在POSIX标准下,malloc函数需要使用brk系统调用进行实现[27],但这么做会产生两个问题:1)容易因内存不断回收释放而产生内存碎片;2)不断的通过系统调用进入内核态有可能造成效率的下降.因此,本工作选择以内存池的方式对malloc函数进行了实现,即在用户态维护一个容纳多块内存的内存池,在程序申请内存时首先在内存池中进行查找,若存在符合要求的空闲内存则直接分配给该程序;否则再向内核申请一块对应大小的内存分配给程序.而在程序进行内存释放时,相关函数只需要在内存池中更改对应的标记,从而减少内存碎片并提高内存申请调用的速度.

基于内存池方案,并使用权能重构的malloc函数实现如图3所示.malloc函数在被调用时首先会将请求的内存大小与页面大小4KB进行对齐,计算出实际需要分配的内存大小;之后,该函数会遍历内存池,寻找合适大小的未分配内存块;若查找成功,则将该内存块从内存池中移除并准备将其返回给应用程序.若内存池中不存在相匹配的内存块,则malloc函数需要通过系统调用向内核申请对应大小的内存.

图3 基于权能机制的malloc流程Fig.3 Workflow of malloc in capability mechanism

在系统调用过程中,内核首先需要为该段动态内存分配一块Rgn类型的权能,并将该权能加入调用进程的权能空间中;之后,内核还需要根据内存大小申请一定数量的Page类型的权能,并同样加入权能空间中;最后,内核将分配的物理页加入页表并映射到用户空间,同时上移brk指针并返回.此外,系统调用后,用户态程序也必须上移其brk指针.

在将分配好的内存返回之前,malloc函数还会检查堆顶对应的内存块是否空闲.若该内存块空闲,则malloc函数会通过系统调用进入内核态,在内核态中完成Rgn、Page两种权能的删除、物理内存的释放以及brk指针的下移,来完成该部分内存的释放工作.

2)free

free函数在动态内存管理系统中负责对动态申请内存的释放.该函数接受一个指针为参数,并尝试释放该指针所指向的内存区域.

由于使用了内存池机制,free函数的实现相对简单.该函数会首先对传入的指针进行判断,在确认该指针指向一块动态分配内存之后,该函数会去查找对应的动态内存,将其状态标记为空闲,并加入内存池的空闲链表中.

3)calloc

calloc函数接受两个参数:元素个数nelem与元素大小elsize,并向系统申请一块nelem×elsize大小的内存,并保证该块内存已被初始化为0.

在MOS中,calloc函数的实现使用了malloc函数;首先根据参数计算申请的空间大小,将其传入malloc函数,并将malloc函数返回的空间清零,最后返还给用户程序.

4)realloc

realloc函数接受动态内存指针ptr与新的内存大小size,尝试将ptr指针所指的动态内存的大小更改至size,并返回指向更改后的动态内存的指针.

由于逻辑相似性,realloc函数的实现同样使用了malloc函数.realloc函数首先对传入的新内存大小与原有内存大小进行比较,若后者小于等于前者,则不做任何修改,直接返回原指针;若后者大于前者,则需要调用malloc函数分配一块新的内存,并将旧的内存中的内容拷贝过去,最后调用free函数释放旧内存.

3.2.2 共享内存的设计

共享内存一般指多个进程在系统的支持下将不同虚地址空间中的地址映射到同一物理页上,从而使得多个进程能够访问同一内容.由于其读写不需要系统内核的额外操作,共享内存的效率要高于管道、消息队列等通信工具,是一种高效的进程间通信方式.

在MOS中,共享内存的实现主要基于两个结构体:内核态共享内存信息结构体kshm与用户态基本信息结构体shm.kshm结构体中保存了该结构体对应的共享内存的大小、使用该共享内存的进程数,以及该共享内存的权能等信息;而shm结构体中保存了共享内存基址、共享内存大小等基础信息.之所以在用户态维护记录了共享内存基本信息的shm结构体,是为了相关函数能够在内存分配的过程中在用户态同步更新brk指针,以防止动态分配内存机制与共享内存分配机制之间发生冲突.

MOS共提供了3个共享内存操作函数:shmget,shmat和shmdt.

1)shmget

shmget调用用来根据约定好的索引值获取相对应的共享内存的标识符.该函数接受3个参数:约定的共享内存结构体索引值key,共享内存大小size和标记位shmflg.

shmget函数首先会在用户态根据索引值key查询共享内存记录;若该key在用户态有对应记录,则返回该记录;若不存在对应记录,则将其传入内核进行进一步查询.如果在内核中也查询不到对应的记录,程序会在内核中新建一个kshm结构体,使用size与shmflg对其各个字段进行初始化,并将其权能指针rgn_cte字段置NULL;返回到用户态后,函数会将信息同步到对应的shm结构体中,并最终返回其标识符.

2)shmat

shmat函数用来将指定的共享内存映射到传入的虚拟地址上,从而使得共享内存可以被程序直接使用.

shmat函数接受共享内存标识符shmid,目标虚拟地址shmaddr和标记位shmflg,并返回映射后的共享内存起始地址.接受参数后,函数首先检查shmaddr对应的共享内存是否已经被绑定;若已被绑定则直接返回其对应的地址.否则,shmat会通过系统调用进入内核态,并建立类型为Rgn的权能r供进程使用.在此之后,内核态程序会找出shmid对应的kshm结构体t并检测是否有其他进程已经映射了该共享内存:若没有进程映射过,内核需要申请对应的Page类型的权能,完成页表映射,并将该权能写入权能空间,最终将权能r保存到t中;否则只需要查找结构体t中的权能数组,映射t中的虚拟地址与该权能所对应的物理地址,再将r写入t中即可.完成以上操作后,程序返回用户态,在shm结构体中进行相应的记录,最后返回起始地址.

3)shmdt

shmdt函数负责无效化通过shmat函数与进程绑定的共享内存.区别于其他系统,MOS的shmat函数中涉及到了权能的创建;因此在shmdt中这些权能需要被正确地销毁.

shmdt函数接受待无效化的共享内存地址addr作为参数.shmdt首先检验addr是否存在,之后将其通过系统调用传入内核.在内核中,程序先通过addr查找权能空间中的对应权能;再在kshm结构体中查找包含该全能的记录.若查找成功,则将kshm结构体中的权能销毁,取消页表映射并返回用户态;用户态程序再完成用户态的清理工作,最后返回应用程序.若上述查找过程失败,则说明传入的地址无效或者权能无效,程序直接返回.

3.2.3 消息队列的设计

除去共享内存外,消息队列也是一种常见的进程间通信手段.消息队列是一种可在进程间共享的公共资源,不同的进程可以打开同一消息队列并接收或发送消息,从而达到通信的目的.在MOS中,消息队列被设计为一种独立于所有进程的内核对象,并通过MQ类型的权能进行管理.除了全能之外,MOS中与消息队列有关的结构还有3个:消息队列结构体kmq_header(存放使用标识、消息队列索引、权能指针等),消息队列属性结构体kmq_attr(存放最大消息数、消息大小等)和消息队列节点结构体kmq_node(存放消息、消息的优先级等).其具体组织结构如图4所示.

图4 消息队列结构示意图Fig.4 Structure of message queue

修改后的MOS提供了一套完整的基于权能的消息队列接口,包括mq_open,mq_close,mq_unlink,mq_receive,mq_send等众多函数.以下对消息队列的主要功能所设计的函数进行介绍.

1)消息队列的获取

用户程序如果想要新建或者打开消息队列资源,则需要调用mq_open接口.在MOS中,mq_open函数被设计为只接受3个参数:消息队列的键值key,标志位oflag,以及消息队列的属性attr(仅在新建队列时有效).

被调用时,mq_open函数首先根据传入的key进行查找,若成功找到则增加该消息队列的计数器并返回;若未能找到对应消息队列,则需要根据oflag与attr新建一个MQ类型的权能与kmq_header结构体,将其与key互相关联并返回.

2)消息队列的销毁

MOS中,与消息队列销毁相关的接口主要有两个:mq_close和mq_unlink.其中前者负责关闭消息队列,后者则负责销毁无用的消息队列.

mq_close函数接受一个消息队列描述符作为参数.该函数会根据描述符查询到消息队列的信息,并检查当前消息队列打开数是否为1;若为1,则调用mq_unlink函数将其删除;否则将其打开计数器减一并返回.

mq_unlink同样接受一个消息队列描述符作为参数.查找到消息队列后,如果该消息队列打开数不为1,则直接返回;否则,该函数会释放该消息队列资源以及该消息队列中所含的所有消息节点资源,再从调用进程的权能空间中删除对应的消息队列权能,最终返回.

3)消息的发送与接收

消息队列中消息的发送与接收分别依赖于两个接口:mq_send与mq_receive.

mq_send接口接受一个消息队列描述符,一条消息以及消息的优先级;该函数在调用时首先会通过传入的消息队列描述符获取对应的权能,并检查进程是否具有可写权限;确认写入操作合法之后,该函数还会对消息的合法性进行校验;通过所有检验之后,函数在内核中新建一个消息节点,将其加入消息队列并返回.

mq_receive接口则主要接受一个消息队列描述符,以及消息存放位置等其他信息;与mq_send类似,该函数也会首先获取权能并检查写权限,并根据消息队列信息检查参数的合法性;在确认合法之后,该函数会遍历消息队列节点以找到优先级最高的消息,将该消息以及相关信息一并返回.

3.3 IPC机制与进程服务器

MOS是一个微内核系统;这使得MOS的内核中只包括最基础的页表映射、进程管理等功能,而其他大部分的功能的实现均依赖于用户态程序.因此,MOS的用户态中有一些进程需要为其他用户进程提供服务,从而实现操作系统的全部功能;这些进程被称为进程服务器.为了充分利用微内核结构的优势,同时为用户态进程提供与权能相关的一众接口,本工作为MOS中添加了一套原生的IPC机制,并基于该IPC机制设计了一个处理权能相关调用的进程服务器.

为了存放通信的内容,本工作在进程控制块中加入了包括msg与caps两个字段的ipc_buffer结构,其中msg数组存放IPC传递的参数,而caps数组则负责传递权能指针;用户态程序可通过系统调用来获取本结构中的信息.

在上述结构的基础上,本工作设计了如表4所示的一套接口,包括了测试连接、监听、请求发送等接口,以此来实现进程服务器的全部功能.

表4 MOS中的进程服务器接口Table 4 Server interfaces in MOS

为了实现消息的发送与接收,MOS系统中的用户进程通过Endpoint资源来记录其发送或接收的请求;同时,内核中设计了全局的发送Endpoint链表与接收Endpoint链表,用来存放因IPC发送或接收而被阻塞的进程所提交的Endpoint.当用户进程进行发送或接收时,若对方尚未准备好,则用户进程的Endpoint会被保存到列表中,同时用户进程被阻塞;但为了保证进程服务器能够对多个进程进行正常服务,在进程服务器进行接收操作但没有进程向其发送请求时,服务器会让出时间片而非被阻塞.

用户程序向进程服务器发送请求的详细过程如图5所示.应用程序首先需要向内核申请Endpoint;在得到该Endpoint对应的权能之后,应用程序通过系统调用修改自身的ipc_buffer,并先后调用send与recv来向进程服务器发送请求并得到请求结果.在内核中,send与recv两次调用所提供的Endpoint会被分别加入发送链表与接收链表,同时用户程序被阻塞,直至进程服务器完成该请求的相应.当进程服务器发现该请求后,对应的Endpoint会被从链表中移除;服务器进程根据Endpoint信息通过系统调用获得用户程序在ipc_buffer中储存的调用参数,根据调用信息进行处理之后将结果放入自身的ipc_buffer中,通过send调用返回给用户程序.

图5 IPC请求流程示意图Fig.5 Sequence diagram of IPC request

4 实验评估

本工作针对实现了权能机制的MOS操作系统,从权能系统的安全性与权能系统的效率两方面对其进行了评估.

4.1 实验环境及准备

该本工作的试验环境为使用Qemu仿真器模拟的ARMv8架构环境;该Qemu仿真器运行在Ubuntu操作系统中.作为试验对象的MOS系统使用aarch64-elf-gcc进行编译.其具体参数如表5所示.

表5 实验环境Table 5 Experiment environment

4.2 实验结果与分析

4.2.1 权能的共享与撤销

算法4.权能共享方测试样例

输入:无

输出:无

1. cap=vka_alloc_rgn(size)//初始化权能

2. new_cap=cl_sendcap_rgn(0x1111,cap,0)//分配权能

3. cl_revoke_rgn(new_cap)//释放权能

4. mos_yield()

5. new_cap=cl_sendcap_rgn(0x2222,cap,CAP_RW)

6. cl_revoke_rgn(new_cap)

7. mos_yield()

8. new_cap=cl_sendcap_rgn(0x3333,cap,CAP_COPY)

9. cl_revoke_rgn(new_cap)

10. mos_yield()

11. new_cap=cl_sendcap_rgn(0x4444,cap,CAP_DEEPCOPY)

12. cl_revoke_rgn(new_cap)

算法5.权能接收方测试样例

输入:无

输出:无

1. cap=cl_recvcap_rgn(0x1111)//接收分享权能

2. addr=mos_rgn_map(cap)//读写测试

3. mos_yield()

4. cap=cl_recvcap_rgn(0x2222)

5. addr=mos_rgn_map(cap)

6. mos_yield()

7. addr=mos_rgn_map(cap)

8. mos_yield()

9. cap=cl_recvcap_rgn(0x3333)

10. slot=mos_cnode_allocslot(disp,SLOT_RGN)

11. //拷贝测试

copy_cap=mos_cnode_copy(cap,SLOT_RGN,slot)

12. mos_yield()

13. slot=mos_cnode_allocslot(disp,SLOT_RGN)

14. copy_cap=mos_cnode_copy(cap,SLOT_RGN,slot)

15. mos_yield()

16. cap=cl_recvcap_rgn(0x4444)

17. slot=mos_cnode_allocslot(disp,SLOT_RGN)

18. //深拷贝测试

copy_cap=mos_cnode_deepcopy(cap,SLOT_RGN,slot)

19. mos_yield()

20. slot=mos_cnode_allocslot(disp,SLOT_RGN)

21. copy_cap=mos_cnode_deepcopy(cap,SLOT_RGN,slot)

算法4与算法5分别展示了本实验为验证权能系统有效性而编写的两段测试代码.在实验过程中,这两段代码分别运行在两个用户进程中;其中共享方代码负责向内核申请创建Rgn类型的权能,其先后将无权限、可读写、可拷贝、可深拷贝四种权限不同的权能分享给另一个进程,并在完成对应测试后撤销该权能;而接收方则负责不断地接收分享的权能,并在权能撤销前与撤销后不断的尝试使用权能进行读写、拷贝、深拷贝操作,以测试权能系统的有效性.

实验得到的结果如表6所示.在读写测试中,接收方只有在拥有读写权能的前提下才能够进行读写操作;而在拷贝操作测试中只有接收方进程的权能被撤销之前才能进行对应操作,符合预期.

表6 测试结果Table 6 Test result

4.2.2 权能系统的性能测试

本工作在MOS系统中共实现了Page、CNode、Endpoint、Pcb、Rgn、MQ 6大类提供具体调用的权能,不同的权能类型对应的调用种类也不尽相同;本节实验借助ARMv8中全局系统计数器[28],在函数的入口与出口分别全局系统计数器中的值进行读取,从而测量MOS中实现的各类权能调用的性能.

Page,Endpoint,Rgn 3种权能的调用周期数如图6所示;CNode,Pcb,MQ 3种权能的调用周期数如图7所示.在6类权能调用中,对应进程控制块的Pcb与对应权能组织结构的CNode两类权能调用相比之下较为基础,因而消耗周期较少;而涉及内存管理机制的Rgn类型权能调用需要维护内存结构,其内部逻辑较为复杂,因而所消耗周期最多.

图6 Page,Endpoint,Rgn类型权能调用开销Fig.6 Overhead of calls on Page,Endpoint and Rgn Capabilities

图7 CNode,Pcb,MQ类型权能调用开销Fig.7 Overhead of calls on CNode,Pcb and MQ capabilities

从总体来看,大部分的权能调用的时钟周期数都在7500以下,而页表、进程控制块等底层调用的时钟周期数基本在5000以下,相对较快;Rgn类权能调用虽然经过一定的优化,但在所有调用中开销最高;此外,Endpoint类调用中的endpoint_send函数的消耗周期数均明显高于同类其他调用,这是因为这该函数流程中进行了较多的系统调用,在基础功能之外增加了一定的周期消耗.

5 结 论

本工作在ARMv8的多核环境下,在微内核操作系统MOS上设计并实现了基于权能的安全系统.本工作首先对MOS的内核对象进行了分类,并根据各类权能的具体作用设计了其对应的权能调用与权能权限;之后,本工作通过实现权能的创建、铸造、复制、移动、删除5种基本操作与寻址算法,建立起了基础的权能系统;此外,本工作对一部分的Libc接口基于权能体系进行了重写,将用户程序的相关操作置于权能系统监督之下;最后,本工作还在MOS中设计了进程服务器,使得应用程序可以通过一套IPC接口来获得权能的相关服务.完成设计之后,本工作验证了权能系统维护系统安全的有效性,并对与权能相关的一系列操作的性能进行了测试与分析.

猜你喜欢
共享内存权能内核
五指成拳 靶向发力 拓展股权权能 助力富民增收
强化『高新』内核 打造农业『硅谷』
宅基地资格权:原则遵循、性质定位和权能阐述
通过QT实现进程间的通信
基于嵌入式Linux内核的自恢复设计
Linux内核mmap保护机制研究
基于Linux内核的文件服务器模型的研究与构建
基于PCI总线的多处理器协同机制研究
“四荒”土地承包经营权能抵押吗
农地产权权能扩展及管理措施完善研究