I think that there is a misunderstanding of ideas here. Perhaps we need to differentiate 'sleeping', which is when a process is removed from the process scheduling for a period of time, and 'idling', which is when the CPU itself is inactive. What you describe is idling, but you seem to be using it for causing processes to sleep, which will be a Problem for you later on.
A sleeping process shouldn't be run at all while it is sleeping; rather, it just needs to be held out of the 'running' process queue of the scheduler until the timer is reached.
The process of testing the timers generally should be done by the scheduler, which needs to keep track of when the sleeping processes can be woken. Fortunately, there are a few efficient ways to do this, the simplest being a 'delta queue', which is a simple linked list with nodes consisting of the process IDs and relative time offsets (either in time units or CPU ticks, your choice).
Code:
typedef struct PSLEEP {
Process* sleeping_process;
time_offset delta;
PSLEEP* next;
} PSleep_Node;
typedef PSleep_Node* Sleep_Queue;
You can simply add the offset and the link pointer to the Process records directly,instead, but that trades off needing an additional record type for added overhead in all process records. For now I'll write it this way to avoid clutter.
When a process sleeps, the time offset from the time it is inserted to when it can be woken is computed. I am assuming that the PSleep_Node has been allocated already, the PID set, and the initial offset computed. I am writing this recursively, because I am brain-damaged that way, but you can do it iteratively if you want. Also, this assumes that you can just subtract the deltas when scheduling and decrement it on a tick.
Code:
void schedule_sleep(Sleep_Queue queue, PSleep_node* dog) // bad pun, I know, I know
{
time_offset delta_new = dog->delta;
// start by checking of the queue is empty
if (queue == NULL)
{
queue = dog;
}
else if (queue->delta <= dog->delta)
{
dog->next = queue;
queue->delta = dog->delta - queue->delta;
}
else if (queue->next == NULL)
{
queue->next = dog;
}
else
dog->delta -= queue->delta;
schedule_sleep(queue->next, dog);
}
In this code, the scheduler walks the list, subtracting the offset of the new node from that of the nodes as it goes down (retaining the modified offset from the previous subtraction) until it finds one that would be negative or zero. It then sets the node's offset to the last non-negative offset it found and inserts it in front of the the last tested node (the one which would have given a negative offset).
This means that when you go to tick the clock (assuming a tick-based model), you simply decrement the head element of the sleep queue and test to see if it is zero. You then move the process record from the sleep queue to running queue, as well as any other processes immediately following the head whose offsets (relative to the head) are zero.
Code:
// inside the scheduler, following a clock tick.
// The variable 'sleep_q' is a pointer to the head of the sleep queue.
sleep_q->delta--;
while (sleep_q->delta == 0)
{
insert(running_q, sleep_q->sleeping_process);
sleep_q = sleep_q->next);
}
// ... continue scheduling
Let's take a quick look at how this works. Let's insert a sleep of 5 clock ticks to the sleep queue. The sequence of offsets is now:
Code:
6
When the clock ticks, we decrement that head offset, to get:
Code:
5
Let's assume that after two more clock ticks (decremented as above), another process calls for a sleep of 5 ticks at the third tick. We get:
Code:
2, 3
Now at the next tick, we get a sleep of 3. This gives us:
Code:
1, 3, 0
at the next tick, the head element is woken up, and the one following it becomes the new head:
Code:
3, 0
One tick later, another sleep of seven is added.
Code:
2, 0, 5
One tick more, and we get:
Code:
1, 0, 5
Another tick later, the head, and the one following it with a zero offset, get moved to running_q:
Code:
5
Then on the next tick a sleep of 3 is called, leading to the sequence:
Code:
3,1 < -- note that the '1' is the element that was already in the queue:
5 - 1 - 3 = 1
2,1
1,1
1
Finally, the last sleeping process is woken and the sleep queue is empty again.
This is just a quick sketch of the technique, the actual code will depend on the rest of your kernel design. Note that I didn't address the issue of the thread's priority on being woken up; depending on the scheduler algorithm and the CPU load, there is no guarantee that the woken process will run next without additional steps being taken (and if two or more processes are woken together, at least one of them will need to wait unless there are available CPU cores to schedule them to).