[comp.sys.ibm.pc] MSC medium model TSR questions

desmond@smaug.dec.com (11/13/87)

I am working on a program that I want to terminate and stay resident and
I have several questions that I would like answers to.  First some background.
The program will be written in a combination of Microsoft C v4.0 and 8086
assembly.  It will be compiled under medium model in C and the .EXE will
be approximately 200K.  Now the questions:

1)  How does one figure out where to mark the end of resident memory?  I
    believe all data is loaded above the code so in small model I would
    probably be safe to take the address in DS plus 64K as the end of the
    image but what about the stack?  Also since this is a medium model
    program, where are the far data elements loaded and how can I find out
    the highest address needed by my program after termination?

2)  Once the program is resident, can it be terminated a second time with
    a normal exit thereby releasing all of its memory back to the system?
    Or is the only way to get back all available memory a reboot?  I know
    there are some PD utilities that supposedly remove TSRs but I don't
    want to use something like that.  I want a way for my program to remove
    itself if possible.

3)  If after termination, the resident C code does some calls to malloc(),
    how is that allocation handled?  If I confine my malloc calls to only
    near data, will all the data which is dynamically allocated come from
    my data segment?  The problem is that once I terminate and give back
    all the memory I'm not using, some other application may take it.  I
    don't want my malloc calls to fail because of insufficient memory.

I would appreciate any information about these questions and anything else
I should know about writing a TSR.  Thanks.

						John

dave@westmark.UUCP (Dave Levenson) (11/14/87)

In article <8711130253.AA04513@decwrl.dec.com>, desmond@smaug.dec.com writes:
> I am working on a program that I want to terminate and stay resident and
> I have several questions that I would like answers to...

> 1)  How does one figure out where to mark the end of resident memory? 

I suggest that you call malloc() requesting 16 bytes.  That will get
you the next available address in the heap above your data.  Round
that address up to the next paragraph boundary, and pass that as the
end-mark in your tsr system call.

> 2)  Once the program is resident, can it be terminated a second time with
>     a normal exit thereby releasing all of its memory back to the system?
>     Or is the only way to get back all available memory a reboot?  I know
>     there are some PD utilities that supposedly remove TSRs but I don't
>     want to use something like that.  I want a way for my program to remove
>     itself if possible.

No, you cannot call exit, unless you want to terminate the process
that MS-DOS thinks is running.  You can, however, unlink yourself
from the interrupt you use to gain control while terminated and kept
resident, and then free your memory with memory de-allocation system
calls.  You must free your PSP and your environment block, to fully
clean up.

> 3)  If after termination, the resident C code does some calls to malloc(),
I would recommend doing all of your malloc() calls before you
terminate and stay resident.  Otherwise, you'll probably get an
error return from a malloc() call as some other process will
probably have claimed the memory above yours.

> I would appreciate any information about these questions and anything else

Watch out if you issue MS-DOS system calls from your TSR.  In
particular, if MS-DOS is already processing a system call, you
should wait until it returns to its caller.  If you open any files,
make your PSP current, or you'll be taking file descriptors from the
running process.  If you save the previous content of any interrupt
vectors, in an attempt to restore them later on, be aware that the
TSRs that get loaded after yours does are probably doing the same
thing, so you'll have to free your TSR's in the opposite order of
that in which they were loaded.
-- 
Dave Levenson
Westmark, Inc.		A node for news.
Warren, NJ USA
{rutgers | clyde | mtune | ihnp4}!westmark!dave

dipto@umbc3.UMD.EDU (Dipto Chakravarty ) (11/16/87)

In article <263@westmark.UUCP> dave@westmark.UUCP (Dave Levenson) writes:
>In article <8711130253.AA04513@decwrl.dec.com>, desmond@smaug.dec.com writes:

>> I am working on a program that I want to terminate and stay resident and
>> I have several questions that I would like answers to...
>
>> 1)  How does one figure out where to mark the end of resident memory? 
>
>I suggest that you call malloc() requesting 16 bytes.  That will get
>you the next available address in the heap above your data.  Round
>that address up to the next paragraph boundary, and pass that as the
>end-mark in your tsr system call.
>--------
 	Is there an equivalent of _TSIZE (in Lattice C) variable that holds
	the size of the loaded program in Microsoft ? 

	If so then you can do the following:
			#include <dos.h>
			main ()  {
				extern int _TSIZE;
				union REGS input, output;	

				input.x.ax = 0x3112   	/*31H...return code*/
				input.x.ax = _TSIZE;    /*the program size */
				intdoss(&input,&output);/*function call 31 */
				}

	I will be interested to know how this project is coming up, as I
	myself is in the middle of developing a TSR based communications 
	software. Feel free to send me email. FYI, I used to work with 
	Lattice C before getting into this program which needed to be done
	in Microsoft 4.0. Of course, there is nothing like Unix on a VAX!
 	(but ...)
								Dipto

dennis@rlgvax.UUCP (Dennis.Bednar) (11/16/87)

In article <263@westmark.UUCP>, dave@westmark.UUCP (Dave Levenson) writes:
> ...                If you open any files,
> make your PSP current, or you'll be taking file descriptors from the
> running process.

How do you do this?
-- 
FullName:	Dennis Bednar
UUCP:		{uunet|sundc}!rlgvax!dennis
USMail:		CCI; 11490 Commerce Park Dr.; Reston VA 22091
Telephone:	+1 703 648 3300

gary@apex.UUCP (Gary Wisniewski) (11/21/87)

In article <8711130253.AA04513@decwrl.dec.com> desmond@smaug.dec.com writes:
>I am working on a program that I want to terminate and stay resident and
>I have several questions that I would like answers to.  First some background.
>The program will be written in a combination of Microsoft C v4.0 and 8086
>assembly.  It will be compiled under medium model in C and the .EXE will
>be approximately 200K.  Now the questions:

We have developed a product with the same specifications.  Our product is
FrontRunner.  It is MSC 4.0 medium with assembler (12%) We range from 100K
to 160K memory image and support EMM, too.

>1)  How does one figure out where to mark the end of resident memory?  I

Two concerns: (1) do you want to relinquish as much memory as possible
to DOS, or (2) do you want to be safe and allocate all 64K to the data
segment.  If you want to do the latter, you can just (as you suggested)
take DGROUP+1000H and make that the argument to the DOS TSR call.

Our approach was to have a command line switch which specifies how large
the data segment is to be.  You can find the *current* size of the data
segment by examining "__abrktb.sz".  You can find the last used paragraph
(usually DS+1000H unless you're running near the ceiling) at PSP:2 (a
word).  You can obtain your PSP pointer through DOS 62 or by examining
the word at __psp.  The MSC startup code (provided with the compiler) is
a useful source of information about how the environment is set up.  In
fact, most TSR applications will want to modify the startup code to
better accomodate their environments (we didn't have to bite the bullet
untill we added EMS support).

>2)  Once the program is resident, can it be terminated a second time with
>    a normal exit thereby releasing all of its memory back to the system?
>    Or is the only way to get back all available memory a reboot?  I know

A normal DOS exit will deallocate memory blocks, but is not the safest way
to clear a TSR from memory.  Upon exit, you should:

	1)	Assure you're the last program in memory.  You don't
		really have to do this, but it's a courtesy which prevents
		a memory hole.  You can check this by examining all memory
		blocks starting with your environment (loaded right in
		front of the PSP) and determining whether they belong either
		to you (same owner as your PSP) or whether they belong to
		the program which called you (owner is less than your
		PSP).
	2)	Clean up any interrupts you use (you certainly *must* use
		some, no?)
	3)	Free your environment block (pointed to by PSP:2C) as well
		as your memory block (PSP) with DOS call 49h, then just
		swap back to the currently running application stack.

>3)  If after termination, the resident C code does some calls to malloc(),
>    how is that allocation handled?  If I confine my malloc calls to only
>    near data, will all the data which is dynamically allocated come from
>    my data segment?  The problem is that once I terminate and give back
>    all the memory I'm not using, some other application may take it.  I
>    don't want my malloc calls to fail because of insufficient memory.

This is kind of a sore area.  Here are the problems:

    1)	MSC 4.0 has an absolutely horrible small and medium model memory
	allocator.  It favors speed in favor of efficient garbage
	collection.  Thus, it is very very fast at allocating (and
	especially deallocating) memory, but can often fail when
	attempting to allocate 100 bytes even though 20000 are
	available.  The problem is excessive fragmentation.  You can
	minimize the damage by using fewer malloc() calls, or by
	fixing the size of malloced items.  We use memory frequently
	and have a great diversity in the sizes of objects we allocate
	and deallocate.  We had to write our own memory allocator.  It's
	about 15% slower, but is exceptionally good at reclaiming
	lost space and avoiding fragmentation.

    2)	You can't use fmalloc() since it attempts to create DOS memory
	control blocks.  This causes DOS memory to become fragmented
	and soon you can't run any other applications.

    3)	You really must allocate the maximum amount of memory you plan
	to use, then deal with crises when they occur.  There is a
	distinction between memory which you've allocated via malloc()
	and memory allocated through DOS.  In the medium memory model,
	MSC reserves the entire 64K (from DOS's point of view) and
	manages it internally.  Thus, you really aren't competing with
	other applications when you do a malloc().  This isn't true in
	the larger models, or when you use fmalloc().

    4)	You must be able to effectively deal with exhausted memory
	situations.  Our product goes through some pretty exhaustive
	gyrations to avoid THE ULTIMATE CRISIS (not enough memory to
	report the problem).  As a last resort, our product frees
	as much as it can and enters panic-mode, where it tells the
	user that it has been pushed too far.  This sort of confrontation
	is generally unnecessary in most TSR applications, but ours is
	a programming environment.  The developer can exhaust the
	memory by creating too many variables/windows/arrays and so on.

Here are some other things to watch out for:

Do you do any disk I-O?  Do you use fopen() or open()?  If so, you should
watch out for the following:

    -	fopen() and open() use the DOS handle calls.  Thus, you are sharing
	a file table with the current application.  This usually causes
	problems.  It will certainly cause problems if you leave files
	opened after you pop down.  The solution is to use the DOS 50H/51H
	pair to save and restore the process id (which controls the file
	handle table and nothing else really).

    -	The FCB calls are supposed to be "safe" in TSR's ... don't believe
	it.

    -	Some TSR's get away with closing all of their files before returning
	to the application.  This works, but is less flexible and can exhaust
	the per-process file limit more easily.

There are host of problems associated with hotkey detection.  Most of these
center around the "which interrupts do I take over?" question.   A short
summary:

INT 9H		You generally must take over this interrupt to detect unusual
		keyboard sequences (like CTRL-ALT).  An alternative is to
		take over 8H (you probably have to anyway) and use INT 16H
		to check the shift key state.  But don't interrupt INT 16H
		while it's in use (rarely occurs, but sad when it does).  This
		leads you to take over INT 16H as well, or just use INT 9H
		to begin with.

INT 8H		The timer interrupt.  You just can't pop into an application
		from INT 9H when the hotkey occurs.  DOS may be busy (you
		have to check the DOS busy flag).  When it is, you can
		usually wait for INT 28H, then pop up.  But some applications,
		such as Lotus, never call INT 28H or any DOS function which
		does (such as DOS 0ah).  Thus, you have to remember (in INT 9H)
		that you found a hotkey and then pop up from INT 8H when DOS
		is idle.

INT 21H		If you pop up from INT 28H (and some other situations) you
		are prohibited from using DOS calls 0H-0CH.  You must either
		(1) not use these calls, (2) replace INT 21H and simulate
		them yourself.

INT 28H		This is the DOS IDLE call.  DOS calls it when it's idle.
		This call was originally intended to support the PRINT
		command, but has fallen into the wrong hands (us).  INT 28H
		provides a convenient means for popping up at the right time,
		but is often not called enough to rely upon.

INT 16H		If you want to paste characters or write a keyboard
		enhancer, you'll have to take over INT 16.

INT 13H		Unless you use INT 28H consistently, you will probably
		have to watch INT 13H for stray calls.  INT 13H is the
		hard disk I-O call.  Usually, DOS is front-ending this
		kind of activity.  But sometimes, a program (such as the
		Norton utilities or a copy-protected program) will call
		INT 13H.  If you happen to pop up at that moment, any
		number of things (all of them bad) can happen.  Not
		everyone is interested in a garbled hard disk, so
		watch out for this one.

INT 23H/24H/0H	Ctrl-BREAK, Critical error, Divide overflow.  You will
		probably want to take over these, but only while you're
		active.  MSC prints some nasty messages when these occur in
		TSRs.  Also, INT 24H uses DOS calls in the 0H-0CH range,
		so if you popped up from INT 28H, DOS will crunch itself
		when critical errors occur.

Hopefully, you're writing this application for in-house use.  If so, you
are in much better shape.  You know your environment and which applications
may be running.  If you want to sell your product, or supply it to
users who have unknown environments, you have to be much more careful.

There is a reasonably good book which just came out from Addison-Wesley
called "Memory Resident Programming on the IBM PC" by Thomas A. Wadlow (often
seen in BYTE).  It's pretty good, but uses only assembly language and avoids
many "thin ice" issues (like just about everything I've mentioned above).
It does, however, cover the basics and is well written.  Sometimes it's
easier to start with a good sample program.

If there are any more questions, email or post and I'll try to respond.
I've dealt with most TSR issues and have some solutions for some of
the trickier problems (at least I THINK I have solutions ... oh well).
I still haven't solved the DOS 2.11 INT 51/50 stack-swap anomaly ... any
answers to that one would be appreciated.

Good luck,

Gary J. Wisniewski
Apex Software Corporation

uucp:	{allegra,bellcore,cadre}!pitt!darth!apex!gary
phone:	(412) 681-4343
USmail:	4516 Henry Street
	Suite 406
	Pittsburgh, PA 15213