2.2 seL4-Capabilities 练习

307 阅读8分钟

初始化程序

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

依次执行后生成的是capability的文件夹,并进行了编译和运行。下面的教程中会逐步完善这个程序。

首次运行之后是这样的:

image.png

ctrl+a然后x退出。

练习

教程一开始,提供了一个Bootinfo的结构体,并且计算了初始CNode的大小(以slot为单位,就是CNode有多少个slot的大小)。

int main(int argc, char *argv[]) {

    /* parse the location of the seL4_BootInfo data structure from
    the environment variables set up by the default crt0.S */
    seL4_BootInfo *info = platsupport_get_bootinfo();

    size_t initial_cnode_object_size = BIT(info->initThreadCNodeSizeBits);
    printf("Initial CNode is %zu slots in size\n", initial_cnode_object_size);

我们直接运行教程时,看到的输出就是下面这样的:

Booting all finished, dropped to user space
Initial CNode is 65536 slots in size
The CNode is 0 bytes in size
<<seL4(CPU 0) [decodeInvocation/645 T0xffffff801fe08400 "rootserver" @4013c2]: Attempted to invoke a null cap #65535.>>
main@main.c:31 [Cond failed: error]
        Failed to set priority

做完这个练习之后,所有的程序日志输出都会是有意义的。现在的输出还是夹杂一些错误的。 第一行输出是kernel打印的。 第二行输出是上面那段程序中的printf打印的。告诉我们初始的CNode的大小是65535个slot。 第三行打印的是CSpace中slot的数量,这里的数量是错误的,我们的第一个任务就是修复这个错误。

那么问题来了:CSpace 有多大?

练习:参考2.1介绍的内容,计算初始线程CSpace占用的空间大小。

    size_t initial_cnode_object_size_bytes = 0; // TODO calculate this.
    printf("The CNode is %zu bytes in size\n", initial_cnode_object_size_bytes);

我们要修改的就是这里的程序。 我们上面获取到的,是他这个初始的CNode有多大(这里的CSpace只有这么一个初始的CNode),然后我们知道了这个CNode有65535个slot,那每个slot又有多大呢? 他这个计算方式就很神奇了,他为了兼容各个平台,定义的大小是需要移位来计算实际对应的byte的。 具体的程序如下:

    size_t initial_cnode_object_size_bytes = initial_cnode_object_size * (1 << seL4_SlotBits);
    printf("The CNode is %zu bytes in size\n", initial_cnode_object_size_bytes);

重新编译执行后,输出如下:

Booting all finished, dropped to user space
Initial CNode is 65536 slots in size
The CNode is 2097152 bytes in size
<<seL4(CPU 0) [decodeInvocation/645 T0xffffff801fe08400 "rootserver" @4013c2]: Attempted to 
invoke a null cap #65535.>>
main@main.c:31 [Cond failed: error]
        Failed to set priority

在两个CSlot之间复制capability

在输出CNode的大小之后,这里打印了一个错误:

<<seL4(CPU 0) [decodeInvocation/645 T0xffffff801fe08400 "rootserver" @4013c2]: Attempted to invoke a null cap #65535.>>
main@main.c:31 [Cond failed: error]
        Failed to set priority

程序里是这样的:

    seL4_CPtr first_free_slot = info->empty.start;
    seL4_Error error = seL4_CNode_Copy(seL4_CapInitThreadCNode, first_free_slot, seL4_WordBits,
                                       seL4_CapInitThreadCNode, seL4_CapInitThreadTCB,                                                seL4_WordBits,
                                       seL4_AllRights);
    ZF_LOGF_IF(error, "Failed to copy cap!");
    seL4_CPtr last_slot = info->empty.end - 1;
    /* TODO use seL4_CNode_Copy to make another copy of the initial TCB capability to the last slot in the CSpace */

    /* set the priority of the root task */
    error = seL4_TCB_SetPriority(last_slot, last_slot, 10);
    ZF_LOGF_IF(error, "Failed to set priority");

看程序里,它给first slot复制了一个seL4_CapInitThreadTCB,但是后面的last_slot并没有做任何的处理。 所以对着前面的first slot去做就行了。

int main(int argc, char *argv[]) {

    /* parse the location of the seL4_BootInfo data structure from
    the environment variables set up by the default crt0.S */
    seL4_BootInfo *info = platsupport_get_bootinfo();

    size_t initial_cnode_object_size = BIT(info->initThreadCNodeSizeBits);
    printf("Initial CNode is %zu slots in size\n", initial_cnode_object_size);


    size_t initial_cnode_object_size_bytes = initial_cnode_object_size * (1 << seL4_SlotBits);
    printf("The CNode is %zu bytes in size\n", initial_cnode_object_size_bytes);

    seL4_CPtr first_free_slot = info->empty.start;
    seL4_Error error = seL4_CNode_Copy(seL4_CapInitThreadCNode, first_free_slot, seL4_WordBits,
                                       seL4_CapInitThreadCNode, seL4_CapInitThreadTCB, seL4_WordBits,
                                       seL4_AllRights);
    ZF_LOGF_IF(error, "Failed to copy cap!");
    seL4_CPtr last_slot = info->empty.end - 1;
    /* TODO use seL4_CNode_Copy to make another copy of the initial TCB capability to the last slot in the CSpace */

    error = seL4_CNode_Copy(seL4_CapInitThreadCNode, last_slot, seL4_WordBits,
                            seL4_CapInitThreadCNode, seL4_CapInitThreadTCB, seL4_WordBits,
                            seL4_AllRights);
    ZF_LOGF_IF(error, "Failed to copy cap!");

运行后log如下:

Booting all finished, dropped to user space
Initial CNode is 65536 slots in size
The CNode is 2097152 bytes in size
<<seL4(CPU 0) [decodeCNodeInvocation/95 T0xffffff801fe08400 "rootserver" @4013c2]: CNode Copy/Mint/Move/Mutate: Destination not empty.>>
main@main.c:43 [Cond failed: error != seL4_FailedLookup]
        first_free_slot is not empty

如何删除capability

教程的代码,会检查first slot和last slot是否为空,我们刚才给他俩复制了capability,所以当然不是空的。

检查slot是否为空使用的是一个简单的hack方式:尝试把slot移动到他自己的位置上。这样如果slot为空就会报错误seL4_FailedLookup,不为空就会报错误seL4_DeleteFirst。 练习:删除两个复制来的TCB capability

  • 我们可以使用两次seL4_CNode_Delete删除复制的capability
  • 或者在源capability上使用seL4_CNode_Revoke来实现对副本的删除

原来的程序是这样的:

 // TODO delete the created TCB capabilities

    // check first_free_slot is empty
    error = seL4_CNode_Move(seL4_CapInitThreadCNode, first_free_slot, seL4_WordBits,
                            seL4_CapInitThreadCNode, first_free_slot, seL4_WordBits);
    ZF_LOGF_IF(error != seL4_FailedLookup, "first_free_slot is not empty");

    // check last_slot is empty
    error = seL4_CNode_Move(seL4_CapInitThreadCNode, last_slot, seL4_WordBits,
                            seL4_CapInitThreadCNode, last_slot, seL4_WordBits);
    ZF_LOGF_IF(error != seL4_FailedLookup, "last_slot is not empty");

然后我们删除一下这两个capability:

 // TODO delete the created TCB capabilities
    seL4_CNode_Delete(seL4_CapInitThreadCNode, first_free_slot, seL4_WordBits);
    seL4_CNode_Delete(seL4_CapInitThreadCNode, last_slot, seL4_WordBits);

另一种方式就是这样:

    seL4_CNode_Revoke(seL4_CapInitThreadCNode, seL4_CapInitThreadTCB, seL4_WordBits);

得到的输出如下:

Booting all finished, dropped to user space
Initial CNode is 65536 slots in size
The CNode is 2097152 bytes in size
<<seL4(CPU 0) [decodeCNodeInvocation/107 T0xffffff801fe08400 "rootserver" @4013c2]: CNode Copy/Mint/Move/Mutate: Source slot i>
<<seL4(CPU 0) [decodeCNodeInvocation/107 T0xffffff801fe08400 "rootserver" @4013c2]: CNode Copy/Mint/Move/Mutate: Source slot i>
Suspending current thread
main@main.c:56 Failed to suspend current thread

为啥出现了这个错误呢?下面继续介绍

调用capability

练习:使用seL4_TCB_Suspend尝试挂起当前的线程. 原程序如下:

    printf("Suspending current thread\n");
    // TODO suspend the current thread
    ZF_LOGF("Failed to suspend current thread\n");

修改后:

    printf("Suspending current thread\n");
    // TODO suspend the current thread
    seL4_TCB_Suspend(seL4_CapInitThreadTCB);

    ZF_LOGF("Failed to suspend current thread\n");

然后程序的输出如下:

Booting all finished, dropped to user space
Initial CNode is 65536 slots in size
The CNode is 2097152 bytes in size
<<seL4(CPU 0) [decodeCNodeInvocation/107 T0xffffff801fe08400 "rootserver" @4013c2]: CNode Copy/Mint/Move/Mutate: Source slot i>
<<seL4(CPU 0) [decodeCNodeInvocation/107 T0xffffff801fe08400 "rootserver" @4013c2]: CNode Copy/Mint/Move/Mutate: Source slot i>
Suspending current thread

到这里你发现什么? 我们想挂起这个线程的时候,需要有对应的令牌才行,需要有这么一个权限,而这个权限由capability提供,也就是seL4_CapInitThreadTCB这个东西。所以seL4_TCB_Suspend()这个函数才能真正起到效果。换成其他的东西是不能把线程正常挂起的。而且,这个函数是调用的方式执行的,就不需要指定前面函数那些CNode(seL4_CapInitThreadCNode)和深度(seL4_WordBits)信息了。

更多练习

到这里关于capability的练习就没了。下面还有一些练习可以帮助我们更深入的了解CSpace。

  • 使用一个数据结构来跟踪CSpace中哪个CSlot是空闲的
  • 复制seL4_BootInfo描述的整个CSpace
  • 使用其他CNode调用:CNode invocations.

这个练习的内容就没有答案可以参考了,我就凭我的理解来写了。

用一个数据结构来跟踪哪个slot是空的

在程序中我们就能看到seL4_BootInfo里面就有一个结构体来存储初始的slot的状态,我们搞一个一模一样的结构图,把它复制下来。

typedef struct seL4_BootInfo {
    seL4_Word         extraLen;        /* length of any additional bootinfo information */
    seL4_NodeId       nodeID;          /* ID [0..numNodes-1] of the seL4 node (0 if uniprocessor) */
    seL4_Word         numNodes;        /* number of seL4 nodes (1 if uniprocessor) */
    seL4_Word         numIOPTLevels;   /* number of IOMMU PT levels (0 if no IOMMU support) */
    seL4_IPCBuffer   *ipcBuffer;       /* pointer to initial thread's IPC buffer */
    seL4_SlotRegion   empty;           /* empty slots (null caps) */
    seL4_SlotRegion   sharedFrames;    /* shared-frame caps (shared between seL4 nodes) */
    seL4_SlotRegion   userImageFrames; /* userland-image frame caps */
    seL4_SlotRegion   userImagePaging; /* userland-image paging structure caps */
    seL4_SlotRegion   ioSpaceCaps;     /* IOSpace caps for ARM SMMU */
    seL4_SlotRegion   extraBIPages;    /* caps for any pages used to back the additional bootinfo information */
    seL4_Word         initThreadCNodeSizeBits; /* initial thread's root CNode size (2^n slots) */
    seL4_Domain       initThreadDomain; /* Initial thread's domain ID */
#ifdef CONFIG_KERNEL_MCS
    seL4_SlotRegion   schedcontrol; /* Caps to sched_control for each node */
#endif
    seL4_SlotRegion   untyped;         /* untyped-object caps (untyped caps) */
    seL4_UntypedDesc  untypedList[CONFIG_MAX_NUM_BOOTINFO_UNTYPED_CAPS]; /* information about each untyped */
    /* the untypedList should be the last entry in this struct, in order
     * to make this struct easier to represent in other languages */
} seL4_BootInfo;

也就是复制empty这个结构体:

typedef struct seL4_SlotRegion {
    seL4_SlotPos start; /* first CNode slot position OF region */
    seL4_SlotPos end;   /* first CNode slot position AFTER region */
} seL4_SlotRegion;

但是这个结构体只存储了头和尾,不够用。 所以我们可以设计这样一个结构体:

typedef struct seL4_FreeSlot {
    seL4_CPtr slots[65535];
    seL4_Uint8 status[65535];
} seL4_FreeSlot;

上面用来记录slot,下面用来记录状态。

这样感觉有点浪费空间,如果是顺序使用的,就用上面的数组就可以。 但是实际上slot对应的就是一个数字,我觉得我们仅使用一个数组就可以记录所有slot的状态了。

seL4_Uint8 status[65535];

复制seL4_BootInfo描述的整个CSpace

分别打印empty slot中的fist和last,发现他们是介于406和65535之间的,说明前面的0到405就是整个CSpace了。 我们可以使用seL4_CNode_Copy按顺序复制整个空间。

其他调用

这个就不多说了,就是一堆函数,慢慢看吧。我也写到晚上十二点了还没洗澡,好困啊。明天又是给资本家打工的新的一天了。

教程答案

这些教程都是自带答案的,生成答案的方法是:

./init --tut capabilities --solution