Operating-System-Simulator / Sim04 / simulator.c
simulator.c
Raw
#include "simulator.h"

/*
 * Function Name: accessMemory
 * Algorithm: given a list of MemoryNodes, checks if the logical address of a 
 *            process has been allocated and can be used
 * Precondition: MemoryNode list allocated and pointer passed to function
 * Postcondition: bool returned of whether access was successful or not
 * Exceptions: None
 * Notes: None
 */
bool accessMemory( MemoryNode *memNodePtr, int startIndex, int endIndex, int pid )
   {
    // initialize function variables
    int difference = 0;
    MemoryNode *tempPtr = memNodePtr;
    bool found = false;
    
    while( tempPtr->logicalStartIndex != END_OF_MEM_LIST )
       {
        difference = startIndex - tempPtr->logicalStartIndex;
        // if logical address fits within node logical address bounds
        if( startIndex >= tempPtr->logicalStartIndex && difference <= 
                 ( tempPtr->logicalEndIndex - ( tempPtr->logicalStartIndex + difference ) ) ) 
           {
            found = true;
           }
        tempPtr = tempPtr->nextNode;
       }

    return found; 
   }

/*
 * Function Name: allocateMemory
 * Algorithm: checks whether memory allocation is valid
 *            if so, adds allocation to MemoryList, otherwise 'segfaults'
 * Precondition: pointer to memory list / base and offset values provided to function
 * Postcondition: allocated memory accounted for in list structure
 * Exceptions: if invalid allocation, terminate the program
 * Notes: None
 */
void allocateMemory( ConfigDataType *configPtr, fileWriteList **fileWriteListPtr, PCBLinkedList **pcbLLPtr,
                     MemoryNode *memNodePtr, MemoryNode **headPtr, PCB *pcbPtr, int base, int offset )
   {
    memNodePtr = ( *headPtr );

    // initialize function variables
    int logicalEndIndex;
    bool collision = false;
    MemoryNode *tempPtr = memNodePtr;
    MemoryNode *openPtr;

    // convert base and offset values to start / end index (offset += base - 1)
    logicalEndIndex = base + offset - 1;

    // check that end index is not greater than maximum memory value specified in config file
    if( logicalEndIndex >= configPtr->memAvailable )
       {
        // output allocation failed and perform necessary operations to end program
            // function: segfault 
        segfault( configPtr, fileWriteListPtr, pcbLLPtr, memNodePtr, headPtr, pcbPtr );
       }

    // check that memory allocation does not clash with all other current allocations
    while( tempPtr->logicalStartIndex != END_OF_MEM_LIST )
       {
        // verify proposed allocation is valid
            // funtion: verifyLogicalAllocation
        verifyLogicalAllocation( tempPtr, base, logicalEndIndex, &collision );
        tempPtr = tempPtr->nextNode;
       }
    if( collision )
       {
        segfault( configPtr, fileWriteListPtr, pcbLLPtr, memNodePtr, headPtr, pcbPtr );
       }

    // made to end of list, so logical allocation must be valid
    
    // test for enough space in physical address
    // function: findOpenMemory
    openPtr = findOpenMemory( base, logicalEndIndex, memNodePtr );
    // assuming for now openPtr is at the end

    if( openPtr == NULL )
       {
        segfault( configPtr, fileWriteListPtr, pcbLLPtr, memNodePtr, headPtr, pcbPtr );
       }
    // create new MemoryNode prior to openPtr, then change openPtr indices accordingly
    else
       {
        tempPtr = memNodePtr;
        // if open node is the only one in the list
        if( tempPtr == openPtr )
           {
            tempPtr = ( MemoryNode * )malloc( sizeof( MemoryNode ) );
            tempPtr->nextNode = openPtr;
            memNodePtr = tempPtr;
            *headPtr = tempPtr;
           }
        else
           {
            // loop until just before open allocation
            while( tempPtr->nextNode != openPtr )
               {
                tempPtr = tempPtr->nextNode;
               }

            tempPtr->nextNode = ( MemoryNode * )malloc( sizeof( MemoryNode ) );
            tempPtr = tempPtr->nextNode;
            tempPtr->nextNode = openPtr;
           }

        // regardless set to the same values
        tempPtr->pid = pcbPtr->pid;
        tempPtr->status = USED;
        tempPtr->logicalStartIndex = base;
        tempPtr->logicalEndIndex = logicalEndIndex;
        tempPtr->physicalStartIndex = openPtr->physicalStartIndex;
        tempPtr->physicalEndIndex = tempPtr->physicalStartIndex + ( logicalEndIndex - base );
        openPtr->physicalStartIndex = tempPtr->physicalEndIndex + 1;
       }
    
    if( pcbPtr->state != EXIT )
       {
        if( configPtr->logToCode != LOGTO_FILE_CODE )
           {
            printMemoryList( *headPtr, configPtr, fileWriteListPtr, SUCCESS );
           }
       }
   }
 
/*
 * Function Name: changeProcessState
 * Algorithm: changes state in PCB struct and displays results as needed
 * Precondition: PCB passed into function
 * Postcondition: Process state changed and results shown
 * Exceptions: if the state parameter is the same as in the PCB do nothing 
 * Notes: None
 */
void changeProcessState( ConfigDataType *configPtr, fileWriteList **fileWriteList, PCB *PCB, int state )
   {
    char *writeStr;
    int pid = 0;
    char buffer[MAX_STR_LEN];

    if( PCB->state != state )
       {
        pid = PCB->pid;
        char *possibleStates[5];
             possibleStates[ 0 ] = "NEW";
             possibleStates[ 1 ] = "READY";
             possibleStates[ 2 ] = "RUNNING";
             possibleStates[ 3 ] = "WAITING";
             possibleStates[ 4 ] = "EXIT";

        writeStr = malloc( sizeof( char ) * MAX_STR_LEN );
        char *firstState = possibleStates[PCB->state];
        char *nextState = possibleStates[state];
        
        if( state == RUNNING )
           {
            sprintf( buffer, "OS: Process %d set from %s -to-> %s\n\n", pid, firstState, nextState );
           }
        else
           {
            sprintf( buffer, "OS: Process %d set from %s -to-> %s\n", pid, firstState, nextState );
           }
        copyString( writeStr, buffer );
        displayHandler( configPtr, fileWriteList, writeStr, false, false );
        
        PCB->state = state;
        free( writeStr );
       }
   }

/*
 * Function Name: checkProgramEnd
 * Algorithm: checks if all PCBs are in exit state
 * Precondition: PCBs are initialized in PCBLinkedList
 * PostCondition: bool result returned
 * Exceptions: None
 * Notes: None
 */
bool checkProgramEnd( PCBLinkedList **PCBLinkedListPtr )
   {
    // initialize function variables
    PCBLinkedList *tempPtr = *PCBLinkedListPtr;
    bool result = true;
    
    // while not at the end of PCB Linked List structure
    while( tempPtr->PCB->pid != END_OF_PCB_LIST )
       {
        if( tempPtr->PCB->state != EXIT )
           {
            result = false;
           }
        tempPtr = tempPtr->nextPCB;
       }

    return result; 
   }

/*
 * Function Name: chooseNextProcess
 * Algorithm: depending on the scheduling type, 
 *            picks the next process based on PCB info
 * Precondition: All processes are not currently executing 
 *               (since we are assuming a single CPU), 
 *               and scheduling type is valid and selected
 * Postcondition: the next process to be run is selected and pointer to PCB is returned
 * Exceptions: If all processes are waiting or in exit state cannot choose a process
 * Notes: None
 */
PCB *chooseNextProcess( ConfigDataType *configPtr, fileWriteList **fileWriteList, 
                                                PCBLinkedList *PCBLinkedListPtr ) 
{
 // check which scheduler is chosen
 // FCFS-N, SJF-N, SRTF-P, FCFS-P, RR-P
 
 // switch statement to select based on scheduler
 switch( configPtr->cpuSchedCode )
    {
     // For now all cases use FCFS_N_Choice
     case CPU_SCHED_SJF_N_CODE:
         return SJF_N_Choice( &PCBLinkedListPtr, configPtr, fileWriteList );
         break;
     case CPU_SCHED_SRTF_P_CODE:
         return SRTF_P_Choice( &PCBLinkedListPtr, configPtr, fileWriteList );
         break;
     case CPU_SCHED_FCFS_P_CODE:
         return FCFS_P_Choice( &PCBLinkedListPtr, configPtr, fileWriteList );
         break;
     case CPU_SCHED_RR_P_CODE:
         return RR_P_Choice( &PCBLinkedListPtr, configPtr, fileWriteList );
         break;
     case CPU_SCHED_FCFS_N_CODE:
         return FCFS_N_Choice( &PCBLinkedListPtr, configPtr, fileWriteList );
         break;
    }
 return NULL;
}

/*
 * Function Name: clearMemoryList
 * Algorithm: recursively frees memory used in linked list structure for memory operations
 * Precondition: None
 * Postcondition: Linked List is freed and NULL is returned
 * Exceptions: None
 * Notes: None
 */
MemoryNode *clearMemoryList( MemoryNode *memoryList )
   {
    if( memoryList->logicalStartIndex != END_OF_MEM_LIST )
       {
        clearMemoryList( memoryList->nextNode );
       }

    free( memoryList );

    // all allocations are freed, return null
    return NULL;   
   }

/*
 * Function Name: clearPCBList
 * Algorithm: frees memory used in Linked List structure used for process operations
 * Precondition: None
 * Postcondition: Linked List is freed and NULL returned
 * Exceptions: None
 * Notes: None
 */
PCBLinkedList *clearPCBList( PCBLinkedList *pcbList )
   {
    // check if pcbList is null
    if( pcbList->PCB->pid != END_OF_PCB_LIST )
       {
        // recurse until empty PCB
        clearPCBList( pcbList->nextPCB );
       }
    // free PCB and PCBLinkedList node
    free( pcbList->PCB );
    free( pcbList );
    
    // structs are all freed
    return NULL; 
   }

/*
 * Function Name: clearProcessMemory
 * Algorithm: frees memory used by a specific process in the memoryList
 * Precondition: Process has been moved to exit state
 * Postcondition: MemoryNode list of memory used by exited process is freed
 * Exceptions: None
 * Notes: None
 */
MemoryNode *clearProcessMemory( MemoryNode *memNodePtr, MemoryNode **headPtr, int pid )
   {
    // initialize function variables
    MemoryNode *tempPtrOne = memNodePtr;
    MemoryNode *tempPtrTwo = tempPtrOne->nextNode;
    MemoryNode *nodeToDelete;

    // remove all nodes in the beginning of the same pid
    // for now just go until end of list
    while( memNodePtr->pid == pid || memNodePtr->logicalStartIndex != END_OF_MEM_LIST ) 
       {
        tempPtrOne = memNodePtr;
        memNodePtr = memNodePtr->nextNode;
        free( tempPtrOne );
        *headPtr = memNodePtr;
       }
    // set head pointer to new first node
    tempPtrOne = memNodePtr;

    if( memNodePtr->logicalStartIndex == END_OF_MEM_LIST )
       {
        // sets last node in MemoryNode list back to zero
        // TODO: for preemption, need to track freed memory and give to open allocations
        memNodePtr->physicalStartIndex = 0;
        // for now loop headPtr to end
        while( ( *headPtr )->logicalStartIndex != END_OF_MEM_LIST )
           {
            *headPtr = ( *headPtr )->nextNode;
           }

        return memNodePtr;
       }

    // this section is for later use when there is more than one open segment
    tempPtrTwo = memNodePtr->nextNode;

    while( tempPtrTwo->logicalStartIndex != END_OF_MEM_LIST )
       {
        if( tempPtrTwo->pid == pid )
           {
            nodeToDelete = tempPtrTwo;
            tempPtrTwo = tempPtrTwo->nextNode;
            tempPtrOne->nextNode = tempPtrTwo;
            free( nodeToDelete );
           }
       }

    if( tempPtrOne->pid != pid )
       {
        memNodePtr = tempPtrOne;
       }
    else
       {
        free( tempPtrOne );
        memNodePtr = tempPtrTwo;
       }

    // sets last node in MemoryNode list back to zero
    // TODO: for preemption, need to track freed memory and give to open allocations
    memNodePtr->physicalStartIndex = 0;

    return memNodePtr;
   }

/*
 * Function Name: clearWriteList
 * Algorithm: Recursive method to free memory of data structure used to 
 *            write to file
 * Precondition: None
 * Postcondition: Memory allocated for file write operations freed
 * Exceptions: None
 * Notes: None
 */
fileWriteList *clearWriteList( fileWriteList **fileWriteListPtr )
   {
    fileWriteList *fileWriteList = *fileWriteListPtr;
    if( compareString( fileWriteList->strToWrite, END_OF_STRLIST ) != STR_EQ )
       {
        clearWriteList( &( fileWriteList->nextStr ) );
       }  
    free( fileWriteList );
    return NULL;
   }

/*
 * Function Name: createPCB
 * Algorithm: helper function for scanForNewPCBs, makes new PCB struct 
 * Precondition: given pointer to start op code
 * Postcondition: new PCB created, pointer referencing now contains PCB struct
 * Exceptions: None
 * Notes: None
 */
void createPCB( OpCodeType *startOpCode, PCBLinkedList **PCBLLIterPtr, int cycleTime )
   {
    // allocate memory for next node in Linked List
    ( *PCBLLIterPtr )->nextPCB = ( PCBLinkedList * )malloc( sizeof( PCBLinkedList ) );

    // set pid to -1 to mark end of list
    ( *PCBLLIterPtr )->nextPCB->PCB = ( PCB * )malloc( sizeof( PCB ) );
    ( *PCBLLIterPtr )->nextPCB->PCB->pid = END_OF_PCB_LIST;
    ( *PCBLLIterPtr )->nextPCB->PCB->state = EXIT; // keep as exit? see FCFS_P_Choice

    // set various values for PCB
    ( *PCBLLIterPtr )->PCB->pid = getNextPID();
    ( *PCBLLIterPtr )->PCB->state = NEW;
    ( *PCBLLIterPtr )->PCB->programCounter = startOpCode;
    ( *PCBLLIterPtr )->PCB->timeRemaining = cycleTime;
    ( *PCBLLIterPtr )->PCB->interruptable = true;
    ( *PCBLLIterPtr )->PCB->ranForCycle = false;
    ( *PCBLLIterPtr )->PCB->opCodeComplete = false;
    ( *PCBLLIterPtr )->PCB->startedOp = false;
    ( *PCBLLIterPtr )->PCB->cycleRemainder = 0;

    // step PCBLL pointer forward to nextPCB (empty)
    *PCBLLIterPtr = ( *PCBLLIterPtr )->nextPCB;
   }

/* 
 * Function Name: decrementCycles
 * Algorithm: given an op code and a WAITING PCB, find out if they are both the same 
 *            op code type. If they aren't, keep track of the remainder by storing in the PCB.
              Additionally, subtract from the timeRemaining in the WAITING PCB
 * Precondition: op code is currently running and PCB given is 'running' in the background
 * Postcondition: integer returned of the amount of cycles to decrement. occasionally will be 
 *                greater than one to make up for differences in op codes.
 * Exceptions: None
 * Notes: None
 */
int decrementCycles( ConfigDataType *configPtr, OpCodeType *opCodePtr, PCB *PCB )
   {
    int timeDec = 0;
    int opCycleTime = 0;
    int remainder = 0;
    int cyclesToDec = 1;

    // set op code cycle time
    if( compareString( opCodePtr->command, "dev" ) == STR_EQ )
       {
        opCycleTime = configPtr->ioCycleRate; 
       }
    else if( compareString( opCodePtr->command, "cpu" ) == STR_EQ )
       {
        opCycleTime = configPtr->procCycleRate;
       }

    // set pcb cycle time / time to deduct from timeRemaining
    // PCB op code will always be I/O if waiting but oh well
    if( compareString( PCB->programCounter->command, "dev" ) == STR_EQ )
       {
        timeDec = configPtr->ioCycleRate; 
       }
    else if( compareString( PCB->programCounter->command, "cpu" ) == STR_EQ )
       {
        timeDec = configPtr->procCycleRate;
       }
    PCB->timeRemaining = PCB->timeRemaining - timeDec;

    remainder = opCycleTime - timeDec;
    if( remainder != 0 )
       {
        if( remainder > 0 )
           {
            PCB->cycleRemainder = PCB->cycleRemainder + remainder;
           }
        else
           {
            cyclesToDec--;
            PCB->cycleRemainder = PCB->cycleRemainder + opCycleTime;
           }
       }

    // "cash in" remainder to add another cycle to waiting process
    if( PCB->cycleRemainder >= timeDec )
       {
        cyclesToDec++;
        PCB->cycleRemainder = PCB->cycleRemainder - timeDec;
       }

    return cyclesToDec; 
   }

/*
 * Function Name: displayHandler
 * Algorithm: different operations depending on if the output is to be 
 *            directed to a file, to std output, or both
 * Precondition: Config file specifies type of output 
 *               ( which must already be working at this point in the program )
 * Postcondition: program operations are properly displayed, can be during 
 *                simulator run as well as all at once to a file at termination
 * Exceptions: None
 * Notes: None
 */
void displayHandler( ConfigDataType *configPtr, fileWriteList **fileWriteListPtr, 
                                    char *toWrite, bool writeOnly, bool newLine )
   {
    // initialize variable for time string and string for output
    char *timeStr = malloc( sizeof(char) * MAX_STR_LEN );
    char *tempStr = malloc( sizeof(char) * MAX_STR_LEN );
    timeStr[0] = '\0';
    tempStr[0] = '\0';

    // get time from start of simulation
    accessTimer( LAP_TIMER, timeStr );
    
    // TODO: handle spacing of string so that the length of time string doesn't
    //       affect indendation
    
    // for readability of output, separate certain lines
    if( newLine )
       {
        sprintf( tempStr, "\n%s, %s", timeStr, toWrite );
       }
    else
       {
        sprintf( tempStr, "%s, %s", timeStr, toWrite );
       }

    // shortcut if need to write to monitor but config specifies to file
    if( !writeOnly )
       {
        // initialize function variables
        fileWriteList *ptrToIterNode = *fileWriteListPtr;

        // if only to file, write out to notify user that program is still working
        static bool firstStatement = true;

        // if we are writing to file (and using the data structure)
        if( configPtr->logToCode != LOGTO_MONITOR_CODE )
           {
            // make sure iter pointer is at end node and fileStrPtr points to the penultimate node
            while( compareString( ( ptrToIterNode->strToWrite), (END_OF_STRLIST) ) != STR_EQ )
               {
                ptrToIterNode = ptrToIterNode->nextStr;
               }
           }

        switch( configPtr->logToCode )
           {
            case LOGTO_MONITOR_CODE:
                fprintf( stderr, tempStr );
                break;       

            case LOGTO_FILE_CODE:
                if( firstStatement )
                   {
                    fprintf( stderr, "Simulator Running - outputting to file only...\n" );
                    firstStatement = false;
                   }

                // create next node after last
                ptrToIterNode->nextStr = ( fileWriteList * )malloc( sizeof( fileWriteList ) );

                // swap values
                ptrToIterNode->nextStr->strToWrite = END_OF_STRLIST;
                ptrToIterNode->strToWrite = tempStr;
                break;

            case LOGTO_BOTH_CODE:
                fprintf( stderr, tempStr );

                // create next node after last
                ptrToIterNode->nextStr = ( fileWriteList * )malloc( sizeof( fileWriteList ) );

                // swap values
                ptrToIterNode->nextStr->strToWrite = END_OF_STRLIST;
                ptrToIterNode->strToWrite = tempStr;
                break;
           }
       }
    else
       {
        fprintf( stderr, toWrite );
       }

    free(timeStr);
    // TODO: Free memory without corrupting write to file
    // when running in valgrind and freeing tempStr memory, there is no file corruption
    // free(tempStr);
   }

/*
 * Function Name: findOpenMemory
 * Algorithm: given a linked list and start & end indices, find an 'Open' memory address.
 *            returns pointer to Open memory node.
 * Precondition: MemoryNode list and indices provided
 * Postcondition: Open node with adequate space provided
 * Exceptions: If none large enough found, returns NULL
 * Notes: None
 */
MemoryNode *findOpenMemory( int logicalStartIndex, int logicalEndIndex, MemoryNode *memNodePtr )
   {
    int totalNeeded = logicalEndIndex - logicalStartIndex + 1;

    MemoryNode *returnPtr = memNodePtr;
    MemoryNode *tempPtr = memNodePtr;

    // currently finds last open segment
    // TODO: perhaps implement other memory segment choice algorithms
    while( tempPtr != NULL )//|| tempPtr->logicalStartIndex != END_OF_MEM_LIST )
       {
        if( tempPtr->status == OPEN )
           {
            // if open memory spot is large enough
            if( ( tempPtr->physicalEndIndex - tempPtr->physicalStartIndex + 1 ) >= totalNeeded )
               {
                returnPtr = tempPtr;
               }
           }
        tempPtr = tempPtr->nextNode;
       }

    if( returnPtr->status != OPEN )
       {
        returnPtr = NULL;
       }
    return returnPtr;
   }

/*
 * Function Name: FCFS_N_Choice
 * Algorithm: first come first-served. chooses lowest pid process, non-preemptive
 * Precondition: Processes with PCBs created exist
 * Postcondition: PCB with lowest pid is chosen and returned
 * Exceptions: None
 * Notes: None
 */
PCB *FCFS_N_Choice( PCBLinkedList **pcbList, ConfigDataType *configPtr, fileWriteList **fileWriteListPtr )
   {
    // initialize function variables
    PCBLinkedList *pcbListPtr = *pcbList;
    char *outputStr = malloc( sizeof( char ) * MAX_STR_LEN );
    int pid = 0;
    int timeRemaining;

    // set return pointer to first pcb
    PCB *returnPtr = ( pcbListPtr->PCB );
    while ( pcbListPtr != NULL && pcbListPtr->PCB->pid != END_OF_PCB_LIST )
       {
        // if pid is less than returnPtr pid set returnPtr to current PCB
        if( ( ( pcbListPtr->PCB->pid ) < ( returnPtr->pid ) && pcbListPtr->PCB->state == READY ) 
                        || ( returnPtr->state ) == EXIT )
           {
            returnPtr = ( pcbListPtr->PCB );
           }
        pcbListPtr = pcbListPtr->nextPCB;
       }

    // perform output operations
    if( returnPtr->state == READY )
       {
        pid = returnPtr->pid;
        timeRemaining = returnPtr->timeRemaining;
        sprintf( outputStr, "OS: Process %d selected with %d ms remaining\n", pid, timeRemaining );
        displayHandler( configPtr, fileWriteListPtr, outputStr, false, false ); 
       }

    free( outputStr );

    return returnPtr; 
   }

/*
 * Function Name: FCFS_P_Choice
 * Algorithm: first come first-served. chooses lowest pid process in ready queue, preemptive
 * Precondition: Processes with PCBs created exist
 * Postcondition: PCB with lowest pid is chosen and returned
 * Exceptions: None
 * Notes: None
 */
PCB *FCFS_P_Choice( PCBLinkedList **pcbList, ConfigDataType *configPtr, fileWriteList **fileWriteListPtr )
   {
    // initialize function variables
    PCBLinkedList *pcbListPtr = *pcbList;
    char *outputStr = malloc( sizeof( char ) * MAX_STR_LEN );
    int pid = 0;
    int timeRemaining;
    static int lastPid = -1;

    // set return pointer to first pcb
    PCB *returnPtr = ( pcbListPtr->PCB );
    while ( pcbListPtr != NULL && pcbListPtr->PCB->pid != END_OF_PCB_LIST )
       {
        // if pid is less than returnPtr pid set returnPtr to current PCB
        if( ( ( pcbListPtr->PCB->pid ) < ( returnPtr->pid ) && pcbListPtr->PCB->state == READY ) 
                        || ( returnPtr->state ) == EXIT 
                        || ( ( returnPtr->state ) == WAITING && pcbListPtr->PCB->state != EXIT ) )
           {
            returnPtr = ( pcbListPtr->PCB );
           }
        pcbListPtr = pcbListPtr->nextPCB;
       }

    // perform output operations
    pid = returnPtr->pid;
    // due to preemptive nature, only output when process actually changes
    if( lastPid != pid )
       {
        timeRemaining = returnPtr->timeRemaining;
        if( returnPtr->state != WAITING )
           {
            sprintf( outputStr, "OS: Process %d selected with %d ms remaining\n", pid, timeRemaining );
            displayHandler( configPtr, fileWriteListPtr, outputStr, false, false ); 
           }
        lastPid = pid;
       }

    free( outputStr );

    return returnPtr; 
   }

/*
 * Function Name: SRTF_P_Choice
 * Algorithm: shortest remaining time first. chooses the process with the 
 *            lowest timeRemaining attribute in ready queue, preemptive
 * Precondition: Processes with PCBs created exist
 * Postcondition: PCB with lowest pid is chosen and returned
 * Exceptions: None
 * Notes: None
 */
PCB *SRTF_P_Choice( PCBLinkedList **pcbList, ConfigDataType *configPtr, fileWriteList **fileWriteListPtr )
   {
    // initialize function variables
    PCBLinkedList *pcbListPtr = *pcbList;
    char *outputStr = malloc( sizeof( char ) * MAX_STR_LEN );
    int pid = 0;
    int timeRemaining;
    static int lastPid = -1;

    // set return pointer to first pcb
    PCB *returnPtr = ( pcbListPtr->PCB );
    while ( pcbListPtr != NULL && pcbListPtr->PCB->pid != END_OF_PCB_LIST )
       {
        // if pid is less than returnPtr pid set returnPtr to current PCB
        if( ( ( pcbListPtr->PCB->timeRemaining ) < ( returnPtr->timeRemaining ) && pcbListPtr->PCB->state == READY ) 
                        || ( returnPtr->state ) == EXIT 
                        || ( ( returnPtr->state ) == WAITING && pcbListPtr->PCB->state != EXIT ) )
           {
            returnPtr = ( pcbListPtr->PCB );
           }
        pcbListPtr = pcbListPtr->nextPCB;
       }

    // perform output operations
    pid = returnPtr->pid;
    // due to preemptive nature, only output when process actually changes
    if( lastPid != pid )
       {
        timeRemaining = returnPtr->timeRemaining;
        if( returnPtr->state != WAITING )
           {
            sprintf( outputStr, "OS: Process %d selected with %d ms remaining\n", pid, timeRemaining );
            displayHandler( configPtr, fileWriteListPtr, outputStr, false, false ); 
           }
        lastPid = pid;
       }

    free( outputStr );

    return returnPtr; 
   }

/*
 * Function Name: RR_P_Choice
 * Algorithm: Round Robin Preemptive. Evenly distributes the given quanta of time (from the config file).
 * Precondition: Processes with PCBs created exist
 * Postcondition: PCB with lowest pid is chosen and returned
 * Exceptions: None
 * Notes: None
 */
PCB *RR_P_Choice( PCBLinkedList **pcbList, ConfigDataType *configPtr, fileWriteList **fileWriteListPtr )
   {
    // initialize function variables
    PCBLinkedList *pcbListPtr = *pcbList;
    char *outputStr = malloc( sizeof( char ) * MAX_STR_LEN );
    int pid = 0;
    int PCBListLength = getPCBListLength( pcbList );
    int timeRemaining;
    int quantumLim = configPtr->quantumCycles;
    static int lastPid = 0;
    static int printDifPid = -1;
    static int quantumCycles = 0;

    // reset lastPid to cycle through linked list
    if( quantumCycles >= quantumLim )
       {
        lastPid++;      
        quantumCycles = 0;
        if( lastPid > PCBListLength )
           {
            lastPid = 0;          
           }
       }

    // set return pointer to first pcb
    PCB *returnPtr = ( pcbListPtr->PCB );
    while ( pcbListPtr != NULL && pcbListPtr->PCB->pid != END_OF_PCB_LIST )
       {
        if( ( ( pcbListPtr->PCB->pid ) == lastPid && 
                quantumCycles < quantumLim        && 
                pcbListPtr->PCB->state == READY ) ||
                ( returnPtr->state ) == EXIT      || 
                ( ( returnPtr->state ) == WAITING && pcbListPtr->PCB->state != EXIT ) ) 
           {
            returnPtr = ( pcbListPtr->PCB );          
           }

        pcbListPtr = pcbListPtr->nextPCB;
       }

    // perform output operations
    pid = returnPtr->pid;

    // due to preemptive nature, only output when process actually changes
    if( printDifPid != pid )
       {
        timeRemaining = returnPtr->timeRemaining;
        if( returnPtr->state != WAITING )
           {
            sprintf( outputStr, "OS: Process %d selected with %d ms remaining\n", pid, timeRemaining );
            displayHandler( configPtr, fileWriteListPtr, outputStr, false, false ); 
           }
        printDifPid = pid;
       }

    free( outputStr );

    if( returnPtr->state == READY )
       {
        quantumCycles++;
       }
    return returnPtr; 
   }

/* 
 * Function Name: SJF_N_Choice
 * Algorithm: shortest job first non-preemptive. 
 *            chooses the process with the least amount of time remaining
 * Precondition: Processes with PCBs created exist in the linked list structure
 * Postcondition: PCB with the shortest time remaining chosen and returned
 * Exceptions: None
 * Notes: None
 */
PCB *SJF_N_Choice( PCBLinkedList **pcbList, ConfigDataType *configPtr, fileWriteList **fileWriteListPtr )
   {
    // initialize function variables
    PCBLinkedList *pcbListPtr = *pcbList;
    char *outputStr = malloc( sizeof( char ) * MAX_STR_LEN );
    int pid = 0;
    int timeRemaining;

    // set return pointer to first pcb
    PCB *returnPtr = ( pcbListPtr->PCB );

    while ( pcbListPtr != NULL && pcbListPtr->PCB->pid != END_OF_PCB_LIST )
       {
        // if timeRemaining is less than returnPtr set to current PCB.
        // else will naturally default to FCFS when the timeRemaining is equal,
        // as processes are in order of pid in the structure
        if( ( ( pcbListPtr->PCB->timeRemaining ) < ( returnPtr->timeRemaining ) 
                                && pcbListPtr->PCB->state == READY ) 
                                || ( returnPtr->state ) == EXIT )
           {
            returnPtr = ( pcbListPtr->PCB );
           }
        pcbListPtr = pcbListPtr->nextPCB;
       }

    // perform output operations
    pid = returnPtr->pid;
    timeRemaining = returnPtr->timeRemaining;
    sprintf( outputStr, "OS: Process %d selected with %d ms remaining\n", pid, timeRemaining );
    displayHandler( configPtr, fileWriteListPtr, outputStr, false, false ); 

    free( outputStr );

    return returnPtr; 
   }

/*
 * Function Name: getNextPID
 * Algorithm: keeps track of the pid by using a static variable to ensure 
 *            the process id of each process is always one more than the last
 * Precondition: None
 * Postcondition: PCB structs will be created with strictly linearly increasing pids
 * Exceptions: None
 * Notes: None
 */
int getNextPID()
   {
    static int pidCount = -1;
    pidCount++;
    return pidCount;
   }

/*
 * Function Name: getPCBListLength
 * Algorithm: returns an integer of the length (zero-indexed) of the PCBLinkedList. If empty 
 *            returns -1
 * Precondition: PCBLinkedList passed to function
 * Postcondition: Integer signifying list length returned
 * Notes: None
 */
int getPCBListLength( PCBLinkedList **PCBList )
   {
    int length = -1;
    // if non-empty
    if( ( *PCBList )->PCB->pid != END_OF_PCB_LIST )
       {
        PCBLinkedList *iterPtr = *PCBList;

        while( iterPtr->PCB->pid != END_OF_PCB_LIST )
           {
            length++;
            iterPtr = iterPtr->nextPCB;
           }
        }

    return length;
   }

/*
 * Function Name: getTimeFromOpCode
 * Algorithm: Given an op code, returns the time it will take in ms
 * Precondition: Valid op code
 * Postcondition: time in ms (int) returned
 * Exceptions: None
 * Notes: None
 */
int getTimeFromOpCode( ConfigDataType *configPtr, OpCodeType *opCodePtr )
   {
    // initialize function variables
    int multiplier = 1;
    char *cmd = opCodePtr->command;

    if( compareString( cmd, "dev" )== STR_EQ )
       {
        multiplier = configPtr->ioCycleRate;
        return ( long )( opCodePtr->intArg2 * multiplier );
       }
    else if( compareString( cmd, "cpu" ) == STR_EQ )
       {
        multiplier = configPtr->procCycleRate;
        return ( long )( opCodePtr->intArg2 * multiplier );
       }
    // if neither for now return 0
    return 0; 
   }

/*
 * Function Name: initializeProcesses
 * Algorithm: Sets all PCBs to ready state and displays accordingly
 * Precondition: PCBs initialized
 * Postcondition: All processes in ready queue
 * Exceptions: None
 * Notes: None
 */
void initializeProcesses( ConfigDataType *configPtr, fileWriteList **fileWriteListPtr,
                          PCBLinkedList **PCBLinkedListPtr )
   {
     PCBLinkedList *pcbListPtr = *PCBLinkedListPtr;

     while( pcbListPtr != NULL && pcbListPtr->PCB->pid != END_OF_PCB_LIST ) 
        {
         changeProcessState( configPtr, fileWriteListPtr, pcbListPtr->PCB, READY );
         pcbListPtr = ( pcbListPtr )->nextPCB;
        }
   }

/*
 * Function Name: printPCBList
 * Algorithm: prints out information about PCBs for testing purposes
 * Precondition: PCBLinkedList passed is not empty
 * Postcondition: Data about each PCB is shown
 * Exceptions: None
 * Notes: None
 */
void printPCBList( PCBLinkedList **pcbLLPtr )
   {
    PCBLinkedList *tempPtr = *pcbLLPtr;

    while( tempPtr != NULL )
       {
        if( tempPtr->PCB->pid != END_OF_PCB_LIST )
           {
            // not used during simulation, only used for testing purposes
            fprintf( stderr, "Iter pointer location:\t%p\n", (void*)pcbLLPtr );
            printf( "Process:\t%d\n", (tempPtr)->PCB->pid );
            printf( "State:\t%d\n", (tempPtr)->PCB->state );
            printf( "Time Remaining:\t%d\n\n", (tempPtr)->PCB->timeRemaining );
            
            // step linked list pointer forward
            tempPtr = ( tempPtr )->nextPCB;
           }
        else
           {
            tempPtr = NULL;
           }
       }
   }

/*
 * Function Name: printMemoryList
 * Algorithm: prints out information about MemoryNode list for testing purposes
 * Precondition: MemoryNode list passed is not empty
 * Postcondition: Data about each MemoryNode is shown
 * Exceptions: None
 * Notes: None
 */
void printMemoryList( MemoryNode *memNodePtr, ConfigDataType *configPtr, 
                      fileWriteList **fileWriteListPtr, int displayType )
   {
    MemoryNode *tempPtr = memNodePtr;
    char *writeStr = ( char * )malloc( sizeof( char ) * MAX_STR_LEN );

    switch( displayType )
       {
        case INIT:
        writeStr = "After memory initialization\n";
        break;
        case SUCCESS:
        writeStr = "After allocate success\n";
        break;
        case CLEARONE:
        writeStr = "After clear one process success\n";
        break;
        case FAIL:
        writeStr = "After allocate overlap failure\n";
        break;
        case CLEARALL:
        writeStr = "After clear all process success\nNo memory configured\n";
        break;
        case ACCESS_SUCCESS:
        writeStr = "After access success\n";
        break;
        case ACCESS_FAIL:
        writeStr = "After access failure\n";
        break;
       }
    displayHandler( configPtr, fileWriteListPtr, STR_NEWLINE, true, false );
    displayHandler( configPtr, fileWriteListPtr, writeStr, true, false );

    if( displayType != CLEARALL )
       {
        while( tempPtr->logicalStartIndex != END_OF_MEM_LIST )
           {
            // not used during simulation, only used for testing purposes
            if( tempPtr->status == OPEN )
               {
                fprintf( stderr, "%d [ Open, P#: x, 0-0 ] %d\n",  
                    tempPtr->physicalStartIndex, tempPtr->physicalEndIndex );
               }
            else
               {
                fprintf( stderr, "%d [ Used, P#: %d, %d-%d ] %d\n",
                    tempPtr->physicalStartIndex,
                    tempPtr->pid,
                    tempPtr->logicalStartIndex, 
                    tempPtr->logicalEndIndex,
                    tempPtr->physicalEndIndex );
               }
            // step linked list pointer forward
            tempPtr = tempPtr->nextNode;
           }
 
        if( tempPtr->status == OPEN )
           {
            fprintf( stderr, "%d [ Open, P#: x, 0-0 ] %d\n",  
                tempPtr->physicalStartIndex, tempPtr->physicalEndIndex );
           }
        else
           {
            fprintf( stderr, "%d [ Used, P#: %d, %d-%d ] %d\n",
                tempPtr->physicalStartIndex,
                tempPtr->pid,
                tempPtr->logicalStartIndex, 
                tempPtr->logicalEndIndex,
                tempPtr->physicalEndIndex );
           }
 
       }
    displayHandler( configPtr, fileWriteListPtr, STR_NEWLINE, true, false );

    // causes segfault
    //free( writeStr );
   }

/*
 * Function Name: printWriteList
 * Algorithm: displays fileWriteList for testing purposes
 * Precondition: List has at least one node ( with END_OF_STRLIST as strToWrite )
 * Postcondition: Linked list of file display structs shown
 * Exceptions: None
 * Notes: None
 */
void printWriteList( fileWriteList **fileWriteListPtr )
   {
    // not used during simulation, only used for testing purposes
    fileWriteList *fileWriteList = *fileWriteListPtr;
    fprintf( stderr, "\nPrinting fileWriteList:\n" );

    while( compareString( fileWriteList->strToWrite, END_OF_STRLIST ) != STR_EQ )
       {
        fprintf( stderr, fileWriteList->strToWrite );
        fileWriteList = fileWriteList->nextStr;
       }

    fprintf( stderr, fileWriteList->strToWrite );
    fprintf( stderr, "\n" );
   }

/*
 * Function Name: scanForNewPCBs
 * Algorithm: looks through metadata file and creates a PCB struct for each app start
 * Precondition: given pointer to metadata file
 * Postcondition: all new PCBs have been created and initialized
 * Exceptions: None
 * Notes: None
 */
void scanForNewPCBs( OpCodeType **metaDataMstrPtr, PCBLinkedList **PCBLinkedListPtr, 
                     ConfigDataType *configPtr )
   {
    // initialize function variables
    PCBLinkedList *PCBLinkedListIterPtr = *PCBLinkedListPtr;  // create iter pointer to PCBLL
    OpCodeType *tempCode = *metaDataMstrPtr;   
    OpCodeType *appStartCode = *metaDataMstrPtr;
    int timeRemaining = 0;
   
    // loop through metadata
    while( tempCode != NULL )
       {
        // if start is found
        if( compareString(tempCode->command, "app" ) == STR_EQ )
           {
            if( compareString(tempCode->strArg1, "start" ) == STR_EQ )
               {
                appStartCode = tempCode;
               }
           }
        // if not app end
            // do the math, add the time for a given op code    create PCB after totaled timeRemaining?
        if( compareString( tempCode->command, "app" ) != STR_EQ ||
                        compareString(tempCode->strArg1, "end" ) != STR_EQ)
           {
            timeRemaining = timeRemaining + getTimeFromOpCode( configPtr, tempCode );
           }
        // else app end
        else if( compareString( tempCode->command, "app" ) == STR_EQ && 
                        compareString(tempCode->strArg1, "end") == STR_EQ )
           {
            // create new PCB node in linked list
                // function: createPCB
            createPCB( appStartCode, &PCBLinkedListIterPtr, timeRemaining );
            timeRemaining = 0;
           }
            
        // step program counter to next op code
        tempCode = tempCode->nextNode;
       }
    // end loop through metadata
   }

/*
 * Function Name: runProcess
 * Algorithm: runs through op code operations by taking in PCB struct and 
 *            utilizing the program counter
 * Precondition: Process is in ready mode
 * Postcondition: Process has executed until app end or an interrupt
 * Exceptions: None
 * Notes: None
 */
PCB *runProcess( ConfigDataType *configPtr, fileWriteList **fileWriteList, 
                 PCBLinkedList *PCBLinkedListPtr, PCB *PCB, MemoryNode *memNodePtr, MemoryNode **headPtr )
   {
    // int to keep track of number of op codes
    // TODO: could cause issues with preemption, may need to add to PCB struct
    static int count = 0;
    PCB->ranForCycle = false;    // for use with preemptive scheduling, exit loop after single cycle
    PCB->opCodeComplete = false; // when opcode is fully finished, ( cycles left is 0 ) for use with display

    if( PCB->state == READY )
       {
        // set process to running
        changeProcessState( configPtr, fileWriteList, PCB, RUNNING );
       }

    // initialize function variables
    OpCodeType *opCodePtr = PCB->programCounter;
    // long opTime = 0;
    char *displayStr = ( char * ) malloc ( sizeof(char) * MAX_STR_LEN );
    char buffer[MAX_STR_LEN];

    // keep track of first allocation for output message
    static bool firstAlloc = true;
    bool accessSuccess = false;

    // set to next op code as to not display app start as an operation
    if( compareString( opCodePtr->command, "app" ) == STR_EQ &&
        compareString( opCodePtr->strArg1, "start" ) == STR_EQ )
       {
        opCodePtr = opCodePtr->nextNode;
        PCB->programCounter = opCodePtr;
       }

    // while op code is not app end                   TODO: exit while loop after a single cycle
    while( ( compareString(opCodePtr->command, "app" ) != STR_EQ  && 
             compareString(opCodePtr->strArg1, "end" ) != STR_EQ ) 
             && PCB->state != EXIT // may not need this conditional
             && !PCB->ranForCycle )
       {
        // BEGIN OP SECTION

        // if haven't outputted beginning of operation yet
        if( !PCB->startedOp )
           {
            // output op code start
            // if dev, specify input or output
            if( compareString( opCodePtr->command, "dev" ) == STR_EQ )
               {
                sprintf( buffer, "Process: %d, %s %s operation start\n", 
                                PCB->pid, opCodePtr->strArg1, opCodePtr->inOutArg ); 
                copyString( displayStr, buffer );
               }
            // if mem, try to allocate memory
            else if( compareString( opCodePtr->command, "mem" ) == STR_EQ )
               {
                if( compareString( opCodePtr->strArg1, "allocate" ) == STR_EQ )
                   {
                    sprintf( buffer, "Process: %d, %s allocate request (%d, %d)\n",
                             PCB->pid, opCodePtr->command, opCodePtr->intArg2, opCodePtr->intArg3 );
                    copyString( displayStr, buffer );

                    displayHandler( configPtr, fileWriteList, displayStr, false, false );

                    // if first allocation display memory map
                    if( firstAlloc )
                       {
                        if( configPtr->logToCode != LOGTO_FILE_CODE )
                           {
                            printMemoryList( *headPtr, configPtr, fileWriteList, INIT );
                           }
                        firstAlloc = false;
                       }

                    allocateMemory( configPtr, fileWriteList, &PCBLinkedListPtr, memNodePtr, headPtr, 
                                    PCB, opCodePtr->intArg2, opCodePtr->intArg3 ); 
                    // TODO: memory alloc functions may affect tracking if start of operation has been printed
                    // if state = exit startedOp = false?
                   }
                else if( compareString( opCodePtr->strArg1, "access" ) == STR_EQ )
                   {
                    sprintf( buffer, "Process: %d, %s access request (%d, %d)\n",
                             PCB->pid, opCodePtr->command, opCodePtr->intArg2, opCodePtr->intArg3 );
                    accessSuccess = accessMemory( memNodePtr, opCodePtr->intArg2, opCodePtr->intArg3, PCB->pid );
                    copyString( displayStr, buffer );
                    displayHandler( configPtr, fileWriteList, displayStr, false, false );

                    if( accessSuccess )
                       {
                        if( configPtr->logToCode != LOGTO_FILE_CODE )
                           {
                            printMemoryList( *headPtr, configPtr, fileWriteList, ACCESS_SUCCESS );
                           }
                       }
                    else
                       {
                        if( configPtr->logToCode != LOGTO_FILE_CODE )
                           {
                            printMemoryList( *headPtr, configPtr, fileWriteList, ACCESS_FAIL );
                           }
                       }
                   }
               }
            else
               {
                sprintf( buffer, "Process: %d, %s %s operation start\n", 
                                PCB->pid, opCodePtr->command, opCodePtr->strArg1 ); 
                copyString( displayStr, buffer );
               }

            // test if in exit state to have memory operation output in order
            if( PCB->state != EXIT && compareString( opCodePtr->command, "mem" ) != STR_EQ )
               {
                // if first op code of process, write newline
                if( count == 0 )
                   {
                    displayHandler( configPtr, fileWriteList, displayStr, false, true );
                    count++;
                   }
                else
                   {
                    displayHandler( configPtr, fileWriteList, displayStr, false, false );
                    count++;
                   }
               }
            PCB->startedOp = true;
           }
        
        // TIMER SECTION //

        // exit loop after running individual cycle, and keep track of if OpCode has started
        // don't allow 'mem' op code as runTimerForCycle will not decrement form intArg2 resulting in infinite loop
        while( opCodePtr->intArg2 > 0 && !PCB->ranForCycle && compareString( opCodePtr->command, "mem" ) != STR_EQ )
           { 
            runTimerForCycle( configPtr, opCodePtr, PCB, &PCBLinkedListPtr, fileWriteList );

            // if preemptive, only run for a single cycle and exit
            if( configPtr->cpuSchedCode == CPU_SCHED_FCFS_P_CODE || 
                configPtr->cpuSchedCode == CPU_SCHED_SRTF_P_CODE ||
                configPtr->cpuSchedCode == CPU_SCHED_RR_P_CODE )
               { 
                PCB->ranForCycle = true; 
               }
           }

        // after loop, check if op code can be finished / displayed
        if( compareString( opCodePtr->command, "mem" ) != STR_EQ && opCodePtr->intArg2 <= 0 )
           {
            PCB->opCodeComplete = true;  
           }
        // mem op code does not take time, so mark as finished
        else if( compareString( opCodePtr->command, "mem" ) == STR_EQ )
           {
            PCB->opCodeComplete = true;
            
            // need to mark that ran for cycle, otherwise will continue in loop and only run one cycle in next op code as opCodeComplete is now true
            if( configPtr->cpuSchedCode == CPU_SCHED_FCFS_P_CODE || 
                configPtr->cpuSchedCode == CPU_SCHED_SRTF_P_CODE ||
                configPtr->cpuSchedCode == CPU_SCHED_RR_P_CODE )
               { 
                PCB->ranForCycle = true; 
               }
           }

        // END OP SECTION //

        // if complete, output op code finished
        if( PCB->opCodeComplete )
           {
            //PCB->startedOp = false;
            PCB->opCodeComplete = false;

            // output op code end
            // if dev, specify input or output
            if( compareString( opCodePtr->command, "dev" ) == STR_EQ )
               {
                sprintf( buffer, "Process: %d, %s %s operation end\n", 
                                PCB->pid, opCodePtr->strArg1, opCodePtr->inOutArg ); 
                copyString( displayStr, buffer );
               }
            else if( compareString( opCodePtr->command, "mem" ) == STR_EQ )
               {
                if( compareString( opCodePtr->strArg1, "allocate" ) == STR_EQ )
                   {
                    if( PCB->state != EXIT )
                       {
                        // assume successful allocation since program has not segfaulted
                        sprintf( buffer, "Process: %d, succesful mem allocate request\n", PCB->pid); 
                        copyString( displayStr, buffer );
                       }
                     // failed, so process will segfault. reset count to print newline before next op code
                     else
                        {
                         count = 0;
                        }
                   }
                else if( compareString( opCodePtr->strArg1, "access" ) == STR_EQ )
                   {
                    if( accessSuccess )
                       {
                        sprintf( buffer, "Process: %d, succesful mem access request\n", PCB->pid); 
                        copyString( displayStr, buffer );
                       }
                    else
                       {
                        sprintf( buffer, "Process: %d, failed mem access request\n", PCB->pid); 
                        copyString( displayStr, buffer );
                       }
                   }
               }
            else
               {
                sprintf( buffer, "Process: %d, %s %s operation end\n", 
                                PCB->pid, opCodePtr->command, opCodePtr->strArg1 ); 
                copyString( displayStr, buffer );
               }

            if( PCB->state != EXIT )
               {
                displayHandler( configPtr, fileWriteList, displayStr, false, false );
               }
            
            // set next op code
            opCodePtr = opCodePtr->nextNode;

            // set pcb op code 
            PCB->programCounter = opCodePtr;

            PCB->startedOp = false;
          }
       }

    // END PROCESS SECTION //

    // if preemptive && last op code, or if not preemptive 
    if( ( (  configPtr->cpuSchedCode == CPU_SCHED_RR_P_CODE   ||
             configPtr->cpuSchedCode == CPU_SCHED_SRTF_P_CODE ||
             configPtr->cpuSchedCode == CPU_SCHED_FCFS_P_CODE ) && 
           ( compareString(opCodePtr->command, "app" ) == STR_EQ  && 
             compareString(opCodePtr->strArg1, "end" ) == STR_EQ ) ) ||
             configPtr->cpuSchedCode == CPU_SCHED_FCFS_N_CODE || 
             configPtr->cpuSchedCode == CPU_SCHED_SJF_N_CODE )
       {
        // reset first op code counter
        count = 0;

        // write that process has ended
        sprintf( displayStr, "OS: Process %d ended\n", PCB->pid ); 
        displayHandler( configPtr, fileWriteList, displayStr, false, true );

        changeProcessState( configPtr, fileWriteList, PCB, EXIT );
        
        clearProcessMemory( memNodePtr, headPtr, PCB->pid );

        if( configPtr->logToCode != LOGTO_FILE_CODE )
           {
            printMemoryList( *headPtr, configPtr, fileWriteList, CLEARONE );
           }

        free( displayStr );
       }

    return PCB; 
   }

/*
 * Function Name: runSim
 * Algorithm: master driver for simulator operations; conducts OS simulation 
 *            with varying scheduling strategies and varying numbers 
 *            of processes
 * Precondition: given head pointer to config data and meta data
 * Postcondition: simulation is provided, file output is provided as configured
 * Exceptions: None
 * Notes: None
 */
void runSim( ConfigDataType *configPtr, OpCodeType *metaDataMstrPtr )
   {
    // initialize function variables
    bool programEnding;
    // bool to keep track of if 'waiting for all processes' has been displayed yet or not
    bool displayedWaitingForProcess = false;
    int quantumCounter = 0;
    int quantumLim = configPtr->quantumCycles;
    char buffer[ MAX_STR_LEN ];
    char *displayStr = malloc( sizeof( char ) * MAX_STR_LEN );
    PCB *currentProcess;
    PCB *lastProcess;

    // initialize PCBLinkedList node and PCB
    PCBLinkedList *PCBLinkedListPtr = ( PCBLinkedList * )malloc( sizeof( PCBLinkedList ) );
    PCBLinkedListPtr->PCB = ( PCB * )malloc( sizeof( PCB ) );
 
    // initialize beginning of memory list
    MemoryNode *memoryListPtr = ( MemoryNode * )malloc( sizeof( MemoryNode ) );
    memoryListPtr->logicalStartIndex = END_OF_MEM_LIST;
    memoryListPtr->logicalEndIndex = 0;
    memoryListPtr->pid = END_OF_PCB_LIST;
    memoryListPtr->status = OPEN;
    memoryListPtr->physicalStartIndex = 0;
    memoryListPtr->physicalEndIndex = configPtr->memAvailable - 1;
    MemoryNode **memoryHeadPtr = &memoryListPtr;

    // initialize timeStr to zero timer
    char *timeStr = ( char * )malloc( sizeof( char ) * MAX_STR_LEN );

    // initialize fileWriteList if logging to file
    fileWriteList *toFileList = ( fileWriteList * )malloc( sizeof( fileWriteList ) );
    // set end of list str for first node
    toFileList->strToWrite = END_OF_STRLIST;

    // create all PCBs using helper function
    // function: scanForNewPCBs
    scanForNewPCBs( &metaDataMstrPtr, &PCBLinkedListPtr, configPtr );

    // start timer
    accessTimer( ZERO_TIMER, timeStr );
    free( timeStr );

    displayHandler( configPtr, &toFileList, "OS: Simulator start\n", false, false );

    // search for processes in new and set them to ready
        // function: initializeProcesses
    initializeProcesses( configPtr, &toFileList, &PCBLinkedListPtr );

    // initialize 'lastProcess' for quantum time use
    lastProcess = chooseNextProcess( configPtr, &toFileList, PCBLinkedListPtr );

    // start main loop 
    programEnding = checkProgramEnd( &PCBLinkedListPtr );
    // while all processes are not in exit state
    while( !programEnding )
       {
        // find next process
            // function: chooseNextProcess
        currentProcess = chooseNextProcess( configPtr, &toFileList, PCBLinkedListPtr );
        if( currentProcess != NULL && currentProcess->state != EXIT )
           {
            // run process
            // function: runProcess
            if( currentProcess->state != WAITING )
               {
                if( configPtr->cpuSchedCode == CPU_SCHED_FCFS_P_CODE || 
                    configPtr->cpuSchedCode == CPU_SCHED_SRTF_P_CODE ||
                    configPtr->cpuSchedCode == CPU_SCHED_RR_P_CODE )
                   { 
                    // keep track of time quanta to intermittently interrupt processes that take longer
                    if( currentProcess->pid != lastProcess->pid || !currentProcess->startedOp )
                       {
                        quantumCounter = 0;                  
                       }
                    // display quantum timeout operations
                    if( quantumCounter == quantumLim )
                       {
                        sprintf( buffer, "OS: Process %d quantum time out, %s %s operation end\n", 
                                currentProcess->pid, 
                                currentProcess->programCounter->command, 
                                currentProcess->programCounter->strArg1 ); 
                        copyString( displayStr, buffer );
                        displayHandler( configPtr, &toFileList, displayStr, false, true );
                        sprintf( buffer, "Process: %d, %s %s operation start\n", 
                                currentProcess->pid, 
                                currentProcess->programCounter->command, 
                                currentProcess->programCounter->strArg1 ); 
                        copyString( displayStr, buffer );
                        displayHandler( configPtr, &toFileList, displayStr, false, true );
                        quantumCounter = 0;
                       }
                   }
                lastProcess = currentProcess;
                displayedWaitingForProcess = false;
                runProcess( configPtr, &toFileList, PCBLinkedListPtr, 
                            currentProcess, memoryListPtr, memoryHeadPtr );
                quantumCounter++;
               }
            else 
               {
                if( !displayedWaitingForProcess )
                   {
                    displayHandler( configPtr, &toFileList, "OS: all processes waiting\n", false, false );
                    displayedWaitingForProcess = true;
                   }
                // decrement from cycles from waiting processes so eventually one will be ready
                runTimerForCycle( configPtr, currentProcess->programCounter, 
                                  currentProcess, &PCBLinkedListPtr, &toFileList ); 
               }

            // TODO check for process allocation still in MemoryList of exited processes
            
            // check for app end ( all processes in exit state )
            programEnding = checkProgramEnd( &PCBLinkedListPtr );
           }
        else 
           { 
            // fprintf( stderr, "I don't know what I'm doing aaa\n");
            currentProcess = chooseNextProcess( configPtr, &toFileList, PCBLinkedListPtr );
           // programEnding = true; 
           }
       }
    // end main loop
    displayHandler( configPtr, &toFileList, "OS: System stop\n", false, false );

    // display that all memory has been cleared
    if( configPtr->logToCode != LOGTO_FILE_CODE )
       {
        printMemoryList( memoryListPtr, configPtr, &toFileList, CLEARALL );
       }

    // free PCB struct memory
    // function: clearPCBList
    clearPCBList( PCBLinkedListPtr );

    // free Memory List of allocations
    // function: clearMemoryList
    clearMemoryList( memoryListPtr );

    displayHandler( configPtr, &toFileList, "OS: Simulation end\n", false, false );

    // if config specifies writing to file 
    if ( ( configPtr->logToCode == LOGTO_FILE_CODE ) 
           || ( configPtr->logToCode == LOGTO_BOTH_CODE ) )  
       {
        // write to file and free fileWriteList memory 
        // function: writeToFile
        writeToFile( configPtr, &toFileList );
       }

    free( displayStr );
   }

/*
 * Function Name: runTimerFor
 * Algorithm: creates a thread and sets limit for inputted milliseconds
 * Precondition: None
 * Postcondition: None
 * Exceptions: None
 * Notes: None
 */
void runTimerFor( long milliseconds )
   {
    // initialize threadInfo struct  
    threadInfo *timerThreadDataPtr = ( threadInfo * )malloc( sizeof( threadInfo ) );
    timerThreadDataPtr->milliseconds = milliseconds;
    timerThreadDataPtr->currentTime = 0;
    timerThreadDataPtr->timeString = malloc( 17 * sizeof( char ) );
    timerThreadDataPtr->outputTime = false;
    timerThreadDataPtr->keepGoing = true;

    void *threadExitStatus;

    pthread_t timerThread;
    
    // create pthread
    pthread_create( &timerThread, NULL, &runTimerThread, (void * )timerThreadDataPtr );
    
    // join pthread
    pthread_join( timerThread, &threadExitStatus );
    
    // free threadInfo struct
    free( timerThreadDataPtr->timeString );
    free( timerThreadDataPtr );
   }

/*
 * Function Name: runTimerForCycle
 * Algorithm: creates a thread and runs timer for however many ms a CPU cycle takes.
 *            Additionally handles decrementing from cycles of waiting processes.
 * Precondition: ConfigDataType pointer passed with cpu cycle information
 * Postcondition: program delayed for a CPU cycle's worth of ms, waiting processes' 
                  op codes are decremented.
 * Exceptions: None
 * Notes: None
 */
void runTimerForCycle( ConfigDataType *configPtr, OpCodeType *opCodePtr, PCB *PCB, 
                       PCBLinkedList **PCBLinkedListPtr, fileWriteList **writeList )
   {
    // initializing ms to 0 allows for function to be called with memory 
    // allocation/access op code without issue
    long milliseconds = 0;
    char *cmd = opCodePtr->command;
    int toDecrement = 0;
    // char buffer[ MAX_STR_LEN ];
    // char *outputStr;
 
    PCBLinkedList *tempPtr = *PCBLinkedListPtr;

    if( compareString( cmd, "dev" ) == STR_EQ )
       {
        if( ( configPtr->cpuSchedCode == CPU_SCHED_FCFS_P_CODE || 
            configPtr->cpuSchedCode == CPU_SCHED_SRTF_P_CODE ||
            configPtr->cpuSchedCode == CPU_SCHED_RR_P_CODE ) 
            && opCodePtr->intArg2 > 0 )
           { 
            if( PCB->state != WAITING )
               {
                // TODO: fix sprintf bugginess/corruption
                //outputStr = malloc( sizeof(char) * MAX_STR_LEN );
                //if( compareString( opCodePtr->strArg1, "in" ) == STR_EQ )
                //   {
                //    sprintf( buffer, "OS: Process %d blocked for input operation\n", PCB->pid );
                //   }
                //else if( compareString( opCodePtr->strArg1, "out" ) == STR_EQ )
                //   {
                //    sprintf( buffer, "OS: Process %d blocked for output operation\n", PCB->pid );
                //   }
                //copyString( outputStr, buffer );
                //displayHandler( configPtr, writeList, outputStr, false, true );
                //free( outputStr );

                // change process to WAITING as is an I/O operation
                changeProcessState( configPtr, writeList, PCB, WAITING );
               }
           }
        milliseconds = ( long ) configPtr->ioCycleRate;
        
        // decrement from op code cycles remaining
        opCodePtr->intArg2 = opCodePtr->intArg2 - 1;
       }
    else if( compareString( cmd, "cpu" ) == STR_EQ )
       {
        milliseconds = ( long ) configPtr->procCycleRate;
        // cannot decrement outside of if statement as could affect mem op codes
        opCodePtr->intArg2 = opCodePtr->intArg2 - 1;
       }

    runTimerFor( milliseconds );

    PCB->timeRemaining = PCB->timeRemaining - milliseconds;

    // loop through list of op codes and subtract a cycle off / account for remainders
    // TODO: subtract timeRemaining from different PCBs
    while( tempPtr->PCB->pid != END_OF_PCB_LIST && 
           compareString( opCodePtr->command, "mem" ) != STR_EQ )
       {
        // TODO: account for timings of different op code types
        if( tempPtr->PCB->state == WAITING )
           {
            // Already decremented intArg2 in current PCB
            if( tempPtr->PCB->pid != PCB->pid )
               {
                // check how many cycles to decrement ( due to differences in I/O / CPU cycle time )
                // function decrementCycles
                toDecrement = decrementCycles( configPtr, opCodePtr, tempPtr->PCB );
                tempPtr->PCB->programCounter->intArg2 = tempPtr->PCB->programCounter->intArg2 - toDecrement;
               }
            if( tempPtr->PCB->programCounter->intArg2 <= 0 )
               {
                changeProcessState( configPtr, writeList, tempPtr->PCB, READY );
               }
            //fprintf( stderr, "pcb: %d\t cycles: %d\n", tempPtr->PCB->pid, tempPtr->PCB->programCounter->intArg2 );
           }
        tempPtr = tempPtr->nextPCB;
       }

   }

/*
 * Function Name: writeToFile
 * Algorithm: Destructive method that simultaneously writes from linked list 
 *            structure to file and frees memory from structures. Intended for 
 *            use at the end of the program
 * Precondition: Config file must specify log to file or both
 * Postcondition: File specified written to and data structure will be cleared,
 *                returns NULL
 * Exceptions: None
 * Notes: None
 */
fileWriteList *writeToFile( ConfigDataType *configPtr, fileWriteList **printList )
   {
    // initialize function variables
    char *filename = ( char * )configPtr->logToFileName;
    fileWriteList *fileWriteListPtr = *printList;

    // open file
    FILE *filePtr = fopen( filename, "w" );

    // if opening file fails, exit with error message
    if( filePtr == NULL )
       {
        displayHandler( configPtr, NULL, "File creation has failed, aborting operation\n", true, false );
        exit( -1 );
       }

    // write linked list to file
    while( compareString( fileWriteListPtr->strToWrite, END_OF_STRLIST ) != STR_EQ )
       {
        // output to file
        fprintf( filePtr, "%s", fileWriteListPtr->strToWrite );
        
        // keep track of next location
        fileWriteListPtr = fileWriteListPtr->nextStr;
       }

    // close the file
    fclose( filePtr );

    // free linked list with recursive helper method
    // function: clearWriteList
    clearWriteList( printList );

    // return empty fileWriteList
    return NULL;
   }

/*
 * Function Name: segfault
 * Algorithm: Performs necessary operations after a memory allocation is found to be invalid
 * Precondition: None
 * Postcondition: None
 * Exceptions: None
 * Notes: None
 */
void segfault( ConfigDataType *configPtr, fileWriteList **printList, 
               PCBLinkedList **pcbLLPtr, MemoryNode *memNodePtr, MemoryNode **headPtr, PCB *pcbPtr )
   {
    if( configPtr->logToCode != LOGTO_FILE_CODE )
       {
        printMemoryList( *headPtr, configPtr, printList, FAIL );
       }

    // initialize function variables
    char *outputStr = ( char * )malloc( sizeof(char) * MAX_STR_LEN );

    // output allocation failed
    sprintf( outputStr, "Process %d, failed mem allocate request\n", pcbPtr->pid );
    displayHandler( configPtr, printList, outputStr, false, false ); 

    // set process to exit
    // need pcb to call function: changeProcessState
    changeProcessState( configPtr, printList, pcbPtr, EXIT );
    pcbPtr->opCodeComplete = true;
    pcbPtr->ranForCycle = true;

    clearProcessMemory( memNodePtr, headPtr, pcbPtr->pid );

    free( outputStr );
   }

/*
 * Function Name: verifyLogicalAllocation
 * Algorithm: Given the start and end index in memory, check if invalid allocation
 *            Do nothing if valid; if invalid, 'segfault' by setting boolean value to true
 * Precondition: None
 * Postcondition: None
 * Exceptions: None
 * Notes: None 
 */
void verifyLogicalAllocation( MemoryNode *memNodePtr, int logicalStartIndex, int logicalEndIndex, bool *collision )
   {
    // TODO: Ensure only verifies between logical addresses of the same pid
    if( (  memNodePtr->logicalStartIndex >= logicalStartIndex 
           && memNodePtr->logicalStartIndex <= logicalEndIndex ) 
           || ( (  memNodePtr->logicalEndIndex >= logicalStartIndex 
                && memNodePtr->logicalEndIndex <= logicalEndIndex ) ) )
       {
        *collision = true;  
       }
   }