xv6 磁盘中断流程和启动时调度流程
- 首发公号:Rand_cs
本文讲述 xv6 中的一些细节流程,还有对之前文中遗留的问题做一些补充说明,主要有以下几个问题:
- 一次完整的磁盘中断流程
- 进入调度器后的详细流程
- sched 函数中的条件判断
- scheduler 函数中为什么要周期性关中断
一次完整的磁盘流程
此节讲述完整的磁盘读写流程,读写的流程总体差不多,这里以读为例子,先看“流程图”(看代码时的笔记图)

read
int $T_SYSCALL
sys_read
fileread
readi
bread
bget
iderw
idestart
还是从 A 进程的用户态 read 函数开始:
- A 进程用户态调用 read 读取磁盘上的数据
- read 通过 INT 0x80 软件中断,通过中断门进入内核,此时会关中断(NOTE 这里我以中断门来实现系统调用为例,会关中断,xv6 源代码是以陷阱门实现系统调用,不会关中断)
- 期间多次取锁放锁,进行了多次 pushcli 和 popcli,但总是成对存在,所以目前总体还是处于 0 次 puchcli 状态
- 如果磁盘数据没有缓存,调用 iderw 来读写磁盘
A 进程内核态,iderw 函数:
- acquire(&idelock),获取磁盘锁,pushcli,cpu.IF = 0,1 次pushcli 状态
- 调用 idestart,将要读写的命令,扇区号等信息写进磁盘端口,以此来请求磁盘操作。写磁盘端口是通过 out 指令实现的。向磁盘发送命令后,磁盘就会工作,磁盘完成工作后就会向 cpu 发送中断信号。
- A 进程调用 sleep 等待磁盘操作完成。在 sleep 函数中,获取 ptable.lock,释放 idelock,然后调用 sched 让出
by the way,这里补充说明 sched 函数中的条件检查,之前的文章都一笔带过了:
void sched(void) //让出CPU,重新调度
{
int intena;
struct proc *p = myproc();
if(!holding(&ptable.lock)) // 必须持有 ptable.lock
panic("sched ptable.lock");
if(mycpu()->ncli != 1) // 1 次 pushcli 状态
panic("sched locks");
if(p->state == RUNNING) // 只可能是 SLEEPING、RUNNABLE、ZOMBIE 三种状态之一
panic("sched running");
if(readeflags()&FL_IF) // 此时肯定处于关中断状态(通过中断门进入内核会关中断,1次pushcli状态也应该对应关中断状态)
panic("sched interruptible");
intena = mycpu()->intena;
swtch(&p->context, mycpu()->scheduler);
mycpu()->intena = intena;
}
sched 函数中有 4 个条件检查:
- xv6 是个多 CPU 多任务系统,在 sched 任务调度的时候,需要持有 ptable.lock,不然进程的上下文会发生紊乱,举个简单的例子,A 时间片到了,先将 A 的状态设置为 RUNNABLE,然后调用 sched 让出 CPU,如果此时没有持有 ptable.lock,那么 A 进程便可能在另一个 CPU 上被调度,那么便出现一个进程在两 CPU 上运行的情况,Error
- 在 sched 函数中应当只有 1 次 pushcli 状态,这个条件检查感觉有点难以理解。从实践看代码确实,不管从哪条路径到达 sched 函数,都应该只有 1 次 pushcli,这是获取 ptable.lock 的锁造成的。从个人理解上说,sched 是为了调度进程,是要从 A 进程到 B 进程,那么 A 进程的开关中断(pushcli popcli 次数)不应该带入 B 进程,除了一种情况——调度,那就是 A 进程获取 ptable.lock 但是要在 B 进程中释放 ptable.lock。所以 sched 中应当只有 1 次 pushcli
- 在真正切换进程上下文之前,会首先修改旧进程的状态,在 xv6 中是 SLEEPING、RUNNABLE、ZOMBIE 三种之一
- 在 sched 中理应处于关中断状态,如果是通过中断门进入内核的,那么本身就处于关中断。如果是通过陷阱门进入内核,那么有 1 次 pushcli,也会处于关中断状态
回到磁盘中断,当 A 进程调用 sched 切换到 B 进程,这里假如 B 进程最初是因为时间片到了,调用 yield->sched->swtch 主动让出 CPU 的,则 B 进程的流程如下:
- 回到 B 进程 sched 函数中的 swtch 下一条指令处,然后释放 ptable.lock,此时 cpu.IF = 0,0 次 pushcli 状态,cpu.IF = 0 仍然处于关中断状态 是因为 A 进程通过中断门进入内核关中断造成的
- B 进程经过一些列指令后,最后执行 iret 返回 B 进程的用户态,此时会开中断
- NOTE,这里我们假设 CPU 内部逻辑:每条指令执行后都会检查是否有中断发生,如果有中断发生且开中断的情况下,则去处理中断。再假设,此前的磁盘操作已完成,已经向 CPU 发生了中断信号。但是在此之前 CPU 一直没有去处理中断,是因为在此之前一直处于关中断状态。
- Now,CPU 处于开中断状态,继续执行 B 进程的指令,开中断后的第一条指令执行完成后,检查是否有中断发生,发现有磁盘中断,那么中断 B 进程,通过中断门进入内核(该过程关中断)
- 执行磁盘中断处理程序,也就是执行 insl 指令从 0x1f0 端口将磁盘数据读取到内存,然后唤醒等待该磁盘事件的进程,在我们的例子当中就是 A 进程
- 中断执行完成,iret 返回 B 进程用户态(该过程开中断)
- 继续执行 B 进程的指令
- 时钟中断 B 进程,再次通过中断门进入内核(关中断),发现 B 进程的时间片到了,那么调用 yield->sched->swtch 重新调度进程,这里假设调度到 A 进程
回到 A 进程的内核态,准确来说回到 iderw->sleep->sched->swtch 的下一条指令
- A 进程执行 release(ptable.lock)、acquire(idelock)、release(idelock),此时状态: cpu.IF = 0,0 次 pushcli
- 将磁盘中断获取的数据 cp 到 A 进程内核态
- A 进程层层返回
- 最后 iret 返回 A 进程用户态(开中断)
系统启动进入调度器后的流程

main
userinit //准备好 initcode 进程
mpmain
scheduler // 进入调度器
调度器上下文:
- 第一次进入 scheduler,for 循环找到 RUNNABLE 进程,目前就只有一个 initcode 进程为 RUNNABLE 进程,找到并切换上下文到 initcode 进程
initcode 进程上下文:
- 执行 forkret 函数,因为是第一次执行,会首先执行 iinit 来初始化根文件系统
- 执行 readsb 从磁盘中读取超级块数据,期间会使用 iderw 读写磁盘,具体流程见第一小节。总之,initcode 进程会调用 sleep 函数让出 CPU 来等待磁盘操作
- 在 sleep->sched->swtch 中再次切换上下文到 调度器上下文
调度器上下文:
- 切换到内核页表,然后遍历任务队列,寻找 RUNNABLE 进程,但是目前只有一个且处于 SLEEPING 状态的进程,所以这里调度器会轮询空转,直到磁盘中断处理完成,initcode 进程被唤醒。
- 再次切换上下文到 initcode 进程
initcode 进程上下文
- 回到 forkret->iinit->readsb->bread->iderw->sleep->sched->swtch 的下一条指令处,然后层层返回到 forkret
- 再执行 initlog 恢复日志,这里会涉及到两次磁盘读写,道理同上,不再赘述
- 第 4 次调度到 initcode 进程后,forkret 函数执行完毕,再执行 trapret 函数,其中包含了 iret 指令,至此回到用户态,开始执行 initcode 进程的逻辑
by the way again,这里解释为什么在 scheduler 函数中需要周期性的开中断:
void scheduler(void)
{
for(;;){
sti(); // 周期性开中断
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ //循环找一个RUNNABLE进程
...
}
}
}
进入调度器上下文有两条路径:
- 系统刚启动进入 scheduler
- sched 函数中 swtch 上下文到调度器
回想前面说的 sched 函数,在它切换到新进程并返回用户态之前理应都处于关中断的状态。而一直处于关中断且没有开中断的话会引发死循环。
举个例子,假设没有周期性的开中断,也就是 scheduler 代码长这样的话:
void scheduler(void)
{
for(;;){
// 遍历进程列表,寻找 RUNNABLE 进程
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
...
}
}
}
假如当前系统只有一个进程(shell进程),它需要等待键盘输入而被阻塞(state==SLEEPING),内层循环是找不到 RUNNABLE 进程的,便回到外层循环,外层循环现在相当于什么也不做,便又再次进入内层循环。如此下来死循环。
而加入周期性的开中断后,CPU 便会响应中断。当有键盘输入时,中断当前的调度上下文而进入中断上下文,执行键盘中断处理程序,唤醒 shell 进程,中断处理完成后再回到调度上下文。此时内层循环便能找到一个 RUNNABLE 进程,然后切换到它的上下文执行。
本文就先补充这么多吧,这补充系列的文章是之前做了关于 xv6、nemu 的项目,将 xv6 启动到 nemu 上,这需要对很多地方细扣,对 xv6 的理解又增加了一些,分享出来。
停更这么久啊,一直再忙工作,学习新的东西,时间不是很多,当然也有懒的原因,后面慢慢克服回归吧。OK,那有什么问题欢迎来讨论交流。
- 首发公号:Rand_cs
xv6 磁盘中断流程和启动时调度流程的更多相关文章
- MySQL指定mysqld启动时所加载的配置文件
mysqld.exe --init-file=file_name 以下为配置文件参数优化和中文详解: [client]port = 3306socket = /tmp/mysql.sock [mysq ...
- Linux系统开机启动时的工作原理
Linux系统开机启动时的工作原理也是深入了解Linux系统核心工作原理的一个很好的途径. 启动第一步--加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的重要,以至 ...
- gevent调度流程解析
gevent是目前应用非常广泛的网络库,高效的轮询IO库libev加上协程(coroutine),使得gevent的性能非常出色,尤其是在web应用中.本文介绍gevent的调度流程,主要包括geve ...
- linux文件系统启动流程、启动脚本
linux文件系统启动流程.启动脚本 下面是一张Linux启动流程图: 在了解启动流程之前,我们应该先知道系统的几个重要脚本和配置文件,他们对应的路径为: 1. /sbin/init 2. /etc/ ...
- 《转》深入理解Activity启动流程(三)–Activity启动的详细流程1
本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--Activity启 ...
- Kubernetes调度流程与安全(七)
一.Kubernetes中的调度流程 1,介绍 Scheduler 是 k8s 中的调度器,主要的任务是把定义的 Pod 分配到集群的节点上.Scheduler 是作为一个单独的程序运行的,启动之后会 ...
- worker 启动时向 etcd 注册自己的信息,并设置一个带 TTL 的租约,每隔一段时间更新这个 TTL,如果该 worker 挂掉了,这个 TTL 就会 expire 并删除相应的 key。
1.通过etcd中的选主机制,我们实现了服务的高可用.同时利用systemd对etcd本身进行了保活,只要etcd服务所在的机器没有宕机,进程就具备了容灾性. https://mp.weixin.qq ...
- 以TiDB热点问题来谈Region的调度流程
什么是热点问题 说这个话题之前我们先回顾一下TiDB的主要结构和概念. TiDB的核心架构分为TiDB.TiKV.PD三个部分,其中TiKV是一个分布式数据存储引擎用来存储真实的数据,在TiKV中又对 ...
- scheduler源码分析——调度流程
前言 当api-server处理完一个pod的创建请求后,此时可以通过kubectl把pod get出来,但是pod的状态是Pending.在这个Pod能运行在节点上之前,它还需要经过schedule ...
- linux启动时文件系统错误问题
linux开机启动时,文件系统错误(磁盘有坏轨或文件系统出现错误) 原因: 1.系统运行时,突然断电或不正常关机,导致文件系统错误(文件系统错误并不是硬件错误,而是软件数据的问题) ...
随机推荐
- SVN Windows10的安装
SVN Windows安装与配置 先去到官网的下载链接:Download Apache Subversion Sources 然后点这个 binary packages 在这里能看到大多数的操作系统的 ...
- HarmonyOS NEXT应用开发之深色模式适配
介绍 本示例介绍在开发应用以适应深色模式时,对于深色和浅色模式的适配方案,采取了多种策略如下: 固定属性适配:对于部分组件的颜色属性,如背景色或字体颜色,若保持不变,可直接设定固定色值或引用固定的资源 ...
- 如何迁移 Flink 任务到实时计算
简介: 本文由阿里巴巴技术专家景丽宁(砚田)分享,主要介绍如何迁移Flink任务到实时计算 Flink 中来. 通常用户在线下主要使用 Flink run,这会造成一些问题,比如:同一个配置因版本而变 ...
- SAE助力「海底小纵队学英语」全面拥抱Serverless,节省25%以上成本
简介: 阿里云Serveless应用引擎SAE 具备免运维IaaS.按需使用.按量计费.低门槛服务应用上云,并且支持多种语言和高弹性能力等特点,刚好完美解决了客户长期以来运维复杂.资源利用率不高.开发 ...
- Spring官方RSocket Broker 0.3.0发布: 快速构建你的RSocket架构
简介:Spring官方的RSocket Broker其实开发已经非常久了,我以为会伴随着Spring Cloud 2021.0发布的,但是没有发生.不过Spring RSocket Broker还是 ...
- 【GUI开发】用python爬YouTube博主信息,并开发成exe软件!
目录 一.背景介绍 二.代码讲解 2.1 爬虫 2.2 tkinter界面 2.3 存日志 三.说明 一.背景介绍 你好,我是@马哥python说,一名10年程序猿. 最近我用python开发了一个G ...
- Docker手工部署GO环境
参考: (最新2020)Golang 使用Dockerfile 打包部署到 docker https://blog.csdn.net/weixin_44042863/article/details/1 ...
- linux文件查找工具详解
linux文件查找详解 目录 linux文件查找详解 1.linux文件查找工具 1.1 find命令详解 1.1.1 根据文件名查找 1.1.2 根据属主属组查找 1.1.3 根据文件类型查找 1. ...
- Mysql8.0在windows系统安装一直卡在Starting the server的解决方案
报错:Beginning configuration step: Starting Server Attempting to start service MySQL80 一直卡在这里,手动启动服务也起 ...
- shape-outside
shape-outside定义一个由内容区域的外边缘封闭形成的形状 shape-outside 是一个非常实用的属性,可以实现一些比较复杂的文本环绕效果. shape-outside 的兼容性比较好, ...