初始化程序
./init --tut capabilities
cd capability_build
ninja
./simulate
依次执行后生成的是capability的文件夹,并进行了编译和运行。下面的教程中会逐步完善这个程序。
首次运行之后是这样的:
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