+ Reply to Thread
Page 1 of 2 12 LastLast
Results 1 to 10 of 13

Thread: Hello I'm a task switcher

  1. #1
    That furred thing Black Phoenix's Avatar
    Join Date
    Feb 2007
    Location
    Kyiv, Ukraine
    Posts
    3,565

    Default Hello I'm a task switcher

    Code:
    //This is a complete task switcher for ZCPU, based on the WireOS5 task switcher
    //It uses internal ZCPU timer to call thread switch procedure, which changes
    //current thread running on the ZCPU. This allows several programs to run at
    //once on a single CPU.
    //
    //Features:
    // Supports single-CPU systems only right now
    // Every thread has own stack (with variable size)
    // Threads can have different timesplices (higher timesplice -> more priority)
    // Threads can GIVE BACK some of the timesplice (using "nmiint 32")
    // Own set of registers for every thread (NOT DEPENDANT ON OTHER THREADS)
    // You can pause thread by setting timesplice to 0
    // Works FAST, damn fast. Not much slower than hardware multithreading
    // Supports critical sections, but needs some work
    //
    //TODO:
    // Error handling (threads CAN crash, right now it'll mess up everything)
    // More thread API (fork, delete, suspend, pause, etc)
    // Handle the case when ALL threads are gone (NumThreads == 0)
    // Various bollocks associated with memory and stuff (its all possible)
    // Multi-CPU support (requires shared RAM, a lot of shared RAM)
    // You need to check state of IF (interrupt flag), because threads may already
    //  be created when IF = 0 (and STI in the end of Thread_Create could cause
    //  erratic behaviour)
    // Setting stack size properly in the Thread_Create (it might fail if you create it with thread with small stack, even though that stack isn't used up)
    
    //Go and initialize the threads
    jmp Main;
    
    //== Thread parameters =========================================================
    //These arrays store various parameters about all the threads running
    ThreadStackPointer: alloc 16;
    ThreadStackOffset: alloc 16;
    ThreadStackSize: alloc 16;
    ThreadTimeSlice: alloc 16;
    alloc CurrentThread; //Index of thread that currently executes
    alloc NumThreads; //Total number of threads
    
    
    //==============================================================================
    // Interrupt table
    InterruptTable: alloc 512;
    
    
    
    //==============================================================================
    // Thread switcher
    ContextSwitch:
      //0. Disable interrupts, so we dont get thread switching while we are thread switching
      cli;
      mov DS,0;              //Set this so we can access thread list and stuff stuff quickly
      mov LS,#CurrentThread; //DS/LS would be reset by NMIRET anyway (it pops/pushes all registers)
    
      //Purpose:
      //DS = 0: we set it to 0 so we can access all those variables, even if thread set DS to something else
      //LS = #CurrentThread: ZCPU computes real address of memory value like this:
      //                     Address = Segment + Address
      //                     We can cheat fast array access like this:
      //                     Address = #CurrentThread + ThreadStackPointer
    
      //1. Store old stack pointer (because it might have been changed by the thread)
      mov LS:#ThreadStackPointer,ESP;
      
      //2. Move on to next thread
      inc #CurrentThread;  mod #CurrentThread,#NumThreads;
      mov LS,#CurrentThread; //Don't forget to update our fast array access cheat...
    
      //3. Perform context switch
      mov SS,LS:#ThreadStackOffset; //Stack segment
      mov ESP,LS:#ThreadStackPointer; //Stack pointer
      cpuset 9,LS:#ThreadStackSize; //Stack size
      cpuset 65,LS:#ThreadTimeSlice; //Time slice
    
      //CPUSET sets some internal registers which don't have names:
      //9  is ESZ (stack size)
      //65 is TimerRate (how fast timer fires)
      
      //4. Reset interrupt timer (gives proper amount of instructions to every thread)
      cpuget EAX,29; //Read instruction counter
      add EAX,4;     //Account for 4 instructions that follow (add,cpuset,sti,nmiret)
      cpuset 66,EAX; //Write last timer fire time
    
      //If you remove "add EAX,4" it will give threads 4 instructions less than it should
      //Alternatively you should know that changing timer mode also resets
      //last fire time (but changing fire rate does not)
      
      //5. Restore interrupts
      sti;
    nmiret;
    //=============================================================================
    
    
    //=============================================================================
    // Create new thread in same address space as host thread
    //
    // EAX: ptr to thread
    // EBX: ptr to stack
    // ECX: stack size
    // EDX: time slice
    //
    alloc IFState;
    Thread_Create:
      //Store current interrupt flag state, so we dont accidently enable
      //interrupts when not wanted (they MUST be turned off for this function, but
      //they dont have to be turned off outside of it)
      cpuget #IFState,32;
    
      //Clear interrupt flag state/enter critical section
      cli;
    
      //Store current stack state
      push ESI; push EDI; push EBP;
      mov ESI,SS; mov EDI,ESP;
    
      //Initialize thread state
      mov ESP,ECX; sub ESP,1; //Stack pointer on top of the stack (ESP = Size - 1)
      mov SS,EBX;
    
      //Push all of the registers to stack
      //Thread state is stored in two ways:
      // If thread is running:
      //  Thread state is stored in CPU registers
      // If thread is standby:
      //  Thread state is stored on stack (as NMI return information)
      //
      //So we just push all registers as zeroes here
      push 0;//LS
      push 0;//KS
      push 0;//ES
      push 0;//GS
      push 0;//FS
      push 0;//DS
      push 0;//SS <-- Dont worry about stack ones, they are force-changed by task switcher
      push 0;//CS
    
      push 0;//EDI
      push 0;//ESI
      push 0;//ESP
      push 0;//EBP
      push 0;//EDX
      push 0;//ECX
      push 0;//EBX
      push 0;//EAX
    
      push 0;   //CMPR
      push EAX; //IP
    
      //These two are not used by NMI interrupt, but instead they are for INT return
      push 0;
      push 0;
      
      //Setup thread list entry
      mov EBP,ThreadStackPointer; add EBP,#NumThreads;
      mov #EBP,ESP;
    
      mov EBP,ThreadStackOffset; add EBP,#NumThreads;
      mov #EBP,SS;
    
      mov EBP,ThreadStackSize; add EBP,#NumThreads;
      mov #EBP,ECX;
    
      mov EBP,ThreadTimeSlice; add EBP,#NumThreads;
      mov #EBP,EDX;
    
      inc #NumThreads;
    
      //Restore caller thread state
      mov SS,ESI; mov ESP,EDI;
      pop EBP; pop EDI; pop ESI;
    
      cpuset 32,#IFState; //Critical section end
    ret
    
    
    
    Main:
      cli;
    
      //Setup interrupt handler
      lidtr InterruptTable;
      mov EBP,InterruptTable;
      add EBP,128; //Interrupt 32
      mov #EBP,ContextSwitch;
      inc EBP;
      mov #EBP,0; //CS
      inc EBP;
      mov #EBP,0; //reserved
      inc EBP;
      mov #EBP,96; //NMI + enabled flags
    
      //Enable extended mode
      stef;
    
      //Initialize thread list
      mov #CurrentThread,0;
      mov #NumThreads,0;
    
      //Create threads
      mov EAX,thread1;
      mov EBX,thread1_stack;
      mov ECX,8192;
      mov EDX,16;
      call Thread_Create;
    
      mov EAX,thread2;
      mov EBX,thread2_stack;
      mov ECX,8192;
      mov EDX,16;
      call Thread_Create;
    
      mov EAX,thread3;
      mov EBX,thread3_stack;
      mov ECX,4096;
      mov EDX,16;
      call Thread_Create;
      
      mov EAX,thread4;
      mov EBX,thread4_stack;
      mov ECX,2048;
      mov EDX,8; //How about a small timesplice?
      call Thread_Create;
    
      //Initialize hardware timer (WAY #1 - TIMER register way)
    //  cpuset 65,0.10; //fire 10 times per second
    //  cpuset 64,1;
    
      //You should NOT use way #1, and here is why:
      //TIMER register updates only every CPU tick
      //Time step of TIMER register = time step of server frames
      //That value is limited to about 50hz. Your task switching will
      //execute certain (MOST LIKELY DIFFERENT ALL THE TIME) amount of operations,
      //which depends on frequency of the CPU. It'll also probably give some threads
      //two of these timeslices, not one, in case you set interval close to timer error
      //
      //The second way, on the other hand, will switch threads independantly from CPU
      //ticks. In fact, this is the way you give threads time slices based on an
      //instruction counter.
      //
      //How timer works:
      //It fires NMI interrupt at specific rate (every X seconds, or every N instructions)
      //It's pretty much same as if you wired pulser to NMI input of the CPU
      //You can set what NMI it calls, rate, and you can control when timer
      //will fire next.
    
    
      //Initialize hardware timer (WAY #2 - TMR register way)
      cpuset 65,32; //Set some big value so timer doesn't trigger yet
      cpuset 64,2;
      //Don't worry, timer wont fire yet anyway, we dont have interrupts enabled yet
    
      //Enable interrupts and force context switch to the first thread
      //Standby threads are holding their full state on their stacks
      //We just switch our stack (original stack given us by the ZCPU) to
      //one of the thread stacks. It already holds full thread state there,
      //as if NMI interrupt was called.
      //
      //If we now call NMIRET, ZCPU will restore thread state, and begin
      //execution
      mov ESP,#ThreadStackPointer;
      mov SS,#ThreadStackOffset;
      cpuset 9,#ThreadStackSize;
      cpuset 65,#ThreadTimeSlice;
      sti;  //NOTE: interrupts may only happen TWO instructions after NMI
    nmiret; //Just look at this:
            //
            // sti;
            //      <--- interrupt >>>NEVER<<< happens here
            // mov eax,0;
            //      <--- interrupt MIGHT happen here
            // mov ebx,0;
            //      <--- might happen here too
            // cli;
            //      <--- will NEVER happen here
    
      
      
    
    //== Thread 1 =================================================================
    thread1:
      mov eax,0; //threads have own registers
      thread1_loop:
        add eax,3; //3 instructions in this loop :)
        mov port0,eax;
        jmp thread1_loop;
    
    thread1_stack: alloc 8192;
    //=============================================================================
    
    
    
    //== Thread 2 =================================================================
    thread2:
      mov port1,124812948;
      mov eax,0;
      thread2_loop:
        add eax,3;
        mov port1,eax;
        //idle; <--- DONT do this, you SHOULD NOT have "idle" in any children threads, or it'll make shit run slow yo
        jmp thread2_loop;
    
    thread2_stack: alloc 8192;
    //=============================================================================
    
    
    
    //== Thread 3 =================================================================
    thread3:
      mov eax,0;
      thread3_loop:
        add eax,3;
        mov port2,eax;
        nmiint 32; //PRETTY MUCH SAME AS "IDLE" OPCODE FOR MULTITHREADED SYSTEMS
                   //GIVES BACK THREAD TIME SPLICE TO OS
                   //We just call task switching interrupt before the timer does
        jmp thread3_loop;
    
    thread3_stack: alloc 4096;
    //=============================================================================
    
    
    
    //== Thread 4 =================================================================
    thread4:
      mov eax,0;
      thread4_loop:
        add eax,3;
        mov port3,eax;
        jmp thread4_loop;
    
    thread4_stack: alloc 4096;
    //=============================================================================
    
    
    
    
    
    
    
    
    
    //=============================================================================
    // What will you see in the demo output (to run this you only need a data port)
    //
    // Thread 1 and thread 2 run pretty much at same rate
    // Thread 3 will run very slowly, because it gives back most of its timesplice
    // Thread 4 will run approx twice slower than threads 1 and 2 (it has smaller timesplice)
    (I've decided to create a set of "Hello I'm ...." tutorials. They are pretty much just a thread with commented code in it)
    Last edited by Black Phoenix; 06-22-2010 at 03:00 PM.
    I'm a wire-crazy person with a tail.

    Take a daily journey into my brain

    D2K5

  2. #2
    Wirererer Xandaros's Avatar
    Join Date
    Apr 2007
    Location
    Bremerhaven, Germany
    Posts
    364

    Default Re: Hello I'm switching tasks

    This looks useful.
    But I have a few questions, though:
    When do the threads actually get switched? nmiint 128?
    When is the first thread actually run? The others get run through switching, but I don't get where you start the first thread.

    Edit: Did I just look half a hour on this code? :O
    Edit2: I guess the time went up to a few hours... lol
    Last edited by Xandaros; 06-22-2010 at 04:17 PM.
    I love very poor avatars

    Quote Originally Posted by Dav1d View Post
    It's too big to copy all in one.

  3. #3
    That furred thing Black Phoenix's Avatar
    Join Date
    Feb 2007
    Location
    Kyiv, Ukraine
    Posts
    3,565

    Default Re: Hello I'm switching tasks

    Quote Originally Posted by Xandaros View Post
    This looks useful.
    But I have a few questions, though:
    When do the threads actually get switched? nmiint 128?
    When is the first thread actually run? The others get run through switching, but I don't get where you start the first thread.

    Edit: Did I just look half a hour on this code? :O
    Threads are switched in "ContextSwitch" routine - it's a NMI interrupt which is called several times per second by internal CPU timer.
    The first thread is ran in the end of "Main" routine (it simply restores thread state, look in the code for detailed description)
    I'm a wire-crazy person with a tail.

    Take a daily journey into my brain

    D2K5

  4. #4
    Ursus maritimus Drunkie's Avatar
    Join Date
    Feb 2009
    Location
    Canada
    Posts
    5,657
    Blog Entries
    1

    Default Re: Hello I'm switching tasks

    Awesome

  5. #5
    GUN
    GUN is offline
    Wire Sofaking GUN's Avatar
    Join Date
    Jan 2008
    Location
    California
    Posts
    668

    Default Re: Hello I'm a task switcher

    see.. ive always wanted and a few times tried to do this, but always fail'd, probably cause i don't know asm as good as you, but meh.. my way of trying to do it was a bit different, i tried to copy the "cell broadband" processor did things, one main cpu told 4 others what to program and then once they did, they sent it back to that main processor to do whatever needed to be done with it, but that also didn't work out :/ was a good idea tho.

    Maybe we're all gonna die, but we're gonna die in *really cool ways*.





  6. #6
    Wirererer Xandaros's Avatar
    Join Date
    Apr 2007
    Location
    Bremerhaven, Germany
    Posts
    364

    Default Re: Hello I'm a task switcher

    That would have been slow as hell.
    And the way he implemented it was not possible until today. (Well, yesterday in my timezone)
    He uses cpuset for 64,65 and iirc 66. These 3 registers were added today(yesterday) and are still undocumented.

    btw BP... would you mind to tell what these exactly do? I know it is the timer, but... ...

    Also, the Documentation ends with 52... What are 53-63?
    I love very poor avatars

    Quote Originally Posted by Dav1d View Post
    It's too big to copy all in one.

  7. #7
    Wire Sofaking N00bDud3's Avatar
    Join Date
    Jul 2009
    Location
    Error: Unknown Location
    Posts
    1,252

    Default Re: Hello I'm a task switcher

    This is pretty neat, though I barely understand ASM. You need to work on your WireOS stuff some more, because when I started with wiremod, that was when that stuff died out... It was one of the reasons I even started with wiremod.



  8. #8
    Wirererer CCFreak2K's Avatar
    Join Date
    Apr 2007
    Posts
    111

    Default Re: Hello I'm a task switcher

    64 is "TimerMode", which causes the timer to fire:
    * 0 = never (disabled)
    * 1 = every X seconds
    * 2 = every X ticks

    65 is "TimerRate", which is the time between interrupt calls, interpreted as given above.

    66 is "TimerPrevTime", which I guess stores the previous rate or perhaps the actual time the last thread ran.

    67 is "TimerAddress", the vector that the timer interrupt will actually execute at.

  9. #9
    Success: An illusion turck3's Avatar
    Join Date
    Jun 2007
    Location
    USA
    Posts
    1,744
    Blog Entries
    4

    Default Re: Hello I'm a task switcher

    Well the first step, of course BP, is admitting it. Props to you and jolly good work.

    On another note, nice tutorial.

  10. #10
    Ursus maritimus Drunkie's Avatar
    Join Date
    Feb 2009
    Location
    Canada
    Posts
    5,657
    Blog Entries
    1

    Default Re: Hello I'm a task switcher

    Quote Originally Posted by Black Phoenix View Post
    I've decided to create a set of "Hello I'm ...." tutorials. They are pretty much just a thread with commented code in it
    Need a "Hello I'm a 3D Terrain/Landscape" tutorial. ^_^

+ Reply to Thread
Page 1 of 2 12 LastLast

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
proceed-collector
proceed-collector
proceed-collector
proceed-collector
linguistic-parrots
linguistic-parrots
linguistic-parrots
linguistic-parrots