• Please review our updated Terms and Rules here

Minimum TSR size in various DOS versions?

Krille

Veteran Member
Joined
Aug 14, 2010
Messages
1,104
Location
Sweden
I'm writing a fairly small TSR and in my limited testing (so far only under NTVDM in Windows 7 and under DOS 6.22 running in Bochs) I'm having problems due to the resident size being too small.

According to RBIL the minimum is 11h paragraphs in DOS 2.x and 06h paragraphs in DOS 3.0+. However, in NTVDM under Window 7 (yes, I know it's not "real" DOS) the limit is 0Ch paragraphs. Anything less than that will seem to work - a first mem/c will list the TSR but on subsequent runs it's removed and sometimes other strange things happens such as crashes with an invalid instruction error message with Close or Ignore buttons (selecting Ignore usually causes junk to be printed and in the end I have to close the CMD window anyway).

Of course, this in itself is not a problem. The TSR isn't supposed to work under Windows 7. But then when I test it under DOS 6.22 running in Bochs the machine appears to hang and the Bochs console log window keeps spewing the same message over and over again.

So at this point I start googling and find a post on stackoverflow that links to the source for MS-DOS 2.0 on github: https://github.com/microsoft/MS-DOS...74892b2bb1713bde0f7f/v2.0/source/PROC.ASM#L70

Reading that makes two things clear. The limit is actually 06h paragraphs in DOS 2 (methinks good ole Ralph got confused and mixed things up) and that DOS will actually adjust the value in DX if it's too small.

So, can I count on all DOS versions to behave like this? Will the paragraph count in DX (or the byte count if using INT 27h) be adjusted if too small? I'm guessing not since DOS 6.22 crashed under Bochs and the NTVDM which is supposed to mimic DOS version 5 apparently doesn't.

Can I assume that DOS 3.0+ will work with 11h paragraphs? Or better yet, is there a list of the limits for each version of DOS somewhere (in case the limit for DOS 5 actually is 0Ch paragraphs)?
 
If you want the smallest possible TSR, you can allocate the memory necessary with int 21h ah=48, change the owner id in the MCB to 7 (system), copy your code there, install your interrupt handlers, then terminate normally.
 
When I've written small TSRs, I've always released excess program memory and even the copy of the environment variables explicitly. I don't recall what the lower limit was. The point is that there's some "baggage" that needs taking care of when a TSR is loaded.
 
smallest tsr code would be 1 block (16 bytes). as suggested, allocate with 48h and change the MCB owner.

one thing to remember with those minimum paragraphs with func 31/int 21, is its from the PSP. so if you have a COM tsr whose code is at 0x100, and your trying to do a minimum number of paragraphs like 6... thats 0x60 into the PSP, past the FCBS buffers etc.

You can use my TSR skeleton if you like, it will use UMB if they exist or not etc. loads + unloads itself.
 

Attachments

  • tsr.zip
    1.6 KB · Views: 12
OK, an update to this. Sorry for the late response.

When I've written small TSRs, I've always released excess program memory and even the copy of the environment variables explicitly. I don't recall what the lower limit was. The point is that there's some "baggage" that needs taking care of when a TSR is loaded.
Yeah, I was doing that in my original version. Releasing the environment and also moving the TSR to offset 5Ch in the PSP. Then Plasma inspired me to make a version 2.
If you want the smallest possible TSR, you can allocate the memory necessary with int 21h ah=48, change the owner id in the MCB to 7 (system), copy your code there, install your interrupt handlers, then terminate normally.
So I did this, except I set the owner ID to 8, not 7. I guess you really meant 8? Setting it to 8 (DOS owns the MCB) makes the TSR "invisible", meaning it's not listed when doing "mem /c" for example. Which is kind of nifty but could be confusing to a user I suppose. If I set it to be self-owned (segment address of the memory block of the TSR) then it's listed but there's just a blank name unless I also write the name to offset 8 in the MCB. This is what I ended up doing.

Anyway, I figured I might as well try to make the TSR load in the UMA, or, if that doesn't work, at the top of conventional memory (to avoid fragmenting memory) so I set the allocation strategy and UMB link state accordingly.

However, my assumptions (mostly based on what I've read in RBIL) regarding how this works was all wrong.

My first mistake was that I thought that DOS would retain the last set valid/supported allocation strategy. So I started out with setting it to "Low memory, Last fit" for the benefit of DOS versions older than DOS 5. Then setting it to "High memory, Best fit" for DOS 5 and higher. This way, I thought, meant that if the latter failed, I would still have the former in affect. This was apparently wrong because it appears to reset the strategy to the default of "Low memory, First fit"?

My second mistake was that I thought that having the "High memory, Best fit" allocation strategy in affect would mean that the call to allocate memory (with INT 21h/AH=48h) would fail if there was no UMB handler loaded or no "DOS=UMB" in config.sys. This was also wrong because it will happily allocate a block of memory in conventional memory instead. BTW, reading the code BloodyCactus posted I see that it verifies that the allocated block is actually above or equal to A000h and frees it if not. I'm currently trying to make this as simple and efficient as possible so I might end up doing the same.

A couple of random observations and thoughts;
  • I've noticed that "mem /c" lists my TSR as having a size of 128 bytes under MS-DOS 6.22 whereas in Windows 7 it's just 112 bytes. I guess the MS-DOS 6.22 version includes the MCB in the memory count (which makes the most sense when you think about it).
  • RBIL says that a COM-program is loaded to the largest available block in conventional memory. Is that really true? I would expect DOS to put it in the first free block large enough to hold it, because that's what the default allocation strategy of "Low memory, First fit" would/should do.

Anyway, my TSR adventures continues! :D
 
Really, all these questions can be answered by looking at the source code for MSDOS 6.0 that was made available some years ago on the web. In this case, the answer is in the file msproc.asm, around the label "Exec_Norm_Com_Alloc", where an allocate call is made with BX=0xffff; i.e. return the largest contiguous block of memory available.

The code is fascinating for all of the special casing, such as .COM programs in ROM or restricting the names of programs that can be executed.
 
  • I've noticed that "mem /c" lists my TSR as having a size of 128 bytes under MS-DOS 6.22 whereas in Windows 7 it's just 112 bytes. I guess the MS-DOS 6.22 version includes the MCB in the memory count (which makes the most sense when you think about it).
I think so, yes. I read something similar yesterday or the day before, but can't find the URL now. IIRC, Volkov Commander's Memory Info should also show 112 bytes.
Some developers say, that the MCB is part of the operating system and as such should be excluded from the size of a program. I agree, but when a program is loaded, its MCB is also in use and those 16 bytes are not available to other programs.
 
Back
Top