xv6系统优先级调度·进程运行信息
1.实验目的
实现优先级调度,同时可以手动修改进程的优先级,并显示所有进程的运行状况信息,而且可以跟踪每个进程的调度性能,当进程结束时打印进程的准备时间,开始时间,运行时间,结束时间。
2.实验内容
在分配的这一部分中,您将把调度程序从简单循环更改为优先级调度程序。为每个进程添加一个优先级值(假设取0到31之间的范围)。范围并不重要,它只是概念的证明。从就绪列表中进行调度时,始终会首先调度优先级最高的线程/进程。
添加一个系统调用来更改进程的优先级。进程可以在任何时候改变它的优先级。如果优先级低于就绪列表中的任何进程,则必须切换到该进程。
添加一个系统调用来显示所有进程的信息。
添加字段来跟踪每个进程的调度性能。这些值应该允许您计算每个流程的周转时间和等待时间。添加一个系统调用来提取这些值,或者在进程退出时将它们打印出来。
4. 实验环境
Linux虚拟机
操作系统:Ubantu 16.04 32位
虚拟机软件:VMware Workstation 15
虚拟处理器:1个2核
5. 程序设计和实现
5.1 系统函数修改
5.1.1 proc.h
首先修改PCB内容,增添上我们需要记录的信息,优先级和相关事件。
// Per-process state
struct proc {
uint sz; // Size of process memory (bytes)
pde_t* pgdir; // Page table
char *kstack; // Bottom of kernel stack for this process
enum procstate state; // Process state
int pid; // Process ID
struct proc *parent; // Parent process
struct trapframe *tf; // Trap frame for current syscall
struct context *context; // swtch() here to run process
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
int create_time;
int ready_time;
int run_time;
int finish_time;
int priority;
};
5.1.2 trap.c
我们将使用xv6系统中代表时间量的信息,它的本质是技术系统启动以来经过的时间片的数量,而一个时间片的大小是固定的,因此从某种意义上来说,ticks就是一个度量时间的单位,ticks与时间片数量的关系由如下代码展示:
case T_IRQ0 + IRQ_TIMER:
if(cpuid() == 0){
acquire(&tickslock);
ticks++;
wakeup(&ticks);
release(&tickslock);
}
lapiceoi();
break;
5.1.3 proc.c
5.1.3.1首先修改allocproc
,这个函数主要负责初始化
我们在这个函数里面使得所有新建的进程的优先级为12,同时标记启动时间。
// PAGEBREAK: 32
// Look in the process table for an UNUSED proc.
// If found, change state to EMBRYO and initialize
// state required to run in the kernel.
// Otherwise return 0.
static struct proc *
allocproc(void)
{
struct proc *p;
char *sp;
acquire(&ptable.lock);
for (p = ptable.proc; p < &ptable.proc[NPROC]; p++)
if (p->state == UNUSED)
goto found;
release(&ptable.lock);
return 0;
found:
p->state = EMBRYO;
p->pid = nextpid++;
p->create_time = ticks;
p->priority = 12;
release(&ptable.lock);
// Allocate kernel stack.
if ((p->kstack = kalloc()) == 0)
{
p->state = UNUSED;
return 0;
}
sp = p->kstack + KSTACKSIZE;
// Leave room for trap frame.
sp -= sizeof *p->tf;
p->tf = (struct trapframe *) sp;
// Set up new context to start executing at forkret,
// which returns to trapret.
sp -= 4;
*(uint *) sp = (uint) trapret;
sp -= sizeof *p->context;
p->context = (struct context *) sp;
memset(p->context, 0, sizeof *p->context);
p->context->eip = (uint) forkret;
return p;
}
5.1.3.2 修改fork
函数,这里面使进程处于可执行状态,这里记录上我们的准备好的时间
// Create a new process copying p as the parent.
// Sets up stack to return as if from system call.
// Caller must set state of returned proc to RUNNABLE.
int
fork(void)
{
int i, pid;
struct proc *np;
struct proc *curproc = myproc();
// Allocate process.
if ((np = allocproc()) == 0)
{
return -1;
}
// Copy process state from proc.
if ((np->pgdir = copyuvm(curproc->pgdir, curproc->sz)) == 0)
{
kfree(np->kstack);
np->kstack = 0;
np->state = UNUSED;
return -1;
}
np->sz = curproc->sz;
np->parent = curproc;
*np->tf = *curproc->tf;
// Clear %eax so that fork returns 0 in the child.
np->tf->eax = 0;
for (i = 0; i < NOFILE; i++)
if (curproc->ofile[i])
np->ofile[i] = filedup(curproc->ofile[i]);
np->cwd = idup(curproc->cwd);
safestrcpy(np->name, curproc->name, sizeof(curproc->name));
pid = np->pid;
acquire(&ptable.lock);
np->state = RUNNABLE;
np->ready_time = ticks;
release(&ptable.lock);
return pid;
}
5.1.3.3 修改exit
函数,这个函数负责程序结束时的一些操作,我们在这里记上进程结束的时间
// Exit the current process. Does not return.
// An exited process remains in the zombie state
// until its parent calls wait() to find out it exited.
void
exit(void)
{
struct proc *curproc = myproc();
struct proc *p;
int fd;
if (curproc == initproc)
panic("init exiting");
// Close all open files.
for (fd = 0; fd < NOFILE; fd++)
{
if (curproc->ofile[fd])
{
fileclose(curproc->ofile[fd]);
curproc->ofile[fd] = 0;
}
}
begin_op();
iput(curproc->cwd);
end_op();
curproc->cwd = 0;
acquire(&ptable.lock);
// Parent might be sleeping in wait().
wakeup1(curproc->parent);
// Pass abandoned children to init.
for (p = ptable.proc; p < &ptable.proc[NPROC]; p++)
{
if (p->parent == curproc)
{
p->parent = initproc;
if (p->state == ZOMBIE)
wakeup1(initproc);
}
}
// Jump into the scheduler, never to return.
curproc->state = ZOMBIE;
curproc->finish_time = ticks;
sched();
panic("zombie exit");
}
5.1.3.4 修改wait
函数,这个函数处理程序已经结束被清除掉的时候操作,这里我们在程序被清除前打印进程的所有信息
// Wait for a child process to exit and return its pid.
// Return -1 if this process has no children.
int
wait(void)
{
struct proc *p;
int havekids, pid;
struct proc *curproc = myproc();
acquire(&ptable.lock);
for (;;)
{
// Scan through table looking for exited children.
havekids = 0;
for (p = ptable.proc; p < &ptable.proc[NPROC]; p++)
{
if (p->parent != curproc)
continue;
havekids = 1;
if (p->state == ZOMBIE)
{
// Found one.
pid = p->pid;
cprintf("**************************\n");
cprintf("pid %d Created time: %d\n", pid, p->create_time);
cprintf("pid %d Ready time: %d\n", pid, p->ready_time);
cprintf("pid %d Run time: %d\n", pid, p->run_time);
cprintf("pid %d Finish time: %d\n", pid, p->finish_time);
cprintf("Current time: %d\n", ticks);
cprintf("**************************\n");
kfree(p->kstack);
p->kstack = 0;
freevm(p->pgdir);
p->pid = 0;
p->parent = 0;
p->name[0] = 0;
p->killed = 0;
p->state = UNUSED;
release(&ptable.lock);
return pid;
}
}
// No point waiting if we don't have any children.
if (!havekids || curproc->killed)
{
release(&ptable.lock);
return -1;
}
// Wait for children to exit. (See wakeup1 call in proc_exit.)
sleep(curproc, &ptable.lock); //DOC: wait-sleep
}
}
5.1.3.5 修改scheduler
函数,这个函数负责进行调度进程,所以也是我们需要主要关注的函数,这里我们使得高优先级进程总是被选择去执行
//PAGEBREAK: 42
// Per-CPU process scheduler.
// Each CPU calls scheduler() after setting itself up.
// Scheduler never returns. It loops, doing:
// - choose a process to run
// - swtch to start running that process
// - eventually that process transfers control
// via swtch back to the scheduler.
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);
struct proc *TempProcess;
struct proc *HighPriorityProcess = 0;
for (p = ptable.proc; p < &ptable.proc[NPROC]; p++)
{
if (p->state != RUNNABLE)
continue;
HighPriorityProcess = p;
for (TempProcess = ptable.proc; TempProcess < &ptable.proc[NPROC]; TempProcess++)
{
if (TempProcess->state != RUNNABLE)
continue;
if (HighPriorityProcess->priority < TempProcess->priority)
HighPriorityProcess = TempProcess; //find the runnable process with highest priority
}
// Switch to chosen process. It is the process's job
// to release ptable.lock and then reacquire it
// before jumping back to us.
p = HighPriorityProcess;
c->proc = p;
switchuvm(p);
p->state = RUNNING;
p->run_time=ticks;
swtch(&(c->scheduler), p->context);
switchkvm();
// Process is done running for now.
// It should have changed its p->state before coming back.
c->proc = 0;
}
release(&ptable.lock);
}
}
5.1.3.6 新增changepri
函数,用于修改对应的进程的优先级
int
changepri(int pidNum, int priority)
{
struct proc *p;
acquire(&ptable.lock);
for (p = ptable.proc; p < &ptable.proc[NPROC]; p++)
{
if (p->pid == pidNum)
{
p->priority = priority;
break;
}
}
release(&ptable.lock);
return pidNum;
}
5.1.3.7 新增showpid函数,用于展示所有进程的信息
int
showpid(void)
{
struct proc *p;
sti();
acquire(&ptable.lock);
cprintf("name \t pid \t state \t priority \n");
for (p = ptable.proc; p < &ptable.proc[NPROC]; p++)
{
if (p->state == SLEEPING)
{
cprintf("%s \t %d \t SLEEPING \t %d\n", p->name, p->pid, p->priority);
}
else if (p->state == RUNNING)
{
cprintf("%s \t %d \t RUNNING \t %d\n", p->name, p->pid, p->priority);
}
else if (p->state == RUNNABLE)
{
cprintf("%s \t %d \t RUNNABLE \t %d\n", p->name, p->pid, p->priority);
}
}
cprintf("\n");
release(&ptable.lock);
return 23;
}
5.1.4 syscall.c
添加系统调用
extern int sys_showpid(void);
extern int sys_changepri(void);
[SYS_showpid] sys_showpid,
[SYS_changepri] sys_changepri,
5.1.5 syscall.h
#define SYS_showpid 22
#define SYS_changepri 23
5.1.6 sysproc.c
建立系统调用和函数关系
int
sys_changepri(void)
{
int pidnum;
int priority;
if (argint(0, &pidnum) < 0)
{
return -1;
}
if (argint(1, &priority))
{
return -1;
}
return changepri(pidnum, priority);
}
int sys_showpid(void)
{
return showpid();
}
5.1.7 user.h
int showpid(void);
int changepri(int, int);
5.1.8 usys.S
SYSCALL(showpid)
SYSCALL(changepri)
5.1.9 defs.h
proc.c
中添加
int changepri(int pidNum, int priority);
int showpid(void);
5.1.10 ps.c
新建ps,用于调用showpid
#include "types.h"
#include "stat.h"
#include "user.h"
int
main(int args,char *argv[])
{
showpid();
return 0;
}
5.1.10 change.c
用于调用changepri,来修改进程优先级
#include "types.h"
#include "user.h"
int main(int argc, char *argv[])
{
if (argc < 3)
{
printf(0, "error(params)\n");
printf(0, "the buffer is not enough\n");
return 0;
}
else
{
int pidnum = atoi(argv[1]);
int priority = atoi(argv[2]);
if (priority < 0 || priority >31)
{
printf(0, "priority num error\n");
return 0;
}
else
{
changepri(pidnum, priority);
}
}
return 0;
}
5.1.10 myfork.c
自己写一个进程,让他做一些无用的计算来做测试
#include "types.h"
#include "stat.h"
#include "user.h"
int
main(int argc, char *argv[])
{
int id = fork();
if (id < 0)
{
printf(1, "%d failed in fork!\n", getpid());
}
else if (id == 0)
{ // child
printf(1, "Child %d created\n", getpid());
double b = 1;
for (int a = 0; a < 1000000000000; a += 1)
{
b += 0.001;
b = b * 101010101.1 - 0.005 / 10.0;
}
}
else
{ //parent
wait();
printf(1, "Parent %d killed\n", getpid());
}
return 0;
}
5.1.11 Makefile
修改Makefile,将需要编译的程序加入到清单中
_ps\
_change\
_myfork\
5.2 编译运行
make qemu 启动xv6系统后执行我们的函数myfork,同时使用ps指令展示此时进程
可以看到子进程5正在运行,这时候我们来更改他的优先级,把他改到15,再执行ps来展示信息
可以看到5进程的优先级成功的被更改到了15,我们再新增自己的进程
这时候可以看到又建立了11进程,由于新建的进程优先级为12所以系统仍然在执行5进程,我们这时候降低5进程的优先级
可以看到低优先级的进程这时候处于等待状态,11进程正在运行,这时候我们结束掉11进程
可以看到11进程被结束了,同时输出了相应的创建时间,准备时间,开始时间和结束时间。
这时候由于11进程已经被结束了,所以系统又让5进程开始执行了