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); }