seL4 Threads(四)

news2024/9/27 15:30:47

官网链接: Threads

Threads

这篇教程主要是使用seL4中的threads。

TCB Thread Control Blocks

seL4提供了线程代表执行的上下文以及管理处理器时间。seL4中的线程是通过线程控制块对象(TCB)实现的,每个内核线程都有一个线程控制块。
线程控制块包括以下的这些信息:

  • 优先级以及最大控制优先级
  • 寄存器状态以及浮点数上下文
  • CSpace 能力
  • VSpace 能力
  • 发送错误信息的端点能力
  • 回复能力槽

调度模式

seL4的调度器选择下一个线程在特定的处理核心上运行,并且该调度器是一个基于优先级的轮转调度器。调度器选择运行的线程状态时runnable,也就是说,可恢复且不会阻止任何的IPC操作。

priorities 优先级

调度器每次选择最高优先级的可运行的线程。seL4提供了0-255的优先级,255是最大优先级(seL4_MinPrio、seL4_MaxPrio)。
TCBs也有一个最大控制优先级(MCP),它作为优先级上的非正式能力控制。

Round robin 轮转调度

当多个线程都可运行且具有相同的优先级的时候,他们的调度是按照先进先出(队列)的方式轮转。更具体的来讲,内核时间以固定时间片(时钟周期)来计算,每个TCB都有一个时间片字段,标识该TCB在被抢占之前可以执行的时钟周期数量。内核定时器驱动程序被配置为触发一个周期性中断来标记每个时钟周期。当时间片耗尽时,轮询调度将被应用。线程可以使用seL4_Yield系统调用放弃其当前时间片。

Domain scheduling 域调度

为了提供保密性,seL4提供了一个顶级的分层调度器,该调度器提供静态的,循环的调度分区(域)调度。域在编译时通过循环调度进行静态配置,并且是不可抢占的,导致域的完全确定性调度。
线程可以被指定域,并且线程只有在他们的域被激活的时候才会被调度。跨域的进程通信(IPC)会被延迟到域切换的时候,且在不同域之间无法使用seL4_Yield系统调用。当一个被调度的域中没有可以运行的线程的时候,一个特定的域闲置线程将会运行知道域切换时。
给一个线程指定域需要seL4_DomainSet能力。该能力允许一个线程可以被添加到任何的域。

seL4_Error seL4_DomainSet_Set(seL4_DomainSet _service, seL4_Uint8 domain, seL4_TCB thread);

Thread Attribute 线程属性

seL4线程是通过调用TCB对象来配置的。

实操 Exercises

这篇教程指导你通过使用TCB调用在相同的地址空间中去创建一个新的线程,并且传递参数给新的线程。此外,你将会了解如何调试一个虚存错误。
这篇教程最后,您希望生成(spawn)一个新线程来调用下面代码示例中的函数。

CapDL loader

之前的教程都是在根任务中进行的,其是由seL4启动协议设置的起始CSpace布局。这篇教程使用capDL loader,是一个分配静态配置的对象和能力的根任务。
capDL loader解析系统的静态描述以及相关的ELF二进制文件。它通常是在Cmakes工程中使用,但是我们在该课程中使用它以减少相关的冗余代码。你构建的程序最终将会以他自己的CSpace和VSpace结束,也就是和根任务是相分离的,这就意味着像seL4_CapInitThreadVSpace 这样的CSlots在被capDL loader加载的程序中没有意义。

CapDL工程相关的信息可以看这个链接

Configure a TCB 配置一个TCB

当第一次构建和运行该教程时,可以看到像下面的输出。额…我也不知道为啥输出的没对齐
没对齐
Dumping all tcbs! 并且下面的那个表格也是由seL4_DebugDumpScheduler()这个调试系统调用生成的。seL4有一些列的调试系统调用,这些系统调用在调试内核构建中是可用的。
seL4_DebugDumpScheduler()用于输出调度器的当前状态,在系统似乎挂起的情况下,这个函数可以用来调试。
在TCB表格之后,你能看到seL4_Untyped_Retype这个方法由于无效参数而调用失败。该加载器已经被配置如下的能力和符号。

// the root CNode of the current thread
extern seL4_CPtr root_cnode;
// VSpace of the current thread
extern seL4_CPtr root_vspace;
// TCB of the current thread
extern seL4_CPtr root_tcb;
// Untyped object large enough to create a new TCB object

extern seL4_CPtr tcb_untyped;
extern seL4_CPtr buf2_frame_cap;
extern const char buf2_frame[4096];

// Empty slot for the new TCB object
extern seL4_CPtr tcb_cap_slot;
// Symbol for the IPC buffer mapping in the VSpace, and capability to the mapping
extern seL4_CPtr tcb_ipc_frame;
extern const char thread_ipc_buff_sym[4096];
// Symbol for the top of a 16 * 4KiB stack mapping, and capability to the mapping
extern const char tcb_stack_base[65536];
static const uintptr_t tcb_stack_top = (const uintptr_t)&tcb_stack_base + sizeof(tcb_stack_base);

练习: 使用上面提供的能力修复如下的seL4_Untyped_Retype 这个调用(下面代码),因此可以在tcb_cap_slot中创建一个新的能力。

int main(int c, char* arbv[]) {

    printf("Hello, World!\n");

    seL4_DebugDumpScheduler();

    // TODO fix the parameters in this invocation
    seL4_Error result = seL4_Untyped_Retype(seL4_CapNull, seL4_TCBObject, seL4_TCBBits, seL4_CapNull, 0, 0, seL4_CapNull,1)
    ZF_LOGF_IF(result, "Failed to retype thread: %d", result);
    seL4_DebugDumpScheduler();

可以看到这个TODO的方法里面有两个seL4_CapNull,这个的意思代表是空能力,用于表示该位置没有有效的能力。这三个seL4_CapNull就是我们需要填充的内容。
根据 Untyped这一章可以确定这三个位置应该分别填写什么,第一个是untyped的能力,第二个是CNode也就是要将新创建的能力放在那个CNode中,可以看到后面两个参数是0,所以是调用寻址,最后一个是要将新创建的能力放在那个CSlot中。结合上面加载器配置好的符号,因此修改代码如下:

seL4_Error result = seL4_Untyped_Retype(tcb_untyped, seL4_TCBObject, seL4_TCBBits, root_cnode, 0, 0, tcb_cap_slot,1)

而也正如教程中所说的一样,一旦TCB被创建,它将会作为’tcb_threads’的孩子显示在seL4_DebugDumpSchedule()的输出中。在整个教程中,你可以使用此系统调用来调试你设置的一些TCB属性,可以看到输出如下。
在这里插入图片描述
可以看到表后输出的另外一个错误,下面我们需要做的是:当前我们有一个TCB对象,然后将其和当前线程配置成一样的CSpace和VSpace。使用我们提供的IPC,但不要设置异常处理器,因为在调试版本中内核将会打印我们收到的任何错误信息。

//TODO fix the parameters in this invocation
result = seL4_TCB_Configure(seL4_CapNull, seL4_CapNull, 0, seL4_CapNull, 0, 0, (seL4_Word) NULL, seL4_CapNull);
ZF_LOGF_IF(result, "Failed to configure thread: %d", result);

先查询一下这个方法的用法。
在这里插入图片描述
因此我们将该代码修改为如下:

result = seL4_TCB_Configure(tcb_cap_slot, //要配置的TCB的能力
							seL4_CapNull, //设置该线程的故障处理端点(fault endpoint),用于处理异常,也就是异常处理器,不做设置
							root_cnode, 			  //新的CSpace的根能力
							seL4_CapNull, //可选参数,与能力空间根相关的权限和配置数据,设置为0则无影响
							root_vspace, 			  //新的VSpace的根能力
							0, 			  //x86和ARM处理器中无用
							(seL4_Word) thread_ipc_buff_sym, //线程IPC缓冲区的虚拟地址,缓冲区不能跨越页边界
							tcb_ipc_frame//用于映射IPC缓冲区的物理内存帧能力
							);

可以看到输出,已经到了下一个TODO,现在新创建的线程和当前线程拥有相同的CSpace和VSpace。
在这里插入图片描述

使用seL4_TCB_SetPriority修改优先级

新创建的线程优先级为0,而由loader创建的线程优先级是254,现在我们需要修改我们创建的新线程优先级以便于使其能与当前线程进行循环调度。
使用seL4_TCB_SetPriority设置优先级。要记住一点,想要设置一个线程的优先级,调用线程必须有权这样做(能力)。在这个例子中,主线程能使用他自己的TCB能力,它的MCP(最大控制优先级)为254。
在这里插入图片描述
因此修改代码如下:

// TODO fix the call to set priority using the authority of the current thread
// and change the priority to 254
result = seL4_TCB_SetPriority(tcb_cap_slot, root_tcb, 254);
ZF_LOGF_IF(result, "Failed to set the priority for the new TCB object.\n");
seL4_DebugDumpScheduler();

可以看到输出来到了下一个TODO:
在这里插入图片描述

设置初始寄存器的状态

在设置了该线程的初始寄存器之后,这个TCB差不多就能跑了。你需要将程序计数器和对战指针设置为有效值,否则线程将会立即崩溃。libsel4utils包含一些用于以与平台无关的方式设置寄存器内容的函数。我们可以使用这些方法去设置程序计数器和堆栈指针。注意:假定的是在所有平台中栈是向下增长的。(栈底在高地址部分)
练习:设置新线程去调用函数new_thread。你可以使用调试系统调用以验证你是否正确设置了IP(instruction pointer)。
先看一眼代码:

seL4_UserContext regs = {0};
int error = seL4_TCB_ReadRegisters(tcb_cap_slot, 0, 0, sizeof(regs)/sizeof(seL4_Word), &regs);
ZF_LOGF_IFERR(error, "Failed to read the new thread's register set.\n");

// TODO use valid instruction pointer
sel4utils_set_instruction_pointer(&regs, (seL4_Word)NULL);
// TODO use valid stack pointer
sel4utils_set_stack_pointer(&regs, NULL);
// TODO fix parameters to this invocation
error = seL4_TCB_WriteRegisters(seL4_CapNull, 0, 0, 0, &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");
seL4_DebugDumpScheduler();

在这里插入图片描述
可以看到seL4_TCB_ReadRegisters方法的作用是将寄存器中的内容读到regs中。它有五个参数:

  • tcb_cap_slot:要读取寄存器的线程的 TCB 能力。
  • 是否要挂起源线程
  • 体系结构相关
  • 要读取的寄存器数目
  • 指向 seL4_UserContext 结构的指针,用于存储读取的寄存器值
    再看看libsel4utils提供的的两个方法:
    在这里插入图片描述
    需要提一下:
// Symbol for the top of a 16 * 4KiB stack mapping, and capability to the mapping
extern const char tcb_stack_base[65536];
// 这个是基地址加上整个数组大小,指向了整个数组的高地址部分作为栈顶,整个栈向下增长,入栈地址减,出栈地址增
static const uintptr_t tcb_stack_top = (const uintptr_t)&tcb_stack_base + sizeof(tcb_stack_base);

因此这部分代码如下:

seL4_UserContext regs = {0};
int error = seL4_TCB_ReadRegisters(tcb_cap_slot, 0, 0, sizeof(regs)/sizeof(seL4_Word), &regs);
ZF_LOGF_IFERR(error, "Failed to read the new thread's register set.\n");

// TODO use valid instruction pointer
sel4utils_set_instruction_pointer(&regs, (seL4_Word)&new_thread);
// TODO use valid stack pointer
sel4utils_set_stack_pointer(&regs, tcb_stack_top);
// TODO fix parameters to this invocation
error = seL4_TCB_WriteRegisters(tcb_cap_slot, 0, 0, sizeof(regs)/sizeof(seL4_Word), &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");
seL4_DebugDumpScheduler();

再次执行之后可以看到:
在这里插入图片描述

开启线程

现在可以启动线程了,使TCB变成runnable且能够被seL4调度器调度。可以通过改变seL4_TCB_WriteRegisters的第二个参数为1且删除掉seL4_TCB_Resume调用做到,也可以通过修改下面的resume调用做到。
修改代码为:

// TODO resume the new thread
error = seL4_TCB_Resume(tcb_cap_slot);
ZF_LOGF_IFERR(error, "Failed to start new thread.\n");

可以看到输出为:
在这里插入图片描述
后面还跟了一个错误,这个错误的格式说明在上一节中说过。

传递参数

通过上面的输出可以看到传递给新线程的参数都是0。你可以通过使用辅助函数sel4utils_arch_init_local_context设置参数或者通过直接操作你体系结构下的目标寄存器来传递参数。
练习:更新使用seL4_TCB_WriteRegisters写入的值,以分别将值1、2、3作为arg1、arg2和arg3传递。修改代码如下:

seL4_UserContext regs = {0};
int error = seL4_TCB_ReadRegisters(tcb_cap_slot, 0, 0, sizeof(regs)/sizeof(seL4_Word), &regs);
ZF_LOGF_IFERR(error, "Failed to read the new thread's register set.\n");

// TODO use valid instruction pointer
//sel4utils_set_instruction_pointer(&regs, (seL4_Word)&new_thread);
// TODO use valid stack pointer
//sel4utils_set_stack_pointer(&regs, tcb_stack_top);
// passing arguments
sel4utils_arch_init_local_context((void *)new_thread,
                                  (void *)1, (void *)2, (void *)3,
                                  (void *)tcb_stack_top,
                                  &regs
                                  );
// TODO fix parameters to this invocation
error = seL4_TCB_WriteRegisters(tcb_cap_slot, 0, 0, sizeof(regs)/sizeof(seL4_Word), &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");
seL4_DebugDumpScheduler();

需要将之前的设置IP和stack的代码注释掉,使用这一个参数可以全部设置掉。输入如下
在这里插入图片描述

解决故障

现在你已经创建并且配置了一个新的线程,并且给了它初始的参数。这个教程的最后一部分是当你的线程出错的时候你应该怎么办。之后有进一步的详细的错误处理教程(官网说的,不是我说的)。现在的话,我们可以依靠内核打印错误信息,因为我们创建的线程没有错误处理程序。
在上面图片的输出中,我们可以看到一个能力错误。错误的第一部分是内核不能发送一个错误给错误处理器,因为没设置这玩意。然后内核就输出了它尝试发送的错误信息。在这个案例中,该错误是虚存错误。新线程尝试访问地址0x2地方的数据,这是一个非法的未映射的地址。输出显示故障时线程的程序计数器为0x401e6c(这条指令出错)。
故障状态寄存器也会输出,可以通过相关架构手册进行解码。此外,内核会从当前栈指针打印原始栈转储。栈转储的大小是可配置的,使用 KernelUserStackTraceLength CMake 变量进行设置。
要调查故障,您可以使用如 objdump 这样的工具来检查导致故障的 ELF 文件中的指令。在这种情况下,ELF 文件位于 ./<BUILD_DIR>/<TUTORIAL_BUILD_DIR>/threads。

这一块我本想操作一下,但是elf文件找了半天,错误信息看起来十分费劲。但教程下面给出了修改的方法:

  1. 确保 arg2 拥有一个有效的地址,而不是直接传入立即数,因为在new_thread方法中有一行代码func(*(int *)arg2);,这表明了arg2必须是一个指向int的指针,且这行代码实际上是调用了arg1所指向的函数,将arg2中存储的整数值作为参数传入,因此如果传入的是一个立即数的话,会把传入的立即数当作地址操作里面的数,这是非法的。
  2. void (*func)(int) = arg1;这行代码说明arg1需要是一个函数指针。

因此代码如下:

// 全局参数
int a2 = 10;

// 自定义函数
void my_function(int arg) {
    // 输出 arg 的值,例如使用 printf
    printf("Argument: %d\n", arg);
}
sel4utils_arch_init_local_context((void *)new_thread,
                                  (void *)my_function, (void *)&a2, (void *)3, //传入a2的地址作为第二个参数
                                  (void *)tcb_stack_top,
                                  &regs
                                  );

输出如下,可以看到此时已经不会再线程报错了,该线程跑的方法中所需要的参数都已合法。
在这里插入图片描述

Further exercises

累了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2165422.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux服务器安装原生的php环境

在CentOS上安装原生的PHP环境相对简单。下面是一个详细的步骤指南&#xff0c;适用于CentOS 7及更高版本。 ### 第一步&#xff1a;更新系统 首先&#xff0c;确保你的系统是最新的&#xff1a; sudo yum update -y ### 第二步&#xff1a;安装EPEL和Remi仓库 1. **安装EP…

Windows内核编程基础(3)

内存分配 在应用层编程时&#xff0c;系统提供了GlobalAlloc/HeapAlloc/LocalAlloc等函数。C/C库提供了malloc函数&#xff0c;以及new操作符在堆上分配内存。 在我前面一个关于Windows页交换文件的博客中&#xff0c;介绍了虚拟内存&#xff0c; 虚拟内存是计算机系统内存管…

古月居全新改版上线:AI 大模型“古月知道”引领 ROS 学习新体验

前言 古月居自成立以来&#xff0c;一直致力于为广大 ROS&#xff08;机器人操作系统&#xff09;爱好者和开发者提供优质的学习资源和社区交流平台。经过长期的用户调研和反馈&#xff0c;我们发现旧版古月居在使用过程中存在一些不便之处。 为了更好地服务大家&#xff0c;…

如何生成谷歌临时邮箱?

谷歌的Gmail作为全球最受欢迎的邮件服务之一&#xff0c;不仅因其稳定性和强大的功能而备受青睐&#xff0c;还因为它支持临时邮箱功能&#xff0c;这一功能能够极大地提升用户在各种场景下的使用灵活性。无论是处理一次性事务、注册新账户还是防止垃圾邮件&#xff0c;Gmail的…

通义模型Prompt调优的实用技巧

1. 目录 1. prompt工程简介 2. Prompt设计 2.1 Prompt主要构成要素 2.2 Prompt编写策略 策略一&#xff1a;对较难被准确遵循的复杂规则可拆分为多条规则&#xff0c;有助于提升效果 策略二&#xff1a;适当冗余关键信息 策略三&#xff1a;使用分隔符给Prompt分段 策…

类与对象—python

一、类的含义 1.1类的作用&#xff08;理解&#xff09; 收集学生信息时&#xff0c;如果让同学们自主填写&#xff0c;信息的顺序、格式不一&#xff0c;内容混乱。如果发给同学们既定的表格&#xff0c;同学们按照规定的顺序、格式进行填写&#xff0c;那信息就会一目了然&…

回归预测 | Matlab基于SO-SVR蛇群算法优化支持向量机的数据多输入单输出回归预测

回归预测 | Matlab基于SO-SVR蛇群算法优化支持向量机的数据多输入单输出回归预测 目录 回归预测 | Matlab基于SO-SVR蛇群算法优化支持向量机的数据多输入单输出回归预测预测效果基本描述程序设计参考资料 预测效果 基本描述 1.Matlab基于SO-SVR蛇群算法优化支持向量机的数据多…

path_provider插件的用法

文章目录 1. 概念介绍2. 实现方法3. 示例代码我们在上一章回中介绍了"如何实现本地存储"相关的内容,本章回中将介绍如何实现文件存储.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 我们在上一章回中介绍的本地存储只能存储dart语言中基本类型的数值,如果遇到…

大数据-147 Apache Kudu 常用 Java API 增删改查

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

【学习笔记】 AD24中元器件重叠系统不报错的解决方案(消除报错)

【学习笔记】 AD24中PCB设计元器件重叠后系统不报错的解决方案&#xff08;如何主动屏蔽报错&#xff09; 一、Component Clearance未开启使能的解决方案二、最小水平间距设置错误的解决方案三、未开启设计规则检查的解决方案四、设计规则检查中 “在线”和“批量”的含义五、为…

开源的CDN:jsDelivr+Github加速图片加载

文章目录 20240530更新 网站加载的图片耗时&#xff0c;将图片使用jsDelivr进行加速。 每次打开静态网站的时候&#xff0c;都会发现页面的内容已经加载出来了&#xff0c;但是图片还是一片白&#xff0c;就考虑如何让图片能够更快的加载出来。 后面发现可以用jsDelivr加速Gi…

自然场景文本定位系统源码分享

自然场景文本定位检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer…

南京市副市长吴炜一行至天洑软件参观调研

近日&#xff0c;南京市副市长吴炜、南京市工信局副局长代吉上、南京市科技局副局长王愿华、江宁开发区管委会副主任易骏飞一行至天洑软件参观&#xff0c;调研工业软件重点企业方案&#xff0c;天洑软件副总经理冯克列、总工程师郭阳、研发部部长谢佳雯陪同调研。 Q1&#xff…

南开大学联合同济大学发布最新SOTA Occ OPUS:使用稀疏集进行占据预测,最快实现8帧22FPS

Abstract 占据预测任务旨在预测体素化的 3D 环境中的占据状态&#xff0c;在自动驾驶社区中迅速获得了关注。主流的占据预测工作首先将 3D 环境离散化为体素网格&#xff0c;然后在这些密集网格上执行分类。然而&#xff0c;对样本数据的检查显示&#xff0c;大多数体素是未占…

Linux:编译,调试和Makefile

一丶vim编译器 ### 基本概念 模式&#xff1a;Vim有几种不同的模式&#xff0c;包括&#xff1a; 命令/正常/普通模式&#xff1a;控制屏幕光标的移动&#xff0c;字符、字或行的删除&#xff0c;移动复制某区段及进入Insert mode下&#xff0c;或者到 last line mode 插入模…

xpath在爬虫中的应用、xpath插件的安装及使用

安装 1、打开谷歌浏览器进入扩展程序安装页面(右上角会有"开发者模式按钮")默认是关闭的&#xff0c;当安装此插件时需要把开发者模式打开。 2、下载下来的xpath_helper是zip格式的&#xff0c;需要解压缩即可安装。 3、重启浏览器&#xff0c;再次点击扩展程序即…

CAN通信详解

1、CAN介绍 1.1、什么是CAN? CAN&#xff08;Controller Area Network&#xff09; 即控制器局域网&#xff0c;是ISO国际标准化的串行通信协议。 开发目的&#xff1a;为了满足汽车产业的“减少线束的数量”、“通过多个LAN&#xff0c;进行大量数据的高速通信”…

9.25 C++继承 多态

手动实现队列 #include <iostream>using namespace std;class My_queue { private:struct Node //队列结构体{int data;Node *next;Node(int value):data(value),next(nullptr){}};Node *front;Node *rear;int size;public:My_queue():front(nullptr),rear(nullptr),siz…

EMQX MQTT 服务器启用 SSL/TLS 安全连接,使用8883端口

1.提前下载安装openssl 2.新建openssl文件夹打开在命令行操作 3.按照下面的操作进行 MQTT 安全 作为基于现代密码学公钥算法的安全协议&#xff0c;TLS/SSL 能在计算机通讯网络上保证传输安全&#xff0c;EMQX 内置对 TLS/SSL 的支持&#xff0c;包括支持单/双向认证、X.509 …

如何使用ssm实现线上旅游体验系统+vue

TOC ssm691线上旅游体验系统vue 绪论 课题背景 身处网络时代&#xff0c;随着网络系统体系发展的不断成熟和完善&#xff0c;人们的生活也随之发生了很大的变化。目前&#xff0c;人们在追求较高物质生活的同时&#xff0c;也在想着如何使自身的精神内涵得到提升&#xff0…