/* ** @file procs.c ** ** @author CSCI-452 class of 20245 ** ** @brief Process-related implementations */ #define KERNEL_SRC #include <common.h> #include <procs.h> #include <user.h> /* ** PRIVATE DEFINITIONS */ // determine if a queue is empty; assumes 'q' is a valid pointer #define PCB_QUEUE_EMPTY(q) ((q)->head == NULL) /* ** PRIVATE DATA TYPES */ /* ** PCB Queue structure ** ** Opaque to the rest of the kernel ** ** Typedef'd in the header: typedef struct pcb_queue_s *pcb_queue_t; */ struct pcb_queue_s { pcb_t *head; pcb_t *tail; enum pcb_queue_order_e order; }; /* ** PRIVATE GLOBAL VARIABLES */ // collection of queues static struct pcb_queue_s pcb_freelist_queue; static struct pcb_queue_s ready_queue; static struct pcb_queue_s waiting_queue; static struct pcb_queue_s sleeping_queue; static struct pcb_queue_s zombie_queue; static struct pcb_queue_s sioread_queue; /* ** PUBLIC GLOBAL VARIABLES */ // public-facing queue handles pcb_queue_t pcb_freelist; pcb_queue_t ready; pcb_queue_t waiting; pcb_queue_t sleeping; pcb_queue_t zombie; pcb_queue_t sioread; // pointer to the currently-running process pcb_t *current; // the process table pcb_t ptable[N_PROCS]; // next available PID uint_t next_pid; // pointer to the PCB for the 'init' process pcb_t *init_pcb; // table of state name strings const char state_str[N_STATES][4] = { [STATE_UNUSED] = "Unu", // "Unused" [STATE_NEW] = "New", [STATE_READY] = "Rdy", // "Ready" [STATE_RUNNING] = "Run", // "Running" [STATE_SLEEPING] = "Slp", // "Sleeping" [STATE_BLOCKED] = "Blk", // "Blocked" [STATE_WAITING] = "Wat", // "Waiting" [STATE_KILLED] = "Kil", // "Killed" [STATE_ZOMBIE] = "Zom" // "Zombie" }; // table of priority name strings const char prio_str[N_PRIOS][5] = { [PRIO_HIGH] = "High", [PRIO_STD] = "User", [PRIO_LOW] = "Low ", [PRIO_DEFERRED] = "Def " }; // table of queue ordering name strings const char ord_str[N_PRIOS][5] = { [O_FIFO] = "FIFO", [O_PRIO] = "PRIO", [O_PID] = "PID ", [O_WAKEUP] = "WAKE" }; /* ** PRIVATE FUNCTIONS */ /** ** Priority search functions. These are used to traverse a supplied ** queue looking for the queue entry that would precede the supplied ** PCB when that PCB is inserted into the queue. ** ** Variations: ** find_prev_wakeup() compares wakeup times ** find_prev_priority() compares process priorities ** find_prev_pid() compares PIDs ** ** Each assumes the queue should be in ascending order by the specified ** comparison value. ** ** @param[in] queue The queue to search ** @param[in] pcb The PCB to look for ** ** @return a pointer to the predecessor in the queue, or NULL if ** this PCB would be at the beginning of the queue. */ static pcb_t *find_prev_wakeup(pcb_queue_t queue, pcb_t *pcb) { // sanity checks! assert1(queue != NULL); assert1(pcb != NULL); pcb_t *prev = NULL; pcb_t *curr = queue->head; while (curr != NULL && curr->wakeup <= pcb->wakeup) { prev = curr; curr = curr->next; } return prev; } static pcb_t *find_prev_priority(pcb_queue_t queue, pcb_t *pcb) { // sanity checks! assert1(queue != NULL); assert1(pcb != NULL); pcb_t *prev = NULL; pcb_t *curr = queue->head; while (curr != NULL && curr->priority <= pcb->priority) { prev = curr; curr = curr->next; } return prev; } static pcb_t *find_prev_pid(pcb_queue_t queue, pcb_t *pcb) { // sanity checks! assert1(queue != NULL); assert1(pcb != NULL); pcb_t *prev = NULL; pcb_t *curr = queue->head; while (curr != NULL && curr->pid <= pcb->pid) { prev = curr; curr = curr->next; } return prev; } /* ** PUBLIC FUNCTIONS */ // a macro to simplify queue setup #define QINIT(q, s) \ q = &q##_queue; \ if (pcb_queue_reset(q, s) != SUCCESS) { \ PANIC(0, "pcb_init can't reset " #q); \ } /** ** Name: pcb_init ** ** Initialization for the Process module. */ void pcb_init(void) { #if TRACING_INIT cio_puts(" Procs"); #endif // there is no current process current = NULL; // set up the external links to the queues QINIT(pcb_freelist, O_FIFO); QINIT(ready, O_PRIO); QINIT(waiting, O_PID); QINIT(sleeping, O_WAKEUP); QINIT(zombie, O_PID); QINIT(sioread, O_FIFO); /* ** We statically allocate our PCBs, so we need to add them ** to the freelist before we can use them. If this changes ** so that we dynamicallyl allocate PCBs, this step either ** won't be required, or could be used to pre-allocate some ** number of PCB structures for future use. */ pcb_t *ptr = ptable; for (int i = 0; i < N_PROCS; ++i) { pcb_free(ptr); ++ptr; } } /** ** Name: pcb_alloc ** ** Allocate a PCB from the list of free PCBs. ** ** @param pcb Pointer to a pcb_t * where the PCB pointer will be returned. ** ** @return status of the allocation attempt */ int pcb_alloc(pcb_t **pcb) { // sanity check! assert1(pcb != NULL); // remove the first PCB from the free list pcb_t *tmp; if (pcb_queue_remove(pcb_freelist, &tmp) != SUCCESS) { return E_NO_PCBS; } *pcb = tmp; return SUCCESS; } /** ** Name: pcb_free ** ** Return a PCB to the list of free PCBs. ** ** @param pcb Pointer to the PCB to be deallocated. */ void pcb_free(pcb_t *pcb) { if (pcb != NULL) { // mark the PCB as available pcb->state = STATE_UNUSED; // add it to the free list int status = pcb_queue_insert(pcb_freelist, pcb); // if that failed, we're in trouble if (status != SUCCESS) { sprint(b256, "pcb_free(0x%08x) status %d", (uint32_t)pcb, status); PANIC(0, b256); } } } /** ** Name: pcb_zombify ** ** Turn the indicated process into a Zombie. This function ** does most of the real work for exit() and kill() calls. ** Is also called from the scheduler and dispatcher. ** ** @param pcb Pointer to the newly-undead PCB */ void pcb_zombify(register pcb_t *victim) { // should this be an error? if (victim == NULL) { return; } // every process must have a parent, even if it's 'init' assert(victim->parent != NULL); /* ** We need to locate the parent of this process. We also need ** to reparent any children of this process. We do these in ** a single loop. */ pcb_t *parent = victim->parent; pcb_t *zchild = NULL; // two PIDs we will look for uint_t vicpid = victim->pid; // speed up access to the process table entries register pcb_t *curr = ptable; for (int i = 0; i < N_PROCS; ++i, ++curr) { // make sure this is a valid entry if (curr->state == STATE_UNUSED) { continue; } // if this is our parent, just keep going - we continue // iterating to find all the children of this process. if (curr == parent) { continue; } if (curr->parent == victim) { // found a child - reparent it curr->parent = init_pcb; // see if this child is already undead if (curr->state == STATE_ZOMBIE) { // if it's already a zombie, remember it, so we // can pass it on to 'init'; also, if there are // two or more zombie children, it doesn't matter // which one we pick here, as the others will be // collected when 'init' loops zchild = curr; } } } /* ** If we found a child that was already terminated, we need to ** wake up the init process if it's already waiting. ** ** Note: we only need to do this for one Zombie child process - ** init will loop and collect the others after it finishes with ** this one. ** ** Also note: it's possible that the exiting process' parent is ** also init, which means we're letting one of zombie children ** of the exiting process be cleaned up by init before the ** existing process itself is cleaned up by init. This will work, ** because after init cleans up the zombie, it will loop and ** call waitpid() again, by which time this exiting process will ** be marked as a zombie. */ if (zchild != NULL && init_pcb->state == STATE_WAITING) { // dequeue the zombie assert(pcb_queue_remove_this(zombie, zchild) == SUCCESS); assert(pcb_queue_remove_this(waiting, init_pcb) == SUCCESS); // intrinsic return value is the PID RET(init_pcb) = zchild->pid; // may also want to return the exit status int32_t *ptr = (int32_t *)ARG(init_pcb, 2); if (ptr != NULL) { // ******************************************************** // ** Potential VM issue here! This code assigns the exit // ** status into a variable in the parent's address space. // ** This works in the baseline because we aren't using // ** any type of memory protection. If address space // ** separation is implemented, this code will very likely // ** STOP WORKING, and will need to be fixed. // ******************************************************** *ptr = zchild->exit_status; } // all done - schedule 'init', and clean up the zombie schedule(init_pcb); pcb_cleanup(zchild); } /* ** Now, deal with the parent of this process. If the parent is ** already waiting, just wake it up and clean up this process. ** Otherwise, this process becomes a zombie. ** ** Note: if the exiting process' parent is init and we just woke ** init up to deal with a zombie child of the exiting process, ** init's status won't be Waiting any more, so we don't have to ** worry about it being scheduled twice. */ if (parent->state == STATE_WAITING) { // verify that the parent is either waiting for this process // or is waiting for any of its children uint32_t target = ARG(parent, 1); if (target == 0 || target == vicpid) { // the parent is waiting for this child or is waiting // for any of its children, so we can wake it up. // intrinsic return value is the PID RET(parent) = vicpid; // may also want to return the exit status int32_t *ptr = (int32_t *)ARG(parent, 2); if (ptr != NULL) { // ******************************************************** // ** Potential VM issue here! This code assigns the exit // ** status into a variable in the parent's address space. // ** This works in the baseline because we aren't using // ** any type of memory protection. If address space // ** separation is implemented, this code will very likely // ** STOP WORKING, and will need to be fixed. // ******************************************************** *ptr = victim->exit_status; } // all done - schedule the parent, and clean up the zombie schedule(parent); pcb_cleanup(victim); return; } } /* ** The parent isn't waiting OR is waiting for a specific child ** that isn't this exiting process, so we become a Zombie. ** ** This code assumes that Zombie processes are *not* in ** a queue, but instead are just in the process table with ** a state of 'Zombie'. This simplifies life immensely, ** because we won't need to dequeue it when it is collected ** by its parent. */ victim->state = STATE_ZOMBIE; assert(pcb_queue_insert(zombie, victim) == SUCCESS); /* ** Note: we don't call _dispatch() here - we leave that for ** the calling routine, as it's possible we don't need to ** choose a new current process. */ } /** ** Name: pcb_cleanup ** ** Reclaim a process' data structures ** ** @param pcb The PCB to reclaim */ void pcb_cleanup(pcb_t *pcb) { #if TRACING_PCB cio_printf("** pcb_cleanup(0x%08x)\n", (uint32_t)pcb); #endif // avoid deallocating a NULL pointer if (pcb == NULL) { // should this be an error? return; } // we need to release all the VM data structures and frames user_cleanup(pcb); // release the PCB itself pcb_free(pcb); } /** ** Name: pcb_find_pid ** ** Locate the PCB for the process with the specified PID ** ** @param pid The PID to be located ** ** @return Pointer to the PCB, or NULL */ pcb_t *pcb_find_pid(uint_t pid) { // must be a valid PID if (pid < 1) { return NULL; } // scan the process table pcb_t *p = ptable; for (int i = 0; i < N_PROCS; ++i, ++p) { if (p->pid == pid && p->state != STATE_UNUSED) { return p; } } // didn't find it! return NULL; } /** ** Name: pcb_find_ppid ** ** Locate the PCB for the process with the specified parent ** ** @param pid The PID to be located ** ** @return Pointer to the PCB, or NULL */ pcb_t *pcb_find_ppid(uint_t pid) { // must be a valid PID if (pid < 1) { return NULL; } // scan the process table pcb_t *p = ptable; for (int i = 0; i < N_PROCS; ++i, ++p) { assert1(p->parent != NULL); if (p->parent->pid == pid && p->parent->state != STATE_UNUSED) { return p; } } // didn't find it! return NULL; } /** ** Name: pcb_queue_reset ** ** Initialize a PCB queue. We assume that whatever data may be ** in the queue structure can be overwritten. ** ** @param queue[out] The queue to be initialized ** @param order[in] The desired ordering for the queue ** ** @return status of the init request */ int pcb_queue_reset(pcb_queue_t queue, enum pcb_queue_order_e style) { // sanity check assert1(queue != NULL); // make sure the style is valid if (style < O_FIRST_STYLE || style > O_LAST_STYLE) { return E_BAD_PARAM; } // reset the queue queue->head = queue->tail = NULL; queue->order = style; return SUCCESS; } /** ** Name: pcb_queue_empty ** ** Determine whether a queue is empty. Essentially just a wrapper ** for the PCB_QUEUE_EMPTY() macro, for use outside this module. ** ** @param[in] queue The queue to check ** ** @return true if the queue is empty, else false */ bool_t pcb_queue_empty(pcb_queue_t queue) { // if there is no queue, blow up assert1(queue != NULL); return PCB_QUEUE_EMPTY(queue); } /** ** Name: pcb_queue_length ** ** Return the count of elements in the specified queue. ** ** @param[in] queue The queue to check ** ** @return the count (0 if the queue is empty) */ uint_t pcb_queue_length(const pcb_queue_t queue) { // sanity check assert1(queue != NULL); // this is pretty simple register pcb_t *tmp = queue->head; register int num = 0; while (tmp != NULL) { ++num; tmp = tmp->next; } return num; } /** ** Name: pcb_queue_insert ** ** Inserts a PCB into the indicated queue. ** ** @param queue[in,out] The queue to be used ** @param pcb[in] The PCB to be inserted ** ** @return status of the insertion request */ int pcb_queue_insert(pcb_queue_t queue, pcb_t *pcb) { // sanity checks assert1(queue != NULL); assert1(pcb != NULL); // if this PCB is already in a queue, we won't touch it if (pcb->next != NULL) { // what to do? we let the caller decide return E_BAD_PARAM; } // is the queue empty? if (queue->head == NULL) { queue->head = queue->tail = pcb; return SUCCESS; } assert1(queue->tail != NULL); // no, so we need to search it pcb_t *prev = NULL; // find the predecessor node switch (queue->order) { case O_FIFO: prev = queue->tail; break; case O_PRIO: prev = find_prev_priority(queue, pcb); break; case O_PID: prev = find_prev_pid(queue, pcb); break; case O_WAKEUP: prev = find_prev_wakeup(queue, pcb); break; default: // do we need something more specific here? return E_BAD_PARAM; } // OK, we found the predecessor node; time to do the insertion if (prev == NULL) { // there is no predecessor, so we're // inserting at the front of the queue pcb->next = queue->head; if (queue->head == NULL) { // empty queue!?! - should we panic? queue->tail = pcb; } queue->head = pcb; } else if (prev->next == NULL) { // append at end prev->next = pcb; queue->tail = pcb; } else { // insert between prev & prev->next pcb->next = prev->next; prev->next = pcb; } return SUCCESS; } /** ** Name: pcb_queue_remove ** ** Remove the first PCB from the indicated queue. ** ** @param queue[in,out] The queue to be used ** @param pcb[out] Pointer to where the PCB pointer will be saved ** ** @return status of the removal request */ int pcb_queue_remove(pcb_queue_t queue, pcb_t **pcb) { //sanity checks assert1(queue != NULL); assert1(pcb != NULL); // can't get anything if there's nothing to get! if (PCB_QUEUE_EMPTY(queue)) { return E_EMPTY_QUEUE; } // take the first entry from the queue pcb_t *tmp = queue->head; queue->head = tmp->next; // disconnect it completely tmp->next = NULL; // was this the last thing in the queue? if (queue->head == NULL) { // yes, so clear the tail pointer for consistency queue->tail = NULL; } // save the pointer *pcb = tmp; return SUCCESS; } /** ** Name: pcb_queue_remove_this ** ** Remove the specified PCB from the indicated queue. ** ** We don't return the removed pointer, because the calling ** routine must already have it (because it was supplied ** to us in the call). ** ** @param queue[in,out] The queue to be used ** @param pcb[in] Pointer to the PCB to be removed ** ** @return status of the removal request */ int pcb_queue_remove_this(pcb_queue_t queue, pcb_t *pcb) { //sanity checks assert1(queue != NULL); assert1(pcb != NULL); // can't get anything if there's nothing to get! if (PCB_QUEUE_EMPTY(queue)) { return E_EMPTY_QUEUE; } // iterate through the queue until we find the desired PCB pcb_t *prev = NULL; pcb_t *curr = queue->head; while (curr != NULL && curr != pcb) { prev = curr; curr = curr->next; } // case prev curr next interpretation // ==== ==== ==== ==== ============================ // 1. 0 0 -- *** CANNOT HAPPEN *** // 2. 0 !0 0 removing only element // 3. 0 !0 !0 removing first element // 4. !0 0 -- *** NOT FOUND *** // 5. !0 !0 0 removing from end // 6. !0 !0 !0 removing from middle if (curr == NULL) { // case 1 assert(prev != NULL); // case 4 return E_NOT_FOUND; } // connect predecessor to successor if (prev != NULL) { // not the first element // cases 5 and 6 prev->next = curr->next; } else { // removing first element // cases 2 and 3 queue->head = curr->next; } // if this was the last node (cases 2 and 5), // also need to reset the tail pointer if (curr->next == NULL) { // if this was the only entry (2), prev is NULL, // so this works for that case, too queue->tail = prev; } // unlink current from queue curr->next = NULL; // there's a possible consistancy problem here if somehow // one of the queue pointers is NULL and the other one // is not NULL assert1((queue->head == NULL && queue->tail == NULL) || (queue->head != NULL && queue->tail != NULL)); return SUCCESS; } /** ** Name: pcb_queue_peek ** ** Return the first PCB from the indicated queue, but don't ** remove it from the queue. ** ** @param queue[in] The queue to be used ** ** @return the PCB poiner, or NULL if the queue is empty */ pcb_t *pcb_queue_peek(const pcb_queue_t queue) { //sanity check assert1(queue != NULL); // can't get anything if there's nothing to get! if (PCB_QUEUE_EMPTY(queue)) { return NULL; } // just return the first entry from the queue return queue->head; } /* ** Scheduler routines */ /** ** schedule(pcb) ** ** Schedule the supplied process ** ** @param pcb Pointer to the PCB of the process to be scheduled */ void schedule(pcb_t *pcb) { // sanity check assert1(pcb != NULL); // check for a killed process if (pcb->state == STATE_KILLED) { // TODO figure out what to do now return; } // mark it as ready pcb->state = STATE_READY; // add it to the ready queue if (pcb_queue_insert(ready, pcb) != SUCCESS) { PANIC(0, "schedule insert fail"); } } /** ** dispatch() ** ** Select the next process to receive the CPU */ void dispatch(void) { // verify that there is no current process assert(current == NULL); // grab whoever is at the head of the queue int status = pcb_queue_remove(ready, ¤t); if (status != SUCCESS) { sprint(b256, "dispatch queue remove failed, code %d", status); PANIC(0, b256); } // set the process up for success current->state = STATE_RUNNING; current->ticks = QUANTUM_STANDARD; } /* ** Debugging/tracing routines */ /** ** ctx_dump(msg,context) ** ** Dumps the contents of this process context to the console ** ** @param msg[in] An optional message to print before the dump ** @param c[in] The context to dump out */ void ctx_dump(const char *msg, register context_t *c) { // first, the message (if there is one) if (msg) { cio_puts(msg); } // the pointer cio_printf(" @ %08x: ", (uint32_t)c); // if it's NULL, why did you bother calling me? if (c == NULL) { cio_puts(" NULL???\n"); return; } // now, the contents cio_printf("ss %04x gs %04x fs %04x es %04x ds %04x cs %04x\n", c->ss & 0xff, c->gs & 0xff, c->fs & 0xff, c->es & 0xff, c->ds & 0xff, c->cs & 0xff); cio_printf(" edi %08x esi %08x ebp %08x esp %08x\n", c->edi, c->esi, c->ebp, c->esp); cio_printf(" ebx %08x edx %08x ecx %08x eax %08x\n", c->ebx, c->edx, c->ecx, c->eax); cio_printf(" vec %08x cod %08x eip %08x eflags %08x\n", c->vector, c->code, c->eip, c->eflags); } /** ** ctx_dump_all(msg) ** ** dump the process context for all active processes ** ** @param msg[in] Optional message to print */ void ctx_dump_all(const char *msg) { if (msg != NULL) { cio_puts(msg); } int n = 0; register pcb_t *pcb = ptable; for (int i = 0; i < N_PROCS; ++i, ++pcb) { if (pcb->state != STATE_UNUSED) { ++n; cio_printf("%2d(%d): ", n, pcb->pid); ctx_dump(NULL, pcb->context); } } } /** ** pcb_dump(msg,pcb,all) ** ** Dumps the contents of this PCB to the console ** ** @param msg[in] An optional message to print before the dump ** @param pcb[in] The PCB to dump ** @param all[in] Dump all the contents? */ void pcb_dump(const char *msg, register pcb_t *pcb, bool_t all) { // first, the message (if there is one) if (msg) { cio_puts(msg); } // the pointer cio_printf(" @ %08x:", (uint32_t)pcb); // if it's NULL, why did you bother calling me? if (pcb == NULL) { cio_puts(" NULL???\n"); return; } cio_printf(" %d", pcb->pid); cio_printf(" %s", pcb->state >= N_STATES ? "???" : state_str[pcb->state]); #if 0 if( pcb->state >= N_STATES ) { cio_puts( " ????" ); } else { cio_printf( " %s", state_str[pcb->state] ); } #endif if (!all) { // just printing IDs and states on one line return; } // now, the rest of the contents cio_printf(" %s", pcb->priority >= N_PRIOS ? "???" : prio_str[pcb->priority]); #if 0 if( pcb->priority >= N_PRIOS ) { cio_puts( " ???" ); } else { cio_printf( " %s", prio_str[pcb->priority] ); } #endif cio_printf(" ticks %u xit %d wake %08x\n", pcb->ticks, pcb->exit_status, pcb->wakeup); cio_printf(" parent %08x", (uint32_t)pcb->parent); if (pcb->parent != NULL) { cio_printf(" (%u)", pcb->parent->pid); } cio_printf(" next %08x context %08x pde %08x", (uint32_t)pcb->next, (uint32_t)pcb->context, (uint32_t)pcb->pdir); cio_putchar('\n'); } /** ** pcb_queue_dump(msg,queue,contents) ** ** Dump the contents of the specified queue to the console ** ** @param msg[in] Optional message to print ** @param queue[in] The queue to dump ** @param contents[in] Also dump (some) contents? */ void pcb_queue_dump(const char *msg, pcb_queue_t queue, bool_t contents) { // report on this queue cio_printf("%s: ", msg); if (queue == NULL) { cio_puts("NULL???\n"); return; } // first, the basic data cio_printf("head %08x tail %08x", (uint32_t)queue->head, (uint32_t)queue->tail); // next, how the queue is ordered cio_printf(" order %s\n", queue->order >= N_ORDERINGS ? "????" : ord_str[queue->order]); // if there are members in the queue, dump the first few PIDs if (contents && queue->head != NULL) { cio_puts(" PIDs: "); pcb_t *tmp = queue->head; for (int i = 0; i < 5 && tmp != NULL; ++i, tmp = tmp->next) { cio_printf(" [%u]", tmp->pid); } if (tmp != NULL) { cio_puts(" ..."); } cio_putchar('\n'); } } /** ** ptable_dump(msg,all) ** ** dump the contents of the "active processes" table ** ** @param msg[in] Optional message to print ** @param all[in] Dump all or only part of the relevant data */ void ptable_dump(const char *msg, bool_t all) { if (msg) { cio_puts(msg); } cio_putchar(' '); int used = 0; int empty = 0; register pcb_t *pcb = ptable; for (int i = 0; i < N_PROCS; ++i) { if (pcb->state == STATE_UNUSED) { // an empty slot ++empty; } else { // a non-empty slot ++used; // if not dumping everything, add commas if needed if (!all && used) { cio_putchar(','); } // report the table slot # cio_printf(" #%d:", i); // and dump the contents pcb_dump(NULL, pcb, all); } } // only need this if we're doing one-line output if (!all) { cio_putchar('\n'); } // sanity check - make sure we saw the correct number of table slots if ((used + empty) != N_PROCS) { cio_printf("Table size %d, used %d + empty %d = %d???\n", N_PROCS, used, empty, used + empty); } } /** ** Name: ptable_dump_counts ** ** Prints basic information about the process table (number of ** entries, number with each process state, etc.). */ void ptable_dump_counts(void) { uint_t nstate[N_STATES] = { 0 }; uint_t unknown = 0; int n = 0; pcb_t *ptr = ptable; while (n < N_PROCS) { if (ptr->state < 0 || ptr->state >= N_STATES) { ++unknown; } else { ++nstate[ptr->state]; } ++n; ++ptr; } cio_printf("Ptable: %u ***", unknown); for (n = 0; n < N_STATES; ++n) { cio_printf(" %u %s", nstate[n], state_str[n] != NULL ? state_str[n] : "???"); #if 0 cio_printf( " %u ", nstate[n] ); if( state_str[n][0] != '\0' ) { cio_puts( state_str[n] ); } else { cio_puts( "???" ); } #endif } cio_putchar('\n'); }