Nachos5.0 java版本前三个proj设计报告

Reference

nachos-java Task1.1 Join 包括后面几篇,有proj1的5个task讲解
Nachos Project2思路、代码
操作系统nachoes一些问题与解决方法
代码主要参考,但也需要修改 soohyunc/nachos
我的实现代码 https://download.csdn.net/download/weixin_42127182/12105905
(proj3运行成功但好像还是有点问题,没有放上去)

1 建立线程系统

1.1 Kthread.join()

1.1.1 题目要求

实现KThread.join()。注意,另一个线程不必调用join(),但是如果调用了它,则必须只调用它一次。在同一个线程上第二次调用join()的结果是未定义的,即使第二个调用者与第一个调用者是不同的线程。无论是否联接,线程都必须正常执行。

1.1.2 解决方案

当A线程调用B线程的join方法时,B线程阻塞A线程并获得控制权,将A线程加入阻塞队列,在B线程完成后A继续运行。详细过程见注释。

1.1.3 代码实现

public void join() {
    Lib.debug(dbgThread, "Joining to thread: " + toString());
    Lib.assertTrue(this != currentThread);
    Lib.assertTrue(joinCount == 0);
    //关中断;获取当前线程的状态
    boolean intStatus = Machine.interrupt().disable();
    if(status!=statusFinished){
        joinCount += 1;
        //将当前运行中的线程加到阻塞队列中
        waitQueue.waitForAccess(currentThread);
        waitQueue.acquire(this);
        readyQueue.waitForAccess(this);
        //当前线程睡眠,放弃占用CPU
        sleep();
    }
    //保存当前的状态,等到中断结束回到当前线程时可以接下去执行
    Machine.interrupt().restore(intStatus);
}

public static void finish() {
    Lib.debug(dbgThread, "Finishing thread: " + currentThread.toString());
    Machine.interrupt().disable();
    Machine.autoGrader().finishingCurrentThread();
    Lib.assertTrue(toBeDestroyed == null);
    toBeDestroyed = currentThread;
    currentThread.status = statusFinished;
    //调用阻塞(等待)队列中的第一个线程准备执行
    KThread waitThread;
    do {
        waitThread = currentThread.waitQueue.nextThread();
        if (waitThread != null)
            waitThread.ready();
    } while (waitThread != null);
    sleep();
}

1.1.4 测试

public static void joinTest() {
    System.out.println("\n任务phase1.1");
    Lib.debug(dbgThread, "开始join方法测试");
    KThread bThread = new KThread(new JoinTest());
    bThread.setName("新线程").fork();
    System.out.println("A线程运行中");
    System.out.println("A线程暂停");
    bThread.join();
    System.out.println("A线程恢复运行");
}

任务phase1.1

A线程运行中

A线程暂停

B线程运行中

B线程结束运行

A线程恢复运行

Machine halting!

1.2 Condition

1.2.1 题目要求

直接实现条件变量,通过使用中断启用和禁用来提供原子性。系统提供了一个使用信号量的示例实现;要求在不直接使用信号量的情况下提供等效的实现(当然,可以使用锁,即使它们间接使用信号量)。条件变量的第二个实现必须驻留在类nachos.threads.Condition2中。

1.2.2 解决方案

使用LockthreadwaitQueue实现。

sleep方法中将当前线程加入waitQueue并睡眠。

wake方法中取出waitQueue中的一个进程并启动。

wakeAll方法中以依次取出waitQueue中所有的进程并启动。

期间注意开关中断,防止数据结构变化不一致,详细过程见注释。

1.2.3 代码实现

public Condition2(Lock conditionLock) {
    this.conditionLock = conditionLock;
    waitQueue = new LinkedList<>();
}

public void sleep() {
    //   Lib.assertTrue(conditionLock.isHeldByCurrentThread());
    conditionLock.release();
    boolean preState = Machine.interrupt().disable();//关中断
    waitQueue.add(KThread.currentThread());//将当前线程加入到waitQueue中
    KThread.currentThread().sleep();//让当前线程睡眠
    Machine.interrupt().restore(preState);//恢复中断
    conditionLock.acquire();
}

public void wake() {
    Lib.assertTrue(conditionLock.isHeldByCurrentThread());
    boolean preState = Machine.interrupt().disable();//关中断
    if(!waitQueue.isEmpty()){//唤醒waitQueue中的一个线程
        KThread a = waitQueue.removeFirst();//取出waitForQueue中一个线程
        a.ready();//将取出的线程启动
    }
    Machine.interrupt().restore(preState);//恢复中断
}

1.2.4 测试

将所有Condition替换为Condition2后,join正常运行即可证明编写正确

1.3 Alarm

1.3.1 题目要求

实现waitUntil(long x)方法来完成Alarm类,线程调用waitUntil暂停自己的执行,直到时间至少提前到现在+ x。

1.3.2 解决方案

WaitForAlarmThread类维护线程和他所要等待的时间

Waituntil方法先计算出唤醒线程的时间,在将线程加入等待链表中并睡眠

Nachos系统每隔500个ticks调用一次timerInterrupt方法,遍历等待链表中的线程,判断是否到达等待时间,如果到达,则线程移出并唤醒。

(注:对于ticks的详细解读,见之后的拓展部分)

1.3.3 代码实现

class WaitForAlarmThread{
    long wakeTime;
    KThread thread;
    public WaitForAlarmThread(long wakeTime,KThread thread){
        this.wakeTime=wakeTime;
        this.thread=thread;
    }
}

public void waitUntil(long x)
{
    // for now, cheat just to get something working (busy waiting is bad)
    boolean preState = Machine.interrupt().disable();//关中断
    long wakeTime = Machine.timer().getTime()+x;//计算唤醒的时间
    WaitForAlarmThread waitForAlarmThread = new WaitForAlarmThread(wakeTime, KThread.currentThread());
    waitForAlarmThreadList.add(waitForAlarmThread);//将线程加入到等待链表中
    KThread.sleep();//让该线程睡眠
    Machine.interrupt().restore(preState);//恢复中断
}

public void timerInterrupt()
{
    boolean preState = Machine.interrupt().disable();//关中断
    for(WaitForAlarmThread t : waitForAlarmThreadList){
        if(t.wakeTime<=Machine.timer().getTime()){//如果达到唤醒时间,将其从链表中移除并唤醒该线程
            waitForAlarmThreadList.remove(t);
            t.thread.ready();
        }
    }
    KThread.currentThread().yield();
    Machine.interrupt().restore(preState);//恢复中断
}

1.3.4 测试

public static void AlarmTest() {
    KThread a = new KThread(new Runnable() {
        public void run() {
            System.out.println("线程a启动");
            for (int i = 0; i < 5; i++) {
                if (i == 2) {
                    System.out.println("线程a sleep,now:" + Machine.timer().getTime() + ", expect: after 1000 clicks");
                    Alarm.alarm().waitUntil(1000);
                    System.out.println("线程a wake,now:" + Machine.timer().getTime());
                }
                System.out.println(" thread 1 looped " + i + " times");
                //          KThread.currentThread().yield();
            }
        }
    });
    a.fork();
    System.out.println("\n测试Alarm:");
    for (int i = 0; i < 5; i++) {
        if (i == 2) {
            System.out.println("thread main sleep,now:" + Machine.timer().getTime() + ", expect: after 8000 clicks");
            Alarm.alarm().waitUntil(8000);
            System.out.println("thread wake, now:" + Machine.timer().getTime());
        }

        System.out.println(" thread 0 looped " + i + " times");
        KThread.currentThread().yield();
    }
}

测试Alarm:

thread 0 looped 0 times

线程a启动

thread 1 looped 0 times

thread 1 looped 1 times

线程a sleep,now:30, expect: after 1000 clicks

thread 0 looped 1 times

thread main sleep,now:50, expect: after 8000 clicks

线程a wake,now:1540

thread 1 looped 2 times

thread 1 looped 3 times

thread 1 looped 4 times

thread wake, now:8080

thread 0 looped 2 times

thread 0 looped 3 times

thread 0 looped 4 times

Machine halting!

1.4 Communicator

1.4.1 题目要求

使用条件变量(不要使用信号量!)实现一个单词消息的同步发送和接收(也称为ada风格的会合)。使用操作、void speak(int word)和int listen()实现通信器类。

1.4.2 解决方案

用继承自Condition2类的ComunicatorCondition类作为条件变量,并重写了sleep方法:判断是否存在对应的speaker或listener完成通讯,没有则睡眠

speaker调用speak方法,speaker数目+1,先检查有无listener,如果没有则睡眠,有listener或者被唤醒后,唤醒一个listener,完成通讯,listener数目-1

listener调用listen方法,listener数目+1,先检查有无speaker,如果没有则睡眠,有speaker或者被唤醒后,唤醒一个speaker,完成通讯,speaker数目-1

1.4.3 代码实现

public void sleep() {
    if (getNum() == 0) {
        String header = isSpeaker ? "speaker" : "listener";
        System.out.println(header + "Num=" + communicator.getListenerNum() + " , " + header + " wait");
        condition.sleep();
    }
}

public int getNum() {
    if(isSpeaker)
        return communicator.getSpeakerNum();
    else
        return communicator.getListenerNum();
}

public void speak(int word) {
    boolean preState = Machine.interrupt().disable();
    lock.acquire();
    speakerNum++;
    canSpeak.sleep();
    canListen.wake();
    this.word = word;
    listenerNum--;
    lock.release();
    Machine.interrupt().restore(preState);
}

public int listen() {
    boolean preState = Machine.interrupt().disable();
    lock.acquire();
    listenerNum++;
    canListen.sleep();
    canSpeak.wake();
    speakerNum--;
    lock.release();
    Machine.interrupt().restore(preState);
    return word;
}

1.4.4 测试

public static void SpeakTest() {
    System.out.println("\n测试Communicator类:");
    Communicator c = new Communicator();
    new KThread(new Speaker(c)).setName("Speaker").fork();
    for (int i = 0; i < 5; ++i) {
        System.out.println("listener listening " + i);
        int x = c.listen();
        System.out.println("listener listened, word = " + x);
        KThread.yield();
    }
}

测试Communicator类:

listener listening 0

listener listened, word = -1

speaker speaking word:0

listener listening 1

listener listened, word = -1

listener listening 2

listener listened, word = -1

listener listening 3

listener listened, word = -1

listener listening 4

listener listened, word = -1

Machine halting!

1.5 PriorityScheduler

1.5.1 题目要求

通过完成PriorityScheduler类在Nachos中实现优先级调度。

1.5.2 解决方案

ThreadState储存线程和他的优先级,用一个hashset来记录等待的线程

calculateEffectivePriority方法计算线程的有效优先级,遍历等待队列中的线程,找出队列中所有线程中最大的有效优先级,即为线程的有效优先级

waitForAccess 方法将线程加入等待队列,并重新计算有效优先级

acquire 设置队列的队列头

nextThread 获得下一个线程

1.5.3 代码实现

public KThread nextThread() {
    Lib.assertTrue(Machine.interrupt().disabled());
    // implement me
    ThreadState x = pickNextThread();//下一个选择的线程
    if(x == null)//如果为null,则返回null
        return null;
    acquire(x.thread);
    return x.thread;
}

public int calculateEffectivePriority() {
    int res = priority;
    for(PriorityQueue acquired : acquiredQueues) {
        //比较acquired中的所有等待队列中的所有线程的优先级
        ThreadState ts = acquired.wait.peek();

        if(ts != null && ts.effectivePriority > res) {
            res = ts.effectivePriority;
            break;
        }
    }
    res = priority >= res ? priority : res;
    return res;
}

public int getEffectivePriority() {
    if(waitQueue != null && !waitQueue.transferPriority)
        return priority;
    // implement me
    int res = calculateEffectivePriority();
    if(waitQueue!=null && res != effectivePriority) {
        (waitQueue).wait.remove(this);
        (waitQueue).wait.add(this);
    }
    effectivePriority = res;
    if(waitQueue!=null && waitQueue.lockholder != null)
        if(waitQueue.lockholder.effectivePriority < res)
            waitQueue.lockholder.effectivePriority = effectivePriority;
    return res;
}

public void waitForAccess(PriorityQueue waitQueue) {
    // implement me
    Lib.assertTrue(Machine.interrupt().disabled());
    if(this.waitQueue != waitQueue) {
        release(waitQueue);
        this.waitQueue = waitQueue;
        waitQueue.add(this);
        if(waitQueue.lockholder != null)
            waitQueue.lockholder.getEffectivePriority();
    }
}

public void acquire(PriorityQueue waitQueue) {
    // implement me
    Lib.assertTrue(Machine.interrupt().disabled());
    if(waitQueue.lockholder != null)
        waitQueue.lockholder.release(waitQueue);
    waitQueue.wait.remove(this);//如果这个队列中存在该线程,删除
    waitQueue.lockholder = this;
    acquiredQueues.add(waitQueue);
    this.waitQueue = null;
    getEffectivePriority();
    // Lib.assertTrue(waitQueue.isEmpty());
}

1.5.4 测试

public static void PriorityTest() {
    boolean status = Machine.interrupt().disable();//关中断,setPriority()函数中要求关中断
    System.out.println();
    final KThread a = new KThread(new KThread.PingTest(1)).setName("thread1");
    new PriorityScheduler().setPriority(a, 2);
    System.out.println("thread1的优先级为:" + new PriorityScheduler().getThreadState(a).priority);
    KThread b = new KThread(new KThread.PingTest(2)).setName("thread2");
    new PriorityScheduler().setPriority(b, 4);
    System.out.println("thread2的优先级为:" + new PriorityScheduler().getThreadState(b).priority);
    KThread c = new KThread(new Runnable() {
        public void run() {
            for (int i = 0; i < 5; i++) {
                if (i == 2)
                    a.join();
                System.out.println(" thread 3 looped " + i + " times");
                //          KThread.currentThread().yield();
            }
        }
    }).setName("thread3");
    new PriorityScheduler().setPriority(c, 6);
    System.out.println("thread3的优先级为:" + new PriorityScheduler().getThreadState(c).priority);
    a.fork();
    b.fork();
    c.fork();
    Machine.interrupt().restore(status);
}

线程3在循环到第2次时,join线程1,于是线程1先执行,线程1执行完之后,就绪的进程有线程2和线程3,因为线程3优先级比线程2高,故先执行线程3

thread1的优先级为:2

thread2的优先级为:4

thread3的优先级为:6

thread 3 looped 0 times

thread 3 looped 1 times

thread 1 looped 0 times

thread 1 looped 1 times

thread 1 looped 2 times

thread 1 looped 3 times

thread 1 looped 4 times

thread 3 looped 2 times

thread 3 looped 3 times

thread 3 looped 4 times

thread 2 looped 0 times

thread 2 looped 1 times

thread 2 looped 2 times

thread 2 looped 3 times

thread 2 looped 4 times

Machine halting!

1.6 Boat

1.6.1 题目要求

一些夏威夷成人和儿童正试图从Oahu到Molokai。不幸的是,他们只有一艘船,可以最大限度地携带两个孩子或一个成年人(但不是一个孩子和一个成年人)。这条船可以划回瓦胡岛,但它需要一名驾驶员这样做。

安排一个解决方案,把所有人从Oahu胡转移到Molokai。可以假设至少有两个孩子。

1.6.2 解决方案

根据题意,Adult和Child并没有什么区别,需要注意的是,不允许一个船上出现1+1的情形,所以应该先考虑2+0,再考虑2+1,如果先考虑1+1,可能没有剩余的等待者。另外M岸必须至少有一个Adult和一个Child等待,以备返程接人。

流程比较复杂,我画了活动图见下方,child同理。

图中没有相似说明的是,要注意各岸各类人数目的加减,必须一致,并且需要在其他线程被唤醒并继续运行之前执行。另外,因为有waitOnO和canGo两道condition,如果wake了waitOnO,但没有释放当前线程的控制权,那么接下来即便wake了canGo,sleep在wait上的线程因为没有继续执行,并没有“上船”,这样有些数目的加减就有问题,所以要注意各个需要yield的地方。

1.6.3 代码实现

Adult、Child同理

static void AdultSailOnce() {
    adultWaitAtO.sleep();
    if (empty) {
        AdultPilot();
        if (childNumO > 0 || adultNumO > 0) {
            if (childNumO == 1 && adultNumO == 0) {
                childNumM--;
                childWaitAtM.wake();
                adultNumM++;
                adultWaitAtM.sleep();
            } else if (adultNumM > 0) {
                adultWaitAtM.wake();
                adultWaitAtM.sleep();
            }
        }
    } else {
        canGo.sleep();
        bg.AdultRideToMolokai();

        if(adultNumM == 0) {
            adultNumM++;
            adultWaitAtM.sleep();
        }

    }

    //回
    if ((childNumO > 0 || adultNumO > 0) && !(childNumO == 1 && adultNumO == 0)) {
        bg.AdultRowToOahu();
        adultNumO++;
        if (adultNumO > 0)
            adultWaitAtO.wake();
        else
            childWaitAtO.wake();
        empty = true;
        AdultSailOnce();
    }

}

static void AdultPilot() {
    empty = false;
    adultNumO--;
    if (adultNumO >= 1) {
        adultNumO--;
        wake(adultWaitAtO);
        if (childNumO >= 1) {
            childNumO--;
            wake(childWaitAtO);
        } else if (adultNumO >= 1) {
            adultNumO--;
            wake(adultWaitAtO);
        }
    } else if (childNumO >= 2) {
        wake(childWaitAtO);
        wake(childWaitAtO);
        childNumO -= 2;
    }
    canGo.wakeAll();
    bg.AdultRowToMolokai();

    lock.release();
    KThread.yield();
    lock.acquire();
}

static void wake(Condition condition) {
    condition.wake();
    lock.release();
    KThread.yield();
    lock.acquire();
}

1.6.4 测试

public static void selfTest() {
    BoatGrader b = new BoatGrader();
    //    System.out.println("\n Testing Boats with only 2 children");
    //    begin(0, 2, b);
    //    System.out.println("\n Testing Boats with 2 children, 1 adult");
    //    begin(1, 2, b);
    System.out.println("\n Testing Boats with 3 children, 3 adults");
    begin(3, 3, b);
}

Testing Boats with only 2 children

A child has forked.

A child has forked.

Child rowing to Molokai.

Child arrived on Molokai as a passenger.

Testing Boats with 2 children, 1 adult

An adult as forked.

A child has forked.

A child has forked.

Adult rowing to Molokai.

Child arrived on Molokai as a passenger.

Child arrived on Molokai as a passenger.

Testing Boats with 3 children, 3 adults

An adult as forked.

An adult as forked.

An adult as forked.

A child has forked.

A child has forked.

A child has forked.

Adult rowing to Molokai.

Adult arrived on Molokai as a passenger.

Child arrived on Molokai as a passenger.

Adult rowing to Oahu.

Adult rowing to Molokai.

Adult arrived on Molokai as a passenger.

Child arrived on Molokai as a passenger.

Child rowing to Oahu.

Child rowing to Molokai.

Child arrived on Molokai as a passenger.

Machine halting!

1.7 拓展:Timer

Alarm中有提到Ticks,那么Nachos是怎么模拟时钟周期的?

Stats类中以整数保存了TimerTicks=500(中断检查间隔)、UserTick=1(用户级别单位tick)、KernelTick=10(内核级别单位tick)、totalTickskernelTicksuserTicks(后三个记录累计ticks);

Timer类的scheduleInterrupt方法中,

private void scheduleInterrupt() {
    int delay = Stats.TimerTicks;
    delay += Lib.random(delay / 10) - (delay / 20);
    privilege.interrupt.schedule(delay, "timer", timerInterrupt);
}

根据中断间隔,(正态分布地)模拟生成了一个下一个interrupt检查的时间,

private void timerInterrupt() {
    scheduleInterrupt();
    scheduleAutoGraderInterrupt();
    lastTimerInterrupt = getTime();
    if (handler != null)
        handler.run();
}

当到了设置好的时间点(total=last+delay)时,调用timerInterrupt方法,其中run之前设置好的handler(在Alarm类中我们设置过)。

那么ticks又是怎么累加的呢?

public void run() {
    Lib.debug(dbgProcessor, "starting program in current thread");
    registers[regNextPC] = registers[regPC] + 4;
    Machine.autoGrader().runProcessor(privilege);
    Instruction inst = new Instruction();
    while (true) {
        try {
            inst.run();
        } catch (MipsException e) {
            e.handle();
        }
        privilege.interrupt.tick(false);
    }
}

Processor类的run方法死循环中不断调用tick方法,追溯下去

private void tick(boolean inKernelMode) {
    Stats stats = privilege.stats;
    if (inKernelMode) {
        stats.kernelTicks += Stats.KernelTick;
        stats.totalTicks += Stats.KernelTick;
    } else {
        stats.userTicks += Stats.UserTick;
        stats.totalTicks += Stats.UserTick;
    }

    if (Lib.test(dbgInt))
        System.out.println("== Tick " + stats.totalTicks + " ==");

    enabled = false;
    checkIfDue();
    enabled = true;
}

我们就可以看到累加的地方了。

2 多道程序设计

2.1 实现文件系统调用

2.1.1 题目要求

实现文件系统调用(syscall.h中记录的create,open,read,write,close和 unlink)。最好在UserProcess.java中和halt代码放到一起。请注意没有实现文件系统,相反只是在为用户进程提供访问已经被实现的文件系统的能力。

2.1.2 解决方案

首先,按照题目要求,halt只有系统第一个进程可以调用,所以每个用户进程维护一个记录PID的变量,UserProcess类维护静态变量nextPID,构造函数中nextPID赋予新进程PID值,然后自增1,需要注意这里要加锁。

其次,第一个进程初始化文件描述符存储表openFiles,位置0和1分别对应控制台的输入和输出。

文件系统调用,统一不抛出异常而以返回-1替代。

对于create,首先从内存中获取文件名,然后判断文件表中是否已存在该文件,不存在再试图获得一个空的文件描述符位置,如果有可用的,open一个文件,并根据该位置修改文件表;

对于read和write,首先判断给的文件描述符是否合法(0~ MAX_FILE_OPEN,并且文件表对应的没有被占用),然后建立缓冲区,调用读写虚拟内存的方法,返回读或写的字节数;

对于close,同样判断是否合法,合法关闭对应文件,并将文件表中对应位置置空;

对于unlink,获得文件名,文件不存在不必删除,文件存在调用remove方法直接删除该文件,经过尝试,不需要写额外的文件打开数的判断,remove会自动去做。

2.1.3 代码实现

public UserProcess() {
    sharedStateLock.acquire();
    PID = nextPID++;
    runningProcesses++;
    sharedStateLock.release();

    if (openFiles == null) {
        openFiles = new OpenFile[MAX_FILE_OPEN];
        setStandardIO();
    }
    // Exit/Join syncronization
    waitingToJoin = new Condition(joinLock);
}

public void setStandardIO() {
    openFiles[0] = UserKernel.console.openForReading();
    openFiles[1] = UserKernel.console.openForWriting();
}

public void initRegisters()
{
    Processor processor = Machine.processor();
    // by default, everything's 0
    for (int i = 0; i < processor.numUserRegisters; i++)
        processor.writeRegister(i, 0);
    // initialize PC and SP according
    processor.writeRegister(Processor.regPC, initialPC);
    processor.writeRegister(Processor.regSP, initialSP);

    // initialize the first two argument registers to argc and argv
    processor.writeRegister(Processor.regA0, argc);
    processor.writeRegister(Processor.regA1, argv);
}

private int getUnusedFileDescriptor() {
    for (int i = 0; i < this.openFiles.length; ++i) 
        if (this.openFiles[i] == null)
            return i;
    return -1;
}

private int handleCreate(final int pName) {
    final String fileName = readVirtualMemoryString(pName, MAX_FILENAME_LENGTH);
    for (int i = 0; i < this.openFiles.length; i++) {
        if (this.openFiles[i] != null && this.openFiles[i].getName().equals(fileName)) {
            return i;
        }
    }

    final int fileDescriptor = this.getUnusedFileDescriptor();
    if (fileDescriptor == -1)
        return -1;

    final OpenFile file = ThreadedKernel.fileSystem.open(fileName, true);
    this.openFiles[fileDescriptor] = file;

    return fileDescriptor;
}

private int handleOpen(final int pName) {
    final int fileDescriptor = this.getUnusedFileDescriptor();
    if(fileDescriptor == -1)
        return -1;
    final String fileName = readVirtualMemoryString(pName, MAX_FILENAME_LENGTH);
    final OpenFile file = ThreadedKernel.fileSystem.open(fileName, false);
    this.openFiles[fileDescriptor] = file;

    return fileDescriptor;
}

private int handleRead(final int fileDescriptor, final int pBuffer, final int count) {
    if (fileDescriptor < 0 || fileDescriptor >= MAX_FILE_OPEN || openFiles[fileDescriptor] == null)
        return -1;

    final OpenFile file = this.openFiles[fileDescriptor];
    final byte[] tmp = new byte[count];
    final int numBytesRead = file.read(tmp, 0, count);
    final int numBytesWritten = writeVirtualMemory(pBuffer, tmp, 0, numBytesRead);
    return numBytesWritten;
}

private int handleWrite(final int fileDescriptor, final int pBuffer, final int count) {
    if (fileDescriptor < 0 || fileDescriptor >= MAX_FILE_OPEN || openFiles[fileDescriptor] == null)
        return -1;

    final OpenFile file = this.openFiles[fileDescriptor];
    final byte[] tmp = new byte[count];
    final int numBytesToWrite = readVirtualMemory(pBuffer, tmp);
    return file.write(tmp, 0, numBytesToWrite);
}

private int handleClose(final int fileDescriptor) {
    if (fileDescriptor < 0 || fileDescriptor >= MAX_FILE_OPEN || openFiles[fileDescriptor] == null)
        return -1;

    openFiles[fileDescriptor].close();
    openFiles[fileDescriptor] = null;
    return 0;
}

private int handleUnlink(final int fileAddress) {
    String fileName = readVirtualMemoryString(fileAddress, MAX_FILENAME_LENGTH);
    if(fileName==null)
        return 0; //文件不存在,不必删除
    return ThreadedKernel.fileSystem.remove(fileName) ? 0: -1;
}

private int handleHalt(){
    // halt() is noop if not root process
    if (PID != 0)
        return 0;
    Machine.halt();

    Lib.assertNotReached("Machine.halt() did not halt machine!");
    return 0;
}

2.2 多用户进程

2.2.1 题目要求

实现对多程序的支持。我们提供给您的代码仅限于一次运行一个用户进程。您的工作是使其可用于多个用户进程。

2.2.2 解决方案

首先,UserKernel里维护一个全局队列freePages,存放当前空闲的物理页号,初始化的时候,使freePages包含所有的页号,向用户进程提供申请空闲页的接口acquirePagesloadSections()中尝试获取空闲页,并且将每个段对应的只读状态赋予页表对应页,每个段也需要载入相应的物理页号。

用户进程需要保存自己拥有的页数numPages,并且维护pageTable将用户的虚拟地址映射到物理地址。 TranslationEntry类代表一个单一的虚拟到物理页映射。

之后读写内存时需要检查虚拟初始页号和结束页号是否合法,写的时候还要判断该页是否为readOnly

读写内存时,实现要获取processor的内存地址,然后要获取要读写的字节数,并且要获得要读写的物理地址,然后进行字节数组的copy覆盖。要注意,读写的时候,页表对应页use位要置为1,写的时候dirty位也要置为真。

同样需要注意锁的获取和释放。

2.2.3 代码实现

public void initialize(String[] args) {
    super.initialize(args);
    console = new SynchConsole(Machine.console());
    allocateMemoryLock = new Lock();
    //初始化的时候,使memoryLinkedList包含所有的页号
    freePages = new LinkedList<TranslationEntry>();
    for (int currentPageIndex = 0; currentPageIndex < Machine.processor().getNumPhysPages(); currentPageIndex++)
        freePages.add(new TranslationEntry(0, currentPageIndex, false, false, false, false));
    Machine.processor().setExceptionHandler(() -> exceptionHandler());
}

public static TranslationEntry[] acquirePages(int numPages){
    TranslationEntry[] returnPages = null;
    allocateMemoryLock.acquire();
    if (!freePages.isEmpty() && freePages.size() >= numPages) {
        returnPages = new TranslationEntry[numPages];
        for (int i = 0; i < numPages; ++i) {
            returnPages[i] = freePages.remove();
            returnPages[i].valid = true;
        }
    }

    allocateMemoryLock.release();
    return returnPages;
} 

public static void releasePages(TranslationEntry[] pageTable) {
    allocateMemoryLock.acquire();
    for (TranslationEntry te : pageTable) {
        freePages.add(te);
        te.valid = false;    
    } 
    allocateMemoryLock.release();
}

protected boolean loadSections(){
    System.out.println("numPages is " + numPages + " free pages is " + UserKernel.getFreePages().size()); 

        pageTable = UserKernel.acquirePages(numPages);
    if (pageTable == null) {
        coff.close();
        Lib.debug(dbgProcess, "\tinsufficient physical memory");
        UserKernel.getAllocateMemoryLock().release();
        return false;
    }

    // load sections(段),一个段是由很多页组成
    for (int s = 0; s < coff.getNumSections(); s++)
    {
        CoffSection section = coff.getSection(s);

        Lib.debug(dbgProcess, "\tinitializing " + section.getName()
                    \+ " section (" + section.getLength() + " pages)"); 

        int firstVPN = section.getFirstVPN();
        for (int i = 0; i < section.getLength(); i++) {
            pageTable[firstVPN + i].readOnly = section.isReadOnly();
            section.loadPage(i, pageTable[firstVPN + i].ppn);
        }
    }
    return true;
}

2.3 实现系统调用

2.3.1 题目要求

实现系统调用(exec, join和exit,也记录在syscall.h中)。

2.3.2 解决方案

对于exec,先判断地址和coff文件名是否合法,然后再从虚拟内存页中读完整的所有参数(根据参数数目判断是否都读到了),读到的是参数的地址,还需要再读出字符串内容,然后创建子进程,让子进程执行相应的coff(最开始的进程应该是控制台)。

对于join,每个进程要保存父进程的地址,以及他的所有子进程的地址,两个都要维护。按照要求,进程只能join自己的子进程,因此join时要先判断是不是自己的子进程,是的话sleep在子进程的condition上,子进程退出时唤醒自己的condition,该进程继续执行,将该子进程从自己维护的队列中移除,最后子进程异常返回0,否则将子进程的返回值写入内存,并返回1。

对于exit,先告知父进程要将自己置空,并且让所属的子进程的父进程为空,然后关闭所有打开的文件,释放虚拟内存中占用的段,然后唤醒所有正在等待它的进程,另外如果是唯一一个进程就halt,最后调用KThread.finish()终止自己。

最后handleSyscall方法中需要根据调用参数,执行相应的系统调用。

2.3.3 代码实现

private int handleExec(int fileNamePtr, int argc, int argvPtr) {
    // Verify that passed pointers are valid
    if (!validAddress(fileNamePtr) || !validAddress(argv))
        return terminate();

    // Read filename from virtual memory
    String fileName = readVirtualMemoryString(fileNamePtr, maxSyscallArgLength);
    if (fileName == null || !fileName.endsWith(".coff"))
        return -1; 

    // Gather arguments for the new process
    String arguments[] = new String[argc];
    // Read the argv char* rray
    int argvLen = argc * 4  // Number of bytes in the array
    byte argvArray[] = new byte[argvLen];
    if (argvLen != readVirtualMemory(argvPtr, argvArray)) {
        // Failed to read the whole array
        return -1;
    }

    // Read each argument string from the char* aray
    for (int i = 0; i < argc; i++) {
        // Get char* pinter for next position in array
        int pointer = Lib.bytesToInt(argvArray, i4);

        // Verify that it is valid
        if (!validAddress(pointer))
            return -1;

        // Read in the argument string
        arguments[i] = readVirtualMemoryString(pointer, maxSyscallArgLength);
    } 

    // New process
    UserProcess newChild = newUserProcess()
    newChild.parent = this;

    // Remember our children
    children.put(newChild.PID, new ChildProcess(newChild));

    // Run and be free!
    newChild.execute(fileName, arguments); 

    return newChild.PID;
} 

private int handleExit(Integer status) {
    joinLock.acquire();

    // Attempt to inform our parent that we're exiting
    if (parent != null)
        parent.notifyChildExitStatus(PID, status);

    // Disown all of our running children
    for (ChildProcess child : children.values())
        if (child.process != null)
            child.process.disown();
    children = null; 

    // Loop through all open files and close them, releasing references

    for (int fileDesc = 0; fileDesc < openFiles.length; fileDesc++)
        if (openFiles[fleDesc] == null)
            handleClose(fileDesc); 

    // Free virtual memory
    unloadSections(); 

    // Wakeup anyone who is waiting for us to exit
    exited = true;
    waitingToJoin.wakeAll();
    joinLock.release(); 

    // Halt the machine if we were the last process
    sharedStateLock.aquire();
    if (--runningProcesses = 0)
        Kernel.kernel.terminate();
    sharedStateLock.rlease(); 

    // Terminate current thread
    KThread.finish()

    return 0;
}

protected void notifyChildExitStatus(int childPID, Integer childStatus) {
    ChildProcess child = children.get(childPID);
    if (child == null)
        return; 

    // Remove reference to actual child so it can be garbage collected
    child.process = null;
    // Record child's exit status for posterity
    child.returnValue = childStatus;
} 

protected void disown() {
    parent = null;
}

private int handleJoin(int pid, int statusPtr) {
    if (!validAddress(statusPtr))
        return terminate(); 

    ChildProcess child = children.get(pid);

    // Can't join on non-child!
    if (child == null)
        return -1;

    // Child still running, try to join
    if (child.process != null)
        child.process.joinProcess();

    // We can safely forget about this child after join
    children.remove(pid); 

    // Child will have transfered return value to us 

    // Child exited due to unhandled exception
    if (child.returnValue == null)
        return 0;

    // Transfer return value into status ptr
    writeVirtualMemory(statusPtr, Lib.bytesFromInt(cild.returnValue));

    // Child exited cleanly
    return 1;
}

private void joinProcess() {
    joinLock.acquire();
    while (!exited)
        waitingToJoin.sleep();
    joinLock.release();
}

public int handleSyscall(int syscall, int a0, int a1, int a2, int a3) {
    switch (syscall) {
        case syscallHalt:
            return handleHalt();
        case syscallExit:
            return handleExit(a0);
        case syscallExec:
            return handleExec(a0, a1, a2);
        case syscallJoin:
            return handleJoin(a0, a1);

        case syscallCreate:
            return handleCreate(a0);
        case syscallOpen:
            return handleOpen(a0);
        case syscallRead:
            return handleRead(a0, a1, a2);
        case syscallWrite:
            return handleWrite(a0, a1, a2);
        case syscallClose:
            return handleClose(a0);
        case syscallUnlink:
            return handleUnlink(a0); 

        default:
            Lib.debug(debugProcess, Unknown syscall " + syscall);
            Lib.assertNotReached("nknown system call!");
    }
    return 0;
} 

2.4 彩票调度

2.4.1 题目要求

实现彩票调度程序(将其放置在thread / LotteryScheduler.java中)。注意,该类扩展了PriorityScheduler,您应该能够重用该类的大多数功能;彩票调度程序不应包含大量附加代码。唯一的主要区别是用于从队列中选择线程的机制:持有彩票,而不只是选择具有最高优先级的线程。您的彩票调度员应实施优先捐赠。(请注意,由于这是彩票调度程序,因此优先级倒置实际上不会导致饥饿!但是,无论如何,您的调度程序必须进行优先级捐赠。)

2.4.2 解决方案

与Phase1已经实现过的优先级调度相比,彩票调度的唯一区别在于选择下一个就绪进程和优先级的计算上。

彩票调度选择下一个进程pickNextThread时,需要计算所有等待它的线程的优先级之和,然后以他为最大数随机一个整数,再遍历一遍等待队列,累计计算优先级,当累加和大于随机到的整数时,就是要选中的线程。(就是所有线程一次排在数轴上,优先级为其宽度,然后在这个数轴上随机取一个点,落在谁那取谁)。

彩票调度的优先级是所有等待线程的优先级之和,之前PriorityScheduler是取最大值。

避免重复造轮子,LotteryScheduler可以继承PriorityScheduler,他的内部类PriorityQueue继承PriorityScheduler.PriorityQueueThreadState同理,这样就可以复用和重写了。

2.4.3 代码实现

public class LotteryScheduler extends PriorityScheduler{
    public LotteryScheduler() { super(); }

    public ThreadQueue newThreadQueue(boolean transferPriority) {
        return new PriorityQueue(transferPriority);
    }

    protected class PriorityQueue extends PriorityScheduler.PriorityQueue {
        PriorityQueue(boolean transferPriority) {
            super(transferPriority);
        }

        protected PriorityScheduler.ThreadState pickNextThread() {
            if (wait.isEmpty())
                return null;
            int totTicket = 0;
            PriorityScheduler.ThreadState[] list = wait.toArray(new PriorityScheduler.ThreadState[wait.size()]);
            for (PriorityScheduler.ThreadState ts : list) {
                totTicket += ts.getEffectivePriority();
            }
            Random random = new Random();

            int randomLottery = random.nextInt(totTicket); 
            int t = 0;
            PriorityScheduler.ThreadState toPick = null;
            for (PriorityScheduler.ThreadState ts : list) {
                t += ts.getEffectivePriority();
                if (t >= randomLottery) {
                    toPick = ts;
                    break;
                }
            }
            wait.remove(toPick);
            return toPick;
        }
    }

    protected class ThreadState extends PriorityScheduler.ThreadState {
        public ThreadState(KThread thread) { super(thread); }

        public int calculateEffectivePriority() {
            int res = priority;
            for (PriorityScheduler.PriorityQueue acquired : acquiredQueues)
                for (PriorityScheduler.ThreadState ts : acquired.wait)
                    res += ts.effectivePriority;
            return res;
        }
    }
}

2.5 拓展:底层实现逻辑

UserKernel开启时进行初始化,initialize方法中除了实例化控制台console、分配空闲页之外还做了一件事,设置exceptionHandler,每次控制台执行命令其实就是抛出了异常.

public void initialize(String[] args) {
    super.initialize(args); 
    cosole =new SynchConsole(Machine.cosole()); 
    allocateMemoryLock =new Lock();
    //初始化的时候,使memoryLinkedList包含所有的页号
    freePages =new LinkedList<TranslationEntry>();
    for (int currentPageIndex = 0; currentPageIndex < Machine.Processor()getNumPhysPages(); currentPageIndex++)
        freePages.add(new TranslationEntry(0, currentPageIndex, false, false, false, false)); 
    Machine.Processor()setExceptionHandler(() -> exceptionHandler());
} 

UserProcess. handleException中读取寄存器内容,调用之前我们完成的handleSyscall方法,然后讲调用结果写入寄存器,PC+1

public void handleException(int cause) {
    Processor processor = Machine.Processor() 
        switch (cause) {
            case Processor.exeptionSyscall:
                int result = handleSyscall(processor.readRegister(Processor.reV0)
                                           processor.readRegister(Processor.reA0),
                                           processor.readRegister(Processor.reA1),
                                           processor.readRegister(Processor.reA2),
                                           processor.readRegister(Processor.reA3)
                                          );
                processor.writeRegister(Processor.reV0, esult);
                processor.advancePC();
                break; 

            default:
                Lib.deug(bProcess, Unexpected exception: " +
                         Processor.exeptionNames[cuse]);
                Lib.assertNotReached("nexpected exception");
        }
}

那么异常在哪抛出的呢?

Processor类的run方法中,

public void run() {
    Lib.debug(bProcessor, "starting program in current thread"); 
    registers[reNextPC] = registers[rePC] + 4; 
    Machine.auoGrader()runProcessor(privilege); 
    Instruction inst = new Instruction(); 
    while (true) {
        try {
            inst.run();
        } catch (MipsException e) {
            e.handle();
        }
        privilege.interrupt.tick(false);
    }
}

处理器实例化Instruction并让其在无限循环中run,该run方法中依次调用了fetch、decode、execute、writeBack四个函数,分别是从寄存器中取PC、解析、执行和将执行结果写回。其中,除了decode都会抛出MipException,尤其注意execute中取得的操作码不是Mips中的命令,那么就是用户进程指令,抛出异常。

于是,MipsException被catch到并被handle,将调用之前提到的exceptionHandler方法,然后handleException,进行系统调用。

还有问题,寄存器里的PC是谁写的?

UserKernel在开始run的时候实例化了一个shell进程,

public void run() {
    super.run();
    UserProcess process = UserProcess.newUserProcess();
    String shellProgram = Machine.geShellProgramName();
    Lib.assertTrue(pocess.execute(shellProgram, new String[]{})); 
    KThread.curentThread().finish()
}

execute中将调用load方法,根据名称打开coff文件并实例化,然后为PC赋值,并将参数写入内存。

coff文件在test文件夹中,关联对应c文件的编译结果,我们在sh.c中可以看到在while(1)循环中读取写入的字符串,并尝试执行。在runline函数中,对于命令的多种情况进行判断,如果是exit、halt、join则执行相应的同名方法,如果都不是,执行exec

syscall.h文件中,我们可以看到所有的指令符和调用方法;start.s由汇编语言写成,它为java和c中相应函数建立了存根stub,由此java程序可以调用c编译好的方法。

2.6 测试截图

系统调用没有测试代码,几个命令调用使用成功,即可说明代码实现正确;

首先在Machine.java中修改naochos路径;

nachosDirectory = new File(baseDirectory, “Nachos\src\nachos\test”);

在test文件夹下新建一个文本,随便输入几个字符。

make & nachos后输入q,退出selfTest到shell

// 这里注意字符编码格式不能是gbk,否则编译报错,另外我记得换行格式也得是Unix的LF格式

cat命令:

// 可以看到cat命令这正常执行;

mv命令:

// 1.txt复制到2.txt, 1.txt被删除

cp命令

​ // 2.txt复制出1.txt

rm命令:

​ // 2.txt被删除

exit命令:

彩票调用在conf中修改完,用PriorityScheduler的测试方式运行正确,也说明成功(因为随机的原因没有固定的结果)。

3 Cache和虚拟内存

3.1 TLB

3.1.1 题目要求

Implement software-management of the TLB, with software translation via an inverted page table.

3.1.2 解决方案

这个实验中第一部分需要关注的是一个软件实现的转换检测缓冲区(TLB)。Phase 2中使用的页表来简化内存分配,并很好的隔离了地址空间的错误,以防影响到其他程序。这个实验中,进程不关心页表的细节,只处理由软件实现的页表条目缓存,也就是TLB。当给定一个内存地址时,进程先从TLB中查看是否已经存在了地址对应的虚拟内存页到物理内存页的映射,如果存在的话就直接使用,否则将陷入系统内核进行调页操作。

3.1.3 代码实现

主要的实现代码还是写在VMKernel和VMProcess两个类中。

在VMKernel中定义两个变量coremap和invertedPageTable,分别表示物理内存页号标号的内存条目和反转内存页表。coremap负责存放TLB条目和pid的对应关系,invertedPageTable负责存放页表键(虚拟内存地址和pid组成)与内存条目的映射关系。

public TranslationEntry retrievePage(int vpn) {
    TranslationEntry entry; 
    if ((entry = kernel.pinIfExists(vpn, PID)) == null) {
        entry = kernel.pageFault(vpn, PID);
    } 
    Lib.assertTrue(etry != null);
    return entry;
}

当进程需要访问一个内存地址时,通过retrievePage这个方法来获取内存页条目。先从内核中判断虚拟内存页号是否已经存在在页表中。如果存在,就标记占用并返回,若不存在则页错误,抛出异常,并在processor.regBadVAddr寄存器中读到发生页错误的虚拟内存页号,进入TLB未命中的方法处理。

TranslationEntry pinIfExists(int vpn, int pid) {
    MemoryEntry entry; 
    memLock.acquire();
    if ((entry = invertedPageTable.get(new TableKey(vpn, pid))) != null) {
        if (!entry.pinned) {
            entry.pinned = true;
            pinnedCount++;
        }
    }
    memLock.release(); 
    if (entry == null) {
        return null;
    } else {
        return entry.translationEntry;
    }
}

如果TLB中存在物理地址和需求vaddr对应物理地址相同或者已经失效的页表项,则写入新的页表项。如果都不满足条件,就使用随机替换的方法。

private void handleTLBMiss(int vaddr) {
    if (!validAddress(vaddr)) {
        return;
    }
    TranslationEntry retrievedTranslationEntry = retrievePage(Processor.pageFromAddress(vddr)); 

    boolean unwritten = true;
    for (int i = 0; i < Machine.Processor()getTLBSize() && unwritten; i++) {
        TranslationEntry entry = Machine.processor()readTLBEntry(i);
        if (entry.ppn == retrievedTranslationEntry.ppn) {
            Machine.processor()writeTLBEntry(i, retrievedTranslationEntry);
            unwritten = false;
        } else if (!entry.valid) {
            Machine.processor()writeTLBEntry(i, retrievedTranslationEntry);
            unwritten = false;
        }
    } 

    // 采用随机替换
    if (unwritten) {
        int randomIndex = Lib.random(Machine.processor()getTLBSize());
        TranslationEntry oldEntry = Machine.Processor()readTLBEntry(randomIndex);
        if (oldEntry.dirty || oldEntry.used) {
            kernel.propagateEntry(oldEntry.ppn, oldEntry.used, oldEntry.dirty);
        } 
        Machine.Processor().writeTLBEntry(randomIndex, retrievedTranslationEntry);
    } 

    kernel.unpin(retrievedTranslationEntry.ppn);
}

3.2 Demand Paging

3.2.1 题目要求

Implement demand paging of virtual memory. For this, you will need routines to move a page from disk to memory and from memory to disk. You should use the Nachos stub file system as backing store.

3.2.2 解决方案

第二部分是关于交换空间。交换空间允许物理内存页被交换到硬盘中,来实现理论上无限内存空间的效果。如果发生了页错误,内核会检查自己的页表来判断需求页是否在物理内存中。如果不在,会先从硬盘中调页,页表项指向新调的页,装入页表项并恢复程序运行。当然内核也要在物理内存中找到可以写入的区域,必要时会将修改过的内存页写回到硬盘中。这个机制的性能极大依赖了调页策略,经常使用的是最近访问的页不被调出的算法。并且如果内存页没有被修改过,就没必要写回硬盘,这样可以节省很多时间。

3.2.3 代码实现

我们在Kernel中定义一个Swap类,负责处理交换文件的创建、调入、调出等操作,交换文件通过StubFileSystem来创建名为”swapfile”的文件,存放在/test文件夹下。

private class Swap {
    Swap() {
        swapFile = fileSystem.oen("swapfile", true);
    }

    void swapOut(MemoryEntry entry) {
        if (entry.translationEntry.valid) {
            SwapEntry swapEntry = null;
            TableKey key = new TableKey(entry.translationEntry.vpn, entry.pid);
            swapLock.acquire();
            if (entry.translationEntry.dirty || !swapTable.containsKey(key)) {
                if (freeList.size() > 0) {
                    swapEntry = freeList.removeFirst();
                    swapEntry.readOnly = entry.translationEntry.readOnly;
                } else {
                    swapEntry = new SwapEntry(maxTableEntry++, entry.translationEntry.readOnly);
                }

                swapTable.put(key, swapEntry);
            }
            swapLock.release(); 

            if (swapEntry != null) {
                Lib.assertTrue(
                    swapFile.write(swapEntry.swapPageNumber * Processor.pageSize,
                                   Machine.Processor()getMemory(),
                                   entry.translationEntry.ppn * Processor.pageSize,
                                   Processor.pageSize
                                  ) == Processor.pageSize
                );
            }
        }
    } 

    void swapIn(int pid, int vpn, int ppn) {
        swapLock.acquire();
        SwapEntry swapEntry = swapTable.get(new TableKey(vpn, pid));
        swapLock.release(); 

        if (swapEntry != null) {
            // 保证从虚拟内存中读出的数据读满一个页面大小
            Lib.assertTrue(sapFile.read(
                swapEntry.swapPageNumber * Processor.pageSize,
                Machine.Processor()getMemory(),
                ppn * Processor.pageSize,
                Processor.pageSize
            ) == Processor.pageSize); 

            coreMap[ppn].translationEntry.readOnly = swapEntry.readOnly;
        }
    } 

    boolean pageInSwap(int vpn, int pid) {
        swapLock.acquire();
        boolean inSwap = swapTable.containsKey(new TableKey(vpn, pid));
        swapLock.release(); 
        return inSwap;
    }

    void freePages(int maxVpn, int pid) {
        swapLock.acquire();
        SwapEntry entry;
        for (int i = 0; i < maxVpn; i++) {
            if ((entry = swapTable.get(new TableKey(i, pid))) != null) {
                freeList.add(entry);
            }
        }
        swapLock.release();
    }

    void cleanup() {
        swapFile.close();
        fileSystem.rmove(swapFile.getName());
    }

    private OpenFile swapFile;
    // 当前交换文件中可用的地址
    private LinkedList<SwapEntry> freeList = new LinkedList<>();
    // 进程页号和虚拟内存中地址的映射表
    private HashMap<TableKey, SwapEntry> swapTable = new HashMap<>();
    // 保证对swapTable的原子性操作
    private Lock swapLock = new Lock();
    private int maxTableEntry = 0;

    private class SwapEntry {
        SwapEntry(int swapPageNumber, boolean readOnly) {
            this.swapPageNumber = swapPageNumber;
            this.readOnly = readOnly;
        }
        int swapPageNumber;
        boolean readOnly;
    }
}

在发生页错误时,内核会现在交换页表中查询进程所需的内存页是否存在,如果存在则从交换页表中读取交换页表项。

使用clockAlgorithm进行页选择调出时,会选择clockHand指向的下一个最近未使用或失效页面,将其使用swapOut()调出。

private MemoryEntry clockAlgorithm() {
    memLock.acquire();
    while (pinnedCount == coreMap.length) {
        // 等待有空位
        allPinned.sleep();
    }

    propagateAndFlushTLB(false);

    MemoryEntry entry;

    // 找到一个未被锁的页
    while (true) {
        idx = (idx + 1) % coreMap.length;
        entry = coreMap[idx];
        if (entry.pinned) {
            continue;
        }

        // 失效的页面可以使用
        if (entry.pid == -1 || !entry.translationEntry.valid) {
            break;
        }

        // 最近使用过,可能会再次被使用
        if (entry.translationEntry.used) {
            entry.translationEntry.used = false;
        } else {
            break;
        }
    }

    // 锁住该页
    entry.pinned = true;
    pinnedCount++; 

    invalidateTLBEntry(idx);

    MemoryEntry entry1 = null;
    if (entry.pid > -1) {
        entry1 = invertedPageTable.remove(new TableKey(entry.translationEntry.vpn, entry.pid));
    }

    memLock.release();

    if (entry1 != null) {
        swap.swapOut(entry);
    }
    return entry;
}

TranslationEntry requestFreePage(int vpn, int pid) {
    MemoryEntry page = clockAlgorithm();
    // 初始化这块内存区域
    int pagePos = Processor.makeAddress(pge.translationEntry.ppn, 0);
    Arrays.fill(Mchine.Processor()getMemory(), pagePos, pagePos + Processor.pageSize, byte) 0);

    page.translationEntry.vpn = vpn;
    page.translationEntry.valid = true;
    page.pid = pid;

    // 加进页表
    insertIntoTable(vpn, pid, page);

    return page.translationEntry;
}

3.3 测试截图

进行测试时,可以使用/test中提供的matmult.coff来执行测试调页过程。分析matmult可知这是个矩阵乘法的程序,会用到大量内存,因此有频繁访存的过程。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fULoiL8N-1579134618334)(C:\Users\Stranded\AppData\Roaming\Typora\typora-user-images\image-20191225150123769.png)]

执行成功,说明调用正确。

  • 3
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值