/** Simulate the Memory Management Unit (MMU) that translates the contents of the Memory Address Register (MAR) to a real physical address. Use the contents of MAR = [segment,offset] as the logical address to be translated to a physical address. <b> Algorithm: </b> \verbatim Set segment to the segment saved in the MAR: If in kernel mode (CPU's mode equals 1) Set segment to be in upper half of Mem_Map (MAR.segment+Max_Segments) Otherwise, in user mode (CPU's mode equals 0) Just use lower half of Mem_Map (MAR.segment) If illegal memory access (access == 0) Create seg. fault event at the current time using the CPU's active process's terminal position (+ 1) as the agent ID Return -1 If address is out of the bounds Create address fault event at the current time using the CPU's active process's terminal position (+ 1) as the agent ID Return -1 Compute physical memory address: Address = base address from segment in Mem_Map + offset saved in MAR Return address \endverbatim @retval address -- physical address into Mem obtained from logical address stored in MAR */ int Memory_Unit( ) { //Set segment to the segment saved in the MAR: int currSeg, currAddress; //If in kernel mode (CPU's mode equals 1) if(CPU.state.mode == 1) { //Set segment to be in upper half of Mem_Map (MAR.segment+Max_Segments) currSeg = MAR.segment + Max_Segments; } //Otherwise, in user mode (CPU's mode equals 0) else { //Just use lower half of Mem_Map (MAR.segment) currSeg = MAR.segment; } //If illegal memory access (access == 0) if(Mem_Map[currSeg].access == 0) { //Create seg. fault event at the current time using the CPU's active process's terminal position (+ 1) as the agent ID Add_Event(SEGFAULT_EVT, CPU.active_pcb->term_pos + 1, &Clock); //Return -1 return -1; } //If address is out of the bounds if(currSeg > (Max_Segments*2 - 1)) { //Create address fault event at the current time using the CPU's active process's terminal position (+ 1) as the agent ID Add_Event(ADRFAULT_EVT, CPU.active_pcb->term_pos + 1, &Clock); //Return -1 return -1; } //Compute physical memory address: //Address = base address from segment in Mem_Map + offset saved in MAR currAddress = Mem_Map[currSeg].base + MAR.offset; //Return address return currAddress; }
/** Start processing I/O request for given device. <b> Important Notes:</b> \li Add_time() and Diff_time() should be used for calculating and incrementing many of the simulation time fields, such as the device's busy time. \li The time at which I/O completes is obtained using rb->bytes and the speed of the device: device->bytes_per_sec and device->nano_per_byte. Using all these values, the seconds and nanoseconds fields of a simulation time structure can be filled in. Note that bytes per sec and nano per byte are, to some degree, reciprocals, which implies that one should divide by the former but multiply with the latter. \li When finally adding the ending I/O event to the event list, the device's ID must be converted into an agent ID. Recall in objective 1 that agent IDs for devices were set to directly follow terminal user IDs. <b> Algorithm: </b> \verbatim If the device is busy Do nothing If the device has no requests to service in its waiting queue Do nothing Get and remove first node from waiting list Mark rb as the current request block in the device; set rb's status as active Update time spent waiting in queue--calculate difference between current time and time it was enqueued and add this value to the current queue wait time--use Add_time() and Diff_time() Compute length of time that I/O will take (transfer time)--compute number of seconds and then use the remainder to calculate the number of nanoseconds Increment device's busy time by the transfer time--use Add_time() Compute time I/O ends--ending time is the current time + transfer time--use Add_time() and Diff_time() Update the number of IO requests served by the device Add ending I/O device interrupt to event list \endverbatim @param[in] dev_id -- ID of device @retval None */ void Start_IO( int dev_id ) { //If the device is busy if(Dev_Table[dev_id].current_rb != NULL && Dev_Table[dev_id].current_rb->status == ACTIVE_RB) { //Do nothing return; } //If the device has no requests to service in its waiting queue if(Dev_Table[dev_id].wait_q == NULL) { //Do nothing return; } rb_list *rbNode = Dev_Table[dev_id].wait_q; //Get and remove first node from waiting list Dev_Table[dev_id].wait_q->prev->next = Dev_Table[dev_id].wait_q->next; Dev_Table[dev_id].wait_q->next->prev = Dev_Table[dev_id].wait_q->prev; Dev_Table[dev_id].wait_q = rbNode->next; if(rbNode == Dev_Table[dev_id].wait_q) { Dev_Table[dev_id].wait_q = NULL; } rbNode->next = rbNode; rbNode->prev = rbNode; //Mark rb as the current request block in the device; set rb's status as active Dev_Table[dev_id].current_rb = rbNode->rb; rbNode->rb->status = ACTIVE_RB; //Update time spent waiting in queue--calculate difference between current time and time it was enqueued and add this value to the current queue wait time--use Add_time() and Diff_time() time_type currTime = Clock; Diff_time(&rbNode->rb->q_time, &currTime); Add_time(&currTime, &Dev_Table[dev_id].total_q_time); //Compute length of time that I/O will take (transfer time)--compute number of seconds and then use the remainder to calculate the number of nanoseconds time_type transferTime; transferTime.seconds = rbNode->rb->bytes / Dev_Table[dev_id].bytes_per_sec; transferTime.nanosec = (rbNode->rb->bytes % Dev_Table[dev_id].bytes_per_sec)*Dev_Table[dev_id].nano_per_byte; //Increment device's busy time by the transfer time--use Add_time() Add_time(&transferTime, &Dev_Table[dev_id].total_busy_time); //Compute time I/O ends--ending time is the current time + transfer time--use Add_time() and Diff_time() time_type ioEndTime = transferTime; Add_time(&Clock, &ioEndTime); //Update the number of IO requests served by the device Dev_Table[dev_id].num_served++; //Add ending I/O device interrupt to event list Add_Event(EIO_EVT,(dev_id+Num_Terminals+1),&ioEndTime); }
/** Start processing I/O request for given device. <b> Important Notes:</b> \li Add_time() and Diff_time() should be used for calculating and incrementing many of the simulation time fields, such as the device's busy time. \li The time at which I/O completes is obtained using rb->bytes and the speed of the device: device->bytes_per_sec and device->nano_per_byte. Using all these values, the seconds and nanoseconds fields of a simulation time structure can be filled in. Note that bytes per sec and nano per byte are, to some degree, reciprocals, which implies that one should divide by the former but multiply with the latter. \li When finally adding the ending I/O event to the event list, the device's ID must be converted into an agent ID. Recall in objective 1 that agent IDs for devices were set to directly follow terminal user IDs. <b> Algorithm: </b> \verbatim If the device is busy Do nothing If the device has no requests to service in its waiting queue Do nothing Get and remove first node from waiting list Mark rb as the current request block in the device; set rb's status as active Update time spent waiting in queue--calculate difference between current time and time it was enqueued and add this value to the current queue wait time--use Add_time() and Diff_time() Compute length of time that I/O will take (transfer time)--compute number of seconds and then use the remainder to calculate the number of nanoseconds Increment device's busy time by the transfer time--use Add_time() Compute time I/O ends--ending time is the current time + transfer time--use Add_time() and Diff_time() Update the number of IO requests served by the device Add ending I/O device interrupt to event list \endverbatim @param[in] dev_id -- ID of device @retval None */ void Start_IO( int dev_id ) { // DECLARE VARIABLES struct rb_list* rbListNode; struct rb_type* rb; struct time_type wait_time; struct time_type transfer_time; struct time_type eio_time; // TODO: verify discrepancy of: if(){ return } conditions // TODO: change to "==" (?) //If the device is busy if( Dev_Table[ dev_id ].current_rb != NULL ){ //Do nothing return; } //If the device has no requests to service in its waiting queue if( Dev_Table[ dev_id ].wait_q == NULL ){ //Do nothing return; } // TODO: revisit discrepancy //Get and remove first node from waiting list rbListNode = Dev_Table[ dev_id ].wait_q; // also store the node's request block to rb since we're freeing the node rb = rbListNode->rb; // does wait_q contain >1 node? if( rbListNode->next != rbListNode ){ // wait_q contains more than 1 node rbListNode->prev->next = rbListNode->next; rbListNode->next->prev = rbListNode->prev; Dev_Table[ dev_id ].wait_q = Dev_Table[ dev_id ].wait_q->next; } else { // wait_q contains exactly 1 node rbListNode->next = NULL; rbListNode->prev = NULL; Dev_Table[ dev_id ].wait_q = NULL; } // TODO: free rbListNode (?) //Mark rb as the current request block in the device; set rb's status as active Dev_Table[ dev_id ].current_rb = rb; rb->status = ACTIVE_RB; // TODO: revisit discrepancy on all time calculations below //Update time spent waiting in queue--calculate difference between current time and time it was enqueued and add this value to the current queue wait time--use Add_time() and Diff_time() wait_time = Clock; Diff_time( &( rb->q_time ), &wait_time ); Add_time( &wait_time, &( Dev_Table[ dev_id ].total_q_time ) ); //Compute length of time that I/O will take (transfer time)--compute number of seconds and then use the remainder to calculate the number of nanoseconds transfer_time.seconds = rb->bytes / Dev_Table[ dev_id ].bytes_per_sec; transfer_time.nanosec = ( rb->bytes % Dev_Table[ dev_id ].bytes_per_sec ) * Dev_Table[ dev_id ].nano_per_byte; //Increment device's busy time by the transfer time--use Add_time() Add_time( &transfer_time, &( Dev_Table[ dev_id ].total_busy_time ) ); //Compute time I/O ends--ending time is the current time + transfer time--use Add_time() and Diff_time() eio_time = Clock; Add_time( &transfer_time, &eio_time ); //Update the number of IO requests served by the device Dev_Table[ dev_id ].num_served = Dev_Table[ dev_id ].num_served + 1; //Add ending I/O device interrupt to event list Add_Event( EIO_EVT, dev_id + Num_Terminals + 1, &eio_time ); }
/** Simulate the Central Processing Unit. The CPU, in general, does the following: \li Fetches the instruction from memory (Mem) \li Interprets the instruction. \li Creates a future event based on the type of instruction and inserts the event into the event queue. Set_MAR() is called to define the next memory location for a Fetch(). The first fetch from memory is based on CPU.state.pc. Instructions that create events are: \li WIO \li SIO \li END The SKIP and JUMP instructions are executed in "real-time" until one of the other instructions causes a new event. An addressing fault could be generated by a call to Fetch() or Write(), so Cpu() should just return in this case (the simulator will later call Abend_Service() from the interrupt handler routine to end the simulated program). <b> Important Notes:</b> \li Use a special agent ID, i.e., 0, to identify the boot program. For all other objectives, the agent ID should be retrieved from the CPU's currently running process--use this process's terminal position and add 1 so that it does not conflict with boot. \li The only instructions processed by Cpu() are SIO, WIO, END, SKIP, and JUMP. No other instructions, such as REQ and device instructions, are encountered in Cpu(). This is by incrementing the program counter by 2 to bypass these unused instructions. \li A while(1) loop is needed to correctly evaluate SKIP and JUMP instructions since multiple SKIP and JUMP instructions may be encountered in a row. The loop is exited when an SIO, WIO, or END instruction is encountered. \li The function Burst_time() should be used to convert CPU cycles for SIO, WIO, and END instructions to simulation time (time_type). <b> Algorithm: </b> \verbatim Identify the agent ID for the currently running program: If CPU has no active PCB, then the program is boot Otherwise, the program is the active process running in the CPU, so use its ID Loop forever doing the following: Set MAR to CPU's program counter Fetch instruction at this address If fetch returns a negative value, a fault has occurred, so Return Determine type of instruction to execute If SIO, WIO, or END instruction If the objective is 3 or higher Increment total number of burst cycles for PCB Calculate when I/O event will occur using current time + burst time Add event to event list Increment PC by 2 to skip the next instruction--device instruction Return from Cpu() (exit from loop) If SKIP instruction If count > 0, Decrement count by 1 Write this change to memory by calling Write() If write returns a negative value, a fault has occurred, so Return Increment the CPU's PC by 2 since the next instruction is to be skipped Otherwise, instruction count equals 0, so Increment the CPU's PC by 1 since the next instruction, JUMP, is to be executed Continue looping If JUMP instruction Set the PC for the CPU so that the program jumps to the address determined by the operand of instruction Continue looping \endverbatim \retval None */ void Cpu( ) { time_type *eventTime; int counter; instr_type *instruction; instruction = (instr_type *) malloc(sizeof(instr_type)); eventTime = (time_type *) malloc(sizeof(time_type)); //Identify the agent ID for the currently running program: //If CPU has no active PCB, then the program is boot if(CPU.active_pcb == NULL) { Agent = 0; } //Otherwise, the program is the active process running in the CPU, so use its ID //use this process's terminal position and add 1 //so that it does not conflict with boot. else { Agent = CPU.active_pcb->term_pos + 1; } //Loop forever doing the following: while(1) //~ for(counter = 0; counter < 3; counter++) { //Set MAR to CPU's program counter Set_MAR(&CPU.state.pc); //Fetch instruction at this address int fetch = Fetch(instruction); //If fetch returns a negative value, a fault has occurred, so if(fetch < 0) { //Return return; } //Determine type of instruction to execute //If SIO, WIO, or END instruction if(instruction->opcode == 0 || instruction->opcode == 1 || instruction->opcode == 5) { //If the objective is 3 or higher if(Objective >= 3) { //Increment total number of burst cycles for PCB //~ CPU.active_pcb->sjnburst++; CPU.active_pcb->sjnburst += instruction->operand.burst; } //Calculate when I/O event will occur using current time = clock + burst time Burst_time(instruction->operand.burst, eventTime); Add_time(&Clock, eventTime); //Add event to event list int eventID; switch(instruction->opcode) { case SIO_OP: eventID = SIO_EVT; break; case WIO_OP: eventID = WIO_EVT; break; case END_OP: eventID = END_EVT; break; } Add_Event(eventID, Agent, eventTime); //Increment PC by 2 to skip the next instruction--device instruction CPU.state.pc.offset += 2; //Return from Cpu() (exit from loop) return; } //If SKIP instruction else if(instruction->opcode == 4) { //If count > 0, if(instruction->operand.count > 0) { //Decrement count by 1 instruction->operand.count--; //Write this change to memory by calling Write() int write = Write(instruction); //If write returns a negative value, a fault has occurred, so if(write < 0) { //Return return; } //Increment the CPU's PC by 2 since the next instruction is to be skipped CPU.state.pc.offset += 2; } //Otherwise, instruction count equals 0, so else { //Increment the CPU's PC by 1 since the next instruction, JUMP, is to be executed CPU.state.pc.offset++; } //Continue looping } //If JUMP instruction else if(instruction->opcode == 3) { //Set the PC for the CPU so that the program jumps to the address determined by the operand of instruction CPU.state.pc.segment = instruction->operand.address.segment; CPU.state.pc.offset = instruction->operand.address.offset; //Continue looping } } }