OS-xv6-lab2:彩票调度

lab2 彩票调度

共享文件夹

1.在vm虚拟机 设置 中设置好共享文件夹的路径。

2.在主机将文件夹设置为共享文件夹 属性 共享 高级共享

3.在虚拟机终端挂载地址,终端输入:

sudo mount -t fuse.vmhgfs-fuse .host:/ /mnt/hgfs -o allow_other

上边代码中,/mnt/hgfs/ 是挂载点,也可以修改为其它挂载点;-o allow_other表示普通用户也可访问共享文件夹。

主机与VMware虚拟机共享文件夹:解决虚拟机找不到共享文件夹问题 - 知乎

然后输入密码,挂载成功。

共享文件夹地址:主目录-其他位置-计算机-mnt-hgfs

开机自动挂载:

在虚拟机终端切换到系统模式,注意到$变为#。

sudo su

然后输入以下即可。

echo '.host:/ /mnt/hgfs fuse.vmhgfs-fuse allow_other,defaults 0 0' >> /etc/fstab
增加一个系统调用

以settickets为例,getpinfo同理。

1.首先在proc.c文件中编写在内核空间实现系统调用的代码,此处是默认在内核状态执行,无需处理用户态的参数传递,而是直接操作内核数据结构。

//settickets系统调用
int settickets(int number) {
    struct proc *p = myproc();
    if (number < 1) {
        return -1;
    }
    // 设置当前进程的彩票数量
    p->tickets = number;
    return 0;
}

2.在sysproc.c文件中编写系统调用的处理函数:

//settickets系统调用的处理函数
int sys_settickets(void) {
  int tickets;
  if(argint(0, &tickets) < 0 || tickets < 1)  
    return -1;
  struct proc *p = myproc();
  acquire(&ptable.lock);
  p->tickets = tickets; 
  release(&ptable.lock);
  return 0;
}

此处注意两者的区别:

proc中的函数是与进程管理相关的内核核心逻辑,是系统调用的底层实现核心,默认是在内核中完全安全的情况下实现。

而sysproc中是系统调用的入口处理函数,它的代码实现逻辑和proc中的基本类似,但它需从用户态的寄存器或内存中提取参数,必须进行合法性的检查,并需自身管理锁。

3.在defs.h文件中增加内核函数声明,使其他内核函数能够调用该函数:

int             sys_settickets(void);

4.在syscall.c中增加系统调用表注册,将函数指针加入系统调用数组,使内核能够根据系统调用号找到对应的处理函数:

[SYS_settickets] sys_settickets,

5.在syscall.h中增加系统调用号定义,唯一标识该系统调用,确保用户态和内核态使用同一编号:

#define SYS_settickets 22

6.在user.S文件中增加系统调用号,此处是用户态到内核态的跳板,通过ecall指令触发中断进入陷阱,再依据系统调用号跳转到指定的陷阱处理程序(即系统调用)中:

SYSCALL(settickets)

7.在user.h文件中为用户程序提供系统调用接口声明,使其能够通过settickets函数触发系统调用。

int settickets(int number);

在用户进行系统调用时,比如调用settickets(10):

首先触发ecall指令陷入内核,根据系统调用号22跳转到指定函数找到sys_settickets,运行该函数,argint获取用户输入的参数并进行验证,再(调用proc的逻辑)实现该系统调用。

头文件保护机制

也称预处理器指令,#idndef为“if not define”的缩写,作用是检查_PSTAT_H_这个宏是否已被定义,若已被定义,则跳过#ifndef和#endif之间的代码,防止头文件被重复包含,避免因重复定义而引发的编译错误。

#ifndef _PSTAT_H_
#endif
局部作用域

原本ptable的定义在proc.c中:

struct {
  struct spinlock lock;
  struct proc proc[NPROC];
} ptable;

结构体类型没有名称,且仅在proc.c中定义,外部文件无法感知它的存在,其他文件无法通过extern正确引用ptable(编译器会认为其类型是“未知的匿名结构体”报错)

修改为在proc.h中声明:

#include "spinlock.h"
struct ptable_struct {   
  struct spinlock lock;
  struct proc proc[NPROC];
};
extern struct ptable_struct ptable;

并在proc.c中定义:

struct ptable_struct ptable;

这样在proc.h中完整定义了ptable_struct结构体类型,头文件包含proc.h的文件可直接使用该类型。并且proc.h中利用extern声明了存在一个ptable的全局变量,其他文件可以访问定义在proc.c中定义的全局变量ptable。

彩票调度进程调度器
void
scheduler(void)
{
  struct proc *p;
  struct cpu *c = mycpu();
  c->proc = 0;
  
  for(;;){
    // Enable interrupts on this processor.
    sti();

    // Loop over process table looking for process to run.
    acquire(&ptable.lock);
    int total_tickets = 0;
    // 计算所有可运行进程的彩票总数
    for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) {
      if (p->state == RUNNABLE) {
          total_tickets += p->tickets;
      }
  }
  if (total_tickets == 0) {
      release(&ptable.lock);
      continue;
  }
	//彩票调度
  int winning_ticket = random_at_most(total_tickets);
  int current_ticket = 0;

  for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) {
      if (p->state == RUNNABLE) {
          current_ticket += p->tickets;
          if (winning_ticket < current_ticket) {
              c->proc = p;
// 进程运行时间片数加 1
              p->ticks++; 
// 切换到该进程的用户空间页表
              switchuvm(p);
              p->state = RUNNING;
              swtch(&(c->scheduler), p->context);
              switchkvm();
              c->proc = 0;
              break;
          }
      }
  }
  release(&ptable.lock);
  }
}   

首先获取当前CPU的引用,并初始化当前运行的进程为0,for(ii)表示调度器永不终止,循环选择进程运行。sti()开启中断,允许响应外部事件,使用自旋锁保护进程表,保持数据一致性,防止多核竞争。

遍历进程表,统计所有RUNNABLE的状态进程的彩票总数total_tickets,若总彩票数为0,释放锁并继续循环。

生成一个随机数,再次遍历进程表,选择首个累计值超过这个数的进程,设置为当前CPU运行的进程,并增加该进程的时间片,切换到该进程的用户空间页表,更新状态为RUNNING,调用swtch保存调度器上下文,切换到进程的上下文执行。进程完成后切换回内核页表,并清除当前CPU的进程引用。完成一次调度后释放进程表锁,进入下一轮循环。

testTicket
#include "types.h"
#include "user.h"
int main(int argc, char *argv[]) {
    if (argc != 2) {  
        printf(2, "Usage: settickets <number_of_tickets>\n");
        exit();
    }
    int tickets = 0;
    char *p = argv[1];
    while (*p >= '0' && *p <= '9') {
        tickets = tickets * 10 + (*p - '0');
        p++;
    }
    if (tickets < 1) {  
        printf(2, "Error: Ticket count must be positive\n");
        exit();
    }
    if (settickets(tickets) < 0) {
        printf(2, "Error: settickets() failed\n");
        exit();
    }
    while (1) {
    }
    exit();
}

根据实验要求,首先接受一个整数作为命令行输出参数,并将其设置为当前进程的彩票数,使用系统调用settickets来设置当前进程的票数为给定值。

testProcInfo
#include "types.h"
#include "user.h"
#include "pstat.h"

int main(void) {
    struct pstat ps;
    if (getpinfo(&ps) < 0) {
        printf(2, "getpinfo failed\n");
        exit();
    }
    printf(1,"--------- Initially assigned Tickets = %d ---------\n",ps.tickets[0]);
    printf(1, "ProcessID\tRunnable\tTickets\t\tTicks\n");
    int total_ticks = 0;
    for (int i = 0; i < NPROC; i++) {
        if (ps.inuse[i]){
            printf(1, "%d\t\t%d\t\t%d\t\t%d\n", ps.pid[i], ps.inuse[i], ps.tickets[i], ps.ticks[i]);
            total_ticks +=ps.ticks[i];
        }
    }
    printf(1,"--------- Total Ticks = %d ---------\n",total_ticks);
    exit();
}

根据实验要求,调用新增的getpinfo来获取进程信息,并按照指定格式输出。

线性同余法伪随机数生成器

原理:
$$
X_0=seed\

X_n=(A*X_{n-1}+C)mod M(n\geq1)
$$

X为随机数,A是乘数,控制递推关系的变动幅度,C为增量,影响序列的分布特性,M是模数,决定随机数的范围(0~M-1)和周期长度。

当A和M互质,M的所有质因子均能整除A-1,且M为2的幂时,序列周期可达最大值M。

采用经典参数A=1103515245,C=12345,M=0x7fffffff(可利用位运算优化计算速度)编写代码:

#include "types.h"
#include "defs.h"

unsigned int random_at_most(unsigned int max) {
  static unsigned int seed = 1;
  seed = (seed * 1103515245 + 12345) & 0x7fffffff;
  return seed % (max + 1);}
测试命令
testTicket 10&; testTicket 15&; testTicket 20&; testTicket 25&; testTicket 30&;
testProcInfo
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值