xv6源码阅读——进程切换
说明
- 阅读的代码是 xv6-riscv 版本的
概述
从一个用户进程(旧进程)切换到另一个用户进程(新进程)所涉及的步骤:
- 通过中断机制,从trap机制进入内核线程
- 调用swtch从该进程进行上下文切换,切换到到调度进程
- 通过调度进程切换到新进程的内核线程
- 在通过trap机制返回到新进程用户线程
上面过程就简单阐述,是怎样进行进程切换的
当然,里面会有很多细节,后面会详细介绍
从用户线程到内核线程
这一部分在上一章是有过介绍的,通过中断机制,通过uservec保存寄存器,然后进入usertrap()
usertrap
void
usertrap(void)
{int which_dev = 0;if((r_sstatus() & SSTATUS_SPP) != 0)panic("usertrap: not from user mode");//省略 ··········// give up the CPU if this is a timer interrupt.if(which_dev == 2)yield();usertrapret();
}
其他部分就不过多赘述了(上一篇博客讲的很详细),通过判断中断类型,这是一个定时器中断,就调用yield()函数
从旧进程进入调度进程
yield
// Give up the CPU for one scheduling round.
void
yield(void)
{struct proc *p = myproc();acquire(&p->lock);p->state = RUNNABLE;sched();release(&p->lock);
}
该函数干的事情很简单,先获取该进程的锁,将进程状态设置为RUNNABLE
然后调用sched()函数进行后续操作
题:为什么再设置进程状态前,要先获取该进程的锁呢
因为一旦将进程状态设置为RUNNABLE,调度器就会认为该进程处于等待状态的可能会让他接下来运行,但实际上,该进程还是处于运行状态的
如果这是一个多核CPU的,就可能会发生一个进程被两个CPU同时运行,程序可能会立马崩溃。
所以我们要保证改变进程状态,保存寄存器,载入另一个进程的寄存器这三步是原子的。
sched
void
sched(void)
{int intena;struct proc *p = myproc();if(!holding(&p->lock))panic("sched p->lock");if(mycpu()->noff != 1)panic("sched locks");if(p->state == RUNNING)panic("sched running");if(intr_get())panic("sched interruptible");intena = mycpu()->intena;swtch(&p->context, &mycpu()->context);mycpu()->intena = intena;
}
- 该函数操作也很简单,进行一系列判断(防御性编程)
- 将mycpu()->intena保存下来,因为切换进程后,他会被改变
- 调用swtch切换上下文,该函数完成后就会进行入了调度进程
- 恢复mycpu()->intena(要等到该进程再次拿到CPU)
swtch
这是一段汇编代码,主要干的工作是将该进程的寄存器保存下来,然后载入调度进程的寄存器
.globl swtch
swtch:# 保存寄存器sd ra, 0(a0)# 省略```````````````````sd s11, 104(a0)# 恢复所要切换进程的寄存器ld ra, 0(a1)# 省略```````````````````ld s11, 104(a1)ret
问题:为什么RISC-V中有32个寄存器,但是swtch函数中只保存并恢复了14个寄存器?
因为swtch函数是从C代码调用的,所以我们知道Caller Saved Register会被C编译器保存在当前的栈上。
该函数完成后,就进入了调度进程
调度
scheduler
void
scheduler(void)
{struct proc *p;struct cpu *c = mycpu();c->proc = 0;for(;;){//通过确保设备可以中断来避免死锁。intr_on();for(p = proc; p < &proc[NPROC]; p++) {acquire(&p->lock);if(p->state == RUNNABLE) {// 切换到所选的进(释放它的锁,然后在跳回我们之前重新获取它)p->state = RUNNING;c->proc = p;swtch(&c->context, &p->context);// 进程目前已完成运行。c->proc = 0;}release(&p->lock);}}
}
进程切换过来,是切换到swtch(&c->context, &p->context)这一行的,后面他会先释放锁
(避免该进程永远无法被调度,该锁是在yield()函数中获取的)scheduler在进程表上循环查找可运行的进程,该进程具有p->state == RUNNABLE。一旦找到一个进程,它将设置CPU当前进程变量c->proc,将该进程标记为RUNINING,然后调用swtch开始运行它
scheduler在一开始要获取进程锁,将RUNNABLE进程转换为RUNNING,在内核线程完全运行之前(在swtch之后,例如在yield中)绝不能释放锁。
调用swtch后就会进入另一个进程了
从调度进程进入新进程
该过程也是在完成swtch后完成切换的
他会从sched函数返回
void
sched(void)
{int intena;struct proc *p = myproc();//、、、、、、、、、、、、、、swtch(&p->context, &mycpu()->context);mycpu()->intena = intena;
}
他会返回到swtch处,然后恢复 mycpu()->intena
从内核线程返回用户线程
这后面就是一步步返回,通过trap机制返回用户层,在上一篇博客讲的很详细,不过多赘述
XV6线程第一次调用swtch函数
allocproc
static struct proc*
allocproc(void)
{struct proc *p;//、、、、、、、、、、省略memset(&p->context, 0, sizeof(p->context));p->context.ra = (uint64)forkret;p->context.sp = p->kstack + PGSIZE;return p;
}
在创建进程时,会设置ra和sp
ra很重要,因为这是进程的第一个switch调用会返回的位置。同时因为进程需要有自己的栈,所以ra和sp都被设置了。这里设置的forkret函数就是进程的第一次调用swtch函数会切换到的“另一个”线程位置。
当调度线程将CPU交给该进程时,该进程会从forkret()函数处返回,执行该函数,
// A fork child's very first scheduling by scheduler()
// will swtch to forkret.
void
forkret(void)
{static int first = 1;// Still holding p->lock from scheduler.release(&myproc()->lock);if (first) {// File system initialization must be run in the context of a// regular process (e.g., because it calls sleep), and thus cannot// be run from main().first = 0;fsinit(ROOTDEV);}usertrapret();
}
从代码中看,它的工作其实就是释放调度器之前获取的锁。函数最后的usertrapret函数其实也是一个假的函数,它会使得程序表现的看起来像是从trap中返回,但是对应的trapframe其实也是假的,这样才能跳到用户的第一个指令中。
xv6源码阅读——进程切换相关推荐
- xv6源码阅读——中断与异常
目录 说明 陷入机制概述 Traps from user space 调用逻辑 ecall uservec usertrap usertrapret userret sret Traps from k ...
- xv6源码阅读——文件系统
说明 阅读的代码是 xv6-riscv 版本的 七层结构 xv6文件系统实现分为七层 文件描述符(File descriptor) 路径名(Pathname) 目录(Directory) 索引结点(I ...
- Android源码阅读---init进程
Android源码阅读-init进程 文章目录 Android源码阅读---init进程 1. 编译命令和进程入口 1. init 进程编译命令 2. main函数流程 2. 主函数处理流程 1. 创 ...
- mysql 1260,MYSQL 源码阅读 六
前期节要 MYSQL源码阅读 一 MYSQL源码阅读 二 MYSQL源码阅读 三 MYSQL 源码阅读 四 MYSQL 源码阅读 五 上次有两个问题没搞明白 1 是 为什么一定要开启调试线程 ? 因为 ...
- 源码阅读:AFNetworking(十六)——UIWebView+AFNetworking
该文章阅读的AFNetworking的版本为3.2.0. 这个分类提供了对请求周期进行控制的方法,包括进度监控.成功和失败的回调. 1.接口文件 1.1.属性 /**网络会话管理者对象*/ @prop ...
- webpack源码阅读——npm脚本运行webpack与命令行输入webpack的区别
原文地址:webpack源码阅读--npm脚本执行webpack与命令行输入webpack执行的区别 如有错误,欢迎指正! webpack是目前被大家广为使用的模块打包器.从命令行输入webpack或 ...
- 【Dubbo源码阅读系列】之远程服务调用(上)
今天打算来讲一讲 Dubbo 服务远程调用.笔者在开始看 Dubbo 远程服务相关源码的时候,看的有点迷糊.后来慢慢明白 Dubbo 远程服务的调用的本质就是动态代理模式的一种实现.本地消费者无须知道 ...
- Spark源码学习之IDEA源码阅读环境搭建
软件准备 (1)Java 1.8 (2)Scala 2.11.12(需要在IDEA中安装) (3)Maven 3.8.2(需要在IDEA中配置) (4)Git 2.33 以上软件需要安装好,并进行环境 ...
- Mycat源码篇 : 起步,Mycat源码阅读调试环境搭建
在研究mycat源码之前必须先把环境搭建好.这篇文章的目标就是搭建mycat源码调试环境.环境主要包括: git jdk maven eclipse mysql 这里假设你知道上面的知识点.我们搭建的 ...
- DM 源码阅读系列文章(二)整体架构介绍
2019独角兽企业重金招聘Python工程师标准>>> 作者:张学程 本文为 DM 源码阅读系列文章的第二篇,第一篇文章 简单介绍了 DM 源码阅读的目的和规划,以及 DM 的源码结 ...
最新文章
- Go 学习笔记(12)— 字典map定义、初始化、读取字典、删除字典、清空字典、map 按 key 进行有序遍历
- R对数秩检验(log rank test)
- (转)access和SQL语句的区别
- 洛谷——P2083 找人
- Kotlin binding+RecyclerView实现支付宝首页更多、应用编辑界面
- [js] 写一个方法,实时验证input输入的值是否满足金额如:3.56(最多只有两位小数且只能数字和小数点)的格式,其它特殊字符禁止输入
- Windows Server 2016补丁更新机制
- @@Autowired依赖注入先后顺序
- java做抽奖小程序_随机抽奖小程序
- (短除法)求两个给定正整数的最大公约数和最小公倍数。
- 『贪心』阿狸和桃子的游戏
- 《今日简史》2018比尔盖茨推荐(pdf, mobi, epub三种格式)
- 弗洛伊德篇-梦的解析
- 2022年(23届)电子信息/通信工程保研|四非上岸浙大的保研之旅(浙大、中科大、哈工大、东南、南开、西电、成电等)
- Your project path contains non-ASCII characters. 解决办法
- 通过js实现切换背景颜色
- 数据结构与算法(陈越版)第五讲 (树下)树的应用——集合及其运算
- python调用百度api接口_python调用百度API
- vb6.0 CLng,CInt等的bug及其解决办法
- 计算机网络虚电路数据报,计算机网络——网络层-虚电路和数据报网络