当前位置: 首页 > news >正文

站长之家爱站网怎么自己做网站免费的

站长之家爱站网,怎么自己做网站免费的,花店网站建设毕设介绍,周口建设局网站Github代码仓库链接 终于#xff0c;我们要开始研究进程和线程了#xff0c;这会使我们的操作系统看起来更像一个操作系统。但是目前我们先不研究进程#xff0c;而是先实现调度的基本单位——线程#xff0c;而且是内核态的线程。 5.1 线程切换 1、进程和线程 磁盘中存储…Github代码仓库链接 终于我们要开始研究进程和线程了这会使我们的操作系统看起来更像一个操作系统。但是目前我们先不研究进程而是先实现调度的基本单位——线程而且是内核态的线程。 5.1 线程切换 1、进程和线程 磁盘中存储的静态代码经过编译、链接后形成的可执行文件称为程序。进程就是被操作系统分配了资源并正在运行中的程序。进程这个词强调动态性占用资源且正在运行。操作系统将静态的程序加载到内存中程序定义的各个段都被正确地映射出来并且还占用一定的资源如页表和文件描述符同时这个进程在适当的时候还可以占有 CPU被 CPU 取指执行。线程是进程中正在运行的程序流更关注其“正在运行”的特性。如果剥离线程进程只需要负责维护操作系统所分配的资源。一个进程可以有多个线程也可以只有一个线程。单个进程内有多个线程的情况下线程会共享进程被分配到的资源。这样剥离开来进程就成了操作系统分配资源的最小单位管理页表、文件等资源。而线程则更专注于执行是 CPU 调度的最小单位。 2、线程的状态表示CPU 中各个寄存器的值关心程序运行过程中产生的中间结果线程栈线程在进行函数调用的时候需要将各种信息压栈并借助寄存器和栈进行参数和返回值的传递。同时函数在运行时的各种局部变量都是在栈上分配的。其他线程不会修改当前线程的栈但是有这个能力我们没有必要保存栈上内容。这样的话我们只需要保存 CPU 中寄存器的值就好了。这就有点像中断发生时保存上下文的场景了不过由于采用函数调用的方式来切换线程我们不用保存所有的寄存器。事实上我们只需要保存以下内容 ra 寄存器用于保存返回地址satp 寄存器保存页表信息本章的线程都可以说是同属于内核进程的共用一个页表s0 ~ s11函数调用中被调用者保存的寄存器 3、线程的实现 定义线程上下文 // kernel/context.h// 线程运行上下文 typedef struct {usize ra;usize satp;usize s[12];InterruptContext ic; } ThreadContext;除去我们说的三部分内容以外还包含了一个中断上下文严格来说这不属于线程运行上下文的一部分我们只是会借助中断返回机制来初始化线程这部分仅会在线程初始化时被保存在栈上后续并不会被保存。 定义线程 // kernel/thread.h// 线程结构定义 typedef struct {// 线程上下文存储的地址usize contextAddr;// 线程栈底地址usize kstack; } Thread;线程上下文只需要保存其地址只有在切换线程时才需要关心线程上下文保存的位置保存栈底地址主要是为了在线程结束后能够回收这片空间栈空间通过动态内存分配后面会提到 4、线程切换通过 switchThread() 这个函数来完成线程的切换切换线程主要就是切换上下文并且跳转到目标线程上次结束的位置。 // kernel/thread.c/** 该函数用于切换上下文保存当前函数的上下文并恢复目标函数的上下文* 输入线程上下文存储的地址由于目标线程切换前最后是保存在栈上的所以该地址即栈顶地址* 输出函数返回时即返回到了新线程的运行位置* naked 防止 gcc 在函数执行前后自动插入开场白和结束语函数调用保存寄存器和栈指针这部分我们自行设计* noinline 防止 gcc 将函数内联有些编译器为了避免跳转、返回会将函数优化为内联上下文切换借助了函数调用返回不应内联*/ __attribute__((naked, noinline)) void switchContext(usize *self, usize *target) {asm volatile(.include \kernel/switch.asm\); // 调用汇编指令切换线程 }// 线程切换 void switchThread(Thread *self, Thread *target) {switchContext(self-contextAddr, target-contextAddr); }这里使用了两个属性naked 表示不要在这个函数执行前后加入任何的开场白prologue和结语epilogue通常的编译器会根据函数调用约定在函数开头自动加入保存寄存器、设置栈寄存器等内容这部分我们自行来设置。noinline 指示编译器不要将该函数内联有些编译器会将函数优化为内联的从而避免了跳转和返回。但是我们切换线程需要借助函数的调用-返回机制因此需要声明此属性。这个函数传入了两个参数分别是指向当前线程上下文保存地址的指针和目标线程上下文地址的指针由于目标线程被切换前是最后被保存在栈上的所以该地址同时还是栈顶地址。根据函数调用约定传入的两个参数分别被保存在 a0 和 a1 寄存器即它们分别保存了“当前线程栈顶地址”所在的地址以及“目标线程栈顶地址”所在的地址。 RISC-V 函数调用约定中的通用寄存器职能 寄存器ABI 名字描述Saverx0zero硬件连线0-x1ra返回地址Callerx2sp栈指针Calleex3gp全局指针-x4tp线程指针-x5-x7t0-t2临时寄存器Callerx8s0/fp保存的寄存器/帧指针Calleex9s1保存寄存器 保存原进程中的关键数据 避免在函数调用过程中被破坏Calleex10-x11a0-a1函数参数/返回值Callerx12-x17a2-a7函数参数Callerx18-x27s2-s11保存寄存器Calleex28-x31t3-t6临时寄存器Caller这里向大家解释一下 Caller 和 Callee 寄存器的区别 Caller Saved寄存器在函数调用的时候不会保存。这里的意思是一个Caller Saved寄存器可能被其他函数重写。假设我们在函数a中调用函数b任何被函数a使用的并且是Caller Saved寄存器调用函数b可能重写这些寄存器。我认为一个比较好的例子就是Return address寄存器注保存的是函数返回的地址你可以看到ra寄存器是Caller Saved这一点很重要它导致了当函数a调用函数b的时侯b会重写Return address。所以基本上来说任何一个Caller Saved寄存器作为调用方的函数要小心可能的数据可能的变化。因为Caller已经被保存到栈上了所以调用时候数据不会保存是可变的。Callee Saved寄存器在函数调用的时候会保存意思是在一些特定的场景下你会想要确保一些数据在函数调用之后仍然能够保存这个时候编译器可以选择使用 Callee 寄存器所以我们在切换到其他线程的时候同样要保存Callee这部分寄存器。 5、线程切换 switch.S 的实现 .equ XLENB, 8 # 寄存器字节数为8addi sp, sp, (-XLENB*14) # 分配栈空间sd sp, 0(a0) # a0为传入的“当前线程上下文存储地址“栈指针移动后更新上下文地址sd ra, 0*XLENB(sp) # 将寄存器 ra 保存到栈上sd s0, 2*XLENB(sp) # s0-s11sd s1, 3*XLENB(sp)sd s2, 4*XLENB(sp)sd s3, 5*XLENB(sp)sd s4, 6*XLENB(sp)sd s5, 7*XLENB(sp)sd s6, 8*XLENB(sp)sd s7, 9*XLENB(sp)sd s8, 10*XLENB(sp)sd s9, 11*XLENB(sp)sd s10, 12*XLENB(sp)sd s11, 13*XLENB(sp)csrr s11, satpsd s11, 1*XLENB(sp) # satpld sp, 0(a1) # 将“目标线程上下文存储地址”传入spld s11, 1*XLENB(sp) csrw satp, s11 # 恢复 satpsfence.vma # 刷新TLB使新配置页表生效ld ra, 0*XLENB(sp) # 恢复 rald s0, 2*XLENB(sp) # 恢复s0-s11ld s1, 3*XLENB(sp)ld s2, 4*XLENB(sp)ld s3, 5*XLENB(sp)ld s4, 6*XLENB(sp)ld s5, 7*XLENB(sp)ld s6, 8*XLENB(sp)ld s7, 9*XLENB(sp)ld s8, 10*XLENB(sp)ld s9, 11*XLENB(sp)ld s10, 12*XLENB(sp)ld s11, 13*XLENB(sp)addi sp, sp, (XLENB*14) # 释放目标线程存储寄存器的栈空间sd zero, 0(a1) # 清除a1寄存器ret这部分代码和中断上下文保存与恢复很像主要需要做的就两件事 将 Callee-saved 寄存器保存在当前栈上并更新传入的“当前栈顶地址”更新栈顶从要恢复的线程栈中恢复目标线程的 Callee-saved 寄存器 5.2 构造线程结构 1、我们要构造一个静止的线程使得当其他正在运行的线程切换到它时就可以将寄存器和栈变成我们想要的状态并且跳转到我们希望的地方开始运行。主要有这三步设置栈顶地址传入可能的参数跳转到线程入口。 首先我们需要分配一个空间作为内核栈。 // kernel/consts.h#define KERNEL_STACK_SIZE 0x80000 /* 内核栈大小 128M */// kernel/thread.c /* * 构建内核线程的内核栈* 输出栈空间的起始地址 */ usize newKernelStack() {/* 将内核线程的线程栈分配在内核堆中 */usize bottom (usize)kalloc(KERNEL_STACK_SIZE); // 在内核堆上分配内存return bottom; }构造新的内核线程上下文ThreadContext并把他压到栈上。 // kernel/thread.c/** 将线程上下文依次压入栈顶* 并返回新的栈顶地址即线程上下文地址 */ usize pushContextToStack(ThreadContext self, usize stackTop) {// 分配栈空间 - 转换指针类型ThreadContext *ptr (ThreadContext *)(stackTop - sizeof(ThreadContext));*ptr self;return (usize)ptr; }/** 创建新的内核线程上下文并将线程上下文入栈* 借助中断恢复机制进行线程的初始化工作即从中断恢复结束时即跳转到sepc就是线程的入口点* 输入线程入口点内核线程线程栈顶内核线程页表* 输出线程上下文地址 */ usize newKernelThreadContext(usize entry, usize kernelStackTop, usize satp) {InterruptContext ic;ic.x[2] kernelStackTop; // 设置sp寄存器为内核栈顶ic.sepc entry; // 中断返回地址为线程入口点ic.sstatus r_sstatus();/* 内核线程返回后特权级为 S-Mode */ic.sstatus | SSTATUS_SPP;/* 开启新线程异步中断使能 */ic.sstatus | SSTATUS_SPIE; // 中断处理发生前的SIE值ic.sstatus ~SSTATUS_SIE; // 禁用SIE不想立即开启中断// 创建新线程上下文ThreadContext tc;// 借助中断的恢复机制来初始化新线程的每个寄存器从 Context 中恢复所有寄存器extern void __restore(); tc.ra (usize)__restore;tc.satp satp; // 设置页表tc.ic ic;return pushContextToStack(tc, kernelStackTop); }由于我们已经将完整的 ThreadContext 压栈待 switchContext() 执行完成返回时会自动回收 ra、satp 和 s0 ~ s11栈顶只剩下一个 InterruptContext这种情况恰好和从中断处理函数返回时是类似的情况ra 的值被设置为 __restore()就正是为了借用中断返回机制来初始化线程的一些寄存器如传参等。从 __restore() 返回就会跳转到 InterruptContext 的 sepc 位置这正是线程的入口点同时栈顶指针 sp 也被正确地设置为 kernelStackTop 更具体地当切换到这个新建的内核线程时由于ra指向__restore所以回到此处继续运行借助中断恢复机制运行完后会跳转到sepc指定位置此时我们指定为真正的新线程入口点entry。同时中断恢复机制从栈中弹出保存的数据此时sp所指位置正好是不包含中断上下文ic的线程上下文tc即kernelStackTop 同时我们还设置了 SSTATUS 寄存器的 SPP 位这样在使用 sret 指令返回后的特权级保持为 S-Mode。同时设置 SPIE 位和置空 SIE 位则是为了使得 S-Mode 线程能够被异步中断打断为了下一节调度做准备。 3、创建线程我们还可以为线程传入初始化参数根据调用约定函数参数保存在 x10 ~ x17 中我们利用中断恢复过程来填充这些寄存器于是将参数保存在 InterruptContext 即可 // kernel/thread.c// 为线程传入初始化参数 // 我们利用中断恢复过程来填充寄存器所以将参数保存到ic void appendArguments(Thread *thread, usize args[8]) {ThreadContext *ptr (ThreadContext *)thread-contextAddr;ptr-ic.x[10] args[0];ptr-ic.x[11] args[1];ptr-ic.x[12] args[2];ptr-ic.x[13] args[3];ptr-ic.x[14] args[4];ptr-ic.x[15] args[5];ptr-ic.x[16] args[6];ptr-ic.x[17] args[7]; }最后可以创建线程了 // kernel/thread.c/** 创建新的内核线程* 创建内核栈创建上下文 */ Thread newKernelThread(usize entry) {// 构建内核线程的内核栈usize stackBottom newKernelStack();// 创建新的内核线程上下文usize contextAddr newKernelThreadContext(entry, // 线程入口点stackBottom KERNEL_STACK_SIZE, // 内核栈顶r_satp() // 创建的内核线程与启动线程同属于一个进程所以直接获取satp赋值);Thread t { // 线程上下文地址 线程栈底地址contextAddr, stackBottom};return t; }我们创建的内核线程与启动线程同属于一个“内核进程”共用一个页表所以可以直接通过 r_satp() 来设置新线程的 satp。 // kernle/riscv.hstatic inline uint64 r_satp() {uint64 x;asm volatile(csrr %0, satp : r (x) );return x; }5.3 从启动线程到新线程切换 1、上一节我们完成了所有线程相关结构的构建这一节我们来尝试切换到一个新的线程再切换回来。 我们首先定义一个函数当作新线程的入口点。新线程需要传入三个参数分别是 from、current 和 cc 是传入的一个字符参数current 代表了这个新线程from 为切换之前的线程我们在这个函数最后手动切换回去。现在这个 from 显然就是指启动线程但是我们身处启动线程内如何构造一个 Thread 代表自身呢答案是构造一个空结构即可。 // kernel/thread.c// 测试函数作为新线程入口点 void tempThreadFunc(Thread *from, Thread *current, usize c) {printf(The char passed by is );consolePutchar(c); // 向终端输出字符consolePutchar(\n);printf(Hello world from tempThread!\n);switchThread(current, from); // 手动线程切换回去 }// 构造空结构Thread表示当前启动线程 // 在调用 switchThread() 函数时会将当前线程的上下文信息保存到这个线程结构中 Thread newBootThread() {Thread t {0L, 0L};return t; }在调用 switchThread() 函数时传入一个空的 Thread 代表当前线程switchContext() 会将当前线程的上下文等信息自动填入空结构中就完成了构建并且还自动存储在了栈上。我们来测试一下线程切换构建一个新线程切换到新线程再切换回启动线程。 // kernel/thread.c// 测试线程切换 void initThread() {// 构建新的启动线程Thread bootThread newBootThread(); // 构建新内核线程入口点为测试函数Thread tempThread newKernelThread((usize)tempThreadFunc); usize args[8];args[0] (usize)bootThread;args[1] (usize)tempThread;args[2] (long)M;// 新内核线程参数初始化将参数传入到tc.icswitchContext恢复完后会借助中断初始化恢复寄存器实现传参appendArguments(tempThread, args); switchThread(bootThread, tempThread); // 线程切换即线程栈和上下文切换printf(Im back from tempThread!\n); }让我们来理一下initThread() 函数全过程也就是第五章的全过程 构建空结构表示当前启动线程构建新的内核线程 创建内核线程的内核栈创建内核线程的上下文借助中断恢复机制来初始化新线程的每个寄存器 由于借助ThreadContext.InterruptContext的恢复机制故可以借助其传递参数进行参数赋值切换到新线程新线程的返回地址为__restore则恢复InterruptContext上下文实现传参在新线程入口函数结尾主动切换回启动线程 通过 switchThread() 进入新线程后再切换回启动线程就会继续执行下一行的 printf() 函数。在 main() 的最后调用这个函数并运行。 Init Interrupt ***** Init Memory ***** ***** Remap Kernel ***** Safe and sound! The char passed by is M Hello world from tempThread! Im back from tempThread! ** TICKS 100 ** ** TICKS 200 ** ....我们成功地进入了新线程传入了参数还成功地切换回来了
http://www.dnsts.com.cn/news/120240.html

相关文章:

  • 自己电脑做网站 路由器网站制作样板
  • 做二手车网站需要什么手续建设工程消防监督管理规定网站
  • 博客社区类网站模板桂林视频网站制作
  • 中国城乡住建部建设部网站余姚专业网站建设公司
  • 校园网站建设目的如何做网络营销宣传
  • 上海省住房与城乡建设厅网站wordpress作品集插件
  • 广州网络推广公司有哪些网站自己怎么做优化
  • 网站备案流程2016公司要做网站
  • 响应式网站怎么设置网络科技公司税收优惠政策
  • 免费软件下载网站入口正能量网站建设 作用
  • 网站设计遵循的原则凡科商城小程序收费吗
  • 网站建设价类型友汇网站建设一般多少钱
  • 电子商务网站开发背景及意义网络测速器
  • 传媒网站集团定制网站建设公司
  • 网站运营推广方案用于做网站的软件
  • vue做的网站有什么天津做网站制作
  • 微商网站北京pk10做号网站
  • 如何在后台做网站流程网站名称填写什么
  • 网站论坛制作搜索公司信息的网站
  • 专业做写生的网站营销网站怎么做
  • 东莞微网站制作公司全国各大网站
  • c 用mysql做的网站网站建设佰金手指科杰三
  • 深圳网站建设费用多少网站建设宣传文案
  • 门户网站免费建站上饶市建设局有什么网站
  • 福州网站备案营销型网站建设项目需求表
  • 做网站需要学php吗深圳网站的网络公司
  • dw做网站图片运用广西旅游必去十大景点排名
  • 做网站前怎么写文档十大永久免费污染软件
  • 公司网站建设需求表福建外贸网站
  • 张家界网站建设多少钱重庆手机网站开发