3.1 seL4-Untyped讲解

317 阅读8分钟

Untyped

这篇是对seL4物理内存管理的简单介绍。

预备知识

需要学过capability课程。

成果

学完这一节课程,我们会熟悉以下内容:

  1. 了解术语untyped,device untyped和bit size
  2. 知道如何在seL4的untyped memory中创建object
  3. 知道怎么回收object

背景

物理内存

在seL4系统中,除了少量的静态的内核内存外,所有的物理内存都由用户层管理。 seL4在boot阶段给object创建的capability和其他由seL4管理的物理内存,都在启动时被传递给了root task。

untyped memory和capability

除了用于创建root task的对象,所有可用的物理内存的capability都传递给了root task,方式是给root task传递untype memory的capability。untyped memory是一个特定大小的连续物理内存块。untype capability就是针对untyped memory的capability。untyped capability可以被retype进内核的对象中(使用他们自己的capability),或者被retype成更小的untyped capability。 Untyped capability有一个布尔类型的属性(device),这个属性用来标识这块内存是否可以被内核写入。不可写入的内存可能是其它设备而不是RAM,或者这部分内存在内核不可寻址的RAM区域。device属性的untyped capability只能被retype成frame对象(物理内存frame,可以被映射到虚拟内存),不能被内存写入。

初始状态

提供给root task的seL4_BootInfo结构体描述了所有untyped capability,包括他们的大小,以及他们是不是device属性的untyped,还有untyped的物理地址。下面的示例代码展示了如何打印seL4_BootInfo提供的初始untyped capability。

   printf("    CSlot   \tPaddr           \tSize\tType\n");
    for (seL4_CPtr slot = info->untyped.start; slot != info->untyped.end; slot++) {
        seL4_UntypedDesc *desc = &info->untypedList[slot - info->untyped.start];
        printf("%8p\t%16p\t2^%d\t%s\n", (void *) slot, (void *) desc->paddr, desc->sizeBits,           desc->isDevice ? "device untyped" : "untyped");
    }

这段程序看起来还非常的怪异。 首先是这些untyped的slot可以从info中的untyped获得,我们可以得到起始和结束的位置。而对于untyped的描述,保罗他的大小、物理地址、是否是device属性,是存储在untypedList里面的。 因为我们slot里面存储的是他的capability,并不是他本人,只是一个权限,这里要注意。

Retyping

这个untyped capability只有一个简单的调用:seL4_Untyped_Retype,这个调用用来从一个untyped capability创建一个新的capability(也可以是一个对象)。特别的,被retyped 调用新创建的capability提供了对原始的capability范围的子集的访问。或者是作为一个更小的capability,也可能是指向一个具有特殊类型的新对象。一个新的capability如果是使用untyped 调用创建的,那他会被引用为原来这个capability的子capability。 Untyped capability是采用一种贪婪地方式从被调用的untyped递增的。为了能在seL4系统中高效的利用系统内存,理解这个递增的方式很重要。每一个untyped capability都维护着一个单一的watermark,watermark之前的就是已经被retyped的部分,不可用了,之后的就是可用的,还没被retyped。在一次retyped的操作中,watermark首先被移动到与被创建的对象对齐的对方。然后再移动到与这个对象大小一致的地方。举个例子,如果我们先创建一个4KiB的对象再创建一个16KiB的对象。这样,在创建完4KiB的对象之后,创建16KiB的对象,这个watermark要再移动12KiB到与16KiB对齐的地方,然后再移动16KiB完成第二个对象的创建。这中间的12KiB就完全浪费了。在这两个对象被撤销之前,中间的12KiB无法被回收。(不知道为啥非要这样对齐) 简而言之:对象要按照大小顺序创建,先创建比较大的对象,再创建比较小的对象。以此防止内存被浪费。

    error = seL4_Untyped_Retype(parent_untyped, // the untyped capability to retype
                                seL4_UntypedObject, // type
                                untyped_size_bits,  //size
                                seL4_CapInitThreadCNode, // root
                                0, // node_index
                                0, // node_depth
                                child_untyped, // node_offset
                                1 // num_caps
                                );

上面的程序片段展示了一个retyped的例子。把一个untyped变成更小的,4KiB大小的untyped capability。(尽管创建的类型名字叫seL4_UntypedObject,但是实际上没有object被创建,新的untyped capability只是简单的指向了父级untyped的一个子集。不过,把这块内存当成一个object来处理,在这个操作中非常的便于理解)。下面我们会使用这段代码来讨论retyped调用的每个参数。可以先回顾一下,调用的第一个参数,是用来完成retyped的untyped capability。

Types

seL4中每个object都有一个特定的类型。并且所有的类型都可以在libsel4中找到。有一些类型是与芯片的架构相关的,其他的类型则是所有架构通用的。在上面的例子中,我们创建了一个新的untyped capability,他可以被进一步的retyped。所有其他的object类型参数创建的就是内核的对象或者是指向他们的capability,而且,创建的对象(或者是capability)的类型决定了可以在这些capability上面使用什么样的调用。例如,我们retype成一个线程控制块(也就是TCB--seL4_TCBObject),那我们就可以在这个TCB object的capability上面使用TCB的调用了,包括seL4_TCB_SetPriority

size

size这个参数决定了新的object的大小。这个参数的具体含义取决于被对象的类型:

  • 大多数seL4的object大小是固定的,对于这些object,kernel会忽略这个size参数
  • 对于seL4_UntypedObjectseL4_SchedContextObject这样的object是大小可以变化的,这个需要在size_bits中指定(下面会详细讲解)
  • 对于seL4_CapTableObject类型,也是一个可变大小的object,这个sixe参数指定的是capability slot的数量。使用的是和size_bits相同的机制,但是数值指的是slot的数量而不是字节的大小。

通常在seL4中,如果大小是以位来衡量的,那这个大小指的是2的幂。啥意思呢?就是说这里的“Bits”不是指这个object占用的bits,而是指的这个object用于描述自己内容所需要的位宽。在另一个场景我们比较熟悉这个概念,比如,一个8位的寄存器。我们指的就不是8个bits,而是8位的位宽。(感觉这个解释也不是特别的贴切)也就是说,一个对象的bit大小是n,那他衡量了2的n次方bytes的大小。而且,在seL4中,一般情况下object和memory的区域都是对齐他们的大小的。例如,一个n bit的object对齐到2的n次方bytes。同样的,object的地址有0作为他们的低位。 对于retype,我们倒是不需要关心太多,就记住参数里的size_bits代表这个object会占用2的size_bits次方的字节就行了。对于seL4_CapTableObject则是需要2的size_bits次方的slot。(我们可以认为它的大小是2的size_bits + seL4_SlotBits次方字节)

Root,node_index & node_depth

这三个参数用于指定把新生成的capability放到哪个CNode。 根据depth参数来直接寻址或者调用找到CSlot(在前面的capability我们讲过这些)。 在上面的例子中,这个node_depth我们设置成了0,这意味着我们要用的是调用的方式:root参数隐式的在当前线程的CSpace root中以seL4_WordBits深度查找。所以这个例子指定的是root task的CNode(seL4_CapInitThreadCNode)。在这个情况下,node_index参数显然是被忽略的。 如果node_depth没有设置成0,说明要采用的是直接寻址的方式。这时候node_index就是用来定位我们要把新的capability放到的CNode capability的位置的。这个node_depth是设计给多级的CSpace用的,在这个教程里没有涵盖到这方面的东西。

Node_offset

node_offset是CSlot在前面上述参数选定的CNode中开始创建新的capability的位置。在这个例子中,就是选择初始CNode中的第一个空的CSlot。

Num_caps

retype调用可以用于一次性创建不止一个capability或者object,这个num_caps参数就是用于指定数量的。需要注意的是这个参数有两个限制:

  • untyped的大小必须足够创建(num_caps * (1u << size_bits)).
  • CNode必须有足够多的连续的CSlot给新创建的capability

这一节到这里就完了,下面我们会讲下untype的练习。