lab2 彩票调度
共享文件夹
1.在vm虚拟机 设置 中设置好共享文件夹的路径。
2.在主机将文件夹设置为共享文件夹 属性 共享 高级共享
3.在虚拟机终端挂载地址,终端输入:
sudo mount -t fuse.vmhgfs-fuse .host:/ /mnt/hgfs -o allow_other
上边代码中,/mnt/hgfs/ 是挂载点,也可以修改为其它挂载点;-o allow_other表示普通用户也可访问共享文件夹。
然后输入密码,挂载成功。
共享文件夹地址:主目录-其他位置-计算机-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