Thread overview
Preemptive multitasker for DM - mtask2.dm.zip
Sep 21, 2003
hee
Sep 22, 2003
Walter
Sep 22, 2003
KarL
Sep 22, 2003
Hugo Etchegoyen
Sep 22, 2003
chris elliott
Sep 22, 2003
KarL
Sep 23, 2003
KarL
Sep 23, 2003
roland
Re: Preemptive multitasker for DM
Sep 25, 2003
Hugo Etchegoyen
Sep 25, 2003
Jan Knepper
September 21, 2003
This multitasking library is a port of something I originally wrote for real-mode DOS with Borland C. Porting it to DM allowed me to break the 640K barrier and it was an easy and pleasant job due to the excellent compiler´s features. It was originally written for educational purposes, since I use it for teaching operating systems and IPC primitives. However, I think now it is perfectly usable for professional applications.

This software is public domain. Please don´t hesitate to send me any feedback, whether criticism, bug reports or improvements.


Hugo Etchegoyen
Buenos Aires, Argentina
hee@fibertel.com.ar
hetchegoyen@hasar.com
September 22, 2003
<hee@fibertel.com.ar> wrote in message news:bkj3vu$ht5$1@digitaldaemon.com...
> This multitasking library is a port of something I originally wrote for real-mode DOS with Borland C. Porting it to DM allowed me to break the
640K
> barrier and it was an easy and pleasant job due to the excellent
compiler´s
> features. It was originally written for educational purposes, since I use
it for
> teaching operating systems and IPC primitives. However, I think now it is perfectly usable for professional applications.

Great! The documentation is here: www.digitalmars.com/user/mtask.html


September 22, 2003
Looks nice.  Certainly better than a couple of method I have seen before (e.g. J.English, S. Kofoed etc)

Since you are using timer int, it is relatively easier to change the counter value to achieve something like a task switch of say 1000Hz.  In this way, you can also have  true milliseconds sleep function.

To set clock rate (real mode) - originally published in Byte.com:

static void setclkrate(unsigned short timerval)
{
    __asm   mov     al, 0x36
    __asm   out     0x43, al
    __asm   mov     ax, timerval
    __asm   out     0x40, al
    __asm   mov     al, ah
    __asm   out     0x40, al
}

Then have a couple of counters to track original 55ms

static unsigned short clkdiv, clkdivh, clkdivl, clkmod;

static void __interrupt clkint()
{

    // Call the clock interrrupt here;;;;

    __asm   mov     ax, clkdivl
    __asm   add     clkmod, ax
    __asm   mov     ax, clkdivh
    __asm   adc     ax, 0
    __asm   jnz     cint8
    __asm   mov     al, 0x20
    __asm   out     020H, al
    __asm   jmp     cint7

cint8:
    (*oldvec)();        // Old clock interrupt

cint7:
    __asm   mov     ax, clkdiv
    __asm   cmp     ax, clkdivl
    __asm   je      exit
    __asm   push    ax
    __asm   call    setclkrate
    __asm   pop     ax
    __asm   mov     clkdivl, ax
    __asm   cmp     ax, 1
    __asm   mov     clkdivh, 0
    __asm   adc     clkdivh, 0
exit: ;
}

<hee@fibertel.com.ar> wrote in message news:bkj3vu$ht5$1@digitaldaemon.com...
> This multitasking library is a port of something I originally wrote for real-mode DOS with Borland C. Porting it to DM allowed me to break the 640K barrier and it was an easy and pleasant job due to the excellent compiler´s features. It was originally written for educational purposes, since I use it for teaching operating systems and IPC primitives. However, I think now it is perfectly usable for professional applications.
>
> This software is public domain. Please don´t hesitate to send me any feedback, whether criticism, bug reports or improvements.
>
>
> Hugo Etchegoyen
> Buenos Aires, Argentina
> hee@fibertel.com.ar
> hetchegoyen@hasar.com
>



September 22, 2003
In article <bklr7g$290s$1@digitaldaemon.com>, KarL says...
>
>Looks nice.  Certainly better than a couple of method I have seen before (e.g. J.English, S. Kofoed etc)
>
>Since you are using timer int, it is relatively easier to change the counter value to achieve something like a task switch of say 1000Hz.  In this way, you can also have  true milliseconds sleep function.
>
>To set clock rate (real mode) - originally published in Byte.com:
>
>static void setclkrate(unsigned short timerval)
>{
>    __asm   mov     al, 0x36
>    __asm   out     0x43, al
>    __asm   mov     ax, timerval
>    __asm   out     0x40, al
>    __asm   mov     al, ah
>    __asm   out     0x40, al
>}
>
>Then have a couple of counters to track original 55ms
>
>static unsigned short clkdiv, clkdivh, clkdivl, clkmod;
>
>static void __interrupt clkint()
>{
>
>    // Call the clock interrrupt here;;;;
>
>    __asm   mov     ax, clkdivl
>    __asm   add     clkmod, ax
>    __asm   mov     ax, clkdivh
>    __asm   adc     ax, 0
>    __asm   jnz     cint8
>    __asm   mov     al, 0x20
>    __asm   out     020H, al
>    __asm   jmp     cint7
>
>cint8:
>    (*oldvec)();        // Old clock interrupt
>
>cint7:
>    __asm   mov     ax, clkdiv
>    __asm   cmp     ax, clkdivl
>    __asm   je      exit
>    __asm   push    ax
>    __asm   call    setclkrate
>    __asm   pop     ax
>    __asm   mov     clkdivl, ax
>    __asm   cmp     ax, 1
>    __asm   mov     clkdivh, 0
>    __asm   adc     clkdivh, 0
>exit: ;
>}
>

You are right, Karl, there is a lot to be improved in the handling of the timer interrupt.

Certainly a 1 kHz interrupt calling the old vector once every 55 counts would be ideal, since 'timer ticks' would be the same as 'milliseconds', my only concern here is whether the overhead of 1k interrupts per second would not be too much. Probably a configurable time granularity would be best, then you could set it to the best compromise according to your needs and to your hardware.

Also trapping the real time interrupt from the application level is not possible right now, since it is trapped in the kernel. It would be nice if the application could install callbacks to be called from the timer interrupt every so many milliseconds, something like the poll feature in Unix.

I don't promise dates, but I will try to include these features. If someone does it before, please tell me.

Thank you,

Hugo.


September 22, 2003
You should look into the mmtimer of windows, which has a rate of up to 1ms , is very accurate and can call code in a dll you supply,

chris

Hugo Etchegoyen wrote:

> In article <bklr7g$290s$1@digitaldaemon.com>, KarL says...
> 
>>Looks nice.  Certainly better than a couple of method I have seen
>>before (e.g. J.English, S. Kofoed etc)
>>
>>Since you are using timer int, it is relatively easier to change the
>>counter value to achieve something like a task switch of say
>>1000Hz.  In this way, you can also have  true milliseconds sleep
>>function.
>>
>>To set clock rate (real mode) - originally published in Byte.com:
>>
>>static void setclkrate(unsigned short timerval)
>>{
>>   __asm   mov     al, 0x36
>>   __asm   out     0x43, al
>>   __asm   mov     ax, timerval
>>   __asm   out     0x40, al
>>   __asm   mov     al, ah
>>   __asm   out     0x40, al
>>}
>>
>>Then have a couple of counters to track original 55ms
>>
>>static unsigned short clkdiv, clkdivh, clkdivl, clkmod;
>>
>>static void __interrupt clkint()
>>{
>>
>>   // Call the clock interrrupt here;;;;
>>
>>   __asm   mov     ax, clkdivl
>>   __asm   add     clkmod, ax
>>   __asm   mov     ax, clkdivh
>>   __asm   adc     ax, 0
>>   __asm   jnz     cint8
>>   __asm   mov     al, 0x20
>>   __asm   out     020H, al
>>   __asm   jmp     cint7
>>
>>cint8:
>>   (*oldvec)();        // Old clock interrupt
>>
>>cint7:
>>   __asm   mov     ax, clkdiv
>>   __asm   cmp     ax, clkdivl
>>   __asm   je      exit
>>   __asm   push    ax
>>   __asm   call    setclkrate
>>   __asm   pop     ax
>>   __asm   mov     clkdivl, ax
>>   __asm   cmp     ax, 1
>>   __asm   mov     clkdivh, 0
>>   __asm   adc     clkdivh, 0
>>exit: ;
>>}
>>
> 
> 
> You are right, Karl, there is a lot to be improved in the handling of the timer
> interrupt. 
> 
> Certainly a 1 kHz interrupt calling the old vector once every 55 counts would be
> ideal, since 'timer ticks' would be the same as 'milliseconds', my only concern
> here is whether the overhead of 1k interrupts per second would not be too much.
> Probably a configurable time granularity would be best, then you could set it to
> the best compromise according to your needs and to your hardware. 
> 
> Also trapping the real time interrupt from the application level is not possible
> right now, since it is trapped in the kernel. It would be nice if the
> application could install callbacks to be called from the timer interrupt every
> so many milliseconds, something like the poll feature in Unix.
> 
> I don't promise dates, but I will try to include these features. If someone does
> it before, please tell me.
> 
> Thank you,
> 
> Hugo.
> 
> 

September 22, 2003
Sorry Chris,  we are talking about DOSX running in embedded systems.

"chris elliott" <biol75@york.ac.uk> wrote in message news:bkms4k$1r5e$1@digitaldaemon.com...
> You should look into the mmtimer of windows, which has a rate of up to 1ms , is very accurate and can call code in a dll you supply,
>
> chris
>
> Hugo Etchegoyen wrote:
>
> > In article <bklr7g$290s$1@digitaldaemon.com>, KarL says...
> >
> >>Looks nice.  Certainly better than a couple of method I have seen before (e.g. J.English, S. Kofoed etc)
> >>
> >>Since you are using timer int, it is relatively easier to change the counter value to achieve something like a task switch of say 1000Hz.  In this way, you can also have  true milliseconds sleep function.
> >>
> >>To set clock rate (real mode) - originally published in Byte.com:
> >>
> >>static void setclkrate(unsigned short timerval)
> >>{
> >>   __asm   mov     al, 0x36
> >>   __asm   out     0x43, al
> >>   __asm   mov     ax, timerval
> >>   __asm   out     0x40, al
> >>   __asm   mov     al, ah
> >>   __asm   out     0x40, al
> >>}
> >>
> >>Then have a couple of counters to track original 55ms
> >>
> >>static unsigned short clkdiv, clkdivh, clkdivl, clkmod;
> >>
> >>static void __interrupt clkint()
> >>{
> >>
> >>   // Call the clock interrrupt here;;;;
> >>
> >>   __asm   mov     ax, clkdivl
> >>   __asm   add     clkmod, ax
> >>   __asm   mov     ax, clkdivh
> >>   __asm   adc     ax, 0
> >>   __asm   jnz     cint8
> >>   __asm   mov     al, 0x20
> >>   __asm   out     020H, al
> >>   __asm   jmp     cint7
> >>
> >>cint8:
> >>   (*oldvec)();        // Old clock interrupt
> >>
> >>cint7:
> >>   __asm   mov     ax, clkdiv
> >>   __asm   cmp     ax, clkdivl
> >>   __asm   je      exit
> >>   __asm   push    ax
> >>   __asm   call    setclkrate
> >>   __asm   pop     ax
> >>   __asm   mov     clkdivl, ax
> >>   __asm   cmp     ax, 1
> >>   __asm   mov     clkdivh, 0
> >>   __asm   adc     clkdivh, 0
> >>exit: ;
> >>}
> >>
> >
> >
> > You are right, Karl, there is a lot to be improved in the handling of the timer interrupt.
> >
> > Certainly a 1 kHz interrupt calling the old vector once every 55 counts would be ideal, since 'timer ticks' would be the same as 'milliseconds', my only concern here is whether the overhead of 1k interrupts per second would not be too much. Probably a configurable time granularity would be best, then you could set it to the best compromise according to your needs and to your hardware.
> >
> > Also trapping the real time interrupt from the application level is not possible right now, since it is trapped in the kernel. It would be nice if the application could install callbacks to be called from the timer interrupt every so many milliseconds, something like the poll feature in Unix.
> >
> > I don't promise dates, but I will try to include these features. If someone does it before, please tell me.
> >
> > Thank you,
> >
> > Hugo.
> >
> >
>



September 23, 2003
Actually, I do have one.  I modified S Kofoed's original version available from Nov. 1995 DDJ to include a few features:

1) ± 1ms resolution sleep(n) function.
2) Real time callback at 1ms.
3) Actived time log.

I found the 1kHz thread switch doesn't cause much overheads.
In fact, if I increase the clock to 5kHz, I get better timing resolution
without compromising speed.  For what I need, my timing
requirements are about 2 to 3 milliseconds.  With the 1kHz
callback, I can drive DAC output or sound chip waveform controls
quite accurately.

It is not pre-emptive but with good coorporative thread design, I am able to use it to run on embedded PC-104 board beautifully. It is not DOSX yet. I might port it one day but since you have done the pre-emptive version, I might leave it as that.

I also have a couple of graphics libraries, serial comms, associated with that library.

If Walter thinks it is a good idea, I would eventually document them and put it on my web site for all.  I haven't done so is because I am too embarass I still don't write good template class. So those code still has some ugly C stuff.

Here's the header file for the portable thread:
// Thread.h//// Based on S. Kofoed, Doctor Dobb's Journal Nov. 1995//// Patched by Kar G Lim to include:////  1. 1 ms Real Time callback Task//  2. Sleep function with 1 ms resolution//  3. Active milliseconds lapsed record//// Fixed critical section bug
#ifndef THREAD_H#define THREAD_H
#include <setjmp.h>
class Thread {
friend class Queue, Semaphore;
    bool            m_bAllocated;       // used or free    unsigned        m_nBlocksize;       // block size
    void (*m_pThreadFn)(const void *);  // pointer to thread function    static void (*sm_pmstask)();        // pointer to millisecond task function
    void           *m_pFnArg;           // argument to thread function    unsigned        m_nStackSze;        // requested stack size
    volatile unsigned m_nSleepTime;    volatile unsigned m_nActiveTime;
protected:    jmp_buf         m_jmpb;             // non-local jumpbuf    Thread         *m_pNextThread;      // pointer to next control block    Thread         *m_pChain;           // next thread in ready or semaphore queue
    static void     schedule();    static void     stackalloc(Thread *, unsigned);    static void     timekeeper();
public:
    static void     sleep(unsigned N);  // relinquish executing control for next N ms    static unsigned activetime();       // get the number of ms current thread has used
    static void     yield();            // Voluntary relinquish executing control                                        // to allow other thread to execute
    static void     init(unsigned, unsigned);    // To be called once
    // Creates a new Thread with runnable function, initial argument and stacksize    static bool     create(void (*)(const void *), void *, unsigned);
    // Set real time millisecond task - use NULL to clear millisecond task    static void     setmstask(void (*)(void));};
class Queue {      // Simple Queue implementationfriend class Thread;protected:    Thread *m_pHead, *m_pTail;          // pointer to first and last element
public:    Queue() { m_pHead = NULL; }
    void append(Thread *p);
    Thread *getfirst();};
class Semaphore : private Queue       // Simple Semaphore implementation{ private:    int             m_nCount;
 public:    Semaphore() { m_nCount = 0; }
    void signal();    void wait();};
#endif  // THREAD_H

"Hugo Etchegoyen" <Hugo_member@pathlink.com> wrote in message news:bkmq3h$1ckf$1@digitaldaemon.com...

> You are right, Karl, there is a lot to be improved in the handling of the timer interrupt.
> 
> Certainly a 1 kHz interrupt calling the old vector once every 55 counts would be ideal, since 'timer ticks' would be the same as 'milliseconds', my only concern here is whether the overhead of 1k interrupts per second would not be too much. Probably a configurable time granularity would be best, then you could set it to the best compromise according to your needs and to your hardware.
> 
> Also trapping the real time interrupt from the application level is not possible right now, since it is trapped in the kernel. It would be nice if the application could install callbacks to be called from the timer interrupt every so many milliseconds, something like the poll feature in Unix.
> 
> I don't promise dates, but I will try to include these features. If someone does it before, please tell me.
> 
> Thank you,
> 
> Hugo.
> 
> 



September 23, 2003
Hi,

Nice, very nice.
I know on DOSX and pure dos mode, tick interrupt can go as fast as 40KHz with a reliability of around 2-3 micoseconds.
For that, when the tick is running that fast, the program must _never_ go 16 bit and back to 32 bit that means it must _never_ call bios:
- mouse driver must be rewritten 32 bits, or lock mouse interrupt
- keyboard driver must be rewritten 32 bits or lock keyboard interrupt,
- no disk access

I have some "raw" source to deal with the timer, int 8 and int 1ch.
Comments are in french and it may be usable only after a lot of work on it.
Just ask, I can give them and answer precise questions.

Just in case ..

roland

September 25, 2003
Hi everybody

Thanks Karl, Roland and Chris. I´ve been working on your proposals and I have a new version of the multitasker (DM 2.01). I think I improved time handling substancially by making the following changes:

- The kernel now measures times directly in milliseconds - no more ticks.

- InitMtask() now has two parameters: the timer frequency in milliseconds per tick (range 1-55), which is used to program the real time counter, and the time slice in milliseconds. If you pass 0 for these arguments the default (version 2.00) values are used (55 and 110 msecs. respectively). Upon program termination the DOS timer frecuency (55 msecs.) is restored.

- I added a GetTime() function that returns the elapsed time in milliseconds fron MTask initialization.

- I added a DelayUntil() primitive that allows precise timing of periodic tasks. I also added a test program (ptime.exe) to show the improvement.

- I have not added timer callbacks yet. Precisely timed, high-priority periodic tasks offer more or less the same functionality (except that they can be defeated by another task getting into non-preemptive mode, timer callbacks can only be defeated by disabling interrupts or IRQ 0). I will probably add them in 2.02.

- The timer interrupt is trapped by the kernel and cannot be hooked by the application, but version 2.00 did not enforce that. In 2.01, if you try to trap IRQ 0 you get a Panic().

Walter, I don´t want to clobber the newsgroup with big postings. I got the idea that I should post the zip file and the documentation in www.digitalmars.com/users and then place a link here, but I don´t know how to do that. Please advise.

Hugo.

September 25, 2003
> Walter, I don´t want to clobber the newsgroup with big postings. I got the idea that I should post the zip file and the documentation in www.digitalmars.com/users and then place a link here, but I don´t know how to do that. Please advise.

Yes, I could setup a user page for you.

Thanks!
ManiaC++
Jan Knepper