/** Service event to wait on an I/O operation. This function services requests to check an I/O operation for completion. If the operation is complete, the process resumes execution immediately; otherwise, the process is blocked. <b> Important Notes:</b> \li Events of type WIO_EVT will result in this call via Interrupt_Handler() in simulator.c after interrupt() in obj1.c sets the global Event variable. \li WIO, unlike SIO, is a blocking I/O operation. As such, the program causing the event will be blocked (removed from the ready queue or from the CPU) after this event is serviced. However, if I/O had already been started from an earlier SIO event, then the process may be able to continue, but only if the I/O request has already been completed. \li Retrieving the request instruction (REQ) from memory is done similarly to finding the device ID in Alloc_rb(). The saved CPU information is used to find the necessary segment in the PCB's segment table. Once the segment has been found, the segment base is added to the current PC offset (minus 1) to get the position in memory that the request instruction is located at. \li The request instruction's operand, which is an address, should be passed to Find_rb() to find the needed request block as the request address holds information pointing to the device being waited on. <b> Algorithm: </b> \verbatim Retrieve pcb from the terminal table that is waiting for I/O Retrieve request instruction using saved CPU information Locate request block that process wants to wait for--call Find_rb() Determine status of request block If rb is still active or pending Block process since I/O not finished: Mark PCB as blocked; mark it Turn CPU and scheduling switches on to schedule a new process Calculate active time for process and busy time for CPU Record time process was blocked Print output message giving blocked procese and burst count If rb has finished Delete it and keep running current process: Delete rb from pcb's list Turn CPU switch on to execute a process Turn scheduling flag off to use current process (i.e., not schedule a new process) \endverbatim */ void Wio_Service( ) { //Retrieve pcb from the terminal table that is waiting for I/O //~ pcb_type *pcb = Term_Table[Agent-1]; pcb_type *pcb = Term_Table[Agent - 1]->rb_q->rb->pcb; //Retrieve request instruction using saved CPU information //~ instr_type instr = Mem[pcb->seg_table[pcb->cpu_save.pc.segment].base + pcb->cpu_save.pc.offset-1]; instr_type instr = Mem[pcb->seg_table[pcb->cpu_save.pc.segment].base + CPU.state.pc.offset-1]; //Locate request block that process wants to wait for--call Find_rb() rb_type *rb = Find_rb(pcb, &instr.operand.address); //Determine status of request block //If rb is still active or pending if(rb->status == ACTIVE_RB || rb->status == PENDING_RB) { //Block process since I/O not finished: //Mark PCB as blocked; mark it pcb->status = BLOCKED_PCB; //Turn CPU and scheduling switches on to schedule a new process CPU_SW = ON; SCHED_SW = ON; //Calculate active time for process and busy time for CPU time_type currTime = Clock; Diff_time(&pcb->run_time, &currTime); Add_time(&currTime, &(pcb->total_run_time)); Add_time(&currTime, &(CPU.total_busy_time)); //Record time process was blocked pcb->block_time = Clock; pcb->wait_rb = rb; //Print output message giving blocked procese and burst count print_out("\t\tUser %s is blocked for I/O.\n", pcb->username); print_out("\t\tCPU burst was %d instructions.\n\n",pcb->sjnburst); } //If rb has finished if(rb->status == DONE_RB) { //Delete it and keep running current process: //Delete rb from pcb's list Delete_rb(rb, pcb); //Turn CPU switch on to execute a process CPU_SW = ON; //Turn scheduling flag off to use current process (i.e., not schedule a new process) SCHED_SW = OFF; } }
/** Select the next process for allocation to the CPU according to the currently active scheduling algorithm. <b> Important Notes:</b> \li Currently, only first-come first-serve scheduling is being used. <b> Algorithm (FCFS): </b> \verbatim Update the number of processes serviced by the CPU Get first pcb in CPU's ready queue Remove first node in queue If not removing last pcb in ready queue Change head of ready queue Calculate time process was ready Increment total ready time for process Increment total CPU queue waiting time Reset the burst count for the next program \endverbatim @retval pcb -- next active process */ pcb_type* Scheduler( ) { //Update the number of processes serviced by the CPU CPU.num_served++; if(CPU.ready_q == NULL) return NULL; //Get first pcb in CPU's ready queue pcb_list *pcbNode = CPU.ready_q; pcb_type *pcb = pcbNode->pcb; //Remove first node in queue CPU.ready_q = CPU.ready_q->next; //If not removing last pcb in ready queue if(pcbNode != CPU.ready_q) { //Change head of ready queue CPU.ready_q->prev->prev->next = CPU.ready_q; CPU.ready_q->prev = CPU.ready_q->prev->prev; //~ pcbNode->prev->next = pcbNode->next; //~ pcbNode->next->prev = pcbNode->prev; //~ CPU.ready_q = pcbNode->next; } else { CPU.ready_q = NULL; } pcb->run_time = Clock; //Calculate time process was ready time_type currTime = Clock; Diff_time(&pcb->ready_time, &currTime); //Increment total ready time for process Add_time(&currTime, &pcb->total_ready_time); //Increment total CPU queue waiting time Add_time(&currTime, &CPU.total_q_time); //Reset the burst count for the next program pcb->sjnburst = 0; //return next active process return pcb; }
/** Service an I/O interrupt event from a device. <b> Important Notes:</b> \li Events of type EIO_EVT will result in this call via Interrupt_Handler() in simulator.c after interrupt() in obj1.c sets the global Event variable. <b> Algorithm: </b> \verbatim Retrieve device that caused the I/O interrupt from the device table Retrieve request block issued to device that is now complete Retrieve process that issued the request block to the device Mark that device no longer has a current request block Mark rb's status as done Service next I/O request block for this device--call Start_IO() Determine current status of process If process is ready to be ran or is already running Turn off both switches If process is blocked due to I/O If process was waiting on this rb The process can now run: Delete rb from pcb's waiting list--call Delete_rb() Mark pcb as ready to run Add pcb to CPU's ready queue--call Add_cpuq() Record time process became ready Calculate and record time process was blocked If CPU is has no currently active process Turn on both switches to schedule a new process to run Otherwise, CPU is already busy, so Turn off both switches If the process is done, but only waiting for I/O to complete. Delete rb from pcb's waiting list--call Delete_rb() Load next program for pcb--call Next_pgm() If one was available Mark pcb's status as ready to run Record time process became ready Calculate and record time process was blocked If CPU is idle Turn on CPU and scheduling switches to run a new process Otherwise, CPU is already busy, so Turn off both switches \endverbatim } */ void Eio_Service( ) { //Retrieve device that caused the I/O interrupt from the device table device_type *device = &(Dev_Table[Agent-Num_Terminals-1]); //Retrieve request block issued to device that is now complete rb_type *currRB = device->current_rb; //Retrieve process that issued the request block to the device pcb_type *currPCB = currRB->pcb; //Mark that device no longer has a current request block device->current_rb = NULL; //Mark rb's status as done currRB->status = DONE_RB; //Service next I/O request block for this device--call Start_IO() Start_IO(Agent - Num_Terminals - 1); //Determine current status of process //If process is ready to be ran or is already running if(currPCB->status == READY_PCB || currPCB->status == ACTIVE_PCB) { //Turn off both switches CPU_SW = OFF; SCHED_SW = OFF; } //If process is blocked due to I/O else if(currPCB->status == BLOCKED_PCB) { //If process was waiting on this rb if(currPCB->wait_rb == currRB) { //The process can now run: //Delete rb from pcb's waiting list--call Delete_rb() Delete_rb(currRB, currPCB); //Mark pcb as ready to run currPCB->status = READY_PCB; currPCB->wait_rb = NULL; //Add pcb to CPU's ready queue--call Add_cpuq() Add_cpuq(currPCB); } //Record time process became ready currPCB->ready_time = Clock; //Calculate and record time process was blocked time_type currTime = Clock; Diff_time(&(currPCB->block_time), &currTime); Add_time(&currTime, &(currPCB->total_block_time)); //If CPU is has no currently active process if(CPU.active_pcb == NULL) { //Turn on both switches to schedule a new process to run CPU_SW = ON; SCHED_SW = ON; } //Otherwise, CPU is already busy, so else { //Turn off both switches CPU_SW = OFF; SCHED_SW = OFF; } } //If the process is done, but only waiting for I/O to complete. if(currPCB->status == DONE_PCB) { //Delete rb from pcb's waiting list--call Delete_rb() Delete_rb(currRB,currPCB); //Load next program for pcb--call Next_pgm() //If one was available if(Next_pgm(currPCB) != 0) { //Mark pcb's status as ready to run currPCB->status == READY_PCB; } //Record time process became ready currPCB->ready_time = Clock; //Calculate and record time process was blocked time_type currTime = Clock; Diff_time(&(currPCB->block_time), &currTime); Add_time(&currTime, &(currPCB->total_block_time)); //If CPU is idle if(CPU.active_pcb == NULL) { //Turn on CPU and scheduling switches to run a new process CPU_SW = ON; SCHED_SW = ON; } //Otherwise, CPU is already busy, so else { //Turn off both switches CPU_SW = OFF; SCHED_SW = OFF; } } }
/** 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 ); }
/** Select the next process for allocation to the CPU according to the currently active scheduling algorithm. <b> Important Notes:</b> \li Currently, only first-come first-serve scheduling is being used. <b> Algorithm (FCFS): </b> \verbatim Update the number of processes serviced by the CPU Get first pcb in CPU's ready queue Remove first node in queue If not removing last pcb in ready queue Change head of ready queue Calculate time process was ready Increment total ready time for process Increment total CPU queue waiting time Reset the burst count for the next program \endverbatim @retval pcb -- next active process */ pcb_type* Scheduler( ) { // DECLARE VARIABLES struct pcb_list* nextPcbListNode; struct pcb_type* nextPcb; struct time_type ready_time; //Update the number of processes serviced by the CPU CPU.num_served = CPU.num_served + 1; //Get first pcb in CPU's ready queue nextPcbListNode = CPU.ready_q; if( nextPcbListNode == NULL ){ return NULL; } //Remove first node in queue nextPcb = nextPcbListNode->pcb; //If not removing last pcb in ready queue if( nextPcbListNode != nextPcbListNode->next ){ //Change head of ready queue // set the last node's next ptr to the second node in the list CPU.ready_q->prev->next = CPU.ready_q->next; // set the second node in the list's prev ptr to the last node CPU.ready_q->next->prev = CPU.ready_q->prev; // set the head of the ready queue to be the second node in the list CPU.ready_q = CPU.ready_q->next; // TODO: add below else for "removing last pcb in ready queue" case } else { CPU.ready_q->next = NULL; CPU.ready_q->prev = NULL; CPU.ready_q = NULL; } // TODO: free(CPU.ready_q) here? //Calculate time process was ready //ready_time.seconds = 0; //ready_time.nanosec = 0; ready_time = Clock; nextPcb->run_time = Clock; Diff_time( &nextPcb->ready_time, &ready_time ); //Diff_time( &Clock, &nextPcb->ready_time ); //Increment total ready time for process Add_time( &ready_time, &nextPcb->total_ready_time ); //Add_time( &nextPcb->ready_time, &nextPcb->total_ready_time ); //Increment total CPU queue waiting time Add_time( &ready_time, &CPU.total_q_time ); //Add_time( &nextPcb->ready_time, &CPU.total_q_time ); //Reset the burst count for the next program nextPcb->sjnburst = 0; return nextPcb; }
/** Compute all simulation statistics and store them in the appropriate variables and data structures for display. It is called by Clean_Up(). <b> Algorithm: </b> \verbatim For each user Calculate total processing time Calculate total job blocked time Calculate total job wait time Calculate total job execution time Calculate efficiency for each process For each device Calculate response time for each device Calculate total idle time for each device Calculate utilization for each device Calculate average user execution time Calculate average user logon time Calculate average user blocked time Calculate average user wait time Calculate response time for CPU Calculate total idle time for CPU Calculate total utilization for CPU \endverbatim */ void Calc_Stats( ) { // DECLARE VARIABLES struct time_type time; // throw-away/local var for calculations //For each user for( int i=0; i<Num_Terminals; i++ ){ // skip if this user serviced 0 IORBs (nothing to calculate) if( Dev_Table[i].num_served == 0 ){ continue; } //Calculate total processing time Add_time( &( Term_Table[i]->total_logon_time ), &Tot_Logon ); //Calculate total job blocked time Add_time( &( Term_Table[i]->total_block_time ), &Tot_Block ); //Calculate total job wait time Add_time( &( Term_Table[i]->total_ready_time ), &Tot_Wait ); //Calculate total job execution time Add_time( &( Term_Table[i]->total_run_time ), &Tot_Run ); //Calculate efficiency for each process time = Term_Table[i]->total_run_time; Add_time( &( Term_Table[i]->total_block_time ), &time ); Term_Table[i]->efficiency = Divide_time( &time, &( Term_Table[i]->total_logon_time ) ) * 100; } //For each device for( int i=0; i<Num_Devices; i++ ){ //Calculate response time for each device time = Dev_Table[i].total_q_time; Add_time( &(Dev_Table[i].total_busy_time), &time ); Average_time( &time, Dev_Table[i].num_served, &(Dev_Table[i].response) ); //Calculate total idle time for each device time = Clock; Diff_time( &( Dev_Table[i].total_busy_time ), &time ); Dev_Table[i].total_idle_time = time; //Calculate utilization for each device Dev_Table[i].utilize = Divide_time( &( Dev_Table[i].total_busy_time ), &Clock ) * 100; } //Calculate average user execution time Average_time( &Tot_Run, Num_Terminals, &Avg_Run ); //Calculate average user logon time Average_time( &Tot_Logon, Num_Terminals, &Avg_Logon ); //Calculate average user blocked time Average_time( &Tot_Block, Num_Terminals, &Avg_Block ); //Calculate average user wait time Average_time( &Tot_Wait, Num_Terminals, &Avg_Wait ); //Calculate response time for CPU time = CPU.total_q_time; Add_time( &( CPU.total_busy_time ), &time ); Average_time( &time, CPU.num_served, &( CPU.response ) ); //Calculate total idle time for CPU time = Clock; Diff_time( &( CPU.total_busy_time ), &time ); CPU.total_idle_time = time; //Calculate total utilization for CPU CPU.utilize = Divide_time( &( CPU.total_busy_time ), &Clock ) * 100; }