• Please review our updated Terms and Rules here

TSR programs with Open Watcom

aitotat

Experienced Member
Joined
Aug 13, 2009
Messages
351
Location
Finland
I'm trying to write 16-bit MS-DOS TSR program with Open Watcom. I've never used Open Watcom before. Actually I've never written a TSR program before but I know the procedure and DOS calls involved.

Open Watcom C Library Reference mentions _dos_keep function to install TSR programs in memory. Here is the example code from the reference:
Code:
#include <dos.h> 

void permanent() 
  { 
    /* . */ 
    /* . */ 
    /* . */ 
  } 

void transient() 
  { 
    /* . */ 
    /* . */ 
    /* . */ 
  } 

void main() 
  { 
    /* initialize our TSR */ 
    transient(); 

    /* 
        now terminate and keep resident 
        the non-transient portion 
    */ 
    _dos_keep( 0, (FP_OFF( transient ) + 15) >> 4 ); 
  }

The example is too simple since I'm going to have several C and ASM source files (I'm going to use the Open Watcom WLINK as a linker). I need to arrange the functions so that transient functions are located last, after C library functions and permanent functions. How to do that?
 
What memory model are you using? Be aware that most linkers (I don't know about WLINK, but I suspect it follows the rule) group all code, then all data together in memory, so that using a code symbol as the memory high-water mark is not a good idea.
 
Small memory model. Compact memory model might be useful later but I'm sure that all the code will fit in a single segment.
 
And there's your problem--the linker (in order to preserve segment addressability) will store your programs like this:

Code for program 1
Code for program 2
....
Data for program 1
Data for program 2

You might be able to do what you're after by declaring the segment and group of your transient programs to be separate from the ones that "C" is using. So, instead of CODE and DATA, you'll need to invent your own names. Note that you'll have to establish your own addressing for data and that stack will not be shared with data.

Your transient programs, of course, will be declared as "FAR".
 
I found out how to define segments and order for them.

Open Watcom has three pragmas for defining segments: code_seg, data_seg and alloc_text.

They are all simple. Every function that comes after code_seg pragma goes to the specified segment. data_seg pragma works the same way for data.
Code:
#pragma data_seg("ResidentData", "RDATA");

int someGlobalVariable = 4;
const int constGlobalVariable = 5;


#pragma code_seg("ResidentCode");

int SomeResidentSegmentFunction(void)
{
	return 0;
}

int ThisFunctionGoesToTheResidentSegmentAsWell(void)
{
	return ++someGlobalVariable;
}


#pragma code_seg("ResidentEnd");

int GetSizeOfResidentSegment(void)
{
	return (int) GetSizeOfResidentSegment;
}
There are three custom segments defined in above code. ResidentData, ResidentCode and ResidentEnd. By default, WLINK will place them after segments defined by compiler.

Segment order can be specified with ORDER Directive passed to WLINK:
Code:
ORDER CLNAME RDATA SEGMENT ResidentData CLNAME CODE SEGMENT ResidentCode SEGMENT ResidentEnd
ResidentData is now the very first segment. Custom class, RDATA, was specified to prevent the linker from placing ResidentData before all DATA segments but after all CODE segments. ResidentCode and ResidentEnd will now be placed after ResidentData.

ResidentEnd segment contains single function that returns its offset. Since ResidentEnd comes right after ResidentCode segment, the offset happens to be the size of ResidentData and ResidentCode segments. When that size is passed to the _dos_keep(), only the ResidentData and ResidentCode segments will stay in memory.

This approach does have one problem. C library functions won't be accessible after calling _dos_keep() since C library functions all go to the _TEXT segment generated by the compiler.

Another approach would be to create custom transient segment and place it before the stack segment. It might just be better to forget about C library since TSR programs should be as small as possible.
 
Also remember that segments belong to segment "groups" and most linkers (I don't know about WLINK) will use the group designation to keep like segments together.
 
There is no need to create custom segments after all. There is BEGTEXT code segment that is the first segment by default. There are no code in it so it is perfect for small TSR programs. After that comes the _TEXT segment where all functions go unless specified otherwise. There is no need for WLINK order directive when placing the resident code and data to the BEGTEXT segment.

By default, the Open Watcom will generate code to check for stack overflow. Watcom does this by placing function call to prologue code for every function. This function exists in the _TEXT segment so stack overflow checks must be disabled. This is done with -s parameter to the compiler. I would disable it anyway when generating code for 8086/8088.

Open Watcom uses default data segment even when accessing data created to BEGTEXT or custom segment. It can be easily fixed with little in-line assembly but is there a better way that would not require assembly?

I'll try to make some simple demo program that could be used as a template for any TSR programs. By the way, what is the best method to deactivate TSR when starting some other program and reactivating the TSR when program execution ends?
 
When you say "deactivate", do you mean to simply defeat its functionality or also to unload it from memory? As far as communicating with a TSR, that's up to you, but Interrupt 2F (multiplex interrupt) is a very common way.
 
To defeat its functionality. This demo program displays time at the upper right corner of the screen. I want to display the clock only when DOS prompt is visible.

I'm having some other problem as well. For some reason I need to add extra bytes (256 works) to the _dos_keep(). There are no necessary functions after GetSizeOfResidentSegment() based on memory map file generated by WLINK. I tried to get the size using offset of main() (it is the first function in the _TEXT segment) but that did not help. I even wrote my own assembly version of _dos_keep() but the result is same. Something requires those extra bytes.

Edit:
At least 256 extra bytes are needed. Anything less won't work.
 
Last edited:
Another problem solved. Open Watcom adjusts CS so that CS:0000h points after PSP (Program Segment Prefix). This adjustment is only done when building .EXE files so CS:0000h points PSP on .COM executables.

PSP size is 256 bytes so that is why _dos_keep() requires those extra 256 bytes. Example code in Open Watcom Library Reference does not add the size of PSP so the example must be for .COM executables. Unfortunately it is not mentioned in the description.
 
Here is the demo program with sources. It is meant to demonstrate how to create TSR program without keeping C libraries in RAM. I would have done many things differently if this would not been a simple TSR demo.

There are far more Assembly in it that is necessary. I wrote some generic TSR managing functions that I'm going to include in my Assembly Libraries.

Makefile is written for WMAKE and that was a lot of trouble. Build with WMAKE release. Debug build does not work since it includes stack overflow checks using the libraries that are removed from memory.

I hope this is useful to somebody.
 

Attachments

  • Open_WatClock.zip
    22.8 KB · Views: 12
I hope this is useful to somebody.

Well a year and 5 months later, but hey- it was useful. I actually used your TSR code to develop a TSR which patches IBM QBASIC on PCDOS 5.0 to work on clone BIOSes without ROMBASIC (a licensing check, so I understand). An explanation of the problem can be found here: http://www.os2museum.com/wp/?p=726 and a link to the source code can be found in the comments section of the same page. I'm rather impressed with the code and intend to use it for other applications as well (if you can actually use my FIXBASIC.EXE, you will see that I gave you credit for the TSR code).

I was wondering however; does your code play nicely with other TSRs since it appears to implement the multiplex interrupt differently than other TSRs (Example: I load WATCLOCK.EXE and MOUSE.COM in that order; I can unload WATCLOCK before MOUSE.COM and WATCLOCK.EXE won't complain and MOUSE.COM appears to work correctly)?
 
What about protected mode TSR programming? Anyone that has any knowledge on it?

I have so far tried DPMI 1.0 TSR functions - to no result. Also tried Tenberry DOS/4G library that allows TSR under protected mode. But it results in a huge portion of code left in conventional memory (though documentation says that resident part will be left in extended memory).
 
Back
Top