hello3-sel4多线程

上一节

seL4 Tutorial 3

第三个教程旨在教授使用端点对象和用户空间分页管理的 seL4 IPC 的基础知识。 您将被引导完成创建新线程的过程(将无类型对象重新键入到 TCB 对象中),并且还需要您手动进行自己的内存管理以分配一些虚拟内存以用作两个线程之间的共享内存缓冲区 .

不要掩盖在 main() 之前声明的全局变量——它们是为了您的利益而声明的,因此您可以掌握一些基本的数据结构。 在执行任务时,根据需要一一取消注释。

您会发现您在第二个教程中已经涵盖的内容已经填写完毕,您不必重复它们:以几乎相同的方式,我们不会在此页面上重复概念解释,如果它们是 本系列之前的教程已涵盖。

Learning outcomes

  • 重复线程的产生。 新线程将再次共享其创建者的 VSpace 和 CSpace。
  • 介绍标记badge的概念,以及创建端点能力的badge副本。 注意:您不会创建 Endpoint 对象的新副本,而是创建引用它的能力的副本。
  • Basic IPC: 发送和接收:如何让两个线程通信。
  • IPC 消息格式和消息寄存器。 seL4将一些开始的Message Registers绑定到硬件寄存器,其余的在IPC buffer中传输。
  • 明白每个线程只有一个IPC缓冲区,由它的TCB指向。 可以使用“绑定通知”让线程在端点和通知上等待。
  • 了解 CSpace 指针,它们实际上只是将多个索引连接成一个的整数。 然而,很好地理解它们对于理解能力如何运作很重要。 确保您理解“CSpace 示例和寻址”幻灯片上的图表。

Exercises

When you first run this tutorial, you will see a fault as follows:

/*--filter TaskCompletion("task-1", TaskContentType.ALL)--*/
Booting all finished, dropped to user space
Caught cap fault in send phase at address (nil)
while trying to handle:
/*-- endfilter -*/
vm fault on data at address 0x70003c8 with status 0x6
in thread 0xffffff801ffb5400 "rootserver" at address 0x402977
With stack:
0x43df70: 0x0
0x43df78: 0x0

TASK 1

正如我们之前提到的,seL4 中的线程进行自己的内存管理。 本质上,您实现了自己的虚拟内存管理器。 在某种程度上,您必须分配一个物理内存帧,然后将其映射到线程的页目录中——甚至在必要时您自己手动分配页表。

这是传统内存管理器分配内存过程的第一步:首先分配一个物理帧。 如您所料,您不能直接写入或读取此物理帧对象,因为它尚未映射到任何虚拟地址空间。适用于使用MMU的内核的标准限制。

/*-- set task_1_desc --*/
    /* TASK 1: get a frame cap for the ipc buffer */
    /* hint: vka_alloc_frame()
     * int vka_alloc_frame(vka_t *vka, uint32_t size_bits, vka_object_t *result)
     * @param vka                       指向vka接口的指针。
     * @param size_bits    		 帧大小: 2^size_bits
     * @param result    				 帧对象的结构。这是初始化。
     * @return 0 on success
     */
    vka_object_t ipc_frame_object;
/*-- endset --*/


/*? task_1_desc ?*/
/*-- filter ExcludeDocs() -*/
/*-- filter TaskContent("task-1", TaskContentType.COMPLETED) -*/
    error = vka_alloc_frame(&vka, IPCBUF_FRAME_SIZE_BITS, &ipc_frame_object);
    ZF_LOGF_IFERR(error, "Failed to alloc a frame for the IPC buffer.\n"
                  "\tThe frame size is not the number of bytes, but an exponent.\n"
                  "\tNB: This frame is not an immediately usable, virtually mapped page.\n")
/*-- endfilter -*/


/*--filter TaskCompletion("task-1", TaskContentType.ALL)--*/
Booting all finished, dropped to user space
/*-- endfilter -*/

在完成后,输出将不会改变。

TASK 2

请注意这之前的代码行:随机选择使用虚拟地址的那一行。 这是因为,正如我们之前解释的那样,进程负责自己的虚拟内存管理。 因此,如果它愿意,它可以将其 VSpace 中的任何页面映射到物理页。 它可以在技术上选择做非常规的事情,比如不取消映射 PFN #0。 地址空间管理方式的控制取决于对该地址空间具有写入能力的线程。 这里隐含着灵活性和责任感。 当然,seL4 本身提供了强大的隔离保证,即使一个线程决定去作恶。

尝试将您之前分配的帧映射到您的 VSpace。 敏锐的读者会意识到这不太可能起作用,因为您需要一个新的页表来包含新的页表条目。 本教程特意引导您完成将帧映射到 VSpace 以及将新页表映射到 VSpace 的过程。

/*-- set task_2_desc --*/
    /* TASK 2: 第一次尝试物理页面框架  */
    /* hint 1: seL4_ARCH_Page_Map()
    *ARCH* 版本的 seL4 系统调用是对 libsel4utils 提供的架构的抽象
      * 这个定义为:
     * #define seL4_ARCH_Page_Map seL4_X86_Page_Map
     *底层函数的签名是:
     * int seL4_X86_Page_Map(seL4_X86_Page service, seL4_X86_PageDirectory pd, seL4_Word vaddr, seL4_CapRights rights, seL4_X86_VMAttributes attr)
     * @param service 							具有对页面进行映射的`能力`。
     * @param pd 									对即将包含映射的`VSpace的能力`。
     * @param vaddr 							要将页面映射到的`虚拟地址`
     * @param rights Rights for the mapping. 			 映射权限。
     * @param attr VM Attributes for the mapping.  映射的 VM 属性。
     * @return 0 on success.
     *
     * 注意:此函数是在构建期间生成的。 它由以下定义生成:
     *
     * hint 2: for the rights, use seL4_AllRights			对于权限,请使用 seL4_AllRights
     * hint 3: for VM attributes use seL4_ARCH_Default_VMAttributes		
     				对于 VM 属性,使用 seL4_ARCH_Default_VMAttributes
     * Hint 4: 
     			    这个函数调用失败是正常的。 这意味着没有带空闲槽的页表——
     			    继续下一步,您将被引导分配一个新的空页表并将其映射到 VSpace,然后再试一次。
     */
/*-- endset --*/


/*? task_2_desc ?*/
/*-- filter ExcludeDocs() -*/
/*-- filter TaskContent("task-2", TaskContentType.COMPLETED) -*/
    error = seL4_ARCH_Page_Map(ipc_frame_object.cptr, pd_cap, ipc_buffer_vaddr,
                               seL4_AllRights, seL4_ARCH_Default_VMAttributes);
/*-- endfilter -*/

完成后,输出将如下所示:

hello-3: main@main.c:260 [Err seL4_FailedLookup]:
/*--filter TaskCompletion("task-2", TaskContentType.COMPLETED)--*/
	Failed to allocate new page table.
/*-- endfilter -*/

TASK 3

因此,正如您以前必须手动重新键入一个新帧以用于您的 IPC 缓冲区一样,您还必须手动重新键入一个新页表对象以用作 VSpace 中的叶页表。

/*-- set task_3_desc --*/
        /* TASK 3:  创建页表*/
        /* hint: vka_alloc_page_table()
         * int vka_alloc_page_table(vka_t *vka, vka_object_t *result)
         * @param vka 						指向 vka 接口的指针。
         * @param result					PageTable 对象的结构。 这被初始化。
         * @return 0 on success
         */
/*-- endset -*/


/*? task_3_desc ?*/
        vka_object_t pt_object;
        error =  vka_alloc_page_table(&vka, &pt_object);
/*-- endfilter -*/


/*--filter TaskCompletion("task-3", TaskContentType.ALL)--*/
Booting all finished, dropped to user space
/*-- endfilter -*/

完成后,您将看到另一个故障。

TASK 4

如果成功地从未类型入的内存对象重新键入新页面表,现在可以将该新页面表映射到VSpace,然后再尝试最终将ipc缓冲区的帧对象映射到VSpace。

/*-- set task_4_desc -*/
        /* TASK 4: map the page table */
        /* hint 1: seL4_ARCH_PageTable_Map()
          *ARCH* 版本的 seL4 系统调用是对 libsel4utils 提供的架构的抽象
          * 这个定义为:
         * #define seL4_ARCH_PageTable_Map seL4_X86_PageTable_Map
         * 底层函数的签名是:
         * int seL4_X86_PageTable_Map(seL4_X86_PageTable service, seL4_X86_PageDirectory pd, seL4_Word vaddr, seL4_X86_VMAttributes attr)
     * @param service 							具有对页面进行映射的`能力`。
     * @param pd 									对即将包含映射的`VSpace的能力`。
     * @param vaddr 							要将页面映射到的`虚拟地址`
     * @param rights Rights for the mapping. 			 映射权限。
     * @param attr VM Attributes for the mapping.  映射的 VM 属性。
     * @return 0 on success.
         *
         *注意:此函数是在构建期间生成的。 它是从以下定义生成的:
         *
         * hint 2: 对于 VM 属性,使用 seL4_ARCH_Default_VMAttributes
         */
/*-- endset -*/


/*? task_4_desc ?*/
/*-- filter ExcludeDocs() -*/
        error = seL4_ARCH_PageTable_Map(pt_object.cptr, pd_cap,
                                        ipc_buffer_vaddr, seL4_ARCH_Default_VMAttributes);
/*-- endfilter -*/


/*--filter TaskCompletion("task-4", TaskContentType.ALL)--*/
Booting all finished, dropped to user space
/*-- endfilter -*/

On completion, you will see another fault.

TASK 5

使用 seL4_ARCH_Page_Map 将物理页映射进来。如果一切都正确完成,则此步骤没有理由失败。 完成它,然后继续。

/*-- set task_5_desc --*/
        /* TASK 5: then map the frame in */
        /* hint 1: use seL4_ARCH_Page_Map() as above
         * hint 2: for the rights, use seL4_AllRights
         * hint 3: for VM attributes use seL4_ARCH_Default_VMAttributes
         				  对于 VM 属性,使用 seL4_ARCH_Default_VMAttributes
         */
/*-- endset -*/


/*? task_5_desc ?*/
/*-- filter ExcludeDocs() -*/
        error = seL4_ARCH_Page_Map(ipc_frame_object.cptr, pd_cap,
                                   ipc_buffer_vaddr, seL4_AllRights, seL4_ARCH_Default_VMAttributes);
/*-- endfilter -*/

On completion, you will see the following:

/*--filter TaskCompletion("task-5", TaskContentType.COMPLETED)--*/
main: hello world
/*-- endfilter -*/
hello-3: main@main.c:464 [Cond failed: seL4_MessageInfo_get_length(tag) != 1]
	Response data from thread_2 was not the length expected.  
	How many registers did you set with seL4_SetMR within thread_2?
	//来自 thread_2 的响应数据不是预期的长度。
	//你在 thread_2 中用 seL4_SetMR 设置了多少个寄存器?

TASK 6

现在我们有一个(完全映射的)IPC 缓冲区——但没有 Endpoint 对象来发送我们的 IPC 数据。 我们必须将一个未类型化的对象重新键入到内核端点对象中,然后再继续。 这可以通过使用 seL4_Untyped_Retype() 的更底层的方法来完成,但是本教程使用 VKA 分配器。 还记得 VKA 分配器是一个 seL4 类型感知对象分配器吗? 所以我们可以简单地向它请求一个特定类型的新对象,它会为我们做低级的重新输入,并根据请求返回一个新对象的能力。

在这种情况下,我们需要一个新的 Endpoint 以便我们进行 IPC。 完成步骤并继续。

/*-- set task_6_desc -*/
    /* TASK 6: create an endpoint */
    /* hint: vka_alloc_endpoint()
     * int vka_alloc_endpoint(vka_t *vka, vka_object_t *result)
     * @param vka  						指向 vka 接口的指针。
     * @param result 					Endpoint 对象的结构。 这被初始化。
     * @return 0 on success
     */
/*-- endset -*/


/*? task_6_desc ?*/
/*-- filter TaskContent("task-6", TaskContentType.COMPLETED, completion="main: hello world") -*/
    error = vka_alloc_endpoint(&vka, &ep_object);
/*-- endfilter -*/

On completion, the output will not change.

TASK 7

Badges用于将端点上排队的消息唯一标识为来自特定发件人。 回想一下,在 seL4 中,每个线程只有一个 IPC 缓冲区。 如果多个其他线程正在向一个监听线程发送数据,那么该监听线程如何区分其每个 IPC 伙伴发送的数据? 每个发送方必须将其能力“标记”到其目标端点。

Note the distinction: the badge is not applied to the target endpoint, but to the sender’s capability to the target endpoint. This enables the listening thread to mint off copies of a capability to an Endpoint to multiple senders. Each sender is responsible for applying a unique badge value to the capability that the listener gave it so that the listener can identify it.

注意区别:badge不应用于目标端点,而是应用于发送者对目标端点的能力。 这使侦听线程能够将一个功能的副本发送给多个发送者的端点。 每个发送者负责将唯一的标记值应用到监听器赋予它的能力,以便监听器可以识别它。

In this step, you are badging the endpoint that you will use when sending data to the thread you will be creating later on. The vka_mint_object() call will return a new, badged copy of the capability to the endpoint that your new thread will listen on. When you send data to your new thread, it will receive the badge value with the data, and know which sender you are. Complete the step and proceed.

在此步骤中,您将标记在将数据发送到您稍后将创建的线程时将使用的端点。 vka_mint_object() 调用将向您的新线程将监听的端点返回一个新的、带标记的功能副本。 当你向你的新线程发送数据时,它会收到带有数据的标记值,并知道你是哪个发送者。 完成步骤并继续。

/*-- set task_7_desc -*/
    /* TASK 7:  在您的cspace制作badged副本。 此副本将用于向原始 cap 发送 IPC 消息 */
    /* hint 1: vka_mint_object()
     * int vka_mint_object(vka_t *vka, vka_object_t *object, cspacepath_t *result, seL4_CapRights rights, seL4_CapData_t badge)
     * @param[in] vka The allocator for the cspace.		cspace 的分配器。
     * @param[in] object 															Target object for cap minting(刚完成的).
     * @param[out] result Allocated cspacepath.				分配的 cspacepath。
     * @param[in] rights 																The rights for the minted cap.
     * @param[in] badge 															The badge for the minted cap.
     * @return 0 on success
     *
     * hint 2: for the rights, use seL4_AllRights
     * hint 3: for the badge use seL4_CapData_Badge_new()
     * seL4_CapData_t CONST seL4_CapData_Badge_new(seL4_Uint32 Badge)
     * @param[in] Badge																The badge number to use
     * @return 																			     	一个 CapData 结构包含所需的badge信息
     * 
     * seL4_CapData_t is generated during build.
     * 类型定义和生成的	字段访问函数  在生成的文件中定义:
     * build/x86/pc99/libsel4/include/sel4/types_gen.h
     *
     * hint 4: for the badge use EP_BADGE
     */
/*-- endset -*/


/*? task_7_desc ?*/
/*-- filter TaskContent("task-7", TaskContentType.COMPLETED, completion="main: hello world") -*/
    error = vka_mint_object(&vka, &ep_object, &ep_cap_path, seL4_AllRights,
                            EP_BADGE);
/*-- endfilter -*/

On completion, the output will not change.

TASK 8

这里我们正式介绍消息寄存器。 乍一看,您可能想知道为什么 sel4_SetMR() 调用不指定消息缓冲区,并且似乎知道要填充哪个缓冲区——这是正确的,因为它们确实指定了。 它们直接对发送线程的 IPC 缓冲区进行操作。 回想一下,每个线程只有一个 IPC 缓冲区。 返回并再次查看您在第 7 步中对 seL4_TCB_Configure() 的调用:您在此函数的最后 2 个参数中为新线程设置了 IPC 缓冲区。 同样,创建您的主线程的线程也为您设置了一个 IPC 缓冲区。

因此,seL4_SetMR()seL4_GetMR() 只是简单地写入您为线程指定的 IPC 缓冲区并从中读取。 MSG_DATA 是无趣的——可以是任何值。 您会在手册中找到解释的 seL4_MessageInfo_t 类型。 简而言之,它是嵌入在每条消息中的标头,其中指定包含有意义数据的消息寄存器的数量,以及将在消息中传输的能力的数量。

/*-- set task_8_desc -*/
    /* TASK 8:  设置要发送的数据。 我们在第一个消息寄存器中发送它 */
    /* hint 1: seL4_MessageInfo_new()
     * seL4_MessageInfo_t CONST seL4_MessageInfo_new(seL4_Uint32 label, seL4_Uint32 capsUnwrapped, seL4_Uint32 extraCaps, seL4_Uint32 length)
     * @param label 													The value of the label field			 标签字段的值
     * @param capsUnwrapped 							The value of the capsUnwrapped field
     * @param extraCaps											The value of the extraCaps field
     * @param length 												The number of message registers to send
     * @return The seL4_MessageInfo_t containing the given values. 
     * 				  包含给定值的 seL4_MessageInfo_t。
     *
     * seL4_MessageInfo_new() is generated during build. It can be found in:
     * build/x86/pc99/libsel4/include/sel4/types_gen.h
     *
     * hint 2: use 0 for the first 3 fields.
     * hint 3: send only 1 message register of data
     *
     * hint 4: seL4_SetMR()
     * void seL4_SetMR(int i, seL4_Word mr)
     * @param i 								要写入的消息寄存器
     * @param mr							消息寄存器的值
     *
     * hint 5: send MSG_DATA
     */
/*-- endset -*/


/*? task_8_desc ?*/
/*-- filter TaskContent("task-8", TaskContentType.COMPLETED) -*/
    tag = seL4_MessageInfo_new(0, 0, 0, 1);
    seL4_SetMR(0, MSG_DATA);
/*-- endfilter -*/

On completion, the output should change as follows:

hello-3: main@main.c:472 [Cond failed: msg != ~MSG_DATA]
	Response data from thread_2's content was not what was expected.

TASK 9

现在您已经构建了您的消息并标记了您将用于发送它的端点,现在是发送它的时候了。 seL4_Call() 系统调用将通过端点同步发送消息。如果目标端点的另一端没有线程等待,则发送方将阻塞,直到有等待者为止。 这样做的原因是因为 seL4 内核不希望在内核地址空间中缓冲 IPC数据,所以它只是让发送者休眠直到接收者准备好,然后直接复制数据。 它简化了 IPC 逻辑。 还有轮询发送操作,以及轮询接收操作,以防您不想在IPC 端点的另一端没有接收方时被迫阻塞。

当您使用 seL4_Call() 发送带标记的数据时,我们的接收线程(我们之前创建的)将获取数据、查看标记并知道是我们发送了数据。 请注意发送线程如何对端点对象使用 badged 能力,而接收线程如何对同一端点使用未修改的原始能力? 发件人必须表明自己的身份。

另请注意,发送方和接收方共享相同的根 CSpace,这使得接收线程可以随意使用原始的、未标记的功能,而无需任何额外的工作来使其可访问。

但是还请注意,虽然发送线程能够授予它跨端点发送数据的全部权利,因为它是创建该能力的人,但接收方的能力不一定会授予它对端点的发送能力(写入能力)。如果发送方不希望发送响应消息,那么接收方完全有可能无法发送响应消息。

/*-- set task_9_desc -*/
    /* TASK 9: send and wait for a reply. */
    /* hint: seL4_Call()
     * seL4_MessageInfo_t seL4_Call(seL4_CPtr dest, seL4_MessageInfo_t msgInfo)
     * @param dest 					The capability to be invoked.  要调用的能力。
     * @param msgInfo The messageinfo structure for the IPC.  This specifies information about the message to send (such as the number of message registers to send).
      				IPC 的消息信息结构。 这指定有关要发送的消息的信息(例如要发送的消息寄存器的数量)。
     * @return A seL4_MessageInfo_t structure.  This is information about the repy message.
         					seL4_MessageInfo_t 结构。 这是有关回复消息的信息。
     *
     * hint 2: seL4_MessageInfo_t is generated during build.
     * The type definition and generated field access functions are defined in a generated file:
     * build/x86/pc99/libsel4/include/sel4/types_gen.h
     */
/*-- endset -*/

/*? task_9_desc ?*/
/*-- filter TaskContent("task-9", TaskContentType.COMPLETED) -*/
    tag = seL4_Call(ep_cap_path.capPtr, tag);
/*-- endfilter -*/

On completion, you should see thread_2 fault as follows:

/*--filter TaskCompletion("task-9", TaskContentType.COMPLETED)--*/
thread_2: hallo wereld
thread_2: got a message 0 from 0
Caught cap fault in send phase at address (nil)
while trying to handle:
/*-- endfilter -*/
vm fault on data at address (nil) with status 0x4
in thread 0xffffff801ffb4400 "child of: 'rootserver'" at address (nil)
With stack:

TASK 10

While this task is out of order, since we haven’t yet examined the receive-side of the operation here, it’s fairly simple anyway: this task occurs after the receiver has sent a reply, and it shows the sender now reading the reply from the receiver. As mentioned before, the seL4_GetMR() calls are simply reading from the calling thread’s designated, single IPC buffer.

虽然这个任务是无序的,因为我们还没有检查操作的接收端,但它相当简单:这个任务发生在接收方发送回复之后,它显示发送方现在正在阅读接收方的回复。如前所述,seL4_GetMR()调用只是从调用线程指定的单个IPC缓冲区中读取。

/*-- set task_10_desc -*/
    /* TASK 10: get the reply message */
    /* hint: seL4_GetMR()
     * seL4_Word seL4_GetMR(int i)
     * @param i 				要检索的消息寄存器
     * @return 					The message register value
     */
/*-- endset -*/


/*?  task_10_desc ?*/
/*-- filter TaskContent("task-10", TaskContentType.COMPLETED, completion="thread_2: hallo wereld") -*/
    msg = seL4_GetMR(0);
/*-- endfilter -*/

On completion, the output should not change.

TASK 11

We’re now in the receiving thread. The seL4_Recv() syscall performs a blocking listen on an Endpoint or Notification capability. When new data is queued (or when the Notification is signalled), the seL4_Recv operation will unqueue the data and resume execution.

Notice how the seL4_Recv() operation explicitly makes allowance for reading the badge value on the incoming message? The receiver is explicitly interested in distinguishing the sender.

我们现在处于接收线程。 seL4_Recv() 系统调用对端点或通知功能执行阻塞侦听。 当新数据排队时(或发出通知时),“seL4_Recv”操作将使数据取消排队并恢复执行。

请注意 seL4_Recv() 操作如何明确允许读取传入消息上的标记值? 接收方明确对区分发送方感兴趣。

/*-- set task_11_desc -*/
    /* TASK 11:  等待消息通过端点传入 */
    /* hint 1: seL4_Recv()
     * seL4_MessageInfo_t seL4_Recv(seL4_CPtr src, seL4_Word* sender)
     * @param src 			The capability to be invoked.  要调用的能力。
     * @param sender The badge of the endpoint capability that was invoked by the sender is written to this address.  
		                           发件人调用的端点功能的标志被写入此地址。
     * @return   A seL4_MessageInfo_t structure
     *
     * hint 2: seL4_MessageInfo_t is generated during build.
     * The type definition and generated field access functions are defined in a generated file:
     * build/x86/pc99/libsel4/include/sel4/types_gen.h
     */
/*-- endset -*/


/*? task_11_desc ?*/
/*-- filter TaskContent("task-11", TaskContentType.COMPLETED) -*/
    tag = seL4_Recv(ep_object.cptr, &sender_badge);
/*-- endfilter -*/

On completion, the output should change slightly:

/*--filter TaskCompletion("task-11", TaskContentType.COMPLETED)--*/
thread_2: got a message 0 from 0x61

TASK 12

这里的这两个调用只是对传输消息保真度的验证。 您不太可能在这里遇到错误。 完成它们并继续下一步。

https://github.com/seL4/seL4/blob/master/libsel4/include/sel4/shared_types_32.bf

/*-- set task_12_desc -*/
    /* TASK 12: make sure it is what we expected */
    /* hint 1: check the badge. is it EP_BADGE?
     * hint 2: we are expecting only 1 message register
     * hint 3: seL4_MessageInfo_get_length()
     * seL4_Uint32 CONST seL4_MessageInfo_get_length(seL4_MessageInfo_t seL4_MessageInfo)
     * @param seL4_MessageInfo 	
     		the seL4_MessageInfo_t to extract a field from    从中提取一个字段的seL4_MessageInfo_t
     * @return	传递的消息寄存器数量
     * seL4_MessageInfo_get_length() is generated during build. It can be found in:
     * build/x86/pc99/libsel4/include/sel4/types_gen.h
/*-- endset -*/


/*? task_12_desc ?*/
/*-- filter TaskContent("task-12", TaskContentType.COMPLETED, completion="thread-2: hallo wereld") -*/
    ZF_LOGF_IF(sender_badge != EP_BADGE,
               "Badge on the endpoint was not what was expected.\n");
    ZF_LOGF_IF(seL4_MessageInfo_get_length(tag) != 1,
               "Length of the data send from root thread was not what was expected.\n"
               "\tHow many registers did you set with seL4_SetMR, within the root thread?\n");
/*-- endfilter -*/

On completion, the output should not change.

TASK 13

Again, just reading the data from the Message Registers.

https://github.com/seL4/seL4/blob/master/libsel4/arch_include/x86/sel4/arch/functions.h

/*-- set task_13_desc -*/
    /* TASK 13: 获取存储在第一个消息寄存器中的消息 */
    /* hint: seL4_GetMR()
     * seL4_Word seL4_GetMR(int i)
     * @param i 					要检索的消息寄存器
     * @return 						The message register value
/*-- endset -*/


/*? task_13_desc ?*/
/*-- filter TaskContent("task-13", TaskContentType.COMPLETED, completion="main: hello world") -*/
    msg = seL4_GetMR(0);
/*-- endfilter -*/

On completion, the output should change slightly:

/*--filter TaskCompletion("task-13", TaskContentType.COMPLETED)--*/
thread_2: got a message 0x6161 from 0x61

TASK 14

And writing Message Registers again.

https://github.com/seL4/seL4/blob/master/libsel4/arch_include/x86/sel4/arch/functions.h

/*-- set task_14_desc -*/
    /* TASK 14: 将修改后的信息复制回信息寄存器 */
    /* hint: seL4_SetMR()
     * void seL4_SetMR(int i, seL4_Word mr)
     * @param i 					要写入的消息寄存器
     * @param mr 				消息寄存器的值
/*-- endset -*/


/*? task_14_desc ?*/
/*-- filter TaskContent("task-14", TaskContentType.COMPLETED, completion="main: hello world") -*/
    seL4_SetMR(0, msg);
/*-- endfilter -*/

On completion, the output should not change.

TASK 15

This is a formal introduction to the Reply capability which is automatically generated by the seL4 kernel, whenever an IPC message is sent using the seL4_Call() syscall. This is unique to the seL4_Call() syscall, and if you send data instead with the seL4_Send() syscall, the seL4 kernel will not generate a Reply capability.

这是对Reply功能的正式介绍,该功能由seL4内核自动生成,只要使用seL4_Call()系统发送发送IPC消息。这是 seL4_Call() 系统调用所独有的,如果您使用seL4_Send()系统调用发送数据,那么seL4内核将不会生成回复功能。

The Reply capability solves the issue of a receiver getting a message from a sender, but not having a sufficiently permissive capability to respond to that sender. The “Reply” capability is a one-time capability to respond to a particular sender. If a sender doesn’t want to grant the target the ability to send to it repeatedly, but would like to allow the receiver to respond to a specific message once, it can use seL4_Call(), and the seL4 kernel will facilitate this one-time permissive response. Complete the step and pat yourself on the back.

Reply 功能解决了接收者从发送者那里获取消息但没有足够的权限来响应该发送者的问题。 “回复”能力是一种一次性的能力,可以对特定的发件人做出回应。如果发送方不想授予目标重复发送的能力,但希望允许接收方对特定消息进行一次响应,它可以使用seL4_Call(),seL4内核将促进这个一次性允许响应。

/*-- set task_15_desc -*/
    /* TASK 15: send the message back */
    /* hint 1: seL4_ReplyRecv()
     * seL4_MessageInfo_t seL4_ReplyRecv(seL4_CPtr dest, seL4_MessageInfo_t msgInfo, seL4_Word *sender)
     * @param dest 				 The capability to be invoked.
     * @param msgInfo 	 	 The messageinfo structure for the IPC.  This specifies information about the message to send (such as the number of message registers to send) as the Reply part.
     			IPC 的消息信息结构。 这指定有关要发送的消息的信息(例如要发送的消息寄存器)作为回复部分。
     * @param sender 			The badge of the endpoint capability that was invoked by the sender is written to this address.  This is a result of the Wait part.
               发件人调用的端点功能的标志被写入此地址。 这是 Wait 部分的结果。
     * @return A seL4_MessageInfo_t structure.  This is a result of the Wait part.
     	    	seL4_MessageInfo_t 结构。 这是 Wait 部分的结果。
     *
     * hint 2: seL4_MessageInfo_t is generated during build.
     * The type definition and generated field access functions are defined in a generated file:
     * build/x86/pc99/libsel4/include/sel4/types_gen.h
/*-- endset -*/


/*? task_15_desc ?*/
/*-- filter TaskContent("task-15", TaskContentType.COMPLETED, completion="main: hello world") -*/
    seL4_ReplyRecv(ep_object.cptr, tag, &sender_badge);
/*-- endfilter -*/

完成后,输出应该会发生变化,故障消息将替换为以下内容:

/*--filter TaskCompletion("task-15", TaskContentType.COMPLETED)--*/
main: got a reply: 0xffffffffffff9e9e
/*-- filter ELF("main") -*/

/*
 * seL4 tutorial part 3: IPC between 2 threads
 */
/* Include Kconfig variables. */
#include <autoconf.h>
#include <stdio.h>
#include <assert.h>
#include <sel4/sel4.h>
#include <simple/simple.h>
#include <simple-default/simple-default.h>
#include <vka/object.h>
#include <vka/object_capops.h>
#include <allocman/allocman.h>
#include <allocman/bootstrap.h>
#include <allocman/vka.h>
#include <vspace/vspace.h>
#include <sel4utils/vspace.h>
#include <sel4utils/mapping.h>
#include <utils/arith.h>
#include <utils/zf_log.h>
#include <sel4utils/sel4_zf_logif.h>
#include <sel4platsupport/bootinfo.h>
/* constants */
#define IPCBUF_FRAME_SIZE_BITS 12 // use a 4K frame for the IPC buffer
#define IPCBUF_VADDR 0x7000000 // arbitrary (but free) address for IPC buffer
#define EP_BADGE 0x61 // arbitrary (but unique) number for a badge
#define MSG_DATA 0x6161 // arbitrary data to send
/* global environment variables */
seL4_BootInfo *info;
simple_t simple;
vka_t vka;
allocman_t *allocman;
/* variables shared with second thread */
vka_object_t ep_object;
cspacepath_t ep_cap_path;
/* 分配器用于引导的静态内存 */
#define ALLOCATOR_STATIC_POOL_SIZE (BIT(seL4_PageBits) * 10)
UNUSED static char allocator_mem_pool[ALLOCATOR_STATIC_POOL_SIZE];
/* stack for the new thread */
#define THREAD_2_STACK_SIZE 512
static uint64_t thread_2_stack[THREAD_2_STACK_SIZE];
/* convenience function */
extern void name_thread(seL4_CPtr tcb, char *name);
/* function to run in the new thread */
void thread_2(void) {
    seL4_Word sender_badge = 0;
    UNUSED seL4_MessageInfo_t tag;
    seL4_Word msg = 0;
    printf("thread_2: hallo wereld\n");
/*? task_11_desc ?*/
/*? include_task_type_append([("task-11")]) ?*/
/*? task_12_desc ?*/
/*? include_task_type_append([("task-12")]) ?*/
/*? task_13_desc ?*/
/*? include_task_type_append([("task-13")]) ?*/
    printf("thread_2: got a message %#" PRIxPTR " from %#" PRIxPTR "\n", msg, sender_badge);
    /* modify the message */
    msg = ~msg;
/*? task_14_desc ?*/
/*? include_task_type_append([("task-14")]) ?*/
/*? task_15_desc ?*/
/*? include_task_type_append([("task-15")]) ?*/
}
int main(void) {
    UNUSED int error;
    /* get boot info */
    info = platsupport_get_bootinfo();
    ZF_LOGF_IF(info == NULL, "Failed to get bootinfo.");
    /* 设置日志记录并给我们起个名字:如果线程出错,对调试很有用 */
    zf_log_set_tag_prefix("hello-3:");
    name_thread(seL4_CapInitThreadTCB, "hello-3");
    /* init simple */
    simple_default_init_bootinfo(&simple, info);
    /* print out bootinfo and other info about simple */
    simple_print(&simple);
    /* create an allocator */
    allocman = bootstrap_use_current_simple(&simple, ALLOCATOR_STATIC_POOL_SIZE,        allocator_mem_pool);
    ZF_LOGF_IF(allocman == NULL, "Failed to initialize alloc manager.\n"
               "\tMemory pool sufficiently sized?\n"
               "\tMemory pool pointer valid?\n");
    /* 创建一个 vka(与底层分配器交互的接口) */
    allocman_make_vka(&vka, allocman);
    /* get our cspace root cnode */
    seL4_CPtr cspace_cap;
    cspace_cap = simple_get_cnode(&simple);
    /* 获取我们的 vspace 根页面目录*/
    seL4_CPtr pd_cap;
    pd_cap = simple_get_pd(&simple);
    /* create a new TCB */
    vka_object_t tcb_object = {0};
    error = vka_alloc_tcb(&vka, &tcb_object);
    ZF_LOGF_IFERR(error, "Failed to allocate new TCB.\n"
                  "\tVKA given sufficient bootstrap memory?");
    /*
     * create and map an ipc buffer:
     */
/*? task_1_desc ?*/
/*? include_task_type_append([("task-1")]) ?*/
   /*  将该帧映射到位于ipc_buffer_vaddr的v空间中。为此,我们首先尝试将其映射到根页面目录中。
   如果在页面目录的适当插槽中已经映射了一个页表,我们可以插入这个帧,那么这将会成功。
   否则,我们首先需要创建一个页面表,并将其映射到页面目录中,然后再在中映射框架。*/ 
   
    seL4_Word ipc_buffer_vaddr = IPCBUF_VADDR;
/*? task_2_desc ?*/
/*? include_task_type_append([("task-2")]) ?*/
    if (error != 0) {
/*? task_3_desc ?*/
/*? include_task_type_append([("task-3")]) ?*/
       ZF_LOGF_IFERR(error, "Failed to allocate new page table.\n");
/*? task_4_desc ?*/
/*? include_task_type_append([("task-4")]) ?*/
       ZF_LOGF_IFERR(error, "Failed to map page table into VSpace.\n"
                      "\tWe are inserting a new page table into the top-level table.\n"
                      "\tPass a capability to the new page table, and not for example, the IPC buffer frame vaddr.\n")
/*? task_5_desc ?*/
/*? include_task_type_append([("task-5")]) ?*/
        ZF_LOGF_IFERR(error, "Failed again to map the IPC buffer frame into the VSpace.\n"
                      "\t(It's not supposed to fail.)\n"
                      "\tPass a capability to the IPC buffer's physical frame.\n"
                      "\tRevisit the first seL4_ARCH_Page_Map call above and double-check your arguments.\n");
   }
    /* set the IPC buffer's virtual address in a field of the IPC buffer 
         在 IPC 缓冲区的字段中设置 IPC 缓冲区的虚拟地址 */
    seL4_IPCBuffer *ipcbuf = (seL4_IPCBuffer*)ipc_buffer_vaddr;
    ipcbuf->userData = ipc_buffer_vaddr;
/*? task_6_desc ?*/
/*? include_task_type_append([("task-6")]) ?*/
   ZF_LOGF_IFERR(error, "Failed to allocate new endpoint object.\n");
/*? task_7_desc ?*/
/*? include_task_type_append([("task-7")]) ?*/
    ZF_LOGF_IFERR(error, "Failed to mint new badged copy of IPC endpoint.\n"
                  "\tseL4_Mint is the backend for vka_mint_object.\n"
                  "\tseL4_Mint is simply being used here to create a badged copy of the same IPC endpoint.\n"
                  "\tThink of a badge in this case as an IPC context cookie.\n");
    /* initialise the new TCB */
    error = seL4_TCB_Configure(tcb_object.cptr, seL4_CapNull, 
                               cspace_cap, seL4_NilData, pd_cap, seL4_NilData,
                               ipc_buffer_vaddr, ipc_frame_object.cptr);
    ZF_LOGF_IFERR(error, "Failed to configure the new TCB object.\n"
                  "\tWe're running the new thread with the root thread's CSpace.\n"
                  "\tWe're running the new thread in the root thread's VSpace.\n");
    /* give the new thread a name */
    name_thread(tcb_object.cptr, "hello-3: thread_2");
    /* set start up registers for the new thread */
    seL4_UserContext regs = {0};
    size_t regs_size = sizeof(seL4_UserContext) / sizeof(seL4_Word);
    /* set instruction pointer where the thread shoud start running */
    sel4utils_set_instruction_pointer(&regs, (seL4_Word)thread_2);
    /* check that stack is aligned correctly */
    const int stack_alignment_requirement = sizeof(seL4_Word) * 2;
    uintptr_t thread_2_stack_top = (uintptr_t)thread_2_stack + sizeof(thread_2_stack);
    ZF_LOGF_IF(thread_2_stack_top % (stack_alignment_requirement) != 0,
               "Stack top isn't aligned correctly to a %dB boundary.\n"
               "\tDouble check to ensure you're not trampling.",
               stack_alignment_requirement);
    /* set stack pointer for the new thread. remember the stack grows down */
    sel4utils_set_stack_pointer(&regs, thread_2_stack_top);
#ifdef CONFIG_ARCH_IA32
    /* set the fs register for IPC buffer */
    regs.fs = IPCBUF_GDT_SELECTOR;
#endif /* CONFIG_ARCH_IA32 */
    /* actually write the TCB registers. */
    error = seL4_TCB_WriteRegisters(tcb_object.cptr, 0, 0, regs_size, &regs);
    ZF_LOGF_IFERR(error, "Failed to write the new thread's register set.\n"
                  "\tDid you write the correct number of registers? See arg4.\n");
    /* start the new thread running */
    error = seL4_TCB_Resume(tcb_object.cptr);
    ZF_LOGF_IFERR(error, "Failed to start new thread.\n");
    /* we are done, say hello */
    printf("main: hello world\n");
    /*
     * now send a message to the new thread, and wait for a reply
     */
    seL4_Word msg = 0;
    seL4_MessageInfo_t tag = seL4_MessageInfo_new(0, 0, 0, 0);
/*? task_8_desc ?*/
/*? include_task_type_append([("task-8")]) ?*/
/*? task_9_desc ?*/
/*? include_task_type_append([("task-9")]) ?*/
/*? task_10_desc ?*/
/*? include_task_type_append([("task-10")]) ?*/
    /* check that we got the expected repy */
    ZF_LOGF_IF(seL4_MessageInfo_get_length(tag) != 1,
               "Response data from thread_2 was not the length expected.\n"
               "\tHow many registers did you set with seL4_SetMR within thread_2?\n");
    ZF_LOGF_IF(msg != ~MSG_DATA,
               "Response data from thread_2's content was not what was expected.\n");
    printf("main: got a reply: %#" PRIxPTR "\n", msg);
    return 0;
}
/*- endfilter -*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值