/** * Suspends the thread that is currently on the processor on the specified event. * * <p>Note that the thread being suspended doesn't need to be running. It can also be waiting for * completion of a pagefault and be suspended on the IORB that is bringing the page in. * * <p>Thread's status must be changed to ThreadWaiting or higher, the processor set to idle, the * thread must be in the right waiting queue, and dispatch() must be called to give CPU control to * some other thread. * * @param event - event on which to suspend this thread. @OSPProject Threads */ public void do_suspend(Event event) { // To suspend a thread, we must first figure out which state to suspend it to. As can // be seen from Figure 3.1, there are two candidates: // If the thread is running, then it is suspended to ThreadWaiting. if (this.getStatus() == ThreadRunning) { // Again steps 1-3 for changing ThreadRunning to another status. MMU.getPTBR().getTask().setCurrentThread(null); MMU.setPTBR(null); this.setStatus(ThreadWaiting); } // If it is already waiting, then the status is incremented // by 1. For instance, if the current status of the thread is ThreadWaiting then it should // become ThreadWaiting+1. else // if (this.getStatus() == ThreadWaiting) only accounted for threadWaiting not +1, +2, etc this.setStatus(this.getStatus() + 1); // Accounts for higher cases than +1; such as +2 // We now must set the new thread status using the method // setStatus() and place it on the waiting queue to the event. // IF and ONLY IF it does not already exist on the queue. if (!(lowPriorityQueue.contains(this)) || !(highPriorityQueue.contains(this))) { // Move to waiting queue event.addThread(this); } // Finally, a new thread must be dispatched using a call to dispatch() dispatch(); }
/** * Frees up main memory occupied by the task. Then unreserves the freed pages, if * necessary. @OSPProject Memory */ public void do_deallocateMemory() { int s = MMU.getFrameTableSize(); for (int i = 0; i < s; i++) { FrameTableEntry frame = MMU.getFrame(i); PageTableEntry page = frame.getPage(); if (page != null && getTask() == page.getTask()) { frame.setPage(null); frame.setDirty(false); frame.setReferenced(false); if (frame.getReserved() == getTask()) { frame.setUnreserved(getTask()); } } } }
/** * The page table constructor. Must call * * <p>super(ownerTask) * * <p>as its first statement. @OSPProject Memory */ public PageTable(TaskCB ownerTask) { super(ownerTask); size = (int) Math.pow(2, MMU.getPageAddressBits()); pages = new PageTableEntry[size]; for (int i = 0; i < size; i++) { pages[i] = new PageTableEntry(this, i); } }
// This method destroys threads. To destroy a thread, its status must be set to ThreadKill // and a number of other actions must be performed depending on the current status of // the thread. (The status of a thread can be obtained via the method getStatus().) // If the thread is ready, then it must be removed from the ready queue. If a running // thread is being destroyed, then it must be removed from controlling the CPU, as // described earlier. public void do_kill() { if (this.getStatus() == ThreadReady) { ThreadCB.lowPriorityQueue.remove(this); ThreadCB.highPriorityQueue.remove(this); } if (this.getStatus() == ThreadRunning) { // Following steps 1-3 change the current status from ThreadRunning to // ThreadKill pages 55-56 of the manual // Step 2 MMU.setPTBR(null); // Step 3 this.getTask().setCurrentThread(null); } if (this.getStatus() == ThreadWaiting) { // Cancel the I/O request by removing the corresponding // IORB from its device queue. This can be done by scanning all devices in the device // table and executing the method cancelPendingIO() on each device. The device table // is an array of size Device.getTableSize() (starting with device 0), where device i // can be obtained with a call to Device.get(). for (int device = 0; Device.getTableSize() > device; ++device) Device.get(device).cancelPendingIO(this); this.setStatus(ThreadKill); } // Includes threadWaiting this.setStatus(ThreadKill); // When a thread is killed, those resources must be released into the common // pool so that other threads could use them. This is done using the static method // giveupResources() of class ResourceCB, which accepts the thread be killed as a // parameter. this.getTask().removeThread(this); ResourceCB.giveupResources(this); // Two things remain to be done now. First, we must dispatch a new thread, since we // should use every interrupt or a system call as an opportunity to optimize CPU usage. dispatch(); // Second, since we have just killed a thread, we must check if the corresponding task still // has any threads left. A task with no threads is considered dead and must be destroyed // with the kill() method of class TaskCB. To find out the number of threads a task // has, use the method getThreadCount() of TaskCB if (this.getTask().getThreadCount() == 0) this.getTask().kill(); }
/** * Selects a thread from the run queue and dispatches it. * * <p>If there is just one theread ready to run, reschedule the thread currently on the processor. * * <p>In addition to setting the correct thread status it must update the PTBR. * * @return SUCCESS or FAILURE @OSPProject Threads */ public static int do_dispatch() { // Context switching. Passing control of the CPU from one thread to another is called // context switching. This has two distinct phases: preempting the currently running // thread and dispatching another thread. Preempting a thread involves the following steps: // 1. Changing of the state of the currently running thread from ThreadRunning to whatever // is appropriate in the particular case. For instance, if a thread looses control of the CPU // because it has to wait for I/O, then its status might become ThreadWaiting. If the // thread has used up its time quantum, then the new status should become ThreadReady. // Changing the status is done using the method setStatus() described later. // This step requires knowing the currently running thread. The call MMU.getPTBR() // (described below) lets you find the page table of the currently scheduled task. The // task itself can be obtained by applying the method getTask() to this page table. The // currently running thread is then determined using the method getCurrentThread(). // 2. Setting the page table base register (PTBR) to null. PTBR is a register of the // memory management unit (a piece of hardware that controls memory access), or MMU, // which always points to the page table of the running thread. This is how MMU knows // which page table to use for address translation. In OSP 2 , PTBR can be accessed // using the static methods getPTBR() and setPTBR() of class MMU. // 3. Changing the current thread of the previously running task to null. The current // thread of a task can be set using the method setCurrentThread(). // When a thread, t, is selected to run, it must be given control of the CPU. This is called // dispatching a thread and involves a sequence of steps similar to the steps for preempting // threads: // 1. The status of t is changed from ThreadReady to ThreadRunning. // 2. PTBR is set to point to the page table of the task that owns t. The page table of a // task can be obtained via the method getPageTable(), and the PTBR is set using the // method setPTBR() of class MMU. // 3. The current thread of the above task must be set to t using the method setCurrentThread(). // In practice, context switch is performed as part of the dispatch() operation, and steps 2 // and 3 in the first list above can be combined with steps 2 and 3 of the second list. // In the degenerate case, when the running thread, t, is suspended and no other thread // takes control of the CPU, consider it as a context switch from t to the imaginary “null // thread.” Likewise, if no process is running and the dispatcher chooses some ready-to-run // thread for execution, we can view it as a context switch from the null thread to t. ThreadCB queueThread = null; // First, some thread should be chosen from the ready queue (or the currently running // thread can be allowed to continue). // highPriorityQueue is not empty and it has been selected to retrieve queue item randomly // Pick an item from a queue. First case is when both queues are not empty. Choose a thread // from one of the queues where 90% of the time the one selected will be from highPriority. if (!(ThreadCB.lowPriorityQueue.isEmpty()) && !(ThreadCB.highPriorityQueue.isEmpty())) { if ((randomGeneratedNumber.nextInt((10 - 1) + 1) > 1)) queueThread = ThreadCB.highPriorityQueue.elementAt(0); else queueThread = ThreadCB.lowPriorityQueue.elementAt(0); } // Case 2 only low priority has anything in its queue. Thread will be selected from the // lowPriority queue. else if (!(ThreadCB.lowPriorityQueue.isEmpty()) && ThreadCB.highPriorityQueue.isEmpty()) queueThread = ThreadCB.lowPriorityQueue.elementAt(0); // High priority queue is the only queue containing any threads. Thread chosen from it. else if (ThreadCB.lowPriorityQueue.isEmpty() && !(ThreadCB.highPriorityQueue.isEmpty())) queueThread = ThreadCB.highPriorityQueue.elementAt(0); // Process currently in CPU and in a queue. Context switch with use of timer at end of time // quantum. if (MMU.getPTBR() != null && queueThread != null /*(!(ThreadCB.highPriorityQueue.isEmpty()) || !(ThreadCB.lowPriorityQueue.isEmpty()))*/) { // Context switching with steps 1-3 given above. We already know that a thread is running on // the CPU from getPTBR() // so we need to switch the currently running thread with the one on the queue after time // quantum has ended. if (HTimer.get() <= 0) { // Add the thread currently on the CPU to the end of the readyQueue if (MMU.getPTBR().getTask().getPriority() > 1) ThreadCB.highPriorityQueue.addElement(MMU.getPTBR().getTask().getCurrentThread()); else ThreadCB.lowPriorityQueue.addElement(MMU.getPTBR().getTask().getCurrentThread()); MMU.getPTBR().getTask().getCurrentThread().setStatus(ThreadReady); MMU.getPTBR().getTask().setCurrentThread(null); MMU.setPTBR(null); MMU.setPTBR(queueThread.getTask().getPageTable()); queueThread.getTask().setCurrentThread(queueThread); MMU.getPTBR().getTask().getCurrentThread().setStatus(ThreadRunning); // Which queue contains the thread? Remove recently set thread to CPU from readyQueue if (ThreadCB.highPriorityQueue.contains(queueThread)) ThreadCB.highPriorityQueue.remove(0); else ThreadCB.lowPriorityQueue.remove(0); } } // There is a thread running in the CPU, but there is nothing left in the queues. Continue // running thread. Return SUCCESS // No switch needed as only thread is already in the CPU. else if ( /*ThreadCB.highPriorityQueue.isEmpty() && ThreadCB.lowPriorityQueue.isEmpty()*/ queueThread == null && !(MMU.getPTBR() == null)) return SUCCESS; // Process is in a queue but not in the CPU else if (MMU.getPTBR() == null && queueThread != null /*(!(ThreadCB.highPriorityQueue.isEmpty()) || !(ThreadCB.lowPriorityQueue.isEmpty()))*/) { MMU.setPTBR(queueThread.getTask().getPageTable()); MMU.getPTBR().getTask().setCurrentThread(queueThread); MMU.getPTBR().getTask().getCurrentThread().setStatus(ThreadRunning); // Which queue contains the thread? Remove recently set thread to CPU from readyQueue if (ThreadCB.highPriorityQueue.contains(queueThread)) ThreadCB.highPriorityQueue.remove(queueThread); else ThreadCB.lowPriorityQueue.remove(queueThread); return SUCCESS; } // No thread is running and there are no threads in the queues. Return FAILURE else if ( /*ThreadCB.highPriorityQueue.isEmpty() && ThreadCB.lowPriorityQueue.isEmpty()*/ queueThread == null && MMU.getPTBR() == null) return FAILURE; return FAILURE; // None of the above cases were true. Something went wrong. }