270 likes | 296 Views
This chapter introduces SOS, the Simple Operating System, and explains the implementation of processes using the SOS architecture. The chapter covers system call interfaces, creating and terminating processes, message queues, disk block operations, and the control and data flow between O/S objects, system calls, and the hardware.
E N D
Chapter 5 - Implementing Processes • Process is the most fundamental object • Process is a program executing in a virtual computer • Process represented in the kernel via a data structure called a process descriptor • Time sharing - interleaving execution of processes (Figure 5.1)
This chapter introduces SOS - The Simple Operating System; explanations and code is given in C++; we will note JavaSOS implementation differences as we go along. • System Call Interface • Simpler than Chapter 3’s • All return a nonnegative integer if they complete successfully, zero if not important and negative if an error occurred • Note that JavaSOS system calls are defined not as function/method calls, but as software interrupt handler values in SOSSyscallIntHandler.java -- look at MakeSystemCall() method in AppTests.java
int CreateProcess(int blockNumber, int numberOfBlocks) - create a new process, returning process ID (PID); you provide disk information of binary location • JavaSOS version: SOSSyscallInthandler.CreateProcessSystemCall interrupt is a bit convoluted - the interrupt handler calls SOSProcessManager.CreateProcessSysProc(), which in turn creates a Java thread in HWSimulation.CreateProcess(); note that since native Java threads are used as JavaSOS processes there is no need for disk location information. The drawback is that this JavaSOS requires all processes to be pre-built into the system (notice the hard-coded process creations in CreateProcess()).
void ExitProcess(int exitCode) - terminate calling process with return code value • JavaSOS version: SOSSyscallInthandler.ExitProcessSystemCall • int CreateMessageQueue(void) - Create OS-managed message queue • JavaSOS version: SOSSyscallInthandler. CreateMessageQueueSystemCall • int SendMessage(int msg_q_id, int *msg) - Send 8 integers to the specified message queue; -1 = not a valid msg_q, -2 = no avail. buffers • JavaSOS version: SOSSyscallInthandler. SendMessageSystemCall (see AppTest.java)
int ReceiveMessage(int msg_q_id, int *msg) - Receive 8 integers from the specified message queue; block if not available; -1 = not a valid msg_q, JavaSOS version: SOSSyscallInthandler. ReceiveMessageSystemCall (see AppTest.java) • int ReadDiskBlock(int blockNumber, char *buffer) - read disk block into memory buffer; note this always succeeds (not realistic:) • JavaSOS version: SOSSyscallInthandler.DiskReadSystemCall (note switching of verb & noun) • int WriteDiskBlock(int blockNumber, char *buffer) - write disk block from memory buffer; note this always succeeds (also not realistic:)
JavaSOS version: SOSSyscallInthandler.DiskWriteSystemCall (note again the switching of verb & noun) • Figure 5.2 shows control and data flow between O/S objects, system calls and the hardware • JavaSOS uses the following user memory addresses during system calls: • 100: base address of parameter block • 101: system call number • 102: syscall parameter # 1 • 103: syscall parameter # 2 • Seen in AppTests.MakeSystemCall(), which uses SIM.hw.SetCell() to write the user memory addresses.
Implementation of SOS • Note that this section of the book covers the C++ SOS used throughout the rest of the book. While not exactly code-compatible to JavaSOS, the data structures and algorithms in JavaSOS were implemented directly from this architecture. • Figure 5.3 - SOS Architecture (data & control flow) • System Constants (p. 122) • Note ProcessSize, TimeQuantum, NumberOfProcesses, and the list of System call numbers. • JavaSOS: SOSData.java contains some; SIM.java contains some and others are scattered about on a per-class basis • Global Data (p. 123) • Note SaveArea, ProcessDescriptor, & interrupt vector pointers • JavaSOS: SOSData.java contains some; SIM.java contains some and others are scattered about on a per-class basis
Implementation of SOS Processes • CreateProcessSysProc(): find a process table entry, initialize, load up binary in pre-assigned memory space, set state of process to Ready. • JavaSOS: SOSProcessManager.CreateProcessSysProc() is similar, except the binary is not loaded from disk (pre-built into JavaSOS). • Process States & the Process State Diagram (Figure 5.4) • Possible states of a process: • Running - process is currently assigned the CPU and is executing (one running process per processor) • Ready - process wants the CPU, but none is available (this is usually a queue) • Blocked - process wants something other than the CPU and is waiting for some event • Know how the Figure 5.4 finite state machine operates!
Dispatcher - name of the part of the operating system that manages the process state finite machine • Finds a process in the ready queue and starts it running • If ready queue is empty, it waits for an interrupt to wake it up in the future to see if there’s anything to do then • Dispatcher - load process state of selected process. Once the ia register is set to the previously-stored value then control resumes in that process (note this is the final thing done by the interrupt handler that called the dispatcher; more than likely the timer interrupt). • The Dispatcher can be called from many points in the O/S, but it never returns! • The Dispatcher allocates a time quantum to the selected process (aka time slice) • In JavaSOS, Dispatcher(), RunProcess(), & SelectProcessToRun() are in SOSProcessManager.java
Preemptive vs non-preemptive CPU scheduling • In preemptive scheduling a process is only allowed to run for it’s current time slice. The end of the time slice is defined by the timer interrupt, which preempts the currently-running process via the interrupt mechanism. This protects the CPU from a CPU-hungry process. Once in the timer interrupt handler, the O/S calls the Dispatcher, which can decide to either resume the same process or select another. • In non-preemptive CPU scheduling no timer mechanism exists to force an interrupt at some point in the near future. A CPU-bound program will hog the CPU until it is finished. • A multi-user/multi-process operating system should use preemptive CPU scheduling! • DOS: non-preemptive
Preemptive vs non-preemptive CPU scheduling • Windows 3.1, 3.11: “friendly” non-preemption (that is, a Windows program needs to occasionally call the O/S to perform a windowing operation, effectively giving up the CPU) • Windows ‘95: (a mess!) For 16-bit applications, it is non-preemptive; for 32-bit applications it is preemptive. • Windows NT: preemptive • Macintosh: “friendly” non-preemption • UNIX: preemptive • JavaSOS: preemptive (almost!) • System stack: unlike a standard procedure/function call, where activation records and local variables are pushed on the stack, the system call processing does tricks to reuse stack space
Timer Interrupt Handler • Handles the timer interrupt, used to protect the CPU. • Book code is similar to JavaSOS code in SOSTimerIntHandler.java: • Save current process’ state, if there was one • Invoke Dispatcher() • Note CRA-1’s SOS use of special instructions to do a block copy of the registers into a special savearea of kernel memory: storeall savearea+16 • On CRA-1, we save ia, psw, base & bound, all 32 of the general-purpose registers and set up the system stack (r30) • The assembly code in the Timer Interrupt Handler is the steps needed for half of a context switch between processes; the Dispatcher() will call RunProcess() to perform the other half.
SOS Initialization • JavaSOS: init code resides in SOSStart.java • Set up interrupt vectors (jump table) • Initialize process table (array of ProcessDescriptors) • Set up the process table entry for PID == 0 (the system process) • Call each of the important subsystems and let them initialize: • Memory • I/O • Process • Call the Dispatcher() to start things rolling. • The initial SOS process • Creates other processes (Figure 5.5) • Useful to pull out O/S initialization code from kernel • On UNIX, this process has a PID == 1 and is known as init, the parent of all other processes
Switching Between Processes • Important to protect CPU by switching control between processes • A context switch involves the saving & restoring of all relevant process data (control registers, user registers, memory pointers, etc.) • Notice how the context switch mechanism is a combination of what parts of the context the hardware interrupt does and what the operating system does (Figure 5.6) • Notice how the standard single-process flow of control (Figure 5.7) differs from the flow of control between multiple processes (Figure 5.8)
Switching Between Processes • Control changes within a process or the operating system are all procedure calls. • Interrupts switch control from a user process to the operating system. • The rti instruction switches control from the operating system to a user process. • JavaSOS: interrupts are simulated by direct Java method calls to the interrupt handlers (like in SOSSysCallIntHandler.java) • System Call Interrupt Handling • Figure 5.10 flowchart for handling syscall interrupt • This flow chart works for JavaSOS one, too!
The SOS C++ code for the System Call Interrupt Handler is fairly identical to the JavaSOS version - a giant switch statement with cases for each system call. • Copying Messages between Address Spaces • The SOS version has two simple routines that do memory copies using the particular process’ base address. • JavaSOS accomplishes this with the separate memory routines found in HWSimulation.java: • GetCell() / SetCell() - Read relative to base+bounds • GetCellUnmapped / SetCellUnmapped()- Absolute read • GetCellUnmappedAsInt() - Absolute read of an Integer
Note Figure 5.11 showing how data is transferred between two cooperating processes (a message sender and a message receiver) • The O/S has to get involved and has to address kernel-managed data (the message queue) as well as data space within each of the two processes
Program Error Interrupt Handler • Invoked when a program attempts an illegal operation • In SOS, we forcibly remove the process from the process table. • JavaSOS does the same thing (code in SOSProgErrIntHandler.java, where else? :) • Disk Driver Subsystem • DiskIO() is called from the System call interrupt handler when a Read or Write of the disk is requested. (Look in SOSDiskDriver.java for JavaSOS). • Since disk is a slow device we can’t afford to have the machine wait in the current system call.
Instead, we insert the disk request in a queue structure, schedule the disk I/O to happen, and call the Dispatcher() to find another process to run. • Note that like with Wait() processing, we set the caller’s state to Blocked, since it can’t continue until the disk activity has completed. • ScheduleDisk() • Returns if disk busy • If not busy, gets the next disk request from the disk request queue and issues the disk request (via IssueDiskRead() or IssueDiskWrite()) • The IssueDisk functions use memory-mapped I/O to set up the 2-word disk request structure, with interrupts enabled • At some point in the future, the disk interrupt handler is called when the disk controller fires off the interrupt
The Disk Interrupt Handler (JavaSOS: SOSDiskIntHandler.java) performs: • Save state of process that was interrupted. • Unblock process that was waiting for the I/O (SOS uses a global process_using_disk variable that is set in ScheduleDisk()). • JavaSOS: The pending_disk_request global variable points to an instance of a SOSDiskRequest, which contains the PID of the process waiting for the I/O to complete. • Call ScheduleDisk() to start the next disk I/O, if anything is queued up. • Call the Dispatcher() to continue user processes, if any.
Implementation of Waiting • An operating system must handle it’s own blocking and resuming for those operations that happen in a particular sequence. • It’s easy enough to suspend execution of a user process while waiting for something (like disk I/O, a child to call Exit() while a parent is blocked on Wait(), etc.) by setting it’s state to Blocked. • We can’t, however, set the operating system to Blocked since it would then block itself! • Instead, we have to keep some state information relevant to the “suspended” system call and have processing of a related system call handle the “resumption”.
Implementation of Waiting • Prime example is from P1: • The WaitProcessSystemCall blocks the parent process and the ExitProcessSystemCall completes the logical conclusion of the Wait() call by having the child unblock the parent. • This means information known at the time of the Wait() must be stored away and usable by the Exit() later on. • One solution stores the parent’s PID in a new location located in the child’s SOSProcessDescriptor. • So, in effect, the Exit system call “resumes” the processing of the parent started in the Wait system call.
Implementation of Waiting • Another example is in the book -- what happens if a ReceiveMessage() happens before a SendMessage()? (Figure 5.12) • One approach would be to wait around in the Receive waiting for the Send to eventually show up. • This isn’t possible while inside the operating system with interrupts disabled and no user programs running! • So, must “suspend” the ReceiveMessage() call by saving message state and “resume” the call by putting the correct Receive code in SendMessage() for when the Sender eventually makes that system call.
Flow of Control in SOS (and JavaSOS) • Figure 5.13 shows the flow of a disk read or write. • Figure 5.14: CreateProcess or Exit • Figure 5.15: CreateMessageQueue • Figure 5.16: Send or Receive Message • Comment on interrupt processing: • SOS disables all interrupts while handling a system call. Some system calls may take a while to complete. • In the real world, some I/O devices may require interrupt handling to happen very frequently (serial port handling bytes one at a time)
Comment on interrupt processing: • To handle interrupts, why not allow interrupts to work while in system/kernel mode? • Possible, but we’d have to keep a stack of the system state as nested interrupts occurred. This includes many global variables, etc. A Difficult Problem. • View of an OpSys as an Event & Table Manager • Figure 5.17: think of an OpSys as a passive program that sits around waiting for an internal (system call) or external (disk or timer interrupt) event. • In addition, kernel tables (data structures) are updated by these events (see table, page 158). • An OpSys is a reactive system.
Process Implementation • Note that the OpSys is NOT a process. • Each process has it’s own process descriptor, a data structure used to keep track of the process. The table of process descriptors is usually memory-resident. • Each process runs with restrictions: CPU is rapidly switched between each process; memory is restricted using memory protection hardware (like base and bounds); instruction set is limited while in user mode. • Process communicates via system calls. • Process can be interrupted at any time by an interrupt from a device. • Interrupt handling passes control to the operating system.
Process Implementation • Process table is used to keep track of process descriptors (PDs). • Process table usually addressed by PID. • Typical fields in a PD: process ID, name, memory pointers, open file table, process state, user name, user protection privs, register save area, accumulated CPU time, pending software interrupts, parent process PID, user ID. • Can be implemented as an array, linked list, etc. • The ready list contains list of PDs that are waiting for the CPU. It can either be a separate list, a threaded list through the PD linked list, or as state values (as in JavaSOS).