This appendix describes the functional aspects of the Z502 computer architecture, that is, its interface to software. This is the interface that's seen by the Operating System, so when you write your OS, this is what you need to know to get to the hardware.
OPERATING SYSTEM
---------------------------------------------
UNDERLYING HARDWARE
It's always difficult to know what to read first. This document is a definition of the whole Z502 architecture and is laid out as a reference document. You would be wise to initially read the sections necessary to implement the beginning stages of the project. This table provides a way of doing that.
TEST |
System Call |
Registers |
Hardware Access |
Test0 | GET_TIME_OF_DAY - CS502 System Calls | Section 3 Below | Section 5.3 Z502ClockStatus |
Test0 | TERMINATE_PROCESS - CS502 System Calls | Section 5.4 - Context | Section 5.4.2 Z502_Destroy_context and Section 5.3.2 |
Test1 | SLEEP - CS502 System Calls | Section 5.3.6, Section 5.3.7 Z502TimerStart & Z502TimerStatus | |
Test1 | GET_TIME_OF_DAY - CS502 System Calls | Section 5.3.5 Z502_Clock | |
Test1 | GET_PROCESS_ID -CS502 System Calls | No hardware access required - handled by the Operating System |
The Z502 can operate in either the User Mode or the Kernel Mode. Each mode has some unique registers and instructions, as well as sharing registers and instructions across both modes.
The instruction set of the User CPU resembles that of your favorite language, with the addition of system calls and some macros. This is described in Section 4. Every User program must be translated into this language before it can be run. In addition, the Kernel Mode CPU is capable of executing privileged instructions, that activate I/O and switch contexts. These privileged instructions are vital to the operating system and are described in Section 5.
The machine has built-in primitives for switching modes. In addition, any mode of execution may be asynchronously exceptioned. When a user-mode execution is exceptioned, the machine is switched automatically to the kernel mode. A user program can invoke the kernel by a software induced exception called a SOFTWARE-TRAP. Interrupts, traps, and faults are described in Section 6.
The machine currently supports the following external devices: disk and delay timer. Characteristics of these I/O devices are described in Section 7.
The memory address translation of the Z502 machine is described in Section 8.
The Z502 machine comes in different configurations in terms of memory size. These configurations are briefly noted in Section 9. The behavior of the machine upon power on is discussed in Section 10.
The Z502 User memory consists of some number of thirty-two (32) bit (i.e., four byte) words. The number of words in this Z502 processor must be a power of two. All user programs reside in this memory and address words in it by zero origin addresses. The operating system resides in the Kernel memory which is disjoint from User memory. The Z502 Kernel memory, per kindness of the machine manufacturer, is not constrained in size.
Here's an overview of the actions allowed using Memory. It isn't simple- "memory" is actually used for three purposes in the Z502:
1. For storing things - this is the use of memory you're familiar with and use in any programming..
2. For accessing IO devices via specific addresses that are "owned" by the
devices - this is called "Memory Mapped IO".
3. Certain memory addresses can be used for locks because the hardware guarantees that actions on these addresses will be atomic -
the atomic action means the hardware can read and modify the data in a lock address without fear the data will be manipulated by some other thread.
While in User Mode (this means code executing in test.c ) | While in Kernel Mode (The code in the OS you've written) | |
How to access User Memory (reading and writing data stored in this memory) |
Use MEM_READ() and MEM_WRITE() |
Use MEM_READ() and MEM_WRITE() You can also use Z502ReadPhysicalMemory( ) |
How to access memory mapped IO (devices such as disk, timer, etc.) | NOT ALLOWED. Will result in hardware fault. From a user program, you should access hardware by using the system calls provided. | Use MEM_READ and MEM_WRITE as described in Section 5.2 I/O Primitives - Memory Mapped IO |
How to access LOCKS | No user program does its own locking. | Use the READ_MODIFY( ) instruction |
While in Kernel Mode the operating system can access User memory through the hardware instructions
Z502ReadPhysicalMemory and Z502WritePhysicalMemory
Unless otherwise specified, the term Z502 memory from now on will be used to refer to the User memory only.
Table 2 shows the variables shared between the OS and the hardware,
|
|
|
---|---|---|
|
||
Your process's Page Table Address |
|
The OS can ask for the address of the Page Table for the currently running process. Do this using Z502Context/Z502GetPageTable (section 5.3.13) using Memory Mapped IO. Alternatively, you can maintain this address in the Process Control Block for this process. |
|
||
Your process's Context |
|
This is the address of the structure maintained by the hardware containing all the hardware information required to run a process. When you create a context, the hardware hands the OS this address; when you want to run that process, you give this address to the hardware. In multiprocessor mode, when there are numerous identical processes, you can find out which process YOU are by issuing a Z502Context/Z502GetCurrentContext (section 5.3.14) using Memory Mapped IO |
|
||
TO_VECTOR |
|
Pointers to exception handlers. Set up in OSInit and you don't need to worry about them |
The page table registers are "seen" by user memory but are modifiable only by the Kernel.
All the other registers and vectors in Table 2 are used in the kernel mode. The use of Context is explained in Section 5.2 on context switching and in Section 6 on exception handling.
You are directed to "CS502SystemCalls" which has extensive detail on the user level system calls. These are the calls executed in test.c
In kernel mode the CPU, as mentioned before, can directly execute your favorite language (which is C :-) ). Aside from the entire language complement, there are also
I/O primitives
memory primitives
context switching primitives
and "other" miscellaneous primitives
that can be invoked as routines.
We will describe the four types of primitives, starting with the I/O primitives
These are the hardware corollaries of the user mode instructions. Upon taking a memory Software Trap, the Z502 hardware directs execution immediately to these routines - the Operating System doesn't get to handle the memory request since it's not a system call.
There are two fundamentally different ways to access memory - either using page tables, or NOT using page tables. Of course in USER mode, the code MUST use page tables (this is Virtual Memory) - after all, this is how the hardware and the OS provide protection in order to keep processes from walking all over someone else's memory. In KERNAL mode, you have the choice of either kind of memory access; with or without page tables. There are times when the OS needs to touch memory belonging to a process that is not currently running and thus doesn't have its page tables recognized by the hardware. To access memory in such situations you must access without page tables - and this is called Physical Memory.
5.1.1 Usage of MEM_READ
INT32 VirtualAddress; // Input
INT32 Data; // Value Return
MEM_READ( VirtualAddress, &Data);
The information stored at location "address" is returned in the variable pointed to by "data". If the address is not valid (for any of a number of reasons), a page fault occurs, resulting in a call to the fault handler This is coded as a C Macro. The result of this call is direct access to the hardware and this action does NOT go through the System Call Service.
5.1.2 Usage of MEM_WRITE
INT32 VirtualAddress; // Input
INT32 Data; // Input Value supplied to be written to memoryMEM_WRITE( address, &data );
The information pointed to by "data" is stored in location "address". If the address is not valid, a page fault occurs, resulting in a call to your fault handler. This is coded as a C Macro. The result of this call is direct access to the hardware and this action does NOT go through the System Call Service.
5.1.3 Usage of Z502ReadPhysicalMemory
INT32 PhysicalFrameNumber; // Input
char *Data; // Value ReturnZ502ReadPhysicalMemory( PhysicalFrameNumber, Data);
The information stored at location PhysicalFrameNumber is returned in the page-size buffer pointed to by "Data". Note that the entire Frame Contents are returned so you need to have allocated a buffer large enough to hold a page-size-quantity of data. If the Frame is not valid (for any of a number of reasons), a page fault occurs, resulting in a call to the fault handler This is a Z502 Hardware Call. The process must be in Kernel Mode in order to use this call
5.1.4 Usage of Z502WritePhysicalMemory
INT32 PhysicalFrameNumber; // Input
char *Data; // Value ReturnZ502WritePhysicalMemory( PhysicalFrameNumber, Data);
The information in the page-size buffer pointed to by "Data" is stored at location PhysicalFrameNumber. Note that the entire Frame Contents are modified by this call. If the Frame Number is not valid (for any of a number of reasons), a page fault occurs, resulting in a call to the fault handler This is a Z502 Hardware Call. The process must be in Kernel Mode in order to use this call
The READ_MODIFY instructions are used to implement locking. These instructions are atomic in the hardware. The read and the modify occur with no possibility of an intervening interrupt. This is what makes this instruction useful as a lock.
The READ_MODIFY instruction is used as follows:
Usage of READ_MODIFY
INT32 Memory_Address;
INT32 Data,
INT32 Suspend_Until_Locked,
INT32 Success_Or_Failure_Returned;
READ_MODIFY( Memory_Address, Data, Suspend_Until_Locked, &Success_Or_Failure_Returned );
This is an atomic hardware instruction. What this means is that you can use this instruction to implement locking. This is necessary because you have data structures that are shared between two threads - the code running in the Operating System, and the code running in your interrupt Handler.
The hardware behaves like this:
1. Look at the requested memory location. Test if that location contains a 0 - then the lock is UNLOCKED. If the location contains a 1, then it is LOCKED.
2. If the memory is 1 and the user requested a lock (1), then reject the request - it's already locked.
3. If the memory is 0, and the user requested a lock, then ATOMICALLY set the memory to 1. Atomically here means that the test and set happen as one instruction. No other process or thread can intervene in the test and set.
Here's an explanation of the parameters to this call:
Memory_Address - There are special memory addresses set aside for locks. These range from a low address of MEMORY_INTERLOCK_BASE
to MEMORY_INTERLOCK_BASE + MEMORY_INTERLOCK_SIZE - 1 inclusive.
Data - The value to be placed in memory. Options are 1 - try to set the lock or 0, try to clear the Lock.
Suspend_Until_Locked - Choices are: TRUE - on a Lock request, don't return from the request until your thread holds the lock. FALSE - on a lock request means return immediately, whether the lock has been attained or not.
Success_Or_Failure_Returned - Returns TRUE if the action of lock/unlock has been successful. Returns FALSE if the action was not successful.
Usage of READ_MODIFY( MEM_ADJUST( lock_word ), &Success_Or_Failure_Returned );
Here are the actions and return values resulting from various input values and initial conditions:
Lock Initial State | Input | Input | Input |
Data = 1, Suspend = TRUE | Data = 1, Suspend = FALSE | Data = 0, Suspend = X | |
Unlocked | Location Becomes Locked Success = TRUE |
Location Becomes Locked Success = TRUE |
Location remains unlocked Success = FALSE |
Previously locked by Caller | Location still Locked Success = FALSE |
Location still Locked Success = FALSE |
Location is unlocked Success =TRUE |
Previously locked by Someone Else | When call returns, you have the lock Success = TRUE |
Location still Locked Success = FALSE |
Location still Locked Success = FALSE |
Additional conditions that are tested by the locking mechanism:
Condition | Value Returned |
Address not in range | Success = FALSE |
Data not 0 or 1 | Success = FALSE |
Suspend not TRUE or FALSE | Success = FALSE |
The way that modern processors communicate with their devices is via memory instructions! That may seem very strange until you realize that “memory instructions” are different from “accessing memory”. What happens is that when a memory request is executed the processor makes a decision; Is this a regular request that goes TO memory, or is it a special request that is diverted and sent TO a device? The processor is able to make this decision based on the address that the memory request is using. There are special addresses reserved for this purpose, which cause the processor to divert the request and send it to the device rather than to memory.
This Table gives an overview of the Reserved Addresses, and the action that results when you read or write to those addresses. For each of these operations, there’s more detail below.
Function | Mode | R/W | What Happens (Briefly) |
Z502Halt | Z502Action | W | Causes the Simulation to End |
Z502Idle | Z502Action | W | Causes the Processor to idle until the next interrupt occurs. |
Z502InterruptDevice | Z502GetInterruptInfo | R | Returns the ID and Status of device causing the interrupt |
Z502Clock | Z502ReturnValue | R | Returns the current hardware time |
Z502Timer | Z502Start | W | Starts the hardware timer |
Z502Timer | Z502Status | R | Tells you if the timer is currently running. |
Z502Disk | Z502DiskRead | W | Sets up the parameters needed to do a disk read, and start the read |
Z502Disk | Z502DiskWrite | W | Sets up the parameters needed to do a disk write, and start the write. |
Z502Disk | Z502Status | R | Tells you if the disk is currently running. |
Z502Disk | Z502CheckDisk | W | Prints a file containing the contents of the specified disk |
Z502Context | Z502InitializeContext | W | Set up the properties for a new context. |
Z502Context | Z502StartContext | W | Start a new context. |
Z502Context | Z502GetPageTable | R | Returns address of the page table associated with the currently running process |
Z502Context | Z502GetContext | R | Returns address of the Context associated with the currently running process |
Z502Processor | Z502SetProcessorNumber | W | Set the number of processors to be run in the simulation |
Z502Processor | Z502GetProcessorNumber | R | Get the number of processors currently running in the simulation |
Z502Processor | Z502GetProcessorID | R | Get the ProcessorID on which the calling process is executing NOT CURRENTLY IMPLEMENTED |
A Memory Mapped IO Instruction contains
two things:
1. The Address / Function that we’re accessing.
2. The address of the MEMORY_MAPPED_IO data structure containing ALL additional information required to implement the call and to return information to the caller
Here's the form of the data structure defined in global.h
typedef struct {
int Mode;
long Field1;
long Field2;
long Field3;
long Field4;
} MEMORY_MAPPED_IO;
Every memory mapped IO call uses this structure to transmit data to and from the OS and the hardware.
Usage of these instructions can be seen in sample.c. Lot's of good examples there.
Here’s information about each of the Memory Mapped IO Addresses.
1
Memory Address | Z502Halt |
Read or Write | MEM_WRITE |
Meaning of Action | Causes the Simulation to end |
Mode | Z502Action |
Field1 | Input: 0, Output: NA |
Field2 | Input: 0, Output: NA |
Field3 | Input: 0, Output: NA |
Field4 | Input: 0, Output: NA |
Error Conditions: | This instruction should never return - no errors possible |
2
Memory Address | Z502Idle |
Read or Write | MEM_WRITE |
Meaning of Action | Causes the Processor to idle until the next interrupt occurs. |
Mode | Z502Action |
Field1 | Input: 0, Output: NA |
Field2 | Input: 0, Output: NA |
Field3 | Input: 0, Output: NA |
Field4 | Input: 0, Output: NA |
Error Conditions: | No errors possible |
3
Memory Address | Z502InterruptDevice |
Read or Write | MEM_READ |
Meaning of Action | Returns the ID and Status of device causing the interrupt |
Mode | Z502GetInterruptInfo |
Field1 | Input: 0, Output: Contains value of Device ID |
Field2 | Input: 0, Output: Contains value of Device Status |
Field3 | Input: 0, Output: NA |
Field4 | Input: 0, Output: Contains error value, or ERR_SUCCESS |
Error Conditions: | Possible errors: ERR_NO_DEVICE_FOUND - no interrupt value found, or ERR_SUCCESS |
Here's the data returned on a Z502InterruptStatus request. The code that you're given in interrupt_handler of base.c gives a good example of how this is used. You should check for errors with this call. Sometimes there is no interrupt - it's been handled in some other way. Simply return from the InterruptHandler in that case. NOTE: Each call to Z502InterruptStatus returns the appropriate data and clears the hardware database. If you were to immediately do another Z502InterruptStatus, you would NOT find this device information available; you might get ERR_NO_DEVICE_FOUND or you might get some other device information.
DEVICE ID | Returned Value and It’s Meaning: |
CPU_ERROR | Fault: ERR_ILLEGAL_ADDRESS – You’ve made a request to the hardware and you gave an illegal address as part of that request. |
Fault: ERR_BAD_PARAM - You’ve made a request to the hardware and you gave an illegal input as part of that request | |
INVALID_MEMORY | Fault: The Virtual Page Number Where a Fault occurred. When a page fault occurs, this is the hardware’s way of telling you what page caused the fault. In your memory management routine, you can then fix this by modifying the page table. |
PRIVILEGED_INSTRUCTION | Fault: Says that you tried to execute a privileged instruction while in user mode. The value returned by Z502InterruptStatus for this Device ID is always 0. |
TIMER_INTERRUPT | Interrupt: ERR_SUCCESS. Everything worked fine. This is a normal interrupt simply indicating that the timer is interrupting you as a result of your previous request for it to do so. When you get this status, proceed on normally. |
Interrupt: ERR_BAD_PARAM. You get this error from the timer only if you requested a time interval less than 0. In other words, you requested that the timer should go off some time in the past and it’s confused. | |
DISK_INTERRUPT | Interrupt: ERR_SUCCESS. Everything worked fine. This is a normal interrupt simply indicating that the disk nterrupting you as a result of your previous request for it to do so. When you get this status, proceed on normally. |
Interrupt: ERR_BAD_PARAM. You gave the disk request a bad parameter. This could be an illegal disk ID or it could be an illegal sector. | |
Interrupt: ERR_NO_PREVIOUS_WRITE. If you’re trying to read from the disk and you haven’t previously written to the requested sector, you will get this error. | |
Interrupt: ERR_DISK_IN_USE. You tried to read or write on a disk that was already engaged in an operation for you. You should check the status of the disk and make sure it’s quiescent before starting a disk operation. |
5
Memory Address | Z502Clock |
Read or Write | MEM_READ |
Meaning of Action | Returns the current hardware time |
Mode | Z502ReturnValue |
Field1 | Input: 0, Output: Current value of the hardware clock |
Field2 | Input: 0, Output: NA |
Field3 | Input: 0, Output: NA |
Field4 | Input: 0, Output: Contains error value, or ERR_SUCCESS |
Error Conditions: | Returns error if mode is inconsistant |
6
Memory Address | Z502Timer |
Read or Write | MEM_WRITE |
Meaning of Action | Starts the hardware timer |
Mode | Z502Start |
Field1 | Input: Number of time units to delay, Output, undefined |
Field2 | Input: 0, Output: Contains value of Device Status on Return |
Field3 | Input: 0, Output: NA |
Field4 | Input: 0, Output: Contains error value, or ERR_SUCCESS |
Error Conditions: | IF you give an illegal (negative) time, the timer will generate a fault that will appear in your interrupt handler. |
7
Memory Address | Z502Timer |
Read or Write | MEM_READ |
Meaning of Action | Tells you if the timer is currently running. Returns either DEVICE_IN_USE or DEVICE_FREE |
Mode | Z502Status |
Field1 | Input: 0, Output: Contains status |
Field2 | Input: 0, Output: NA |
Field3 | Input: 0, Output: NA |
Field4 | Input: 0, Output: Contains error value, or ERR_SUCCESS |
Error Conditions: | Returns error if mode is inconsistant |
8
Memory Address | Z502Disk |
Read or Write | MEM_WRITE |
Meaning of Action | Sets up the parameters needed to do a disk read, and start the read |
Mode | Z502DiskRead |
Field1 | Input: Disk ID to read. Range is 0 to MAX_NUMBER_OF_DISKS - 1: Output NA |
Field2 | Input: Disk Sector to Read From. Range is 0 to NUM_LOGICAL_SECTORS - 1, Output NA |
Field3 | Input: Memory Buffer location where Disk data will be placed, Output: NA |
Field4 | Input: 0, Output: Contains error value, or ERR_SUCCESS |
Error Conditions: |
Errors occur if you give an invalid parameter (ERR_BAD_PARAM). Errors ERR_NO_PREVIOUS_WRITE will occur if you read from a sector that has never been written to. ERR_DISK_IN_USE results if the disk is already in use. Both of these errors result in an interrupt being generated in order to report the error. |
9
Memory Address | Z502Disk |
Read or Write | MEM_WRITE |
Meaning of Action | Sets up the parameters needed to do a disk write, and start the write |
Mode | Z502DiskWrite |
Field1 | Input: Disk ID to write. Range is 0 to MAX_NUMBER_OF_DISKS - 1: Output NA |
Field2 | Input: Disk Sector to Write To. Range is 0 to NUM_LOGICAL_SECTORS - 1, Output NA |
Field3 | Input: Memory Buffer location where outgoing Disk data is located, Output: NA |
Field4 | Input: 0, Output: Contains error value, or ERR_SUCCESS |
Error Conditions: |
Errors occur if you give an invalid parameter (ERR_BAD_PARAM). ERR_DISK_IN_USE results if the disk is already in use. This error results in an interrupt being generated in order to report the error. |
10
Memory Address | Z502Disk |
Read or Write | MEM_READ |
Meaning of Action | Tells you if the disk is currently running. Returns either DEVICE_IN_USE or DEVICE_FREE |
Mode | Z502Status |
Field1 | Input: Disk ID to check. Range is 0 to MAX_NUMBER_OF_DISKS - 1: Output NA |
Field2 | Input: 0, Output Contains Disk Status |
Field3 | Input: 0, Output: NA |
Field4 | Input: 0, Output: Contains error value, or ERR_SUCCESS |
Error Conditions: |
Errors occur if you give an invalid Disk ID == ERR_BAD_PARAM |
11
Memory Address | Z502Disk |
Read or Write | MEM_WRITE |
Meaning of Action | Prints out a map of the contents of a disk into a file named "CheckDiskData". This can be used to examine what you have written to the disk. |
Mode | Z502CheckDisk |
Field1 | Input: Disk ID to print. Range is 0 to MAX_NUMBER_OF_DISKS - 1: Output NA |
Field2 | Input: 0, Output: NA |
Field3 | Input: 0, Output: NA |
Field4 | Input: 0, Output: NA |
Error Conditions: |
Errors occur if you give an invalid Disk ID == ERR_BAD_PARAM |
12
Memory Address | Z502Context |
Read or Write | MEM_WRITE |
Meaning of Action |
Set up the properties for a new context. When completed, return the new ContextID. See Section 5.4 for more details. |
Mode | Z502InitializeContext |
Field1 | Input: 0, Output: This field contains the ContextID of the new context |
Field2 | Input: Address where this context / process will first run, Output: NA |
Field3 | Input: Address of Page Table for this Process / Context, Output: NA |
Field4 | Input: 0, Output: Contains error value, or ERR_SUCCESS |
Error Conditions: | ERROR_BAD_PARAM (if illegal mode) or ERR_SUCCESS |
13
Memory Address | Z502Context |
Read or Write | MEM_WRITE |
Meaning of Action |
Start a new context. The hardware performs two possible operations as a result of this call; which are performed depend on the argument in Field2. If Field2 contains SUSPEND_CURRENT_CONTEXT_ONLY: Do NOT Start running a new Context, but simply suspend the current context. The contents of Field1 are not used. The call to the Z502 will not return until this running Context is re-started. If Field2 contains START_NEW_CONTEXT_ONLY: Start running a new Context as specified in Field1, but do NOT suspend the current context. The call to the Z502 will return after this action. If Field2 contains START_NEW_CONTEXT_AND_SUSPEND: Start running a new Context as specified in Field1, and then suspend the current context. The call to the Z502 will not return until this running Context is re-started. If you are running in UniProcessor mode, this is the ONLY choice you should make. NOTE: If you specify one of the SUSPEND options, this request does NOT return until the (currently running) context is restarted by some other process. See Section 5.4 for more details. |
Mode | Z502StartContext |
Field1 | Input: Contains the ContextID of the context to be started, Output: NA |
Field2 |
Input: Specify SUSPEND_CURRENT_CONTEXT_ONLY or START_NEW_CONTEXT_ONLY, or START_NEW_CONTEXT_AND_SUSPEND Output: NA |
Field3 | Input: 0, Output: NA |
Field4 | Input: 0, Output: Contains error value, or ERR_SUCCESS |
Error Conditions: | ERROR_BAD_PARAM or ERR_SUCCESS. The hardware complains if you give an illegal context. |
14
Memory Address | Z502Context |
Read or Write | MEM_READ |
Meaning of Action |
Returns the address of the page table associated with the currently running process - the process that's making this request |
Mode | Z502GetPageTable |
Field1 | Input: 0, Output: Address of current page table |
Field2 | Input: 0, Output: NA |
Field3 | Input: 0, Output: NA |
Field4 | Input: 0, Output: Contains error value, or ERR_SUCCESS |
Error Conditions: | ERROR_BAD_PARAM (if illegal mode) or ERR_SUCCESS. |
15
Memory Address | Z502Context |
Read or Write | MEM_READ |
Meaning of Action |
Returns the address of the Context associated with the currently running process - the process that's making this request |
Mode | Z502GetCurrentContext |
Field1 | Input: 0, Output: Address of current Context |
Field2 | Input: 0, Output: NA |
Field3 | Input: 0, Output: NA |
Field4 | Input: 0, Output: Contains error value, or ERR_SUCCESS |
Error Conditions: | ERROR_BAD_PARAM (if illegal mode) or ERR_SUCCESS. |
16
Memory Address | Z502Processor |
Read or Write | MEM_WRITE |
Meaning of Action | Set the number of processors to be run in the simulation. |
Mode | Z502SetProcessorNumber |
Field1 | Input: Number of processors in the simulation., Output: NA |
Field2 | Input: 0, Output: NA |
Field3 | Input: 0, Output: NA |
Field4 | Input: 0, Output: Contains error value, or ERR_SUCCESS |
Error Conditions: | ERROR_BAD_PARAM or ERR_SUCCESS |
17
Memory Address | Z502Processor |
Read or Write | MEM_READ |
Meaning of Action | Get the number of Processors in the simulation |
Mode | Z502GetProcessorNumber |
Field1 | Input: 0, Output: Number of processors |
Field2 | Input: 0, Output: NA |
Field3 | Input: 0, Output: NA |
Field4 | Input: 0, Output: Contains error value, or ERR_SUCCESS |
Error Conditions: | ERROR_BAD_PARAM or ERR_SUCCESS |
One of the things to watch out for is the occasion when there’s more than one interrupt outstanding. This will happen in your project when you’re working with disks. It will also happen with Timers if two Timer requests occur chronologically close to each other. You need to establish your interrupt_handler and fault_handler code so that you service ALLthe interrupts that may be requesting action. Look at this piece of INCORRECT logic to see what might happen.
1. Get Device ID
2. Service this Device
3. Return from Interrupt
But what if there were two interrupts? You’ve taken care of only one of them – and the device that wasn’t serviced WILL NOT call your interrupt handler again. This results in a lost interrupt which will screw up everything. The correct way to write your handler is to keep asking for Z502InterruptDevice until you get an error condition of –1 meaning there’s no more interrupt work to do. This pseudo-code outlines a more correct way of accomplishing this:
MEMORY_MAPPED_IO mmio; mmio.Mode = Z502GetInterruptInfo; mmio.Field1 = mmio.Field2 = mmil.Field3 = mmio.Field4 = 0; MEM_READ( Z502InterruptDevice, &mmio ); while ( mmio.Field4 == ERR_SUCCESS ) { // We've found a valid interrupt InterruptingDevice = mmio.Field1; // Do your interrupt handling work // mmio.Mode = Z502GetInterruptInfo; // See if there's another Interrupt mmio.Field1 = mmio.Field2 = mmil.Field3 = mmio.Field4 = 0; MEM_READ( Z502InterruptDevice, &mmio ); } // End of while
Whenever the Z502 machine is executing code, we say it does so in some context. A context is a state of the executing CPU, essentially the contents of its registers, including program counter, page table address, etc. If the machine is in user mode, then it has a user-mode context. Otherwise it has a kernel-mode context.
The current context, that is the contents of those registers mentioned above, can be captured by Z502 hardware and stored away in a data structure, predefined and known to hardware, in the Hardware memory. A context data structure can be loaded into the relevant Z502 registers, thereby causing the CPU to execute that context (and thus the process associated with that context.) This means that any state of execution, that is, a context, may be stored away and later resumed in our machine.
The context data structure as known to the Z502 hardware is not visible to the OS (you don't need to use it.) But your OS does hang on to the address of the context structure and uses it as a handle for telling the hardware what process/context to load next. The context data base is owned by the hardware and shouldn't be messed with by the Operating System.. One convenient way for the operating system to do this is to define a Process Control Block (PCB) that has lots of process related information in it. In addition, it contains the pointer to the context structure. That pointer can be handed to the hardware as needed.
In the Z502 machine, a context data structure can be created or destroyed by invoking the INITIALIZA_CONTEXT primitive. The operating system can also cause a context switch by invoking the START_CONTEXT primitive. When the operating system switches the CPU into a different context we say it resumes that context. These primitives are invoked as C routines in kernel mode as described below:
Usage of Z502Context / Z502InitializeContext and Z502StartContext
To allow the hardware to create a context, you must supply some information that is placed in the MEMORY_MAPPED_IO structure:.
Field1 | The Context Pointer is RETURNED by the InitializeContext call and is required for the StartContext call. |
Field2 | You must specify the starting address of the function where you want this process to begin execution. This could be Test0, etc. In the example below, when we start this new context, it will execute at location SampleCode |
Field3 | The address of the page table for use by this process. Though this will only be used in the last half of the course, you need to provide it now. Just doing it the way shown in the code below will work fine. |
MEMORY_MAPPED_IO mmio; // Declaration of structure for doing memory mapped IO YOUR_PROCESS__CONTROL__BLOCK pcb; // This is YOUR data structure. It will include a field for holding the Context address void *PageTable = (void *) calloc(2, NUMBER_VIRTUAL_PAGES )// Here you declare the address of the page table and allocate memory for the table. Size matters! pcb.PageTable = PageTable; // Save this in your PCB for later use if ((argc > 1) && (strcmp(argv[1], "sample") == 0)) { // Do this if the user has asked to run the sample code mmio.Mode = Z502InitializeContext; // Setup everything to create a context mmio.Field1 = 0; mmio.Field2 = (long) SampleCode; mmio.Field3 = (long) PageTable; mmio.Field4 = 0; MEM_WRITE(Z502Context, &mmio); // Call to the hardware if ( mmio.Field4 != ERR_SUCCESS ) { // It's worth checking that your calls are successful. printf( "Initialize Context has an error\n"); exit(0); } pcb.NewContext = mmio.Field1; // Store the context in an OS structure where YOU can find it mmio.Mode = Z502StartContext; // Set up to start the context mmio.Field2 = 0; MEM_WRITE(Z502Context, &mmio); // Start up the context if ( mmio.Field4 != ERR_SUCCESS ) { // It's worth checking that your calls are successful. printf( "Start Context has an error\n"); exit(0); } } // End of code to create a context and execute that context for the code in sample.c
Note again that Z502InitializeContext only creates a new context data structure that magically resides somewhere in the hardware microstore; the new context is not executed until it is explicitly transferred to by a Z502StartContext primitive.
These are the steps taken by Z502 when Z502StartContext is invoked:
Remember that Z502StartContext is a privileged instruction not available in User mode. Therefore, it can only be used to switch a context in KERNEL_MODE into another kernel context or into a user context. Switches from a user context into a kernel context are performed by hardware exceptions and SOFTWARE TRAP's, as described in the next section.
A switch to OS502 can be forced upon the current execution by a hardware exception.
There are three kinds of exceptions:
1. When a user-mode context wishes to invoke the kernel, presumably for some operating system services, it must execute a system call, that causes a SOFTWARE TRAP to occur. Execution in support of the TRAP starts at svc in base.c. A SOFTWARE TRAP is handled by the hardware just like an exception or fault.
2. When a hardware component - a disk or a timer needs service it will cause an interrupt and will cause execution in the InterruptHandler. This interrupt can be caused by the completion of service - the timer has delayed long enough for instance, or it can be caused because the Operating System requested an illegal operation.
3. A request of some kind - not a hardware request but one involving a privileged instruction or a memory reference may result in execution in the FaultHandler. The various types of action are explained below.
An explanation of the parameters returned by the hardware for these services can be found in global.h.
Exceptions can occur for several reasons. Each time the CPU encounters an exception, it performs the following steps:
These actions are performed in hardware atomically when an exception occurs.
The various reasons for exceptions to occur are:
DISK AND DELAY_TIMER INTERRUPTS
An I/O instruction has completed. There may be more than one process waiting for I/O interrupts. There may be more than one device that raised it's interrupt at the same time. Remember to catch them all.
SOFTWARE_TRAP
The user program has executed a system call or SOFTWARE TRAP instruction, presumably requesting some service from the operating system. The entry in the SYS_CALL_CALL_TYPE is the SOFTWARE TRAP code from the instruction. See the next section on Vector Nitty-Gritty for details.
PAGE_FAULT ( INVALID_MEMORY FAULT )
The virtual to physical address translation for a user instruction has failed (see Section 8 on Address Translation.) A memory IO instruction can be used to gain information about the faulting address.
CPU ERROR FAULT
For some reason, bad data was given to the CPU. An easy way to get this error is to pass a bad pointer to a context block to the hardware. A memory IO instruction can be used to gain information about the error code.
PRIVILEGED INSTRUCTION FAULT
The machine was in USER_MODE ( running a user program ) when a privileged hardware instruction was executed.
This section contains details on how the TO_VECTOR is set up for each type of exception. The TO_VECTOR contains the addresses of the handlers for the three types of exceptions; in os_init(), there are pointers to the routines that will support these exceptions. In the table below are listed the various exceptions, the type of exception represented, and the offset stored in TO_VECTOR.
|
|
|
SOFTWARE_TRAP |
|
svc |
CPU_ERROR |
|
fault_handler |
INVALID_MEMORY |
|
fault_handler |
PRIVILEGED_INSTRUCTION |
|
fault_handler |
TIMER_INTERRUPT |
|
interrupt_handler |
DISK_INTERRUPT |
|
interrupt_handler |
DISK_INTERRUPT + 1 |
|
interrupt_handler |
DISK_INTERRUPT + 2 |
|
interrupt_handlerR_INT_HANDLER_ADDR |
|
|
|
LARGEST_STAT_VECTOR _INDEX (See include files) |
|
interrupt_handler |
NOTE: There is not an explicit mnemonic defined for each of the DISK_INTERRUPTs. Disk number 0 will interrupt with exception number DISK_INTERRUPT; disk number 2 at DISK_INTERRUPT + 1, all the way to the highest disk, MAX_NUMBER_OF_DISKS, which has exception number DISK_INTERRUPT + MAX_NUMBER_OF_DISKS - 1. NOTE ALSO: The hardware ONLY sets values available to Z502InterruptDevice.
The Z502 disk is a single rotating circular surface with a single read/write head that moves along the radius of rotation. The disk surface is divided into NUM_LOGICAL_SECTORS angular sectors. Each sector contains PGSIZE Bytes; each disk contains NUM_LOGICAL_SECTORS of these sectors. The disk rotates at a rate of one revolution every 6 milliseconds.
Only one I/O operation can be outstanding at any time on one disk; however, different disks can be in use simultaneously. The Z502 machine will abort or fault if a disk read or write operation is issued while there is another disk operation still pending on that device. You should thus determine the disk's status before starting a disk operation.
The Z502 delay timer produces an interrupt <argument> milliseconds after it is started. This is useful for maintaining the SLEEP system call, and for managing CPU time limiting. If the requested time delay is less than 0, the timer interrupts immediately with an ERR_BAD_PARAM.
As shown in Table A.1, address translation hardware is only applicable to the User Memory and only in user mode.
User visible memory uses a page table to access Physical Memory. This is the page table that the OS allocated when it did the Z502InitializeContext.
Memory is addressed in increments of BYTES. But 4-bytes, a long integer, is the quantity of information that is read/written.
Virtual addresses are converted to real addresses in the following steps:
Bit |
Bit |
Bit |
(1 Bit) |
Number (12 Bits) |
The masks available to you, defined in C are given here. These masks are used by the hardware, so don't redefine them.
PTBL_VALID_BIT
PTBL_MODIFIED_BIT
PTBL_REFERENCED_BIT
PTBL_PHYS_PG_NO
Note that no Z502 machine can ever have more than 2 ^ 12, or 4K pages of physical memory, since the real page number field is only 12 bits long. But it isn't required to have that much.
NOTE: All references to memory are returned in chunks of 4-bytes (long integers). The reference address need NOT be on 4-byte boundaries however.
The page table information should be set up at the time of the first fault. At that fault time, Z502_PAGE_TBL_ADDR and Z502_PAGE_TBL_LENGTH should be defined; because these items will be saved thereafter by the hardware in the CONTEXT, the Operating System need not worry about maintaining them.
Also at that fault time, and on all subsequent faults, the operating system will modify the contents of the page table as described in the previous section.
The size of the memory of a particular Z502 machine, along with its page size, is contained in the following definition
PGSIZE_PHYSICAL_PAGES |
physical memory length in bytes |
PGSIZE | page length in bytes (16) |
Both NUMBER_PHYSICAL_PAGES and PGSIZE are guaranteed to be powers of 2.
When the machine is powered on, the entire operating system is automatically loaded into the kernel memory. After loading read-only registers with proper values, the machine creates an initial kernel context pointing to the operating system routine os_init(). The machine then switches the CPU into this initial context, causing the execution of the operating system to begin. Note that registers TO_VECTOR are to be initialized by the operating system, not by bootstrapping hardware.