I was able to follow the tutorial, until I got to this version of PIT handler;
Code: Select all
uint64_t time_slice_remaining = 0;
uint64_t time_since_boot = 0;
uint64_t time_between_ticks = 1000000; // 1000 Hz = 1 ms between ticks = 1000000 nanoseconds between ticks
void PIT_IRQ_handler(void) {
thread_control_block * next_task;
thread_control_block * this_task;
lock_stuff();
time_since_boot += time_between_ticks;
// Move everything from the sleeping task list into a temporary variable and make the sleeping task list empty
next_task = sleeping_task_list;
sleeping_task_list = NULL;
// For each task, wake it up or put it back on the sleeping task list
while(next_task != NULL) {
this_task = next_task;
next_task = this_task->next;
if(this_task->sleep_expiry <= time_since_boot) {
// Task needs to be woken up
unblock_task(task);
} else {
// Task needs to be put back on the sleeping task list
task->next = sleeping_task_list;
sleeping_task_list = task;
}
}
// Handle "end of time slice" preemption
if(time_slice_remaining != 0) {
// There is a time slice length
if(time_slice_remaining <= time_between_ticks) {
schedule();
} else {
time_slice_remaining -= time_between_ticks;
}
}
// Done, unlock the scheduler (and do any postponed task switches!)
unlock_stuff();
}
This function is called PIT_IRQ_handler, so I suppose it is called from an interrupt gate assembly wrapper. So, at the beginning of function interrupts are disabled by CPU. Also, PIC is also disabled waiting for "End Of Interrupt" command. Since EOI is not seen here, I will also assume it is sent by assembly wrapper after PIT_IRQ_handler is returned.
First thing this function does is to call lock_stuff() function. This function disabled interrupts and postpones task switching. It is implemented as below;
Code: Select all
void lock_stuff(void) {
#ifndef SMP
CLI();
IRQ_disable_counter++;
postpone_task_switches_counter++;
#endif
}
Next, function unblocks any sleeping threads. This part has nothing to do with my question, so I skip to next part.
In the next part, if current thread used up all its timeslice, schedule function is called. This is the version of schedule function that is supposed to be called at that point in tutorial.
Code: Select all
void schedule(void) {
thread_control_block * task;
if(postpone_task_switches_counter != 0) {
task_switches_postponed_flag = 1;
return;
}
if( first_ready_to_run_task != NULL) {
task = first_ready_to_run_task;
first_ready_to_run_task = task->next;
switch_to_task(task);
} else if( current_task_TCB->state == RUNNING) {
// Do nothing, currently running task can keep running
} else {
// Currently running task blocked and there's no other tasks
task = current_task_TCB; // "task" is the task that we're borrowing while idle
current_task_TCB = NULL; // Make sure other code knows we're not actually running the task we borrowed
uint64_t idle_start_time = get_time_since_boot(); // For future power management support
// Do nothing while waiting for a task to unblock and become "ready to run". The only thing that is going to update
// first_ready_to_run_task is going to be from a timer IRQ (with a single processor anyway), but interrupts are disabled.
// The timer must be allowed to fire, but do not allow any task changes to take place. The task_switches_postponed_flag
// must remain set to force the timer to return to this loop.
do {
STI(); // enable interrupts to allow the timer to fire
HLT(); // halt and wait for the timer to fire
CLI(); // disable interrupts again to see if there is something to do
} while( (first_ready_to_run_task == NULL);
// Stop borrowing the task
current_task_TCB = task;
// Switch to the task that unblocked (unless the task that unblocked happens to be the task we borrowed)
task = first_ready_to_run_task;
first_ready_to_run_task = task->next;
if(task != current_task_TCB) {
switch_to_task(task);
}
}
}
Code: Select all
void unlock_stuff(void) {
#ifndef SMP
postpone_task_switches_counter--;
if(postpone_task_switches_counter == 0) {
if(task_switches_postponed_flag != 0) {
task_switches_postponed_flag = 0;
schedule();
}
}
IRQ_disable_counter--;
if(IRQ_disable_counter == 0) {
STI();
}
#endif
}
switch_task() function will return from another thread. However, we are yet to return from PIT_IRQ_handler. If next task we are switching to was called from PIT_IRQ_handler, things might work out fine. But if it is not, we won't be able to send EOI to PIC, so we can't preempt no more. It is possible for threads to block themselves to sleep for some time, so it is a real possibility.
Another problem I am having is this part in unblock_stuff() function;
Code: Select all
IRQ_disable_counter--;
if(IRQ_disable_counter == 0) {
STI();
}
Looking for your input,
Best regards