dosdir
dosread -a /dev/fd0 HEAP.C > heap.c
dosread -a /dev/fd0 CHILDREN.C > children.c
Let's make certain you get the two source files into your Minix 3 environment as soon as you
receive the diskette from me (almost). Also, keep your eyes on the posted messages in case I
need to announce any further technical help.
cc heap.c -o heap
and execute it by means of
./heap
Run a few experiments to get a feel for it. For the test code that appears in main(),
show by means of tree diagrams how the heap grows, is transformed, and ultimately shrinks, during
the execution of this program. Note that the data is actually held in arrays, but that
conceptually, the heap is a tree. The number of nodes in the tree is heapsize at any
given time. Only the data in the arrays from index 0 to heapsize - 1 is considered to be
part of the heap at any given time. We will discuss all this in class. cat heap.c proc.c.old > proc.c
cc -c proc.c -o proc.o
and then again rebuild the kernel and reboot Minix. If all goes well, when Minix comes up, you'll see the
diagnostic information concerning the heap being displayed all over the place, but you will still be able to
log in. Inspect the diagnostic messages and figure them out (see above). Then halt and reboot using the
safe (unaltered) kernel.
if (istrueuserp(rpc)) {
rpc -> p_max_priority = 1 + rand()%99; /* between 1 and 99 */
rpc -> p_priority = rpc -> p_max_priority;
}
kprintf("(%d %d) ", rp->p_nr, rp->p_priority);
for (q=0; q<1000000; q++);
This way, whenever the heap is used to select the next process to run, we'll see which process it
is (its "nr" number, not its "pid"), and we'll also see its priority. The second line is here to
slow things down a bit so that the information doesn't shoot past you too fast. Depending on your
computer/simulator, you will probably need to adjust "1000000" to a number that gives comfortable
results in your environment. Again, rebuild. This time you should see a stream of little
ordered pairs, indicating processes and their priorities. Still, you should go ahead and log in
as usual, although the login prompt will be followed by tons of these ordered pairs. Once you log
in, go ahead and halt and set the boot monitor to boot the unaltered (safe) kernel, so you can get
around without all those pairs getting in your way.
cc children.c -o children
If you next try to run it using
./children you'll be told that there is not enough room in the process table. That's
because you are using the original kernel. You need to halt again and boot up the altered kernel, the
one with enough process table space and the one that displays all those ordered pairs. Try running
children now. After a couple seconds of displaying pairs, there will be a pause
(corresponding to the sleep(10); statement in children.c). After the delay, all of
the child processes should have been created, and they will all want to execute their code (which is a
useless for-loop). Watch as they vie for the system's attention! They get placed in the heap and
eventually each process gets to execute to completion. Nevertheless, there is essentially starvation
taking place here, because the processes with very low priorities must wait for the processes with
higher priorities to finish (terminate) first before they are given an opportunity to execute. That's
not fair!! Your job now is to go back and alter things so as to maintain a reasonable priority system,
but to adjust it dynamically so as to avoid starvation. A rule here is that you must keep a process's
maximum priority equal to its priority at all times. This will prevent existing Minix 3 code from
automatically changing priorities. That's your job now!
/*
The following code implements a priority queue as a heap.
Each node of the heap has an identifier (id) as well as
a priority. insertIntoHeap() is used to add a node to
the heap. extractFromHeap() is used to extract the node
with the highest priority from the heap and return
its identifier. whoIsOnTopOfHeap() simply returns the
identifier of this node, without affecting the heap.
deleteFromHeap() is used to remove an arbitrary node
from the heap. (See further comments for each individual
function.) The variable "heapsize" is the number
of nodes in the heap at any given moment. getPriority()
can be used to check the priority of a node, and
setPriority() can be used to change the priority,
which will cause the heap to be reconfigured. Also, a
"wait counter" has been added to each node. This counts
the number of nodes that have been removed from the queue
since the given node was inserted. The function
getWaitCounter() can be used to obtain this value. The
function bumpAllPriorities() can be used to change all
priorities by a certain amount. These four utility functions
(or a subset of them) can be used to implement a dynamic
priority policy to avoid starvation.
*/
/* Should below main() function (which is just test code) be included? */
#define INCLUDE_TEST_CODE
/* Should heapsize be defined here (or is it defined elsewhere)? */
#define HEAPSIZE_DEFINED_LOCALLY
/* Are we compiling as part of the Minix kernel? */
/*
#define USING_IN_MINIX
*/
/* If so, should node identifiers really be pointers to proc structures? */
/* In this case, "id" is misleading since it suggests an integer */
/*
#define PTR_TO_PROC_MODE
*/
/* Are we debugging in a stand alone environment? */
#define DEBUG_OUTSIDE_MINIX
/* Are we debugging as part of the Minix kernel? */
/*
#define DEBUG_INSIDE_MINIX
*/
/* If more than one process has the highest priority and if the constant
TRY_TO_BE_FAIR_WHEN_EQUAL_PRIORITIES is defined, then it will "try"
to extract the one added first to the heap.
*/
/*
#define TRY_TO_BE_FAIR_WHEN_EQUAL_PRIORITIES
*/
/* How big can the heap get? */
#define MAX_NODES 1000
/* Now begin generating code for the heap. */
#ifdef INCLUDE_TEST_CODE
#include < stdio.h >
#endif
#ifdef HEAPSIZE_DEFINED_LOCALLY
int heapsize = 0;
#endif
/*
Corresponding triples of entries from the following three arrays constitute
a single entry ("node") in a priority queue, implemented as a heap.
*/
#ifdef PTR_TO_PROC_MODE
struct proc *id_array[MAX_NODES];
#else
int id_array[MAX_NODES];
#endif
int priority_array[MAX_NODES];
int wait_counter_array[MAX_NODES];
#ifdef USING_IN_MINIX
/*
Given pointer to proc structure, returns offset index to it in
the proc array. SHould be same as "nr" but I don't trust.
*/
#ifdef PTR_TO_PROC_MODE
int proc_index(p)
struct proc *p;
{
int i;
if (p == NIL_PROC)
{
#ifdef DEBUG_INSIDE_MINIX
kprintf("!");
#endif
return -1;
}
for (i = 0; i < NR_PROCS; i++)
if (p == pproc_addr[NR_TASKS+i])
{
#ifdef DEBUG_INSIDE_MINIX
/* should never print these */
if (i != p->p_nr) kprintf("@");
if (proc_addr(i) != p) kprintf("&");
#endif
return i;
}
#ifdef DEBUG_INSIDE_MINIX
kprintf("?");
#endif
return -1;
}
#endif /* PTR_TO_PROC_MODE */
#endif /* USING_IN_MINIX */
#ifndef USING_IN_MINIX
#ifdef DEBUG_OUTSIDE_MINIX
/* Display heap (for debugging outside Minix) */
void showHeap(char c)
{
int i;
printf("%c ",c);
for (i=0; i < heapsize; i++) printf("%d ", id_array[i]);
printf("\n ");
for (i=0; i < heapsize; i++) printf("%d ", priority_array[i]);
printf("\n");
}
#endif
#endif
/* Switch two nodes in the heap */
void swapInHeap(i, j)
int i;
int j;
{
#ifdef PTR_TO_PROC_MODE
struct proc *temp1;
#else
int temp1;
#endif
int temp2;
temp1 = id_array[i];
id_array[i] = id_array[j];
id_array[j] = temp1;
temp2 = priority_array[i];
priority_array[i] = priority_array[j];
priority_array[j] = temp2;
temp2 = wait_counter_array[i];
wait_counter_array[i] = wait_counter_array[j];
wait_counter_array[j] = temp2;
}
/* Heapify from the bottom up */
void heapifyUp()
{
int n, parent;
n = heapsize - 1;
while (n > 0) {
parent = (n-1)/2;
if (priority_array[parent] >= priority_array[n]) break;
#ifdef TRY_TO_BE_FAIR_WHEN_EQUAL_PRIORITIES
if (parent % 2 == 1 &&
priority_array[parent] == priority_array[parent+1])
swapInHeap(parent,parent+1);
#endif
swapInHeap(n,parent);
#ifdef TRY_TO_BE_FAIR_WHEN_EQUAL_PRIORITIES
if (parent % 2 == 1 &&
priority_array[parent] == priority_array[parent+1])
swapInHeap(parent,parent+1);
#endif
n = parent;
#ifdef DEBUG_OUTSIDE_MINIX
showHeap('.');
#endif
}
}
/* Add a node to the heap if room for it. */
/* Return -1 if not. */
int insertIntoHeap(id, priority)
#ifdef PTR_TO_PROC_MODE
struct proc *id;
#else
int id;
#endif
int priority;
{
if (heapsize >= MAX_NODES) return -1;
id_array[heapsize] = id;
priority_array[heapsize] = priority;
wait_counter_array[heapsize++] = 0;
heapifyUp();
#ifdef DEBUG_INSIDE_MINIX
kprintf("(+ %d %d)", heapsize, proc_index(id));
#endif
#ifdef DEBUG_OUTSIDE_MINIX
showHeap('^');
#endif
return 0;
}
/* Recursively heapify downwards from index n */
void heapifyDown(n)
int n;
{
int left, right, bigger;
bigger = left = 2*n + 1;
right = 2*n + 2;
if (left >= heapsize) return;
else if (right < heapsize &&
priority_array[left] < priority_array[right])
bigger = right;
if (priority_array[n] < priority_array[bigger]) {
swapInHeap(n,bigger);
#ifdef TRY_TO_BE_FAIR_WHEN_EQUAL_PRIORITIES
if (bigger == right &&
priority_array[left] == priority_array[right])
swapInHeap(left,right);
if (n % 2 == 1 && priority_array[n] == priority_array[n+1])
swapInHeap(n,n+1);
#endif
#ifdef DEBUG_OUTSIDE_MINIX
showHeap('.');
#endif
heapifyDown(bigger);
}
#ifdef DEBUG_OUTSIDE_MINIX
showHeap('V');
#endif
}
/*
Return id of node at top of heap (highest priority)
or -1 or NIL_PROC if empty.
*/
#ifdef PTR_TO_PROC_MODE
struct proc *whoIsOnTopOfHeap()
{
if (heapsize > 0) return id_array[0]; else return NIL_PROC;
}
#else
int whoIsOnTopOfHeap()
{
if (heapsize > 0) return id_array[0]; else return -1;
}
#endif
/*
Extract the top node of the heap (highest priority) and return
the id for this node. Also, increment all counters. If heap is
empty, return -1 or NIL_PROC.
*/
#ifdef PTR_TO_PROC_MODE
struct proc *extractFromTopOfHeap()
#else
int extractFromTopOfHeap()
#endif
{
int i;
#ifdef PTR_TO_PROC_MODE
if (heapsize == 0) return NIL_PROC;
#else
if (heapsize == 0) return -1;
#endif
for (i=0; i < heapsize; i++) wait_counter_array[i]++;
swapInHeap(0,--heapsize);
heapifyDown(0);
#ifdef DEBUG_INSIDE_MINIX
kprintf("(^ %d %d)", heapsize, proc_index([id_array[heapsize]));
#endif
return id_array[heapsize];
}
/* Delete specified node and reheapify. Return -1 if not found. */
int deleteFromHeap(id)
#ifdef PTR_TO_PROC_MODE
struct proc *id;
#else
int id;
#endif
{
int i, hold_heapsize;
for (i=0; i < heapsize; i++)
if (id == id_array[i]) break;
if (i == heapsize) return -1;
if (i == 0) {
swapInHeap(0,--heapsize);
heapifyDown(0);
} else {
swapInHeap(i,--heapsize);
hold_heapsize = heapsize;
for (heapsize = 2; heapsize <= hold_heapsize; heapsize++)
heapifyUp();
heapsize = hold_heapsize;
}
#ifdef DEBUG_INSIDE_MINIX
kprintf("(- %d %d)", heapsize, proc_index(id));
#endif
return 0;
}
/* Return size of heap */
int getHeapsize()
{
return heapsize;
}
/* rebuild whole heap from arrays */
void rebuildHeap()
{
int hold_heapsize;
hold_heapsize = heapsize;
for (heapsize = 2; heapsize <= hold_heapsize; heapsize++)
{
heapifyUp();
#ifdef DEBUG_OUTSIDE_MINIX
showHeap('^');
#endif
}
heapsize = hold_heapsize;
}
/* Find wait count for node with given id. Return -1 if not found. */
int getWaitCount(id)
#ifdef PTR_TO_PROC_MODE
struct proc *id;
#else
int id;
#endif
{
int i;
for (i=0; i < heapsize; i++)
if (id == id_array[i]) return wait_counter_array[i];
return -1;
}
/* Find priority of node with given id. Return -1 if not found. */
int getPriority(id)
#ifdef PTR_TO_PROC_MODE
struct proc *id;
#else
int id;
#endif
{
int i;
for (i=0; i < heapsize; i++)
if (id == id_array[i]) return priority_array[i];
return -1;
}
/* Set the priority of a node. Return 0 if successful, else -1. */
int setPriority(id, priority)
#ifdef PTR_TO_PROC_MODE
struct proc *id;
#else
int id;
#endif
int priority;
{
int i, hold_heapsize;
i = 0;
while (i < heapsize)
{
if (id == id_array[i]) {
priority_array[i] = priority;
rebuildHeap();
return 0;
}
i++;
}
return -1;
}
/* Increase priorities of all nodes by some amount */
void bumpAllPriorities(amount)
int amount;
{
int i;
for (i=0; i < heapsize; i++) priority_array[i] += amount;
}
void promoteIdleNodes(delay, amount)
int delay, amount;
{
int i;
for (i=0; i < heapsize; i++)
if (wait_counter_array[i] >= delay)
{
priority_array[i] += amount;
} else {
wait_counter_array[i]++;
}
}
#ifndef USING_IN_MINIX
#ifdef INCLUDE_TEST_CODE
void main()
{
insertIntoHeap(1,13);
insertIntoHeap(2,22);
insertIntoHeap(3,45);
insertIntoHeap(4,81);
insertIntoHeap(5,13);
insertIntoHeap(6,22);
insertIntoHeap(7,13);
insertIntoHeap(8,45);
insertIntoHeap(9,81);
/*
insertIntoHeap(1,81);
insertIntoHeap(2,45);
insertIntoHeap(3,13);
insertIntoHeap(4,22);
insertIntoHeap(5,13);
insertIntoHeap(6,81);
insertIntoHeap(7,45);
insertIntoHeap(8,22);
insertIntoHeap(9,13);
*/
#ifdef DEBUG_OUTSIDE_MINIX
showHeap('*');
#endif
setPriority(4,13);
setPriority(10,10);
#ifdef DEBUG_OUTSIDE_MINIX
showHeap('*');
#endif
printf("%d\n", extractFromTopOfHeap());
printf("%d\n", extractFromTopOfHeap());
printf("%d\n", extractFromTopOfHeap());
printf("%d\n", extractFromTopOfHeap());
#ifdef DEBUG_OUTSIDE_MINIX
printf("Wait count for %d is %d.\n", 7, getWaitCount(7));
printf("Wait count for %d is %d.\n", 10, getWaitCount(10));
#endif
printf("%d\n", extractFromTopOfHeap());
printf("%d\n", extractFromTopOfHeap());
printf("%d\n", extractFromTopOfHeap());
printf("%d\n", extractFromTopOfHeap());
printf("%d\n", extractFromTopOfHeap());
printf("%d\n", extractFromTopOfHeap());
printf("\n");
}
#endif /* INCLUDE_TEST_CODE */
#endif /* USING_IN_MINIX */
/*
Create lots of child processes and keep them busy.
*/
#include
#define NUM_CHILDREN 50
#define NUM_DELAY_LOOPS 50000000
void main()
{
int i, j, pid, status;
for(i=0; i < NUM_CHILDREN; i++)
if ((pid = fork()) == 0) break;
if (pid != 0) {
/* original process code */
for(i=0; i < NUM_CHILDREN; i++)
waitpid(-1, & status, 0);
printf("Children are all done. Bye.\n");
} else {
/* child process code */
/* wait for everyone to be born before fighting for CPU */
sleep(10);
/* now try to get CPU and waste clock cycles doing nothing! */
for (i=0; i < NUM_DELAY_LOOPS; i++) ;
}
}