seL4 Threads(四)

官网链接: 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 objectextern 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 invocationseL4_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 的值,例如使用 printfprintf("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.rhkb.cn/news/431992.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Web3技术在元宇宙中的应用:从区块链到智能合约

随着元宇宙的兴起&#xff0c;Web3技术正逐渐成为其基础&#xff0c;推动着数字空间的重塑。元宇宙不仅是一个虚拟世界&#xff0c;它还代表着一个由去中心化技术驱动的新生态系统。在这个系统中&#xff0c;区块链和智能合约发挥着至关重要的作用&#xff0c;为用户提供安全、…

Vue | watch监听

Vue | watch监听 在Vue.js的世界里&#xff0c;watch监听器是一个强大且灵活的工具&#xff0c;它允许我们在数据变化时执行特定的逻辑。本文将深入探讨watch的出现背景、使用方法、应用场景、源码原理以及扩展技巧&#xff0c;旨在帮助读者全面掌握这一重要特性。 文章目录 Vu…

TMStarget学习——T1 Segmentation数据处理及解bug

最新学习季公俊老师的神器 TMStarget 的第一个模块基于结构像的靶区计算T1 segmentation。下面上步骤&#xff1a; (1)在github 上下载 TMStarget https://github.com/jigongjun/Neuroimaging-and-Neuromodulation (2)按照要求下载依赖工具软件AFQ、vistasoft、SPM12 &#…

笔试编程-百战成神——Day02

1.简写单词 题目来源&#xff1a; 简写单词——牛客网 测试用例 算法原理 本题的主要难点就是如何识别每一个单词并且返回其首字母大写&#xff0c;最终组成一个新的字符串后输出&#xff0c;这里我们使用while(cin>>str)就可以解决&#xff0c;直接忽略每一个空格直接…

20240926 每日AI必读资讯

一个开源的自托管 AI 入门工具包 - 此工具包利用Docker Compose模板&#xff0c;包含多种本地AI工具并提供AI工作流模板和网络配置&#xff0c;简化了本地AI工具的安装和使用。 安装它将拥有&#xff1a; • 一个拥有 400 多个 AI 组件和集成的低代码平台 • Ollama&#xf…

基于stm32物联网身体健康检测系统

在当今社会&#xff0c;由于经济的发展带来了人们生活水平不断提高&#xff0c;但是人们的健康问题却越来越突出了&#xff0c;各种各样的亚健康随处可在&#xff0c;失眠、抑郁、焦虑症&#xff0c;高血压、高血糖等等侵袭着人们的健康&#xff0c;人们对健康的关注达到了一个…

单细胞Scanpy流程学习和整理(单样本10X数据读取/过滤/降维/聚类)

打算仔细学习一下基于python的单细胞相关分析框架hhh 新手上路写的很繁琐&#xff0c;多多包涵&#xff0c;本次用的IDE是Visual studio code。 流程来自Scanpy官网(Preprocessing and clustering 3k PBMCs (legacy workflow))&#xff1a; https://scanpy.readthedocs.io/e…

01【MATLAB】最小二乘系统辨识

目录 1.系统辨识的定义及其分类 1.1 系统辨识的定义 1.2 系统辨识的分类 2.参数模型 3.系统辨识的步骤 一、最小二乘法&#xff08;Least Squares Method&#xff09;一般步骤 二、LSM原理及应用 三、LSM在控制系统建模中的应用 1.系统辨识的定义及其分类 1.1 系统辨识的…

力扣P1706全排列问题 很好的引入暴力 递归 回溯 dfs

代码思路是受一个洛谷题解里面大佬的启发。应该算是一个dfs和回溯的入门题目&#xff0c;很好的入门题目了下面我会先给我原题解思路我想可以很快了解这个思路。下面是我自己根据力扣大佬写的。 我会进行详细讲解并配上图辅助理解大家请往下看 #include<iostream> #inc…

【机器学习】Flux.jl 生态

官方API https://fluxml.ai/Flux.jl/stable/ecosystem/ 官网给出了 Flux’s model-zoo&#xff0c; 是一个庞大的案例库&#xff0c; 可以提供直观的参考&#xff0c; 并且还列举了基于 Flux.jl 开发的第三方库。 机器视觉 ObjectDetector.jl YOLO 抓取的“预备跑” 图像Met…

使用vite+react+ts+Ant Design开发后台管理项目(一)

前言 本文将引导开发者从零基础开始&#xff0c;运用vite、react、react-router、react-redux、Ant Design、less、tailwindcss、axios等前沿技术栈&#xff0c;构建一个高效、响应式的后台管理系统。通过详细的步骤和实践指导&#xff0c;文章旨在为开发者揭示如何利用这些技…

SpringCloud Alibaba之Seata处理分布式事务

&#xff08;学习笔记&#xff0c;必用必考&#xff09; 问题&#xff1a;Transactional 的9种失效场景&#xff1f; 1、介绍 1.1、简介 官网地址&#xff1a;Apache Seata 源码地址&#xff1a;Releases apache/incubator-seata GitHub Seata是一款开源的分布式事务解决…

Thinkphp5x远程命令执行 靶场攻略

环境配置 靶场&#xff1a;vulhub/thinkphp/5-rce docker-compose up -d #启动环境 漏洞复现 1.访问靶场&#xff1a;http://172.16.1.198:8080/ 2.远程命令执⾏ POC&#xff1a; ?sindex/think\app/invokefunction&functioncall_user_func_array&vars[0]system…

【VUE_ruoyi-vue】基于ruoyi-vue框架实现简单的系统通用文件模块

基于ruoyi-vue框架&#xff0c;新增一个简单的系统通用文件模块&#xff0c;服务与各个模块涉及到文件上传信息的记录和相关展示 运行sql,创建数据库表 DROP TABLE IF EXISTS sys_file_info; CREATE TABLE sys_file_info (id int(11) NOT NULL AUTO_INCREMENT COMMENT id,lin…

在虚幻引擎中实时显示帧率

引擎自带了显示帧率的功能 但是只能在编辑器中显示 , 在游戏发布后就没有了 , 所以我们要自己做一个 创建一个控件蓝图 创建画布和文本 , 修改文本 文本绑定函数 , 点击创建绑定 添加一个名为 FPS 的变量 格式化文本 用大括号把变量包起来 {FPS Int} FPS 然后转到事件图表…

机器学习算法与Python实战 | 三万字详解!GPT-5:你需要知道的一切(下)建议收藏!

本文来源公众号“机器学习算法与Python实战”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;三万字详解&#xff01;GPT-5&#xff1a;你需要知道的一切 作者&#xff1a;Alberto Romero &#xff08;青稞AI整理&#xff09; 原…

Go语言基础学习01-Liunx下Go开发环境配置;源码组织方式;go build/install/get详解

目录 Linux环境下配置安装VScode并配置Go语言开发环境Go语言源码的组织方式Go语言源码安装后的结果Go程序构建和安装的过程go build扩展go get 命令详解 之前学习过Go语言&#xff0c;学习的时候没有记录笔记&#xff0c;最近找了个极客时间的Go语言36讲&#xff0c;打算时间学…

第 1 章:Vue 核心

1. Vue 简介 1.1. 官网 英文官网: https://vuejs.org/中文官网: https://cn.vuejs.org/&#xff1a;中文官网里面【教程】和【API】是比较重要的。用到api就去查询&#xff0c;实践当中记忆更牢靠。 风格指南&#xff1a;官方推荐写的一个代码风格cookbook&#xff1a;编写v…

QT窗口无法激活弹出问题排查记录

问题背景 问题环境 操作系统: 银河麒麟V10SP1qt版本 : 5.12.12 碰见了一个问题应用最小化,然后激活程序窗口无法弹出 这里描述一下代码的逻辑,使用QLocalServer实现一个单例进程,具体的功能就是在已存在一个程序A进程时,再启动这个程序A,新的程序A进程会被杀死,然后激活已存…

视频汇聚EasyCVR视频监控平台调取接口提示“认证过期”是什么原因?

视频汇聚EasyCVR视频监控平台&#xff0c;作为一款智能视频监控综合管理平台&#xff0c;凭借其强大的视频融合汇聚能力和灵活的视频能力&#xff0c;在各行各业的应用中发挥着越来越重要的作用。EasyCVR平台具备强大的拓展性和灵活性&#xff0c;支持多种视频流的外部分发&…