操作系统—修改xv6内核调度算法

文章目录

  • 修改xv6内核调度算法
    • 1.实验环境
    • 2.基于优先级的调度算法
      • (1).基本实现思路
      • (2).实现流程
      • (3).一些问题
    • 3.乐透调度算法
      • (1).思路
      • (2).实现流程
      • (3).一些问题
    • 总结
    • 参考资料

修改xv6内核调度算法

1.实验环境

  这一次的实验因为是在xv6内核中实现一些调度算法,因此我本次实验直接采用了Lab中使用的xv6内核实验环境:
在这里插入图片描述

2.基于优先级的调度算法

(1).基本实现思路

  为了实现的便利,我首先决定实现的调度算法是优先级调度策略,这个策略的实现相对比较简单,我大概只需要在PCB中增加一个priority属性,在修改完调度函数之后,只需要在后续增加调整priority字段的操作即可。

(2).实现流程

  那么首先么当然是修改proc.h当中的struct proc的定义:

struct proc {struct spinlock lock;// p->lock must be held when using these:enum procstate state;        // Process statestruct proc *parent;         // Parent processvoid *chan;                  // If non-zero, sleeping on chanint killed;                  // If non-zero, have been killedint xstate;                  // Exit status to be returned to parent's waitint pid;                     // Process ID// these are private to the process, so p->lock need not be held.uint64 kstack;               // Virtual address of kernel stackuint64 sz;                   // Size of process memory (bytes)pagetable_t pagetable;       // User page tablestruct trapframe *trapframe; // data page for trampoline.Sstruct context context;      // swtch() here to run processstruct file *ofile[NOFILE];  // Open filesstruct inode *cwd;           // Current directorychar name[16];               // Process name (debugging)#ifdef PrioSchedint priority;                // Process priority(default 50)uint64 rtime;                // Process running times#endif
};

  对于优先级调度的两个参数,仅当PrioSched宏定义的时候才会生效,priority就是简单的优先级数值,默认为50,ctime是PCB的创建时间,在优先级相同的情况下优先执行ctime比较小的进程,因此在修改了struct proc之后,还可以修改proc.c的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();int found = 0;#ifdef PrioSchedstruct proc* high_prio = 0;for (p = proc; p < &proc[NPROC]; p++) {acquire(&p->lock);if (p->state == RUNNABLE) {if (high_prio == 0) {high_prio = p;}else {acquire(&high_prio->lock);int is_lock = 1;if (p->priority > high_prio->priority) {release(&high_prio->lock);is_lock = 0;high_prio = p;}else if (p->priority == high_prio->priority) {if (p->rtime < high_prio->rtime) {release(&high_prio->lock);is_lock = 0;high_prio = p;}}if (is_lock) {release(&high_prio->lock);}}found = 1;}release(&p->lock);}if (found == 1) {acquire(&high_prio->lock);if (high_prio->state != RUNNABLE) {release(&high_prio->lock);continue;}high_prio->rtime++;high_prio->state = RUNNING;c->proc = high_prio;swtch(&c->context, &high_prio->context);  c->proc = 0;release(&high_prio->lock);}#endif#ifdef DEFAULT_SCHEDfor (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;found = 1;}release(&p->lock);}#endifif (found == 0) {intr_on();asm volatile("wfi");}}
}

  在定义了PrioSched宏之后,scheduler的行为也会随之改变,CPU会依次查找整个进程表,以找到优先级最高的进程,并且之后进行上下文切换进行执行,它的修改是比较简单的,因此接下来我们要做的就是对进程初始化的一些函数进行修改。

  所以这里首先修改的是allocproc,在这里增加对于优先级和rtime的初始化操作:

static struct proc *allocproc(void) {...
found:// add priority based scheduler args#ifdef PrioSchedp->priority = 50;p->rtime = 0;#endifp->pid = allocpid();// Allocate a trapframe page....
}

  之后操作的就是fork函数,在最后添加了对应增加优先级参数的两行代码:

int fork(void) {...np->state = RUNNABLE;#ifdef PrioSchednp->priority = 25;np->rtime = 0;#endifrelease(&np->lock);return pid;
}

  对应的,在freeproc函数里也要增加相应的操作:

static void freeproc(struct proc *p) {if (p->trapframe) kfree((void *)p->trapframe);p->trapframe = 0;if (p->pagetable) proc_freepagetable(p->pagetable, p->sz);p->pagetable = 0;p->sz = 0;p->pid = 0;p->parent = 0;p->name[0] = 0;p->chan = 0;p->killed = 0;p->xstate = 0;p->state = UNUSED;#ifdef PrioSchedp->priority = 0;p->rtime = 0;#endif
}

  在完成了所有这些操作之后,实际上就完成了:
在这里插入图片描述

  xv6内核成功启动,并且运行各种其中的程序也都是可以正常运行的,实际上这个代码应该是正确的了,因为在之前写的代码中实际上出现了很多次panic

(3).一些问题

  我写的早期的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();int found = 0;#ifdef PrioSchedstruct proc* high_prio = 0;for (p = proc; p < &proc[NPROC]; p++) {acquire(&p->lock);if (p->state == RUNNABLE) {if (high_prio == 0) {high_prio = p;}else {if (p->priority > high_prio->priority) {high_prio = p;}else if (p->priority == high_prio->priority) {if (p->rtime < high_prio->rtime) {high_prio = p;}}}found = 1;}release(&p->lock);}if (found == 1) {acquire(&high_prio->lock);high_prio->rtime++;high_prio->state = RUNNING;c->proc = high_prio;swtch(&c->context, &high_prio->context);  c->proc = 0;release(&high_prio->lock);}#endif...}
}

  实际上最主要的区别就在于最后判断found是否为1准备进行上下文切换的时候没有再次判断已经获取到的high_prio进程目前是否是RUNNABLE状态,这导致了后续尝试启动系统的时候:只有编译时附加CPUS=1,即禁用多处理器情况下才能正常工作,而多处理器情况下都会直接在trap处理程序当中报错

  也就是最下方的报错,这里应该是因为panic或者printf没有保障线程安全所以打印发生了错乱:
在这里插入图片描述

  这个bug困扰了我很久,我尝试定位了具体的错误位置,主要应该就在两个地方:

void kerneltrap() {...if ((which_dev = devintr()) == 0) {printf("scause %p\n", scause);printf("sepc=%p stval=%p\n", r_sepc(), r_stval());panic("kerneltrap");}// give up the CPU if this is a timer interrupt.if (which_dev == 2 && myproc() != 0 && myproc()->state == RUNNING) yield();...
}

  首先是trap.c中的内核trap处理程序kerneltrap,这里会将scause寄存器保存的地址等信息全部打印出来,并且报一个panic,不难发现,报错信息里的panic貌似不是kerneltrap,仔细观察之后发现应该是sched lock,而这个在上一次的Lab当中实际上已经研究过了:

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;
}

  实际上是程序调用sched()函数的时候在判断当前CPU的关中断操作栈的计数是否为1,也就是说实际上在两个CPU同时试图运行同一个进程的时候,会出现上述的一系列问题,这一系列问题目前暂时还不明确机制是什么样的,之后我应该还会继续研究相关的问题,不过至少在我意识到这个问题,加上代码之后,它就可以正常执行了。

  不过实际上基于优先级的调度算法还需要考虑一些别的东西,比如设置进程优先级的系统调用等,这些我暂时都还没有实现。

3.乐透调度算法

(1).思路

  这个调度算法实际上比优先级调度算法要更简单一点,每一个进程都有彩票的张数,每一次遍历进程表时,找到一个就绪态进程就尝试进行抽奖,如果抽出的数量小于某个进程拥有彩票的张数,那么就轮到当前这个进程进行调度,所以它的实现应该是非常简单的。

(2).实现流程

  第一步还是给proc.h里的struct proc增加tickets字段:

struct proc {struct spinlock lock;// p->lock must be held when using these:enum procstate state;        // Process statestruct proc *parent;         // Parent processvoid *chan;                  // If non-zero, sleeping on chanint killed;                  // If non-zero, have been killedint xstate;                  // Exit status to be returned to parent's waitint pid;                     // Process ID// these are private to the process, so p->lock need not be held.uint64 kstack;               // Virtual address of kernel stackuint64 sz;                   // Size of process memory (bytes)pagetable_t pagetable;       // User page tablestruct trapframe *trapframe; // data page for trampoline.Sstruct context context;      // swtch() here to run processstruct file *ofile[NOFILE];  // Open filesstruct inode *cwd;           // Current directorychar name[16];               // Process name (debugging)#ifdef PrioSchedint priority;                // Process priority(default 50)uint64 rtime;                // Process running times#endif#ifdef LotterySchedint tickets;                 // Process tickets for Lottery Scheduler#endif
};

  然后再在allocproc函数中增加对于乐透调度的彩票数初始化,这里初始化的规则是对于每个进程初始都分配一张彩票(同步也在freeproc中增加了对应代码):

static struct proc *allocproc(void) {...
found:// Add priority based scheduler args#ifdef PrioSchedp->priority = 50;p->rtime = 0;#endif#ifdef LotterySchedp->tickets = 1;#endifp->pid = allocpid();...
}

  还需要实现两个函数:

int random(int max) {if(max <= 0) {return 1;}static int z1 = 12345; // 12345 for rest of zxstatic int z2 = 12345; // 12345 for rest of zxstatic int z3 = 12345; // 12345 for rest of zxstatic int z4 = 12345; // 12345 for rest of zxint b;b = (((z1 << 6) ^ z1) >> 13);z1 = (((z1 & 4294967294) << 18) ^ b);b = (((z2 << 2) ^ z2) >> 27);z2 = (((z2 & 4294967288) << 2) ^ b);b = (((z3 << 13) ^ z3) >> 21);z3 = (((z3 & 4294967280) << 7) ^ b);b = (((z4 << 3) ^ z4) >> 12);z4 = (((z4 & 4294967168) << 13) ^ b);// if we have an argument, then we can use itint rand = ((z1 ^ z2 ^ z3 ^ z4)) % max;if(rand < 0) {rand = rand * -1;}return rand;
}int totalTickets() {struct proc* p;int tickets = 0;for (p = proc; p < &proc[NPROC]; p++) {if (p->state == RUNNABLE) {tickets += p->tickets;}}return tickets;
}

  一个用于生成乐透调度过程中的伪随机数(random),还有一个则是统计目前可以用来抽奖的所有进程的彩票总数,最后就是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();int found = 0;...#ifdef LotterySchedfor (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.int total = totalTickets();int prize = -1;if (total > 0 || prize <= 0) {prize = random(total);}prize -= p->tickets;if (prize >= 0) {release(&p->lock);continue;}if (p != 0) {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;found = 1;}}release(&p->lock);}#else...}
}

  最终也成功运行了,经过测试,乐透调度的实现应该也是正确的了
在这里插入图片描述

(3).一些问题

  因为参考的一些实现是基于x86版的xv6内核,x86版的xv6内核在进程调度方面和risc-v版本的略有差异,其实主要体现在:risc-v版内核对每个PCB都有一个单独的自旋锁来保护,而x86版的内核只对整个进程表上一把大锁,所以这时候下面这种写法是合法的:

void scheduler(void) {struct proc *p;struct cpu *c = mycpu();c->proc = 0;for (;;) {// Avoid deadlock by ensuring that devices can interrupt.intr_on();int found = 0;...#ifdef LotterySched...if (prize >= 0) continue;...}
}

  即一旦没有抽中,则立刻continue开始尝试对下一个进程进行遍历,但是在risc-v版本的xv6中如果这么做,就会在没有释放锁的情况下开始后续的遍历,所以这时候错误就显而易见了。

  其实还有一个值得注意的点就是:我实现的totalTickets函数实际上也是基于对整个表上一把锁这个思路去实现的:

int totalTickets() {struct proc* p;int tickets = 0;for (p = proc; p < &proc[NPROC]; p++) {if (p->state == RUNNABLE) {tickets += p->tickets;}}return tickets;
}

  在这里的实现当中完全是无锁的,对于整个进程表上锁的情况来说,这种写法完全是争取的,但是对于risc-v就无法保证了,但是我也思考过直接加锁的情况,实际上问题在于:如果在遍历的时候对每一个进程加锁,最后两个CPU可能会互相死锁,这个问题我可能没有办法解决,所以这样的写法虽然可能会导致没有进程可以被调度,但是至少不会出现死锁,后续可能还要针对这个问题进行改进

总结

  这一次的调度算法实现实际上还是有一定的难度的,因为这一次相当于是在对xv6的关键操作进行修改,在实现第一个基于优先级的调度算法的时候我出现了相当多的问题,花费了一整个早上的时间进行debug,实际上对于内核的debug是相当困难的,有些情况可能断点都非常难命中。

  不过好在最后还是实现了两个调度算法,后续的诸如多级反馈队列(MLFQ)之类的调度算法以后我应该还会继续尝试实现的。

参考资料

  • [Medium]-Modifying riscv-xv6
  • [GitHub]-tweaked-xv6
  • [Medium]-xv6 -Implementing ps, nice system calls and priority scheduling
  • [GitHub]-Customized-xv6-OS
  • [GitHub]-xv6-Custom-Scheduling-Algorithm
  • [GitBook]-Build a OS-XV6 CPU Scheduling

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/804479.shtml

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

相关文章

在Linux系统上实现TCP(socket)通信

一.什么TCP TCP&#xff08;传输控制协议&#xff09;是一种面向连接的、可靠的、基于字节流的传输层通信协议。 二.TCP通信流程 三. TCP 服务器端 1 创建socket int sockfd socket(AF_INET, SOCK_STREAM, 0); //SOCK_STREAM tcp通信2 绑定(bind) struct sockaddr_in myad…

C++实现幻方实验

我们这个实验目的是实现大于2的奇数的n阶幻方 根据上述的例子我们可以看到一些规律&#xff0c;显示1放在最上方中间的位置&#xff0c;然后向右上方延申&#xff0c;在达到n这个数字时&#xff0c;停止延申&#xff0c;然后在n的下方开始n1的新一轮延申。明白了原理之后就很容…

计算机专业,不擅长打代码,考研该怎么选择?

考研其实和你的代码能力关系不大 所以在选学校以前可以看看有哪些学校复试是要求上机撸代码的&#xff0c;可能会要求比较严 初试真的不用担心代码问题&#xff0c;我也是基本零编程能力就开始备考考研的... 本人双非科班出身备考408成功上岸&#xff0c;在这里也想给想考40…

css面试题--定位与浮动

1、为什么需要清除浮动&#xff1f; 在非IE浏览器下&#xff0c;容器不设高度且子元素浮动时&#xff0c;容器高度不能被内容撑开&#xff0c;内容会溢出到容器外面而影响布局。这种现象被称为浮动。 浮动的原理&#xff1a;浮动元素脱离文档流&#xff0c;不占用空间&#xff…

使用 wangeditor 解析富文本并生成目录与代码块复制功能

在 Web 开发中&#xff0c;经常需要使用富文本编辑器来编辑和展示内容。wangeditor 是一个强大的富文本编辑器&#xff0c;提供了丰富的功能和灵活的配置&#xff0c;但是官方并没有提供目录导航和代码块的复制功能&#xff0c;所以我自己搞了一个 <template><div cla…

5个超好用的Python工具,赶紧码住!

Python开发软件可根据其用途不同分为两种&#xff0c;Python代码编辑器和Python集成开发工具&#xff0c;两者配合使用极大的提高Python开发人员的编程效率。掌握调试、语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制等操作。 Python常用工具&…

小白新手学习 Python 使用哪个 Linux 系统更好?

对于小白新手学习Python&#xff0c;选择哪个Linux系统是一个很重要的问题&#xff0c;因为不同的Linux发行版&#xff08;distribution&#xff09;有着不同的特点、优势和适用场景。在选择时&#xff0c;需要考虑到易用性、学习曲线、社区支持等因素。 Ubuntu Ubuntu 是一个…

分布式系统中的唯一ID生成方法

通常在分布式系统中&#xff0c;有生成唯一ID的需求&#xff0c;唯一ID有多种实现方式。我们选择其中几种&#xff0c;简单阐述一下实现原理、适用场景、优缺点等信息。 目录 数据库多主复制UUID工单服务器雪花算法总结 数据库多主复制 数据库通常有自增属性&#xff0c;在单机…

CSS 实现无限波浪边框卡片

CSS 实现无限波浪边框卡片 效果展示 鼠标悬停效果&#xff0c;底部色块的边框是无限滚动的波浪 鼠标没有悬停效果 CSS 知识点 CSS 基础知识回顾使用 radial-gradient 实现波浪边框使用 anumate 属性实现波浪边框动画和控制动画运动 波浪实现原理 波浪边框的实现思路其…

dayjs 判断是否今天、本周内、本年内、本年外显示周几、月份等

效果: 判断是否今天需从 dayjs 中引入 isToday 插件&#xff1b; 判断是否两个日期之间需从 dayjs 中引入 isBetween 插件 import dayjs from dayjs import isToday from dayjs/plugin/isToday import isBetween from dayjs/plugin/isBetween// 注册插件 dayjs.extend(isBet…

DP:子数组模型

一、最大子数组和 . - 力扣&#xff08;LeetCode&#xff09; 二、环形子数组的最大和 . - 力扣&#xff08;LeetCode&#xff09; class Solution { public:int maxSubarraySumCircular(vector<int>& nums) {//动态规划思想解决 //环形数组问题&#xff0c;尝试转…

01-Git 快速入门

https://learngitbranching.js.org/?localezh_CN在线练习git 1. Git 安装好Git以后, 先检查是否已经绑定了用户名和邮箱 git config --list再检查C:\Users\xxx.ssh 下是否存在 id_rsa.pub , 存在的话复制其内容到 GitHub 的 SSH KEY 中 没有这一步, PUSH操作的时候会报错:…

土壤墒情监测系统:洞察土壤水分奥秘

TH-TS400土壤墒情监测系统&#xff0c;作为现代农业科技的重要组成部分&#xff0c;已经成为农业生产过程中不可或缺的一环。该系统通过先进的传感器技术和数据处理能力&#xff0c;能够实时监测土壤的水分状况&#xff0c;为农业生产提供精准、可靠的数据支持。本文将从系统构…

算法打卡day29

今日任务&#xff1a; 1&#xff09;1005.K次取反后最大化的数组和 2&#xff09;134.加油站 3&#xff09;135.分发糖果 1005.K次取反后最大化的数组和 题目链接&#xff1a;1005. K 次取反后最大化的数组和 - 力扣&#xff08;LeetCode&#xff09; 给定一个整数数组 A&…

Java Web-分层解耦

三层架构 当我们所有代码都写在一起时&#xff0c;代码的复用性差&#xff0c;并且难以维护。就像我们要修改一下服务端获取数据的方式&#xff0c;从文本文档获取改为到数据库中获取&#xff0c;就难以修改&#xff0c;而使用三层架构能很好的解决这个问题。 controller: 控…

HJ61 放苹果(递归,苹果多的情况+盘子多的情况)

当苹果数 < 盘子数&#xff0c;有空盘&#xff0c;则忽略一个盘子&#xff0c;在n-1个放苹果&#xff0c;一直递推到n1&#xff0c;有一种摆法苹果数 > 盘子数&#xff0c;可以看作没有空盘。则可以选择忽略一个盘子&#xff0c;如上边做法。还可以选择每个盘子放一个苹果…

GlusterFS分布式存储

目录 前言 一、GlusterFS分布式存储概述 1、GFS概念 2、GFS特点 3、GFS术语 4、GFS构成 5、GFS工作流程 6、后端存储如何定位文件 7、GlusterFs的卷类型 7.1 Distributed Volume&#xff08;分布式卷&#xff09; 7.2 Striped Volume&#xff08;条带卷&#xff09…

线性变换在人工智能领域的深度实践与应用探索

线性变换&#xff0c;作为数学中的一种基本工具&#xff0c;在人工智能领域中发挥着举足轻重的作用。其强大的表示能力和灵活的运算特性使得线性变换成为机器学习、深度学习等多个子领域的核心组成部分。本文将详细探讨线性变换在人工智能领域中的实践应用&#xff0c;旨在揭示…

自动化测试selenium

目录 什么是自动化测试 什么是selenium selenium工作原理 selenium环境搭建 1.查看chrome浏览器版本 2.下载chrome浏览器驱动 3.配置系统环境变量PATH 4.验证环境是否搭建成功 selenium相关API 1.定位元素 CSS选择器定位 xpath定位元素 标签定位元素 2.操作测试对…

浏览器工作原理与实践--渐进式网页应用(PWA):它究竟解决了Web应用的哪些问题

在专栏开篇词中&#xff0c;我们提到过浏览器的三大进化路线&#xff1a; 第一个是应用程序Web化&#xff1b; 第二个是Web应用移动化&#xff1b; 第三个是Web操作系统化&#xff1b; 其中&#xff0c;第二个Web应用移动化是Google梦寐以求而又一直在发力的一件事&#xf…