Here's a very simple piece of code that does the C-LOOK scheduling algorithm from the book. It manages a red-black tree of the transactions, and keeps a global pointer to the node that it is currently servicing. When it's done, it moves onto the next node.
static JRB DT_tree = NULL;
static JRB DT_pointer = NULL;
static int disk_busy = 0;
add_disk_transaction(DT_struct *dts)
{
/* If the disk is busy, just insert the transaction into the tree */
if (disk_busy) {
jrb_insert_int(DT_tree, dts->block_number, new_jval_v(dts));
/* Otherwise, create the tree if it doesn't exist yet */
} else {
if (DT_tree == NULL) {
DT_tree = make_jrb();
}
/* Put the transaction into the tree, and start the disk */
DT_pointer = jrb_insert_int(DT_tree, dts->block_number, new_jval_v(dts));
disk_busy = 1;
start_disk(dts->block_number, dts->buf, dts->readwrite);
}
}
disk_done()
{
DT_struct *dts;
/* Wake up the thread who was waiting for the disk transaction */
dts = (DT_struct) DT_pointer->val.v;
V_kt_sem(dts->sem);
/* Find the next transaction. Kill the previous transaction */
DT_pointer = jrb_next(DT_pointer);
jrb_delete_node(jrb_prev(DT_pointer));
/* If we're at the end of the tree, go back to the beginning */
if (DT_pointer == DT_tree) DT_pointer = jrb_next(DT_pointer);
/* If the tree is empty, mark the disk as idle and return */
if (DT_pointer == DT_tree) {
disk_busy = 0;
return;
}
/* Otherwise, start the next transaction */
dts = (DT_struct) DT_pointer->val.v;
start_disk(dts->block_number, dts->buf, dts->readwrite);
}
This kind of scheduling is pretty much the best, as it doesn't waste
head motions, it keeps the head seek times to a minimum, and it
does not favor one part of the disk over another. See the book (chapter
13) for more text concerning disk scheduling.
A few of you had a different flow of control. You had a global semaphore to make sure that the disk was idle, and always called start_disk() in add_disk_transaction(). And some of you used the red-black tree differently.
Here's code that does both. Now that I look at it, I think that the flow of control here is much cleaner than mine -- well done, those of you who thought of it!
static JRB DT_tree = NULL;
static int current_block;
static kt_sem disk_sem;
static DT_struct *current;
add_disk_transaction(DT_struct *dts)
{
JRB tmp;
/* Initialize if necessary */
if (DT_tree == NULL) {
DT_tree = make_jrb();
current_block = 0;
disk_sem = make_kt_sem(1);
}
/* Put the job in the tree */
jrb_insert_int(DT_tree, dts->block_number, new_jval_v(dts));
/* Now block until the disk is idle */
P_kt_sem(disk_sem);
/* Now choose the next job and start it */
tmp = jrb_find_gte_int(DT_tree, current_block, &found);
if (found == 0) tmp = jrb_first(DT_tree);
current = (DT_struct *) tmp->val.v;
jrb_delete_node(tmp);
current_block = current->block_number+1; /* This prevents starvation */
start_disk(current->block_number, current->buf, current->readwrite);
}
disk_done()
{
/* Wake up the thread who was waiting for the disk transaction */
V_kt_sem(current->sem);
/* Signal that the disk is idle */
V_kt_sem(disk_sem);
}