mirror of
https://github.com/kenshineto/kern.git
synced 2025-04-10 20:47:25 +00:00
1116 lines
25 KiB
C
1116 lines
25 KiB
C
/*
|
|
** @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 %s", pcb->pid,
|
|
pcb->state >= N_STATES ? "???" : state_str[pcb->state]);
|
|
|
|
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]);
|
|
|
|
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)
|
|
**
|
|
** @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) {
|
|
if (nstate[n]) {
|
|
cio_printf(" %u %s", nstate[n],
|
|
state_str[n] != NULL ? state_str[n] : "???");
|
|
}
|
|
}
|
|
cio_putchar('\n');
|
|
}
|