How to code pause function ?

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
extremecoder
Member
Member
Posts: 59
Joined: Tue May 23, 2006 11:00 pm

How to code pause function ?

Post by extremecoder »

I want to write a function like pause() call, which will make the processor to wait for some mili seconds. Currently, I am coding some welcome screens for my Kernel, which will display 3 screens on regular intervals. For that I want each screen to move on to the next screen in 3-4 seconds. How to implement this pause functionality. I want to implement this without loops.
m
Member
Member
Posts: 67
Joined: Sat Nov 25, 2006 6:33 am
Location: PRC

Post by m »

Hi.

A generic solution for timing is to use 8253/8254 PIT(Programmable Interval Timer).8254 is an enhanced one and generally backward compatible,but I think 8253 is enough.Here's an overview on how to do this:

-Mask IRQ0
-Program 8253 for initialization(e.g. mode,frequency etc.,refer to some relavant documents)
-Set up a handler for interval(counting down).Remember to add

Code: Select all

mov al,0x20
out 0x20,al
at the end of the handler to write an EOI
-Initialize an interrupt vector(an item of the interrupt vector table in real mode)/interrupt gate(an item of the IDT in protected mode) pointing to the handler
-Unmask IRQ0 when ready
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: How to code pause function ?

Post by Brendan »

Hi,
extremecoder wrote:I want to write a function like pause() call, which will make the processor to wait for some mili seconds. Currently, I am coding some welcome screens for my Kernel, which will display 3 screens on regular intervals. For that I want each screen to move on to the next screen in 3-4 seconds. How to implement this pause functionality. I want to implement this without loops.
In general, you'd want a sorted queue and a timer. When a task calls "pause()" you work out what time the task needs to wake up, insert an entry for the task into the queue and then tell the scheduler the task is sleeping (so the scheduler doesn't give it any CPU time). The timer IRQ keeps track of the time and wakes up any tasks that are on the queue when their delays expire.

This is only as accurate as your timer IRQ. For example, if your timer IRQ occurs every 100 ms and a task wants to pause/sleep for 250 ms, then you'd need to wake it up after 200 ms or 300 ms because there is no timer IRQ closer to the time it wants.

For very short time delays (e.g. device drivers) it might be good to do polling instead. For example, use the count from RDTSC, the local APIC timer count or the PIT timer count and do a "while(count < expiry) { }". This can be a lot more accurate but wastes CPU cycles, so it's good for short accurate delays and bad for longer delays.

These methods can be combined. For the example above (a task wants to sleep for 250 ms), you could use the first method for the first 200 ms and then the second method for the remaining 50 ms.

There is an alternative here. Rather than using a periodic timer (e.g. an IRQ that occurs at a fixed frequency) you can use a dynamically programmed "one-shot" timer. The idea is similar to the first method (with a sorted queue), but you program the timer's delay to match the soonest wake-up time. For example, if one task wants to sleep for 234 ms and another task wants to sleep for 345 ms, you'd program the timer to generate an IRQ after 234 ms, wake up the first task and then program the timer to generate an IRQ after another 111 ms to wake up the next task on the queue. This is much more accurate than the first method and can have less overhead (no unnecessary IRQs).

The problem is that it makes it harder to keep track of the "wall clock" time. To solve this you could use the CMOS/RTC IRQ8 to keep track of the wall clock time (with a periodic IRQ) and use the PIT (with a dynamic "one-shot" IRQ) for sleeping threads. It can be more complicated though, because one of these IRQs may be needed by the scheduler too.

Also, it's a good idea to write the code so you can use different timers instead. For example, if the computer has local APIC/s and/or HPET it'd be good to use them to improve the accuracy of your timing. This is one of the things the HAL does in Windows (i.e. choose which timers to use for what purpose, and provide the necessary abstractions around that hardware).

Lastly, for some situations a task needs to do something like "if X doesn't happen in N ms, wake me up". For example, consider your welcome screens - it'd be good if the user could press "escape" to cancel the welcome screens. For event-based systems this easy to do by providing a seperate function, where the task sleeps until an event is received or until the time delay expires. Alternatively the timing code could send a "time delay expired" event to the task and the task could sleep until an event is received.

I haven't found a good way to do this on systems that aren't event-based (e.g. POSIX) because there's too many different things the task could be waiting for (in this case I normally do polling with small delays). For example:

Code: Select all

    for(;;) {
        activityFlag = 0;

        if( checkNetworkActivity() ) {
            handleNetwork();
            activityFlag = 1;
        }
        if( checkAsyncFileIO() ) {
            handleAsyncFileIO();
            activityFlag = 1;
        }
        if( checkForKeypress() ) {
            handleKeypress();
            activityFlag = 1;
        }
        if( checkOtherTaskCompletedSomething() ) {
            handleOtherTask();
            activityFlag = 1;
        }
        if( currentTime() >= expiryTime ) {
            doTimeOut();
            activityFlag = 1;
        }
        if( activityFlag == 0) sleep(1);
    }
For event based systems, you'd just do something like:

Code: Select all

    for(;;) {
        getMessageWithTimeout(expiryTime) {
            switch (event.reason) {
                case NETWORK:
                    handleNetwork();
                    break;
                case ASYNCFILEIO:
                    handleAsyncFileIO();
                    break;
                case KEYPRESS:
                    handleKeypress();
                    break;
                case OTHERTASK:
                    handleOtherTask();
                    break;
                case EXPIRY:
                    doTimeOut();
                    break;
            }
        }
    }
This is better than the polling above because it can react to things faster (i.e. without waiting for the "sleep(1);" to complete) and has less overhead because it's not getting CPU time and checking things when nothing happened (i.e. causing pointless task switches).


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Post Reply