首页 > 其他分享 >MIT 6.S081 Thread switching

MIT 6.S081 Thread switching

时间:2023-07-20 10:47:05浏览次数:41  
标签:uint64 context swtch Thread cpu scheduler 进程 S081 MIT

Multiplexing

xv6 通过将 cpu 从一个进程切换到另一个进程来实现 multiplex(多路复用),进程的切换会在两种情形下发生:

  1. xv6 的 sleep 与 wakeup 机制在进程等待 IO 完成或者等待子进程退出又或者在 sleep 系统调用中等待的时候切换进程。
  2. xv6 会周期性地强制切换进程,从而应对那些长时间切换而未 sleep 的进程。

这个 multiplex 机制会让进程产生一种自己完全拥有 cpu 的错觉,就像 xv6 用虚拟内存和 page table 机制让进程觉得自己拥有完整的内存空间一样。

xv6 使用硬件定时器中断来保证 context switch(上下文切换)。

Code: Context switching

用户进程之间的切换步骤如下图所示:

b41nAVjQsSLyDBv

用户进程之间的切换其实会经过两次 context switch,以上图为例,第一次是从 shell 用户进程的 kernel thread 切换到 cpu 的 scheduler thread;第二次从 cpu 的 scheduler thread 切换到新用户进程(例如 cat)的 kernel thread。

在 xv6 中,我们可以认为每个用户进程,包含一个内核线程与一个用户线程,然后每个 cpu 包含一个 scheduler thread,schedular thread 工作在内核中,有只属于它的 kernel stack。

swtch 执行为内核线程切换的保存和恢复工作。swtch 的主要工作就是保存和恢复 riscv 的寄存器,又被称为上下文。

当一个进程要让出 cpu 时,该进程的内核线程会调用 swtch 来把该进程的 context 保存起来,并返回到 scheduler 线程的 context,每个 context 都包含于 strcut contextcontext 结构体可能是 struct proc 的成员或者 struct cpu 的成员( scheduler 线程)。

struct context {
  uint64 ra;
  uint64 sp;
  // callee-saved
  uint64 s0;
  uint64 s1;
  uint64 s2;
  uint64 s3;
  uint64 s4;
  uint64 s5;
  uint64 s6;
  uint64 s7;
  uint64 s8;
  uint64 s9;
  uint64 s10;
  uint64 s11;
};

swtch 函数实际上是利用汇编实现的,它接受两个参数 struct context *oldstruct context *new,将当前寄存器保存在 old,中然后从 new 中加载内容到当前寄存器,然后 return

用户进程要让出 cpu,切换到 scheduler 线程,会经历 usertrap => yield => sched => swtch 的过程,swtch 把当前进程的上下文保存在 p->context 中,从 cpu->context 加载当前 cpu 的 scheduler 线程的 context。

swtch 只会保存 callee-saved 寄存器(这里 sched 是 caller, swtch 是 callee)。swtch 不保存 pc,而是保存 ra 寄存器(ra 寄存器存放了 swtch 调用结束后应该返回到的地址,可以理解为 swtch 语句的下一条语句)。

scheduler 的 context 是在 scheduler 线程调用 swtch 的时候被保存在 cpu->context 的。

进程切换的流程可以这么理解,进程 a 调用 swtch,将进程 a 的 context 保存在 proca->context(包括进程 a 的 ra 寄存器),从 cpu->context 中加载 context,由于 context 中包含了 ra 寄存器,而 swtch 函数的最后一条指令就是 ret,因此会跳转到 ra 寄存器的地址处继续执行,这里应该就是执行 scheduler 函数中的 c->proc = 0,由于 scheduler 本身是个死循环,c->proc = 0 的下一个语句就是一个新的 for 循环,就又会执行到 swtch,将 scheduler 线程的 context 保存在 cpu->context 中,然后从 procb->context 中加载 context(包括进程 b 的 ra 寄存器),然后跳转到进程 b 的 ra 寄存器的地址处继续执行(这里类比进程 a,就是进程 b 调用的 swtch 语句的下一条语句),这里从结果来看,就是让进程 b 会从系统调用或者中断响应中退出,继续执行它的本职工作。

Code: Scheduling

scheduler 的实现如下:

void scheduler(void) {
    struct proc *p;
    struct cpu *c = mycpu();

    c->proc = 0;
    for (;;) {
        // Avoid deadlock by ensuring that devices can interrupt.
        intr_on();

        for (p = proc; p < &proc[NPROC]; p++) {
            acquire(&p->lock);
            if (p->state == RUNNABLE) {
                // Switch to chosen process.  It is the process's job
                // to release its lock and then reacquire it
                // before jumping back to us.
                p->state = RUNNING;
                c->proc = p;
                swtch(&c->context, &p->context);

                // Process is done running for now.
                // It should have changed its p->state before coming back.
                c->proc = 0;
            }
            release(&p->lock);
        }
    }
}

我们可以发现,在调用 swtch 时,xv6 持有 &p->lock,但是这个锁,会在 scheduler 线程中被解锁

彼此之间有意识地通过线程传递 cpu 控制权的进程有时候也被称作协程(coroutines)schedscheduler 就可以看作是彼此的协程。

There is one case when the scheduler’s call to swtch does not end up in sched. allocproc sets the context ra register of a new process to forkret (kernel/proc.c:508), so that its first swtch “returns” to the start of that function. Forkret exists to release the p->lock; otherwise, since the new process needs to return to user space as if returning from fork, it could instead start at usertrapret.

标签:uint64,context,swtch,Thread,cpu,scheduler,进程,S081,MIT
From: https://www.cnblogs.com/zwyyy456/p/17567672.html

相关文章

  • Java详解ThreadLocal
    threadlocal1基础现象:threadlocal作为一个全局变量,在不同的线程去get的时候能够获取不同的值。应用场景:SimpleDateFormat线程不安全,每个线程都要用,new太多,放到threadlocal中线程池可反复使用。一个请求链路很长,经过数个服务,每次都要放到参数带着。改为直接放到threadlocal作为上下......
  • python threadpool
    Python线程池详解在并发编程中,线程池是一种常见的设计模式,它可以提高程序的性能和响应能力。Python中有许多库可以实现线程池,其中最常用的是concurrent.futures模块中的ThreadPoolExecutor类。本文将介绍Python线程池的工作原理、使用方法和一些示例代码。什么是线程池?线程池是......
  • 【linux】gcc编译选项:-fomit-frame-pointer,-fno-tree-vectorize,-fno-strict-aliasing
    Date:2018.9.81、参考https://www.cnblogs.com/islandscape/p/3444122.htmlhttps://blog.csdn.net/chdhust/article/details/8462414https://gcc.gnu.org/onlinedocs/gcc-6.2.0/gcc.pdfhttps://blog.csdn.net/u012927281/article/details/50999138https://blog.csdn.net/sof......
  • 新晋 Committer!来自复旦大学的帅哥一枚
    点亮Star⭐️·支持我们https://github.com/apache/dolphinscheduler最近,社区星力量又迎来一位新晋Committer,这次是来自复旦大学研究生在读的王维饶同学,一起来认识一下吧!个人简介姓名:王维饶职位:复旦大学研究生在读GitHubID:Radeity感兴趣领域:平时在实验室会做一些偏......
  • 新晋 Committer!来自复旦大学的帅哥一枚
    点亮Star⭐️·支持我们https://github.com/apache/dolphinscheduler最近,社区星力量又迎来一位新晋Committer,这次是来自复旦大学研究生在读的王维饶同学,一起来认识一下吧!个人简介姓名:王维饶职位:复旦大学研究生在读GitHubID:Radeity感兴趣领域:平时在实验室会做一些偏......
  • ThreadPoolExecutor线程池用法简介
    ThreadPoolExecutor 是Java中用于管理线程池的类,它提供了一种方便的方式来执行多线程任务。通过使用线程池,我们可以有效地管理和复用线程,提高程序的性能和资源利用率。下面是 ThreadPoolExecutor 线程池的详细用法介绍:创建线程池对象:ThreadPoolExecutorexecutor=ne......
  • MySQL在分页查询时的limit深分页问题
    在平时业务中我们会发现当分页数据特别大的时候,会出现SQL很慢的情况,下面我们来分析下为什么会出现这种情况以及如何去解决一、limit深分页问题解析我们有如下一张表CREATETABLEaccount(idint(11)NOTNULLAUTO_INCREMENTCOMMENT'主键Id',namevarchar(255)DEFAU......
  • git commit 时报错:husky - pre-commit hook exited with code 1 (error)
    在使用git进行commit时出现错误:husky-pre-commithookexitedwithcode1(error)。方式一chatgpt的回答是:报错信息“husky-pre-commithookexitedwithcode1(error)”表示在执行Git提交操作时,pre-commit钩子脚本返回了非零的退出码,表示出现了错误。这种......
  • MIT 6.S081 Multiprocessors and locking
    whylock防止多核并行运行下的racecondition导致的错误。内核中的数据是典型的concurrently-accessed的数据。raceconditionandhowthelockavoiditAraceconditionisasituationinwhichamemorylocationisaccessedconcurrently,andatleastoneaccess......
  • git tag commit ID 标签
    gittag是给commitID标签,这样能让人知道代码在哪个节点,发布了版本,或截至到哪个ID,来做个记录1.查看本地所有tag:gittag或者gittag-l2.查看远程所有tag:gitls-remote--tagsorigin3.指定标签信息tag:gittag-av1.14.创建附注标签示例:gittag-av0.1......