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