2.1 seL4-Capabilities讲解

595 阅读7分钟

想了想还是得把概念搞清楚再继续学习,所以从第一部分开始补充概念的讲解。如果能学得融会贯通了。再考虑整理下所有的文章内容。

Capability

这篇教程主要就是对capability的一个基本的介绍。

初始化

首先我们初始化一个教程的程序。

./init --tut capabilities
cd capability_build
ninja 
./simulate

成果

学完这篇教程,我们会熟悉以下内容: 1.术语:CNode, CSpace, CSlot 2.学会调用一个capability 3.知道怎么删除和拷贝CSlots

背景

什么是一个capability?

一个capability是一个唯一的、不可伪造的令牌。他给予一个进程在系统中访问实体或者对象的权限。我们可以把它当成是一个带有权限控制功能的指针。在seL4中,有三种类型的capability:

  1. 控制对内核对象的访问,比如:线程控制块
  2. 控制对抽象资源的访问,比如:IRQControl
  3. untyped capability:负责内存范围和内存分配(在后面untype会展开去讲)

在seL4中,所有内核控制的资源的capability都在初始化时赋予了root task。需要修改任何资源的状态,用户代码可以使用内核API,它可以在libsel4中对特定capability指向的资源请求操作。 例如,root task有一个capability指向自己线程的控制块(TCB:thread control block),它是sel4_CapInitThreadTCB。这是一个定义在libsel4中的常量。想要改变这个初始TCB的属性,一个方法就是使用这个capability的TCB API 方法API Reference | seL4 docs. 下面是一个例子,改变root task TCB的堆栈指针。这是当root task需要更大的堆栈时的一个常见的操作。

    seL4_UserContext registers;
    seL4_Word num_registers = sizeof(seL4_UserContext)/sizeof(seL4_Word);

    /* Read the registers of the TCB that the capability in seL4_CapInitThreadTCB grants access to. */
    seL4_Error error = seL4_TCB_ReadRegisters(seL4_CapInitThreadTCB, 0, 0, num_registers, &registers);
    assert(error == seL4_NoError);

    /* set new register values */
    registers.sp = new_sp; // the new stack pointer, derived by prior code.

    /* Write new values */
    error = seL4_TCB_WriteRegisters(seL4_CapInitThreadTCB, 0, 0, num_registers, &registers);
    assert(error == seL4_NoError);

整个过程就是从这个TCB中读取出所有的寄存器,然后改变我们要改的属性,再把它们都写回去。

seL4_TCB_ReadRegistersseL4_TCB_WriteRegisters这两个函数使用的第一个参数是seL4_CapInitThreadTCB这个slot中的capability。(这里官方文档感觉有点混淆,上面还说这个就是capability,这里又说这是个slot,根据下文这个东西就是capability) 寻址和slot会在下面再展开叙述。

CNodes和CSlots

CNode,全称capability-node,是一个包含capability的对象。我们可以将CNode看成是一组Capability。这里还称slot为CSlot,意思是capability-slot。在上面的例子中,seL4_CapInitThreadTCB就是一个在root task的CNode中的slot,它包含了指向root task TCB的capability。每个在CNode中的CSlot都可以有下面的状态:

  • empty:CNode的这个slot包含了一个为空的capability
  • full:CNode的这个slot包含了一个指向内核资源的capability

按照惯例,第0个CSlot会保持为空,与虚拟地址空间中,保持NULL不被映射的原因相同:避免未初始化的slots被无意间引用。(感觉这里说法有点问题,其实这里就是在slot中也设计了NULL的概念)

字段info->CNodeSizeBits给出了初始的CNode的大小:得到的结果一般是1 << CNodeSizeBits个CSlot。A CSlot有1 << seL4_SlotBits个字节,所以一个CNode的大小是1 << (CNodeSizeBits + seL4_SlotBits)个字节。

CSpaces

CSpaces,全称是capability-space。它是一个线程可以访问的整个范围的capability。一个CSpace可能由一个或者多个CNode构成。在这个教程中,我们主要关注seL4在初始化的过程中为root task创建的CSpace,它只包含了一个CNode。

CSpace addressing

想要引用一个capability并在其上执行操作,我们必须知道他的地址。seL4的API中有两种方法能寻址capability。 第一种:调用。第二种:直接寻址。 调用这种方式是一个简写,我们会用他来操作root task TCB的寄存器。下面将详细解释什么是调用。

invocation

每个线程都有一个特殊的CNode capability安装在他的TCB中,作为他自己的CSpace的root。这个root可以为空,比如当线程没有被授权调用任何capability时。或者它可以是一个指向某个CNode的capability。 root task永远会有一个CSpace root capability指向一个CNode。

在一个调用中,一个CSlot是被隐式调用这个线程的CSpace root来寻址的。在上面的例子中,我们就是在seL4_CapInitThreadTCBCSlot上使用一个调用来读取和写入的,它是由特定CSlot中的capability所表示的TCB的寄存器。有点绕,往下接着看吧。

seL4_TCB_WriteRegisters(seL4_CapInitThreadTCB, 0, 0, num_registers, &registers);

我感觉在官网上,CSlot和capability在描述的时候经常混用,可能是因为capability就在CSlot中放着,所以每次都大差不差的说了。 在调用线程的能力空间中,系统会隐式地查找TCB的capability,这个capability位于跟任务的CSpace的一个CNode中。换句话说,在执行某个操作时,系统会自动去确定调用的线程是否有权限访问seL4_CapInitThreadTCB. 当上下文非常清楚并且没有其他相关的东西时,我们有时用它指向的对象来标识capability。所以,我们有时会直接说CSpace root,而不是被CSpace root capability指向的CNode。但是如果不太确定,最好还是说得精确一些。 就像C语言中的结构体和指针不能互换一样,object和capability是不能互换的:一个object可以被多个capability指向,并且每个capability可以对object有不同等级的权限。

直接CSpace寻址

与invocation的寻址方式的差别是,直接寻址允许你指定特定的CNode,在指定的CNode中查找。而不是隐式地使用CSpace root。 这种寻址方式主要用来构建或者操作CSpace的结构——可能这个CSpace是别的线程的。需要注意的是,直接寻址也要求invocation:该功能需要调用一个CNode capability,而他的索引是由CSpace root提供的。

下面的字段用于直接寻址CSlot:

  • _service/root :用于操作CNode的capability
  • index:要寻址的CSlot在CNode中的索引
  • deph:在解析CSlot之前,遍历CNode的深度

对于初始的、单级的CSpace,深度永远是seL4_WordBits。对于invocation(调用),深度也是隐式的seL4_WordBits。关于CSpace的深度,会在以后的教程中再深入探讨。 在下面的例子中,我们使用直接寻址,对root task的TCB做一个复制。他的位置就在CSpace root的第0个位置。CNode copy这个函数需要两个CSlot被直接寻址:目标CSlot和源CSlot。seL4_CapInitThreadCNode被作为三个不同的角色来使用:作为源root,作为目标root,作为源slot。作为源和目标root,是因为我们是在同一个CNode中copy它的——初始线程的CNode。作为源slot,是因为在初始线程的CNode中,seL4_CapInitThreadCNode是我们想要复制的capability的slot(目标slot是0)

    seL4_Error error = seL4_CNode_Copy(
        seL4_CapInitThreadCNode, 0, seL4_WordBits, // destination root, slot, and depth
        seL4_CapInitThreadCNode, seL4_CapInitThreadTCB, seL4_WordBits, // source root, slot, and depth
        seL4_AllRights);
    assert(error == seL4_NoError);

所有的CNode invocation都需要直接寻址。

可以看到与上面的invocation相比,这边都要指定CNode是哪个了。

初始化CSpace

root task有一个CSpace,这个CSpace是由seL4在boot阶段配置的,它包含了所有被seL4管理的资源的capability。(这里要理解清楚,他说的是capability,也就是说,root task拿了所有资源的访问权限,这些权限组成了他这个CSpace)我们前面已经见识了几个这个root CSpace的capability了:seL4_CapInitThreadTCB, and seL4_CapInitThreadCNode.这两个都是由libsel4中的常量指定的。单页并不是所有的初始capability都是静态指定的。其他的capability是被seL4_BootInfo数据结构描述的,在libsel4中描述并被seL4初始化。seL4_BootInfo描述初始capability的范围,包括初始的CSpace中可用的空闲的slot。

后面再分一篇来介绍这一章节的练习。