In CP/M, how did a program know when to load a particular overlay?
Because it was part of the calling code/execution sequence?
A program that was too large to put into main memory was broken into several overlay regions. Once you needed data from outside of the currently loaded region, something happened to swap out the overlays. But what is that something?
Er ... a call to a routine?
I guess the main issue with understanding basic overlay usage is to expect some mysterious system component handling overlay management, except there is none.
Everything about overlays is done on application level (new speak userland) by each application on it's own. This is not only true for CP/M but as well for later DOS (*1) and several other basic systems.
Basic support, if at all (*2), was on assembler/compiler level to allow multiple subroutines to be compiled to run at the same address. Everything else was program specific. There was no standard, only language specific solutions - if at all.
At that point the question essentially leaves the RC.SE domain and becomes basic CS. But for the sake of it ... also, I already wrote the following part before realizing that I'm repeating generic CS knowledge.
Before continuing, it may help to remember what program structure is required to use overlay linking (*3):
A program having a main routine(s) and two or more subroutines (groups) which are mutually independent.
With this setup the main program can be linked to one address range, while all subroutines are linked to another address range, the overlay area, each group to the same address range. When some routine in the main program now wants to call a subroutine in an overlay it will not call it directly but by detour thru the overlay management.
To look into let's look at a simple program using a C-ish like syntax - note, this example does NOT assume that there is any language support - having one will of course take away much of those details - but we want to see all the dirty secrets :))
int Var1;
main()
{
Var1 = 1;
SomeSub();
OVL_MGR(Ovl1,FuncA, ... ); - Calls Function A in Overlay 1
OVL_MGR(Ovl2,FuncD, ... ); - Calls Function D in Overlay 2
}
SomeSub()
{
Var1++;
}
Ovl1:FuncA(...)
{
SomeSub(); - Calls direct a function in the root segment
FuncB( ... ); - Calls direct Function B in the same overlay
OVL_MGR(Ovl1,FuncB, ... ); - Calls B thru the manager
}
Ovl1:FuncB(...)
{
OVL_MGR(Ovl2,FuncC, ... );
}
Ovl2:FuncC(...)
{
...
}
Ovl2:FuncD(...)
{
SomeSub();
if(Var1<5)
{
OVL_MGR(Ovl1,FuncB, ... );
};
}
Our overlay capable compiler/linker will now create 3 code segments:
- Root segment, containing functions
- main,
- SomeSub and
- OVL_MGR
- First overlay segment (Ovl1), containing functions
- First overlay segment (Ovl2), containing functions
Address wise the root segment is compiled to Address 0100h, while all overlays are compiled to an address after the root segment. At link time the linker will calculate this from the size of the root segment. After that it will reserve space for Variables, Stack and so on.
So for simplicity lets assume our memory layout like this:
0100h..1FFFh - Code in root segment
2000h..3FFFh - Overlay region
4000h..6CFFh - (Static) Variables
6D00h..7FFFh - Stack
No lets see how running above program may look like:
- When loading only the root segment is loaded into memory - at 0100h as custom for CP/M.
- Startup code will setup Stack and Variables and so on
- Main starts execution
- Var1 at 4000h gets set to 1
- SomeSub within the root segment is called using a regular call
- The overlay manager is called with Ovl1 and FunctA as parameter
- actual overlay number is saved on stack (NULL as none is loaded)
- Check if Ovl1 is loaded
- Not loaded right now ->
- Ovl1 gets loaded from disk to 2000h
- Ovl1's number gets set in a global variable
- FuncA (at address 2000h) gets called
- FuncA calls SomeSub within root using a standard subroutine call
- Same is done with FuncB, as its a regular call within the same segment
- FuncB now calls FuncC in overlay 2 using the overlay manager
- actual overlay number is saved on stack (Ovl1)
- Check if Ovl2 is loaded
- Not loaded right now ->
- Ovl2 gets loaded from disk to 2000h, replacing Ovl1
- Ovl2's number gets set in a global variable
- FuncC (also at address 2000h) gets called
- FuncC does it's job and returns to the overlay manager
- Saved overlay number is pulled from Stack (Ovl1)
- Not the same as the actual one (Ovl2) ->
- Ovl1 gets loaded from disk to 2000h, replacing Ovl2
- Ovl1's number gets set in a global variable
- execution returns to the caller (FuncB)
- FuncB returns the standard way to FuncA
- FuncB is called again but using the overlay manager (maybe it was copied from some other place and not adapted *5)
- actual overlay number is saved on stack (Ovl1)
- New (Ovl1) is the same as the old (Ovl1), so no loading needed
- FuncB (maybe at address 2200h) gets called
- FuncB does it's job (see 9..10) and returns afterwards to the overlay manager
- Saved overlay number is pulled from Stack (Ovl1)
- Actual (Ovl1) is the same as the old (Ovl1), so no (re) loading needed
- execution returns to the caller (FuncA)
- FuncA now calls FuncD in overlay 2
- actual overlay number is saved on stack (Ovl1) *6
- Check if Ovl2 is loaded
- Not loaded right now ->
- Ovl2 gets loaded from disk to 2000h, replacing Ovl1
- Ovl2's number gets set in a global variable
- FuncD (also at address 2000h) gets called
- ... and so on ...
Well, here it gets funny, take your time and see how nice they trash each other. Perfect example how overlays should not be organized :))
*1 - Although DOS did offer a overlay loading function (Int 21h Function 4Bh Subfunction 3h), it did not offer any support past loading a single module. In fact, the overlay manager included in Microsoft languages did not use it at all (*4).
*2 - If not, one had to 'simply' write different programs and do all linking manually.
*3 - Well, there is also the method of simply exchanging programs at whole, like a text editor followed by a mail merger followed by a print formater - sometimes with a rather small root program managing the sequence. This is also sometimes called overlay, but 'chaining' might be a better description.
*4 - Link's overlay manager installs itself by default at INT 3Fh at program start and uninstalls at exit. All Overlay calls are thus coded as INT 3Fh instructions followed by a 16 bit overlay number and a 16 bit function entry address instead of the far call within a regular linked program. By using that Int as custom call instruction this will work with any calling scheme, no matter what language. But that's a different story.
*5 - When overlay support is provided by a language this usually never happens - but this is to show even such case.
*6 - Note, this is no longer NULL, as Ovl1 is still loaded. Saves possible trashing when functions in the same overlay are called further down.
.OVL
etc.). WordStar did this, Turbo Pascal did, too. And there are no ISRs in CP/M.