What makes the OS for QL any better, different, unique ?

A place to discuss general QL issues.
tcat
Super Gold Card
Posts: 633
Joined: Fri Jan 18, 2013 5:27 pm
Location: Prague, Czech Republic

Re: What makes the OS for QL any better, different, unique ?

Post by tcat »

just my experience, cooperative vs preemptive

cooperative

Code: Select all

while (true)
  /*event - inner loops*/
  idle: SystemTask(); // cycles back
end while
preemptive

Code: Select all

while (true)
  /*event - inner loops*/
  idle: Waitms(10); // some ms wait
end while
Cycles back to the system vs wait in order of millis [not to steal the whole cpu]
Possible?


User avatar
tofro
Font of All Knowledge
Posts: 2685
Joined: Sun Feb 13, 2011 10:53 pm
Location: SW Germany

Re: What makes the OS for QL any better, different, unique ?

Post by tofro »

Well - No.

Preemptive can't quite be expressed in C pseudo code - You need to model the timer tick, possibly like so:

Code: Select all

REM Operating system here
EVERY (50ms)
   IF TaskSwitchNeeded() THEN TaskSwitch();
END EVERY

REM User Task here
REPEAT UserTask
	DoUserTaskStuff();
END REPEAT
Cooperative looks a bit like so, in comparison:

Code: Select all

REM Operating system here
SystemCall ()
   IF TaskSwitchNeeded() THEN TaskSwitch();
   DoSystemStuff ();
END SystemCall;

REM User Task here
REPEAT UserTask
	DoStuff();
	SystemCall();
END REPEAT
The main difference is that the check for a task switch is done in a timer routine that forcibly interrupts the user tasks regardless of what they're currently doing in preemtive (that "forced interruption" is the preemption), while the user tasks needs to co-operate with the multitasking by frequently calling a system routine that checks whether a task switch is needed. If that frequent call to the system is missing (user task does not cooperate), that single task will hog the entire system.
Preemptive will even task-switch when the user task doesn't do a single system call - cooperative will not.

QDOS is a bit of a mix between the two: The scheduler (that's the bit of code that does the IF TaskSwitchNeeded() THEN TaskSwitch() part) is entered both through a timer interrupt AND as the last action of most sysem calls.


ʎɐqǝ ɯoɹɟ ǝq oʇ ƃuᴉoƃ ʇou sᴉ pɹɐoqʎǝʞ ʇxǝu ʎɯ 'ɹɐǝp ɥO
tcat
Super Gold Card
Posts: 633
Joined: Fri Jan 18, 2013 5:27 pm
Location: Prague, Czech Republic

Re: What makes the OS for QL any better, different, unique ?

Post by tcat »

QDOS is a bit of a mix between the two
Taking best of the two, or a compromise?

On RPI Linux, not knowing much about scheduler runtime, I coded simple NET [nRF24L01] server

Code: Select all

  while ( true )
    receiveheader()
    case ( type )
      SND: send()
      RCV: receive()
      default: waitms( 10 )
    end case
  end while
Without some millis waiting, CPU usage would go to some 20-30% and that's preepmtive system. I know there are some tricks with threads, but was linked as single threaded [hope], as RPI is nowadays multi[core]processor system [used to be single].


User avatar
tofro
Font of All Knowledge
Posts: 2685
Joined: Sun Feb 13, 2011 10:53 pm
Location: SW Germany

Re: What makes the OS for QL any better, different, unique ?

Post by tofro »

tcat wrote:
QDOS is a bit of a mix between the two
Taking best of the two, or a compromise?
Of course the former ;)

A fixed time-slicing system (only changing context when the time slice of the current task has completely expired) has some downsides - There are normally lots of cases where the current task needs to wait for some external event (like a floppy having data available, keyboard input, printer ready,...). By far the most of those "wait" cases are the result of a system call (e.g. IO.FBYTE) - so it makes sense to give up the current time slice when you know you can't continue anyhow until the byte you asked for is there. The "wait until system has the byte ready" time can then be used by other tasks.


ʎɐqǝ ɯoɹɟ ǝq oʇ ƃuᴉoƃ ʇou sᴉ pɹɐoqʎǝʞ ʇxǝu ʎɯ 'ɹɐǝp ɥO
User avatar
tofro
Font of All Knowledge
Posts: 2685
Joined: Sun Feb 13, 2011 10:53 pm
Location: SW Germany

Re: What makes the OS for QL any better, different, unique ?

Post by tofro »

tcat wrote:
QDOS is a bit of a mix between the two
Taking best of the two, or a compromise?

On RPI Linux, not knowing much about scheduler runtime, I coded simple NET [nRF24L01] server

Code: Select all

  while ( true )
    receiveheader()
    case ( type )
      SND: send()
      RCV: receive()
      default: waitms( 10 )
    end case
  end while
Without some millis waiting, CPU usage would go to some 20-30% and that's preepmtive system. I know there are some tricks with threads, but was linked as single threaded [hope], as RPI is nowadays multi[core]processor system [used to be single].
The high load in that case is very probably not the result of lack of preemption, but rather your receiveheader(), send() and receive() functions busy-waiting for specific conditions on the network(i.e. checking millions of times per s whether something has changed - with the delay you make sure you only check the same thing every 10ms). In such cases you have a tight loop consuming 100% CPU, even if your system is preemptive (as long as there's nothing else to do, Linux will give 100% to you - QDOS will basically do the same in such cases)

Without the capability to be able to trigger an interrupt on state changes on the NET line (and a proper Liniux device driver that handles the interrupts), there's not much more you can do other than frequently hand back control to the system with a wait system call. The downside of your solution is: Your code will still wait for 10ms, even in the case when the system is loaded so heavily by other tasks that it cannot achieve a proper sampling interval. You should actually check the expired time between iterations and only delay when you know you have time to spend.


ʎɐqǝ ɯoɹɟ ǝq oʇ ƃuᴉoƃ ʇou sᴉ pɹɐoqʎǝʞ ʇxǝu ʎɯ 'ɹɐǝp ɥO
User avatar
bwinkel67
QL Wafer Drive
Posts: 1187
Joined: Thu Oct 03, 2019 2:09 am

Re: What makes the OS for QL any better, different, unique ?

Post by bwinkel67 »

So it's going to get confusing quickly trying to describe multitasking by trying to show code both from the OS/scheduler and the user/task side. In preemptive, like QDOS, it is independent of the user/task side and will interrupt a program regardless of where it is, with some caveats of critical system functions like I/O (and of course keeping granularity in mind -- i.e. it's not going to context switch after having printed half of a character and I don't believe any OS really does that). In a cooperative system, the user/task side gives control away and the way it was done originally was not by some arbitrary system calls but through a practical means.

Both Windows and the Mac OS were graphical user interfaces before they added multitasking and so had to deal with events (a completely new way of thinking about code compared to the old "print a menu and give me a choice" approach). Thus each had to continually ask the OS what the next event was via a GetNextEvent call. So most proper behaving code looked something like the code below (even before multitasking existed)...I threw out some initialization commands but the cooperative entry point is GetNextEvent:

Code: Select all

...
   while (GetNextEvent(everyEvent, &myEvent))
   {
      switch (myEvent.what)
      {
         case mouseDown:
                 windowPart = FindWindow(myEvent.where, &whichWindow);
                 DoMouseDown(windowPart, whichWindow, &myEvent);
                 break;
         case keyDown:
         case autoKey: 
                 {
                    register char theChar;

                    theChar = myEvent.message & charCodeMask;
                    if ((myEvent.modifiers & cmdKey) != 0) 
                       return(DoCommand(MenuKey(theChar)));
                    else if (noCTRL)
                       KeyDown(theChar);
                    else
                       { gEvent = myEvent;
                         TESetSelect((**TEH).teLength,(**TEH).teLength,TEH); }
                 }
                 break;
         case activateEvt:
                 if (ours((WindowPtr)myEvent.message))
                 {
                    if (myEvent.modifiers & activeFlag)
                       { TEActivate(TEH);
                         ShowControl(vScroll);
                         DisableItem(myMenus[editM], undoCommand); }
                     else
                       { TEDeactivate(TEH);
                         HideControl(vScroll); }
                 }
                 break;
         case updateEvt: 
                 if (ours((WindowPtr) myEvent.message))
                    UpdateWindow(myWindow);
                 break;
      }
   }
...
So the use of GetNextEvent in MacOS (and something similar in Windows 1 and 2) made adding a cooperative kernel simple and likely why both operating systems went that route initially. The cooperation was implicit and thus required little change. Of course, this depended on the application being one that was very interactive, requiring lots of user input (and the thought back then was for user productivity software and graphics stuff so one expected most users to constantly be adding input as opposed to running batch jobs). When I had to port my interpreter, the problem was that context-switching only happens before something is run and I had to add in GetNextEvent() calls in the parsing loop itself (mainly looking for CTRL-S, CTRL-Q, and CTRL-C) to relinquish control and when I didn't it would hang the system.

I just did a couple of videos on it. The first can be found here and I'm posting the second tomorrow:

https://www.youtube.com/watch?v=atnmVFByt-U

The first is more of an overview but in the second I dive down into some C code on both the QL and the Mac OS to show the difference (and somewhat insane way one had to code in a cooperative environment).

I would also not call QDOS a hybrid preemptive/cooperative OS as it's preemptive regardless of how it preempts the code. As long as you the writer of code doesn't have to worry about how you are context-switched out then you aren't cooperating and thus it's not a cooperative OS. A simple test that you can do on a QL is write the following loop in a C program:

Code: Select all

while (1);
This will not freeze the QL up. Note that Digital 'C' SE is an implementation of Small C so there is not something funky going on with its implementation to make it QL-like and, in fact, it would be impossible to take an off-the-shelf compiler and somehow add system calls into every possible keyword.


tcat
Super Gold Card
Posts: 633
Joined: Fri Jan 18, 2013 5:27 pm
Location: Prague, Czech Republic

Re: What makes the OS for QL any better, different, unique ?

Post by tcat »

Hi Ben,

Nice event loop example, I vaguely remember programmers were also guided to add

Code: Select all

  switch () ...
    default: Idle()
   }
whenever appropriate, to do some app housekeeping, while it is idle, which is most of the time.
Yes, while(1) or until(0)


User avatar
bwinkel67
QL Wafer Drive
Posts: 1187
Joined: Thu Oct 03, 2019 2:09 am

Re: What makes the OS for QL any better, different, unique ?

Post by bwinkel67 »

No, Mac OS didn't have an Idle() call, it was GetNextEvent and WaitNextEvent. The latter just wrapped the functionality of the former and also added the SysetmTask call to handle desk accessories (and maybe one or two other things which you could do separately as well). I believe Apple recommended use of WaitNextEvent though I don't recall if all Multifinder versions supported it -- I used GetNextEvent in my code, I think to be more compatible...quote from Apple Dev documentation:
If you intend to design your application to run in either a single-application environment (such as System 6 without MultiFinder) or a multiple-application environment, the very beginning of your event loop should test to make sure the WaitNextEvent function is available. If WaitNextEvent is not available, your code should use GetNextEvent to retrieve events. If your code uses GetNextEvent, it should also call SystemTask to allow desk accessories to perform periodic actions. However, your code should always use WaitNextEvent if it is available, rather than GetNextEvent. If your application calls WaitNextEvent, it should not call the SystemTask procedure.
I don't know how Windows 3 did things so maybe Idle() is from that software architecture.


User avatar
bwinkel67
QL Wafer Drive
Posts: 1187
Joined: Thu Oct 03, 2019 2:09 am

Re: What makes the OS for QL any better, different, unique ?

Post by bwinkel67 »

tcat wrote:Hi Ben,

Nice event loop example, I vaguely remember programmers were also guided to add

Code: Select all

  switch () ...
    default: Idle()
   }
whenever appropriate, to do some app housekeeping, while it is idle, which is most of the time.
Yes, while(1) or until(0)
Hi Thomas,

Part 2 of my video does code comparison so you can see the difference of coding for a cooperative system you had to do on a Mac -- quite insane. Code walk through starts at the 14 minute mark (before that I show how different applications behave in a cooperative environment).

https://www.youtube.com/watch?v=cnkrGbXpV44

The base code is shared with ZXSimulator (the BASIC only) but you can find the entire parser here:

viewtopic.php?f=3&t=3169&hilit=cl.zip#p32229


tcat
Super Gold Card
Posts: 633
Joined: Fri Jan 18, 2013 5:27 pm
Location: Prague, Czech Republic

Re: What makes the OS for QL any better, different, unique ?

Post by tcat »

Hi Ben,

Nice video, seeing `Think C' on Mac, remember also using MPW (E.T.O.), that had unix like shell environment, perhaps `Apple-Dot' comes from there, it sill lingers in mem.

Re QL, how do you make those blobs, that appear to have some shading perspective, as if the light came from an angle?

Resembles Urs's QL animator, seen that?

Tomas


Post Reply