操作系统—修改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,一经查实,立即删除!

相关文章

Flutter入门指南

文章目录 一、环境搭建二、基本概念三、创建一个简单的Flutter应用四、常用组件及代码示例五、总结推荐阅读 笔者项目中使用Flutter的模块并不多。虽然笔者还没有机会在项目中正式使用Flutter&#xff0c;但是也在学习Flutter的一些基本用法。本文就是一篇Flutter的入门介绍&am…

浏览器滚动条样式终极方案

首先各个浏览器滚动条保持统一是不可能的&#xff0c;因为浏览器不支持大多数滚动条样式属性 从支持可调整的角度来看&#xff0c;我们一般选择 保持chrome样式&#xff0c;其他浏览器样式使用默认效果保持chrome、火狐样式一致&#xff0c;其他浏览器样式使用默认效果 所以这…

C++智能指针2——unique_ptr和weak_ptr

unique_ptr 一个unique_ptr“拥有”它所指向的对象。 与shared_ptr不同&#xff0c;某个时刻只能有一个unique_ptr指向一个给定对象。 当unique_ptr被销毁时&#xff0c;它所指向的对象也被销毁。 和shared_ptr 不同&#xff0c;没有类似make_shared的标准库函数返回一个un…

【双指针】两数之和|| 输入有序数组

两数之和|| 输入有序数组 给你一个下标从 1 开始的整数数组 numbers &#xff0c;该数组已按 非递减顺序排列 &#xff0c;请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] &#xff0c;则 1 < index1 …

在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…

Ubuntu下无法获得锁 / 检测到系统程序错误 / E: Could not get lock /var/lib/apt/lists/lock

这里写自定义目录标题 Ubuntu下无法获得锁 错误 / E: Could not get lock /var/lib/apt/lists/lock Ubuntu下无法获得锁 错误 / E: Could not get lock /var/lib/apt/lists/lock 1、E: Could not get lock /var/lib/apt/lists/lock - open (11: Recource temporarily unavaila…

【双指针】删除有序数组中的重复项Ⅱ

给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次 &#xff0c;返回删除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成 示例 1&…

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…

在 JavaScript或Typescript 中编写异步构造函数的正确方法

参考&#xff1a;The Proper Way to Write Async Constructors in JavaScript - DEV Community

使用 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…

浅谈对线程的理解

一、线程的概念 1、线程的概念 在Python中&#xff0c;想要实现多任务还可以使用多线程来完成。 2、为什么使用多线程&#xff1f; 进程是分配资源的最小单位 , 一旦创建一个进程就会分配一定的资源 , 就像跟两个人聊QQ就需要打开两个QQ软件一样是比较浪费资源的 . 线程是…

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操作的时候会报错:…