Page 1 of 3

Howto keep the processor idle?

Posted: Thu Jul 21, 2016 10:01 am
by mohammedisam
Hi there
I have a multitasking kernel. I implemented an idle task to be run whenever there is no other runnable task. The idle task simply goes into an infinite loop and does nothing. The problem is that this vicious cycle of JMP's keeps the processor busy 100% of the time. I tried using the pause instruction in place of the infinite loop, but the result is the same. I can't disable interrupts as I am checking the presence of runnable tasks in the timer interrupt handler. How can I make the processor truly idle?
Thanks in advance :D

Re: Howto keep the processor idle?

Posted: Thu Jul 21, 2016 10:07 am
by onlyonemac
You're supposed to be able to use hlt to halt the processor until the next interrupt occurs. If that's still using all (or nearly all) of your CPU then I assume that you're doing something wrong in your task scheduler (for example, you might be getting timer interrupts too frequently and thus keeping the CPU busy even when it's supposed to be idle).

Re: Howto keep the processor idle?

Posted: Thu Jul 21, 2016 11:02 am
by mohammedisam
okay. I will try putting HLT instead of my current idle loop and see the result.
thanks!

Re: Howto keep the processor idle?

Posted: Thu Jul 21, 2016 11:21 am
by Boris
Use something like this:

Code: Select all

while(true){hlt(); yield();}
That way if an interrupt handler wakes up a task, your idle task will be replaced with this task.

Re: Howto keep the processor idle?

Posted: Thu Jul 21, 2016 2:57 pm
by onlyonemac
Boris wrote:Use something like this:

Code: Select all

while(true){hlt(); yield();}
That way if an interrupt handler wakes up a task, your idle task will be replaced with this task.
Except that if his scheduler is pre-emptive then he just needs to hlt every time there are no active tasks. It would be easier to do this:

Code: Select all

void switch_to_next_task()
{
    if (task_waiting())
    {
        switch_to_waiting_task();
    }
    else
    {
        cleanup_and_hlt();
    }
}
(i.e. putting the hlt in the scheduler itself)

If an interrupt other than the timer interrupt wakes a task (such as a keypress, disk I/O, etc.) the interrupt will start the CPU and your interrupt handler can then schedule the task and call switch_to_next_task, which will switch to the task that has been woken.

Re: Howto keep the processor idle?

Posted: Fri Jul 22, 2016 1:16 am
by mohammedisam
I updated my idle task as such:

Code: Select all

void idle_task_func()
{
loop:
    __asm__ __volatile__("sti\nhlt":::);
    goto loop;
}
It looks like the CPU is still being busy. I think my timer handler is the culprit and it needs to be more lightweight. Here is the handler:

Code: Select all

void _timer_handler(regs_t r)
{
  __asm__ __volatile__("cli":::);
  _ticks++;
  /* we are using 100Hz frequency, so update seconds every 100 ticks */
  if((_ticks%100) == 0) { _secs++; }
  
  task_t *current = get_current_task();
  /* called before tasking is init-ed */
  if(!current) goto finish;
  
  if(current == idle_task && get_next_runnable() == idle_task) goto finish;
  
  /* current task is sleeping or blocked? Schedule */
  if(current->state != TASK_RUNNING) { scheduler(&r); goto finish; }
  
  if(current->time_left) current->time_left--;
  else { scheduler(&r); goto finish; }
  
finish:
  pic_send_eoi(0);
}
Any suggestions?

Cheers

Re: Howto keep the processor idle?

Posted: Fri Jul 22, 2016 5:37 am
by onlyonemac
mohammedisam wrote:

Code: Select all

if((_ticks%100) == 0) { _secs++; }
I believe modulus to be a somewhat slow operation and you could replace instance where _secs is used with a call to get_secs(), which returns _ticks / 100.
mohammedisam wrote:

Code: Select all

get_current_task();
mohammedisam wrote:

Code: Select all

get_next_runnable()
How complex are these functions? Bear in mind that the former especially is being called on every timer tick.
mohammedisam wrote:

Code: Select all

if(!current) goto finish;
If I'm understanding your code correctly, that line is to prevent a crash if your scheduler isn't initialised yet. In that case, remove the line and only enable the timer once your scheduler has been initialised, otherwise you're adding an extra conditional test on every timer tick.
mohammedisam wrote:

Code: Select all

if(current == idle_task && get_next_runnable() == idle_task) goto finish;
Sorry but I don't understand this line. Is this supposed to call the idle task when there's nothing to schedule? (Because it doesn't.)
mohammedisam wrote:

Code: Select all

if(current->time_left) current->time_left--;
This line can only mean that you're scheduling each task for more than one tick. What is current->time_left initialised to? Because that's how many ticks you're going to process before another task gets scheduled, and if that's too high for your idle task then your idle task isn't going to keep the CPU very idle.

Re: Howto keep the processor idle?

Posted: Fri Jul 22, 2016 7:32 am
by gerryg400
mohammedisam wrote:It looks like the CPU is still being busy
How do you know that it's busy?

Re: Howto keep the processor idle?

Posted: Fri Jul 22, 2016 7:58 am
by onlyonemac
gerryg400 wrote:
mohammedisam wrote:It looks like the CPU is still being busy
How do you know that it's busy?
To be honest I've been wondering that all along. Oh, and bear in mind that if you're focussing on CPU clock speed or something you'll probably have to perform some ACPI initialisation/configuration before CPU speed throttling will take effect.

Re: Howto keep the processor idle?

Posted: Fri Jul 22, 2016 12:59 pm
by mohammedisam
onlyonemac wrote: I believe modulus to be a somewhat slow operation and you could replace instance where _secs is used with a call to get_secs(), which returns _ticks / 100.
It's worth a try.

The code for get_current_task() is one line, returning a pointer to the current task's struct:

Code: Select all

task_t *get_current_task()
{
  return _task;
}
It is merely a convenience function. The code for get_next_runnable() is:

Code: Select all

task_t *get_next_runnable()
{
    task_t *next = ready_queue;
    /* Running queue is empty? run idle task */
    if(!next) next = idle_task;
    return next;
}
where read_queue is a linked list containing the ready-to-run processes.
onlyonemac wrote:

Code: Select all

if(!current) goto finish;
If I'm understanding your code correctly, that line is to prevent a crash if your scheduler isn't initialised yet. In that case, remove the line and only enable the timer once your scheduler has been initialised.
Hmmm. Sounds quite reasonable. I guess I actually don't need the timer before tasking is init'ed.

Code: Select all

if(current == idle_task && get_next_runnable() == idle_task) goto finish;
This was a desperate move, I was trying to check if there is no runnable task other than idle, so as to bypass the rest of the handler function and head out. It turned out it added nothing and still the processor is busy.
onlyonemac wrote: What is current->time_left initialised to? Because that's how many ticks you're going to process before another task gets scheduled, and if that's too high for your idle task then your idle task isn't going to keep the CPU very idle.
Here is how I initialize timeslices for new processes (in order of timer ticks):

Code: Select all

void reset_task_timeslice(task_t *task)
{
  switch(task->priority)
  {
    case PRIO_NORMAL: task->time_left = 20; break;
    case PRIO_LO:     task->time_left = 10; break;
    case PRIO_HI:     task->time_left = 50; break;
    case PRIO_IDLE:   task->time_left =  1; break;
    case PRIO_ZOMBIE: task->time_left =  0; break;
    default:          task->time_left = 20; break;
  }
}
The timeslice for the idle process is only 1, this shouldn't keep the processor busy, right?. But on the other hand, this means the scheduler will be called on each and every timer tick (if the idle task is the running task). This will add to the overhead of the timer handler and compound my problem further, right?

Re: Howto keep the processor idle?

Posted: Fri Jul 22, 2016 1:03 pm
by mohammedisam
gerryg400 wrote:
mohammedisam wrote:It looks like the CPU is still being busy
How do you know that it's busy?
I am testing my kernel using Bochs under Fedora Linux. The GNOME System Monitor shows that at any given time (when my kernel is active) one of my quad processors is running at 100%. When I pause Bochs, the activity returns to its baseline (3-4%). If it runs for a while, all my other programs would considerably slow down, so I have to test my kernel after I close other windows.

Re: Howto keep the processor idle?

Posted: Fri Jul 22, 2016 1:40 pm
by onlyonemac
mohammedisam wrote:I am testing my kernel using Bochs under Fedora Linux. The GNOME System Monitor shows that at any given time (when my kernel is active) one of my quad processors is running at 100%.
Most emulators, especially Bochs, will use all available CPU power even when the emulated CPU is supposed to be idle. I'm afraid I can't suggest a better way of measuring CPU usage as I'm not too familiar with it myself.

Re: Howto keep the processor idle?

Posted: Fri Jul 22, 2016 1:43 pm
by onlyonemac
mohammedisam wrote:The code for get_current_task() is one line, returning a pointer to the current task's struct:

Code: Select all

task_t *get_current_task()
{
  return _task;
}
It is merely a convenience function. The code for get_next_runnable() is:

Code: Select all

task_t *get_next_runnable()
{
    task_t *next = ready_queue;
    /* Running queue is empty? run idle task */
    if(!next) next = idle_task;
    return next;
}
where read_queue is a linked list containing the ready-to-run processes.
You should probably make those functions inline (instead of declaring "task_t *get_next_runnable()", declare "inline task_t *get_next_runnable()"). You might even want to think about removing the first one (although keeping it as a separate function does make the code easier to understand).
mohammedisam wrote:The timeslice for the idle process is only 1, this shouldn't keep the processor busy, right?. But on the other hand, this means the scheduler will be called on each and every timer tick (if the idle task is the running task). This will add to the overhead of the timer handler and compound my problem further, right?
You probably want to keep that timeslice as short as possible, otherwise the idle task will keep running even when something else is ready to run. However, you're still getting timer interrupts 100 times every second no matter which way you're looking at it. When I first commented on that line, I didn't realise that your scheduler had priorities, sorry.

Re: Howto keep the processor idle?

Posted: Fri Jul 22, 2016 2:29 pm
by Techel
Sleeping ~10ms is pretty much. Declaring stuff as inline etc is premature optimization in my opinion, the code is just a few calls here and there. Did you enable realtime sync in bochs?

Re: Howto keep the processor idle?

Posted: Fri Jul 22, 2016 2:49 pm
by mohammedisam
onlyonemac wrote:Most emulators, especially Bochs, will use all available CPU power even when the emulated CPU is supposed to be idle.
Looks like I will have to live with it. I cannot risk testing it on physical device yet.