/* ============================================================================= ********************************* a3_utils.c *********************************** ============================================================================= */ #include #include #include #define Boolean int #define True 1 #define False 0 #define KEYBOARD 0x9 #define VIDEO 0x10 #define COM1 0xc #define LASTROW 23 #define LASTCOL 79 #define STATUS_ROW 24 #define NORM_ATTR 0x07 /* Normal or blank attribute */ #define INV_ATTR 0x70 /* Inverse video attribute */ #define NORMBLNK_ATTR 0x87 /* Normal blinking attribute */ #define INVBLNK_ATTR 0xF0 /* Inverse video blinking */ #define NORMHLIT_ATTR 0x0F /* Normal with highlighting */ #define INVHLIT_ATTR 0x78 /* Inverse video with highlighting */ #define NORMBLNKHLIT_ATTR 0x8F /* Normal, blinking, highlighting */ #define INVBLNKHLIT_ATTR 0xF8 /* Inverse, blinking, highlighting */ #define UNDERLINE_ATTR 0x01 /* Underline attribute */ #define TANK_ATTR 0x1e #define ALIEN_ATTR 0x1c /* Of course, you can increase these numbers if you compile under the large memory model. Make sure the three NUM_XXX defines sum up to the MAX_TASKS value. */ #define MAX_TASKS 20 #define NUM_TANKS 2 #define NUM_ALIENS 8 #define NUM_BULLETS 10 #define BUF_SIZE 500 typedef struct { unsigned char buffer[BUF_SIZE]; unsigned int front, rear; unsigned int char_count; } BUF_STRUCT; /* Increase this if you like under the large memory model. */ #define STACK_SIZE 1200 /* ADD YOUR CODE HERE. Add other members to these structures as necessary. */ typedef struct { int row, col; } AlienParams; typedef struct { int row, col; } TankParams; typedef struct { int row, col; } BulletParams; typedef union { AlienParams a_p; TankParams t_p; BulletParams b_p; } TaskParams; typedef enum {Alien, Tank, Bullet, Bogus} TaskType; struct PCB { unsigned int proc_ss; unsigned int proc_sp; unsigned int *stack_base; Boolean in_use; TaskType task_type; TaskParams *params_ptr; struct PCB *next; }; typedef struct { struct PCB *front; struct PCB *rear; } Queue; typedef struct { int value; Queue *queue; } Sema; BUF_STRUCT KEY_BUF; Boolean DONE; void interrupt (*oldtimer)(void); void interrupt (*oldkeyhand)(void); struct PCB *CURRENT_PROC; struct PCB *PCB_LIST; Sema SCREEN_SEMA; Queue READY_QUEUE; unsigned int system_stack_ss; unsigned int system_stack_sp; void AlienTask(AlienParams *a_params); void TankTask(TankParams *t_params); void BulletTask(BulletParams *b_params); extern unsigned _stklen = 5000; /* ============================================================================= ********************************* ClearScreen ********************************** ============================================================================= */ /* This routine simply clears the screen by scrolling it upward by the number of lines defined between cx and dx registers. */ void ClearScreen(void) { union REGS regs; /* Upper left corner of the screen in cx. */ regs.x.cx = 0; /* Lower right corner of the screen in dx. */ regs.h.dh = (char)LASTROW + 1; regs.h.dl = (char)LASTCOL; /* Attribute bit for the scrolled lines. 0x7 is normal. */ regs.x.bx = (NORM_ATTR << 8) & 0xff00; /* Bios function 0x6 for scroll up (use 0x7 for scroll down); service 0x10. */ regs.x.ax = 0x600; int86( VIDEO, ®s, ®s ); } /* ============================================================================= **************************** WriteStrAtPosWithAttr ***************************** ============================================================================= */ /* Use this routine for the Space Invaders program since it is much faster. */ void WriteStrAtPosWithAttr( char *str, unsigned int attr, unsigned int row, unsigned int col ) { static char far *screen = 0; int i; /* Set the screen pointer. Specific to color screens only. */ screen = MK_FP(0xB800, row*160+col*2); /* Write the string out to the screen. */ for ( i = 0; str[i]; i++ ) { *screen++ = str[i]; *screen++ = attr; } } /* ============================================================================= *********************************** InitBuf ************************************ ============================================================================= */ /* Initialize the buffer data members. */ void InitBuf(BUF_STRUCT *ring) { ring->front = ring->rear = 0; ring->char_count = 0; } /* ============================================================================= ******************************** GetCharFromBuf ******************************** ============================================================================= */ /* This routine gets a character from the ring buffer given by the parameter. We must disable interrupts otherwise this routine may be preempted mid way through the update to the ring buffer variables. */ unsigned char GetCharFromBuf(BUF_STRUCT *ring) { unsigned char current; disable(); /* If characters exist in the buffer, then get one and return it to the calling routine. */ if ( ring->char_count > 0 ) { /* Get a character from the front of the buffer. */ current = ring->buffer[ring->front]; /* Decrement the number in the buffer. */ ring->char_count--; /* Move the pointer (index) forward wrapping to the first position when appropriate. */ ring->front = (ring->front + 1)%BUF_SIZE; } /* No characters - return NULL character to loop. */ else current = '\0'; enable(); return(current); } /* ============================================================================= ********************************* PutCharInBuf ********************************* ============================================================================= */ /* This routine puts a character into the ring buffer if there is room. It is called from the Keyboard ISR defined below. */ void PutCharInBuf(unsigned char c, BUF_STRUCT *ring) { /* Check for ring buffer overflow - character lost if this is the case. */ if ( ring->char_count >= BUF_SIZE ) return; /* Put the character in at the rear of the ring buffer. */ ring->buffer[ring->rear] = c; /* Increment the rear pointer - wrapping around to the beginning when necessary. */ ring->rear = (ring->rear + 1)%BUF_SIZE; /* Update the number of characters currently in the ring buffer. */ ring->char_count++; return; } /* ============================================================================= ********************************** IsBufEmpty ********************************** ============================================================================= */ /* This routine simply checks the ring buffer and returns True (1) if it is empty. */ Boolean IsBufEmpty(BUF_STRUCT *ring) { if ( ring->char_count > 0 ) return(False); else return(True); } /* ============================================================================= ************************************* Beep ************************************* ============================================================================= */ /* Make a nice disturbing beep sound. DO NOT USE 'delay' HERE. */ void Beep() { long i = 0; sound(554); while (i++ < 500000); nosound(); } /* ============================================================================= ********************************* KEYBOARD ISR ********************************* ============================================================================= */ /* Scan code to ASCII translations. I've defined the first 70 (the rest are 0). 1B - ESC, 8 - backspace, 9 - tab, D - carriage return, F - shift in, 1 - F1, 2 - F2, 3 - F3, 4 - F4, 5 - F5, 6 - F6, 7 - F7, A - F8, B - F9, C - F10 - E is invalid */ char trans_table[128] = { 0xE, 0x1B, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '.', '=', 0x8, 0x9, 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', 0xD, 0xE, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0xF, '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0xF, 0xE, 0xE, ' ', 0xF, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xA, 0xB, 0xC, 0xE }; /* ============================================================================= ********************************* NewKeyHandler ******************************** ============================================================================= */ /* This routine will be called whenever the user presses a key. This address of this routine replaces the address at interrupt number 0x9 in the vector table. Note that this routine is called TWICE for each key press - once when pressed and once when released. The release call is ignored. */ void interrupt NewKeyHandler(void) { int scan_code; unsigned char control_port; static Boolean ctrl_flag = False; /* The input port and control port for the keyboard. */ scan_code = inportb(0x60); control_port = inportb(0x61); /* TOGGLE (set to 1 and then to 0) bit 7 of the control port to enable the keyboard to put the next char in the data register. */ outportb( 0x61, control_port | 0x80 ); outportb( 0x61, control_port ); /* Filter out calls caused by the release of the key. These scan_codes are 0x80 larger (128 decimal ) than scan_code returned when that key is pressed. */ if ( scan_code < 128 ) { unsigned char c; if ( scan_code == 29 ) ctrl_flag = True; else if ( trans_table[scan_code] == 'c' && ctrl_flag == True ) ExitProgram(); else if ( trans_table[scan_code] == 'f' ) PutCharInBuf((unsigned char)trans_table[scan_code], &KEY_BUF); else if ( scan_code == 75 ) PutCharInBuf('l', &KEY_BUF); else if ( scan_code == 77 ) PutCharInBuf('r', &KEY_BUF); else Beep(); } else if ( scan_code == 29 + 128 ) ctrl_flag = False; /* Send EOI (end of interrupt signal to 8259). */ outportb( 0x20, 0x20 ); } /* ============================================================================= ********************************* C_Function *********************************** ============================================================================= */ /* This routine simply allows c function calls (in this case, sprintf) to execute on the system stack since they commonly have a large number of nested procedure calls within them. This allows me to reduce the size of each of the process stacks. */ unsigned int temp_ss; unsigned int temp_sp; void C_Function(char *buf, unsigned int val) { asm cli; temp_ss = _SS; temp_sp = _SP; /* Switch to the system stack before calling sprintf. */ _SS = system_stack_ss; _SP = system_stack_sp; sprintf(buf, "%u", val ); _SS = temp_ss; _SP = temp_sp; asm sti; } /* ============================================================================= ************************************ EnQueue *********************************** ============================================================================= */ /* Add the process (PCB) given by the second parameter to the Queue given by the first parameter. */ void EnQueue(Queue *queue, struct PCB *proc ) { /* ADD YOUR CODE HERE - Update 'next' field of the PCBs as well as 'front' and 'rear' pointers of the queue. Enqueue puts the PCB on the END of the list. Single linked list is sufficient. */ } /* ============================================================================= ************************************ DeQueue *********************************** ============================================================================= */ /* Remove the process (PCB) from the Queue given by the parameter. */ struct PCB *DeQueue(Queue *queue) { /* ADD YOUR CODE HERE. Update 'next' field of the PCBs as well 'front' and 'rear' pointers of the queue. Dequeue puts the PCB on the FRONT of the list. Single linked list is sufficient. */ } /* ============================================================================= ************************************ Remove ************************************ ============================================================================= */ /* This routine is called from the Scheduler when a task is put on the blocked queue (because it called Wait and the screen resouce was in use), is being removed from the blocked queue (by a process that has finished using the screen resouce and has called Signal), when a task is Terminated or when a process (Tank) voluntarily gives up the CPU. It saves the context of the currently running process on the stack of that process by pushing the values of the registers. Since the NewTimerHandler routine is likely to restore the process and the Timer handler is an ISR, we have to make the stack look like an ISR stack by pushing the flags, CS and IP. In order to return to the Scheduler, I also have to push the IP again before this routine can return successfully. In other words, we will return TWICE to the Scheduler, once when Scheduler calls this routine and once when the Timer restores this routine. In order to tell the difference in Scheduler, ax is loaded with a 0 or a 1 - see the comments below. */ unsigned int save_BP; unsigned int save_IP; unsigned int save_CS; Boolean Remove(void) { /* The Scheduler calls Remove. Right now, we have di, si, bp and ip on the stack (If you are curious, use 'bcc -S space.c' to determine that this is true.) There is NO cs since this is a near procedure. To make it look like an iret whenever we Restore, we need to replace these values with ip, cs and flags. */ asm { /* Pop off the stack the di and si values pushed by the assembly code generated by the compiler. */ pop ax pop ax /* Save the value of BP since we will need it below. */ pop ax mov save_BP, ax /* Save the value of the IP (which points to the assembly language statement in Scheduler corresponding to the 'if ( Remove() == False )' code in that routine) and save it since we will need it below. */ pop ax mov save_IP, ax } #if defined(__MEDIUM__) || defined(__LARGE__) || defined(__HUGE__) asm pop ax; asm mov save_CS, ax; #endif /* Push flags and CS (since interrupts are far) followed by IP. */ asm { pushf push cs mov ax, save_IP push ax /* Save the return value. This will be the value put in AX when Restore is called. When we return to Scheduler with this value, the 'if ( Remove() == False )' comparison will fail - see the Scheduler code. */ mov ax, 1 /* Push the registers followed by the value of BP. */ push ax push bx push cx push dx push es push ds push si push di push save_BP } /* These will be set into the stack registers by the NewTimerHandler in order to restore this process to the running state. */ CURRENT_PROC->proc_ss = _SS; CURRENT_PROC->proc_sp = _SP; /* Now we need to build a second return frame so that we can successfully return to the Scheduler. Since this is a normal sub-routine return, all we need to do is push the IP and restore the old value of BP. Generate assembly code using 'bcc -S space.c' to see that this is done automatically by the compiler. Note, we would also NEED to push CS if we compiled this program using a LARGE memory model !!!!! */ #if defined(__MEDIUM__) || defined(__LARGE__) || defined(__HUGE__) asm mov ax, save_CS; asm push ax; #endif asm { mov ax, save_IP push ax mov ax, save_BP push ax pop bp mov ax, 0 ret } /* This does nothing - we never make it this far but it makes the compiler happy. */ return 0; } /* ============================================================================= *********************************** Restore ************************************ ============================================================================= */ /* This routine is called from the Schedular whenever a new process is scheduled to run. The NewTimeHandler does NOT call this routine - it just simply changes the stack pointers. This will cause a return to the Scheduler statement 'if ( Remove() == False )' with a value of 1 in ax or it will return to directly to the process that was interrupted by the NewTimerHandler if the timer fired and saved the state of this process. Think about it a while. */ void Restore() { /* Set the stack pointers to point to the state saved for the process given by the CURRENT_PROC PCB and do an iret to restore the registers. */ _SS = CURRENT_PROC->proc_ss; _SP = CURRENT_PROC->proc_sp; asm { pop bp pop di pop si pop ds pop es pop dx pop cx pop bx pop ax iret } } /* ============================================================================= ********************************** Scheduler *********************************** ============================================================================= */ /* Called from routines Wait, Signal, TerminateTask and any process that voluntarily gives up the CPU. When Scheduler is called, it will call Remove and the 'if ( Remove() == False )' statement will succeed when Remove returns. Remove saves the current state TWICE on the stack of the current process. The first state that is pushed will work if an iret is used to restore it. This will happen from either Restore or when the NewTimerHandler routine re-schedules the process. When either of these routines restore the state of this process, ax will be set with a 1 and and 'if ( Remove() == False )' statement will fail the second time Remove() returns. See Remove for an extended explanation. When Scheduler is called explicitly by one of the routines mentioned above, the parameters will determine what is done with the current process (CURRENT_PROC) and what process will be scheduled to run next. I have found that these two parameters handle all possibilities. You may want to do this differently. */ void Scheduler(struct PCB *proc, Boolean enqueue_current) { asm cli; if ( Remove() == False ) { /* ADD YOUR CODE HERE. Enqueue and Dequeue as dictated by the parameters. */ Restore(); } /* Re-enable interrupts. */ asm sti; } /* ============================================================================= ************************************* Wait ************************************* ============================================================================= */ /* The Wait part of the semaphore defined on page 178 in the text book. */ void Wait(Sema *sema) { /* ADD YOUR CODE HERE. Call Scheduler if Screen is in use. */ } /* ============================================================================= ************************************* Signal *********************************** ============================================================================= */ /* The Signal part of the semaphore defined on page 178 in the text book. */ void Signal(Sema *sema) { /* ADD YOUR CODE HERE. Call Scheduler if others are waiting on the screen. */ } /* ============================================================================= ******************************** NewTimerHandler ******************************* ============================================================================= */ /* The timer handler that is run 18 times every second. IT MUST BE INSTALLED AS THE HANDLER FOR VECTOR 0x8 - DO NOT USE 0x1C since the stack pointer is modified before the 0x1C handler is called !!!!!!!!! This will cause this many problems in your debug efforts. */ { /* ADD YOUR CODE HERE. Save _SS and _SP in PCB of CURRENT_PROC, call Enqueue and Dequeue, set _SS and _SP with new PCB values. */ /* Send a EOI signal so the timer will fire again and restore the new process to the running state using an implicit iret (use 'bcc -S space' to convince yourself that this occurs). */ outportb( 0x20, 0x20 ); } /* ============================================================================= ****************************** AllocTaskStructs ******************************** ============================================================================= */ /* This routine allocates the PCBs, stack spaces and parameters for all processes. It should be called in the main program before the first process is 'restored' and made running. */ void AllocTaskStructs(void) { int i; /* Allocate the PCB array structure. */ if (( PCB_LIST = (struct PCB *)malloc(MAX_TASKS * sizeof(struct PCB))) == NULL ) { WriteStrAtPosWithAttr("ERROR: AllocTaskStructs(): Not enough memory for \ PCBs !", NORM_ATTR, STATUS_ROW, 0); exit(0); } /* For each PCB structure (process), allocate a stack and parameters structure. */ for ( i = 0; i < MAX_TASKS; i++ ) { /* Mark all PCBs as available. */ PCB_LIST[i].in_use = False; /* Mark each PCB with the appropiate type. This is necessary so that the parameters are referenced correctly (see below) and to make the game fun to play. */ if ( i < NUM_TANKS ) PCB_LIST[i].task_type = Tank; else if ( i < NUM_TANKS + NUM_ALIENS ) PCB_LIST[i].task_type = Alien; else PCB_LIST[i].task_type = Bullet; /* Create stack space for each of the processes. Save the base in the PCB. */ if (( PCB_LIST[i].stack_base = (unsigned int *)malloc(sizeof(unsigned int)*STACK_SIZE) ) == NULL ) { WriteStrAtPosWithAttr("ERROR: AllocTaskStructs(): No space for \ stack !", NORM_ATTR, STATUS_ROW, 0 ); exit(0); } /* Allocate a parameter structure for each PCB dependent on the task_type defined above. */ if ( PCB_LIST[i].task_type == Tank ) { if (( PCB_LIST[i].params_ptr = (TaskParams *)malloc(sizeof(TankParams))) == NULL ) { WriteStrAtPosWithAttr("ERROR: AllocTaskStructs(): No space for \ params !", NORM_ATTR, STATUS_ROW, 0 ); exit(0); } } if ( PCB_LIST[i].task_type == Alien ) { if (( PCB_LIST[i].params_ptr = (TaskParams *)malloc(sizeof(AlienParams))) == NULL ) { WriteStrAtPosWithAttr("ERROR: AllocTaskStructs(): No space for \ params !", NORM_ATTR, STATUS_ROW, 0 ); exit(0); } } if ( PCB_LIST[i].task_type == Bullet ) { if (( PCB_LIST[i].params_ptr = (TaskParams *)malloc(sizeof(BulletParams))) == NULL ) { WriteStrAtPosWithAttr("ERROR: AllocTaskStructs(): No space for \ params !", NORM_ATTR, STATUS_ROW, 0 ); exit(0); } } } } /* ============================================================================= ********************************** CreateTask ********************************** ============================================================================= */ /* This routine will look for a unused PCB and initialize the PCB structure variables with the values necessary to get the process up and running. The parameters determine what type of task is to be created and the initial values to give to the parameter structure associated with the task. This allows one process to control the attributes of another process. */ unsigned int code_seg, instr_ptr; struct PCB *CreateTask( TaskType tt, unsigned int row, unsigned int col, int direction ) { static unsigned int temp_ss; static unsigned int temp_sp; static TaskParams *params_ptr; static int i, pcb_index; static unsigned int num_bytes; num_bytes = sizeof(unsigned int) * STACK_SIZE; /* Not reentrant code - interrupts MUST be off when this code executes - You have to make sure of this in the calling code or uncomment this and do an 'asm sti;' before the return statements in this routine. */ /* asm cli; */ /* Find an unused PCB. */ pcb_index = -1; for ( i = 0; i < MAX_TASKS; i++ ) if ( PCB_LIST[i].in_use == False && PCB_LIST[i].task_type == tt ) { pcb_index = i; break; } /* If none are available of the type requested, return NULL. */ if ( pcb_index == -1 ) return NULL; /* Make sure its not pointing to any other PCBs and mark the PCB in use. */ PCB_LIST[pcb_index].next = NULL; PCB_LIST[pcb_index].in_use = True; /* Get a local pointer to the parameters structure. */ params_ptr = PCB_LIST[pcb_index].params_ptr; /* Set the parameter structure values with the parameters passed into this routine. ADD YOUR CODE HERE. You may need to add other stuff here yourself. */ if ( tt == Alien ) { code_seg = FP_SEG(AlienTask); instr_ptr = FP_OFF(AlienTask); params_ptr->a_p.row = row; params_ptr->a_p.col = col; } else if ( tt == Tank ) { code_seg = FP_SEG(TankTask); instr_ptr = FP_OFF(TankTask); params_ptr->t_p.row = row; params_ptr->t_p.col = col; } else { code_seg = FP_SEG(BulletTask); instr_ptr = FP_OFF(BulletTask); params_ptr->b_p.row = row; params_ptr->b_p.col = col; } /* Save the segment value and the offset of the stack pointer. Note that stacks grow toward lower memory. Leave room for the address of the parameter to the procedure. Note that FP_OFF(stackptr[i]) is an int - do NOT use STACK_SIZE here since this is in words - not bytes. */ PCB_LIST[pcb_index].proc_ss = FP_SEG(PCB_LIST[pcb_index].stack_base); PCB_LIST[pcb_index].proc_sp = (unsigned int)( FP_OFF(PCB_LIST[pcb_index].stack_base) + num_bytes ); /* Save the current stack values so we can restore them before we return. */ temp_ss = _SS; temp_sp = _SP; /* Set the Stack registers to point to the process stack of the process being created. */ _SS = PCB_LIST[pcb_index].proc_ss; _SP = PCB_LIST[pcb_index].proc_sp; /* First save the pointer to the parameters so that the processes (functions) can reference them correctly. */ #if defined(__MEDIUM__) || defined(__LARGE__) || defined(__HUGE__) _AX = FP_SEG(params_ptr); asm push ax; #endif _AX = FP_OFF(params_ptr); asm push ax; /* Since the scheduler will be returning into the first line of the task, we have to create a dummy stack frame (pretend the task was called via a normal call). Why? The tasks are assembled using the "enter" and "leave" opcodes which automatically generate a stack frame; in doing so they are even smart enough to know whether the call to the procedure is near or far and offset all parameter variables from bp to "miss" the return address. Hence, the below code that pushes either one word (for "near" function models) or two words (for "far" function models). */ #if defined(__MEDIUM__) || defined(__LARGE__) || defined(__HUGE__) asm mov ax, 0; asm push ax; #endif asm { mov ax, 0 push ax /* Make the stack look like an ISR was called. This will allow us to use an iret to restore the state of the process and run it for the first time. */ pushf mov ax, code_seg push ax mov ax, instr_ptr push ax push ax /* AX=BX=CX=DX=0 */ push ax push ax push ax push es /* save es, ds */ push ds push ax /* SI=DI=0 */ push ax push ax /* BP */ } /* Save the new offset of stack - SS would not have changed by the pushes. */ PCB_LIST[pcb_index].proc_sp = _SP; /* Restore the stack pointers and return a pointer to the PCB. */ _SS = temp_ss; _SP = temp_sp; return &PCB_LIST[pcb_index]; } /* ============================================================================= ********************************** BulletTask ********************************** ============================================================================= */ /* This routine represents the bullet process. It has a parameter, which is a pointer to a structure of values used to initialize its position and other attributes. This can be done with local (stack) variables also but using parameters allows one process to create another process with specific values that could not be done reliably through global variables. Since both Tanks and Aliens create bullets, we must use parameters to give the bullet a direction which in turn can be used to determine whether an alien or a tank created the bullet. BulletTask processes live until their bullets move off the top or bottom of the screen. */ void BulletTask(BulletParams *b_p) { /* Loop until user presses Ctrl-C or some game exit condition is satisfied. */ while ( DONE == False ) { /* ADD YOUR CODE HERE. */ /* Erase the Bullet - NOTE - screen updates MUST call Wait before the update and then Signal after the update is complete. */ /* Update its position. */ /* Check if it is off the screen and TERMINATE task if true - call TerminateTask which calls Scheduler to schedule a new task on the Ready Queue. */ /* Redisplay bullet at the new position. */ /* Check for a hit on a Tank or an Alien by scanning PCBs and examining the pointer to the parameters saved in each PCB. */ } /* Clean up and exit. */ exit(0); } /* ============================================================================= *********************************** AlienTask ********************************** ============================================================================= */ /* This routine represents the alien process. AlienTask generates its new position using a random number generator -> 'rand()' - this function returns a value between 0 and 2^15 - 1. */ void AlienTask(AlienParams *a_p) { /* Loop until user presses Ctrl-C or some game exit condition is satisfied. */ while ( DONE == False ) { /* ADD YOUR CODE HERE. */ /* Check for a hit by the Tank - if true, update game variables, erase Alien and Terminate the Alien task. */ /* Erase the Alien task. Update its column position using 'rand()' and possibly its row position. Redraw the Alien. */ /* Fire a bullet occasionally by calling CreateTask/MakeReady. */ } /* Clean up and exit. */ exit(0); } /* ============================================================================= ********************************** TankTask ************************************ ============================================================================= */ /* This routine represents the tank process. TankTask processes keyboard input and puts itself to sleep if nothing is on the keyboard buffer. */ void TankTask(TankParams *t_p) { /* ADD YOUR CODE HERE. */ /* Loop until user presses Ctrl-C or some game exit condition is satisfied. You may want to draw the Tank originally before the while loop because of the condition below. */ while( DONE == False ) { /* ADD YOUR CODE HERE. */ /* Check if the Tank has been hit. Update game variables. DO NOT TERMINATE TANKS since you must have at least one process running at all times. */ /* May want to create new Aliens if none exist. */ /* Check the Keyboard buffer - if a character exists get it otherwise, you MUST call the Scheduler (voluntarily give up CPU). */ /* Erase the Tank. Update its column position by examining the keyboard input - 'l' means move left and 'r' means move right. Redraw the Tank. */ /* Check if a bullet was fired - the keyboard ISR will put an 'f' character on the ring buffer when the user presses 'f' on the keyboard. If so, create a BulletTask. */ } /* Clean up and exit. */ exit(0); } /* ============================================================================= ************************************* main ************************************* ============================================================================= */ void main(void) { Boolean first_fnd = False; char c; int i; /* ADD CODE HERE. Initialize the global data structures. */ ClearScreen(); InitBuf(&KEY_BUF); InitSema(&SCREEN_SEMA); InitQueue(&READY_QUEUE); PCB_LIST = NULL; /* Allocate the data structures (PCBs, Stacks, etc) for all processes. */ AllocTaskStructs(); /* ADD CODE HERE. Create an initial set of tasks, e.g., */ CreateTask(Alien, 0, 10, 0); CreateTask(Alien, 3, 20, 0); CreateTask(Alien, 6, 30, 0); CreateTask(Tank, LASTROW, 40, 0); /* Set CURRENT_PROC to one of the processes and queue up the others on the READY_QUEUE. */ for ( i = 0; i < MAX_TASKS; i++ ) if ( PCB_LIST[i].in_use == True ) { if ( first_fnd == False ) { first_fnd = True; CURRENT_PROC = &PCB_LIST[i]; } else MakeReady(&PCB_LIST[i]); } asm cli; /* Install the Keyboard and Timer ISR. YOU MUST USE 0x8 and NOT 0x1C for the Timer. See the note given in the header comment for NewTimerHandler. */ oldkeyhand = getvect(0x9); setvect(0x9, NewKeyHandler); /* DO NOT MOVE THIS ABOVE CreateTask and MakeReady SINCE THOSE ROUTINES ARE NOT PROTECTED WITH cli and sti !!!!!!!!!! Install the Timer ISR. YOU MUST USE 0x8 and NOT 0x1C. See the note given in the header comment for NewTimerHandler. */ oldtimer = getvect(0x8); setvect( 0x8, NewTimerHandler ); /* Save the system stack register values for C_Function. See the comment in the header for that routine. */ system_stack_ss = _SS; system_stack_sp = _SP; /* Load the context (state) of the current process using the PCB pointed to by CURRENT_PROC by resetting the stack registers as defined in CreateTask. Note that iret will pop the ip, cs and flags registers in that order. */ _SS = CURRENT_PROC->proc_ss; _SP = CURRENT_PROC->proc_sp; asm { pop bp; pop di; pop si; pop ds; pop es; pop dx; pop cx; pop bx; pop ax; iret; } asm iret; /* Program should never come back here */ }