lbr@holos0.UUCP (Len Reed) (02/17/89)
I have an MS-DOS terminate-and-stay resident program that must be able to open/close/read/write hard disk files. It appears that the "file handles" move on me. That is, if this routine opens a file, as the foreground processes spawn and die, I lose my handles since they really belong to the foreground processes. FCB I/O appears to work better, but I'm not crazy about using it since it is primitive. My opens and closes have to be in the current directory, which means I have to perform the following to do an open or close: 1. Lock the foreground out of DOS. (I have a mechanism in place to stall DOS calls.) 2. Save the current directory. (MS-DOS has one directory per disk, not--as in UNIX--one per process.) 3. Go to the proper directory. 4. Perform the open/close. 5. Go back to the original directory. 6. Unlock DOS. This scenerio seems unnecessary for read/writes. I can do them regardless of my current directory. Is there a better way to do this? What I'm doing seems to work; am I missing something that will cause it not to work? BTW--the application is a communications package that sits in the background and steals time to transfer/receive files to/from a mainframe. Thanks for any help. -- - Len Reed
dixon@sagittarius.steinmetz (walt dixon) (02/17/89)
Writing programs which run in the background under MS-DOS is a kludge at best. The existance of the print.com proves that it is possible for one to construct these applications. Print relies on some undocumented DOS services. The bottom line is that you must have a thurough understanding of DOS internals before attempting to write a TSR which needs to make any int 21h requests. I would strongly recommend reading chapter 4 of the MS-DOS Developers Guide 2nd Edition (Howard Sams, 1988). Please note, although I am the author of this chapter, I get no royalties; I'm just citing a known good reference. I'll briefly describe the problem that you are experiencing. MS-DOS stores the address of the Job File Table (JFT) in the program segment prefix. (In most cases the JFT itself is also in the PSP, but there is no guarantee). The file handle is an index into the JFT. JFT entries are System File Numbers (SFNs) which are indicies into the System File Table (SFT). Each SFT entry maintains file position and instructs DOS how to find the correct device driver. When you make a handle i/o request, DOS uses the current PSP to find the correct SFT entry. If your program is in the foreground, DOS will not have any trouble. If a TSR asnychronously decides that it wants to make a handle request, it must ask DOS to change the current PSP. (current PSP is a global variable maintained by MS-DOS). (Int 21H AH=50, BX=PSP changes the current PSP. This function is undocumented). There is a certain amount of houskeeping one must do before changing the PSP including recording the previous PSP and establishing your own break handler. You should set up a critical error handler before performing any io. Now another observation. If you are writting a communications application, you cannot always rely on trapping int 21h requests. Your internal buffer may fill up while then foreground program is compute bound or waiting for console I/O. You should be using int 28H and a timer interrupt as well. You must be particularly careful of the timer interrupt. Walt Dixon {arpa: dixon@ge-crd.arpa } {us mail: ge crd } { po box 8 } { schenectady, ny 12345 } {voice: 518-387-5798 }
dean@sun.soe.clarkson.edu (Dean Swan) (02/21/89)
From article <1872@holos0.UUCP>, by lbr@holos0.UUCP (Len Reed): > I have an MS-DOS terminate-and-stay resident program that must be able > to open/close/read/write hard disk files. It appears that the "file > handles" move on me. That is, if this routine opens a file, as the > foreground processes spawn and die, I lose my handles since they > really belong to the foreground processes. > > BTW--the application is a communications package that sits in the background > and steals time to transfer/receive files to/from a mainframe. I have written more TSR's that do DOS I/O than I'd like to admit. Anyway, your problem is VERY SIMPLE to solve. Ok, DOS I/O from TSR's lesson #1: 1) When doing DOS I/O from TSR's you have to make sure that your routine didn't get called while the foreground process was doing any I/O of it's own. MS-DOS is not re-entrant, and neither is the BIOS, so you have to hook up to a lot more interrupts than you might think just to be able to safely do DOS I/O. 2) INTERRUPTS TO LINK TO: int 1Ch timer interrupt - use it to activate your program int 13h BIOS disk handler You basically have to link a piece of code in that sets a BIOS_busy flag, then calls the bios function, then clears your flag. This flag is then used by your ISR tied to the timer interrupt to tell if its safe to call the BIOS disk handler. If your flag is set when the timer ISR is called it isn't safe. int 28h This is the DOS special interrupt. It gets called only when nothing else is going on. Whenever an ISR linked to this interrupt is called it is always safe to do DOS I/O (and BIOS I/O too.) int 24h DOS critical error handler - hook up to this so you can process DOS errors. int 1Bh break - so you don't get nuked by break interrupts If you want to do a "Hot Key" thing then you also have to grab int09h. 3) Always check the DOS Busy flag when you enter an ISR that's going to do I/O. Int 21h, funtion 34h returns a far pointer to the busy flag in ES:BX. The flag is a byte value and will be 0 if DOS isn't busy. If this flag is non-zero then just return and wait for the next time you get called. 4) Always reset the 8259, or it will get tempermental and refuse to generate any more interrupts. This is easy. Just do an MOV AL,20h OUT 20h, AL or equivalent code at the beginning of your timer ISR and everything will be ok. 5) If your OPEN a file handle in an ISR, BE SURE TO CLOSE THE HANDLE BEFORE PASSING CONTROL BACK TO THE FOREGROUND PROCESS. Basically, never leave a file open when you leave your ISR. If you were writing to a file and need to add more later, re-open it and append to it, instead of trying to leave it open. If you're reading from a file, just remember the current position in the file, close the file, and exit your ISR. The next time your ISR gets called, simply re-open the file and seek to the position you remembered from last time and pick up where you left off. 6) ONE LAST THING: Keep a busy flag for each of your own ISR's because an interrupt can ocurr while you are servicing one and re-call your ISR. Then check the flag whenever you enter your own ISR's. If its set then do and IRET right away. This prevents recursive ISR calls, which can make a debugger's worst nightmare, not to mention lock up your machine. Well, I guess that's it. I write all my TSR's in Turbo C, and use assembly where appropriate. There's not much point to doing everything in assembly anymore, so why make life difficult. The only thing you lose is that in C, the installation code remains resident, but it's usually only a few hundred bytes, so who cares. I'd be happy to talk with anybody on this subject, as well as any of a million other subjects (which I'll refrain from listing here). Send E-mail or Snail-Mail, or if you really need to, call me at the office. Dean Swan Korex Software Corporation Building 3, Unit 5 Seacomm Plaza PO Box 688 Potsdam, NY 13676 (315) 265-1203 dean@sun.soe.clarkson.edu
dixon@sagittarius.steinmetz (walt dixon) (02/21/89)
From article <2506@sun.soe.clarkson.edu>, by dean@sun.soe.clarkson.edu (Dean Swan) >From article <1872@holos0.UUCP>, by lbr@holos0.UUCP (Len Reed): >> I have an MS-DOS terminate-and-stay resident program that must be able >> to open/close/read/write hard disk files. ...(remainder of quoted message deleted) >I have written more TSR's that do DOS I/O than I'd like to admit. Anyway, >your problem is VERY SIMPLE to solve. Ok, DOS I/O from TSR's lesson #1: > > 1) When doing DOS I/O from TSR's you have to make sure that your routine > didn't get called while the foreground process was doing any I/O of > it's own. MS-DOS is not re-entrant, and neither is the BIOS, so you have > to hook up to a lot more interrupts than you might think just to be > able to safely do DOS I/O. This is mostly true. Portions of MS-DOS were retrofitted to provide limited reentrancy. The two main sources of MS-DOS (IBMDOS) non-reentrancy are stack switching within the int 21h dispatcher and the use of static variables. The int 21h dispatcher works with 3 stacks -- auxilliary, user, and disk. A very limited number of requests (ah = 33h,50h,51h,62h) are serviced with no stack switch. Other requests with ah > 0ch are finally serviced on the disk stack. Requests corresponding to ah=1 to 0ch are serviced on the user stack. A request on the user stack can be preempted by an int 21h request ah > 0ch. DOS provides the int 28h hook so a tsr can tell if it is safe to issue an int 21h request. There is also limited reentrency to support critical error processing. > 2) INTERRUPTS TO LINK TO: > > int 1Ch timer interrupt - use it to activate your program > int 13h BIOS disk handler > You basically have to link a piece of code in that > sets a BIOS_busy flag, then calls the bios function, ... Add int 25h and int 26h to this list. The int 25h and int 26h routines switch stacks and touch DOS global variables. These opereations occur with interrupts disabled. So long as the device driver does not mess with interrupts, you can safely ignore these interrupts. I would not recommend taking this chance. PRINT.COM, which should be regarded as a model TSR hooks int 25h and 26h as well as int 13h. > int 28h This is the DOS special interrupt. It gets called > only when nothing else is going on. Whenever an > ISR linked to this interrupt is called it is always > safe to do DOS I/O (and BIOS I/O too.) Only certain io operations are safe. DOS executes an int 28h instruction when time consuming operations (eg keyboard poll) are pending on the user i/o stack. At this time it is safe to issue int 21h requests which are serviced on the disk stack (ah > 0ch) or don't do any stack switching. > int 24h DOS critical error handler - hook up to this so > you can process DOS errors. An absolute necessity. > int 1Bh break - so you don't get nuked by break interrupts Probably not a bad idea, but grabbing int 23h (as PRINT.COM does) is sufficient. The int 9h ISR issues an int 1bh when the break key is pressed. It is standard pactice for the console driver do set up an int 1bH ISR. The console driver in turn executes an int 23h. > If you want to do a "Hot Key" thing then you also have to grab int09h. > 3) Always check the DOS Busy flag when you enter an ISR that's going to > do I/O. Int 21h, funtion 34h returns a far pointer to the busy flag > in ES:BX. The flag is a byte value and will be 0 if DOS isn't busy. > If this flag is non-zero then just return and wait for the next time > you get called. You must check both the DOS busy flag *AND* the critical error flag. In some versions of DOS, the critical error flag is the byte before the busy flag. When DOS detects a critical error, it increments the critical error flag and decrements the busy flag. Checking the busy flag from within an int 28h ISR defeats the purpose of this hook. The busy flag is always set when DOS issues an int 28h. DOS will not issue an int 28h during critical error processing. > 4) Always reset the 8259, or it will get tempermental and refuse to > generate any more interrupts. This is easy. Just do an > > MOV AL,20h > OUT 20h, AL > > or equivalent code at the beginning of your timer ISR and everything > will be ok. This issues an non-specific EOI to the 8259. The DOS timer ISR (int 8h) does its internal processing (update time of day, etc) and then executes an int 1ch. The int 08h ISR does not send its EOI to the 8259 until *AFTER* the int 1ch ISR executes. Since the timer ISR is the highest priority ISR, no interrupts will be reconginzed until the int 08h interrupt is dismissed (by sending an EOI). > 5) If your OPEN a file handle in an ISR, > > BE SURE TO CLOSE THE HANDLE BEFORE PASSING CONTROL BACK TO > THE FOREGROUND PROCESS. > > Basically, never leave a file open when you leave your ISR. If you > were writing to a file and need to add more later, re-open it and ... (Text Deleted) Unless you switch PSPs (undocumented int 21h ah=50h), you are messing with the PSP of the foreground program. There is no guarantee that there will be space in the JFT to open a file. There is also a small risk that some foreground program will mess with the file. The correct procedure for doing background i/o is 1. Determine if it is safe to run (check busy and critical error flags as appropriate). Make sure int 13h, 25h, 26h not in progress. 1a. You may want to look at the 8250 UART to see if an interrupt is pending. (PRINT.COM does this). If you block service of UART interrupts for too long, you could loose characters. 2. Record the current PSP (int 21h ah=51h or 62h). 3. *RECORD THE CURRENT DTA* (DOS uses the DTA in non-intuative ways). 4. Set up your own critical break and critical error handlers. The default action is to terminate the *CURRENT* program. Before altering any variables, you must protect your TSR against termination. It is also poor form to let an error caused by your TSR terminate another program. 5. Change the current PSP to that of your TSR (int 21h ah=50h) 6. *CHANGE THE DTA* to a local value. 7. *SWITCH TO YOUR OWN STACK*. There is no guarantee of stack avaibalility. 8. Undo these actions when your TSR is done. ... (Remainder of article deleted) NB you probably don't want to do anything time consuming from within an int 28h ISR. There are no technical reasons for this caveat, but the user may notice substantial response degredation if you are not careful. If you are implementing a hot key, you may need to save and restore screen contents at appropriate times. You should be careful about preserving adapter mode. Some adapters (MDA/CGA) are very touchy. Loading the wrong value in a key register (scan rate) can fry your hardware. If you must deal with a character device, you may want to record the address of its driver and pass request headers directly to the device. PRINT.COM uses this technique to avoid reentrancy problems and utlize a write until busy request which DOS does not know how to handle. You can find out more about writing TSRs in Chapter 4 of the MS DOS Developer's Guide, 2nd Edition. (Howard Sams, 1988) I am the author of that chapter. I don't get any royalties; I'm just citing a good reference. Sorry about the length of this reply. I felt that the technical issues raised in the quoted article needed further clarification. Walt Dixon {ARPA: dixon@ge-crd.arpa } {US MAIL: ge crd } { po box 8 } { schenectady, NY 12345 } {VOICE: 518-387-5798 } Standard disclaimer's apply.
vlruo02@dutrun.UUCP (Ge van Geldorp) (03/01/89)
In article <13211@steinmetz.ge.com> dixon@sagittarius.steinmetz.ge.com (walt dixon) writes: >... The >int 21h dispatcher works with 3 stacks -- auxilliary, user, and disk. A very >limited number of requests (ah = 33h,50h,51h,62h) are serviced with no stack >switch. Other requests with ah > 0ch are finally serviced on the disk stack. >Requests corresponding to ah=1 to 0ch are serviced on the user stack. A request >on the user stack can be preempted by an int 21h request ah > 0ch. Question: when is the auxilliary stack used? >PRINT.COM, which should be regarded as a model TSR hooks int 25h and 26h as >well as int 13h. Are you sure about this? The 25h and 26h interrupt vectors remain unchanged when I run PRINT.COM (using PC-DOS 3.30). Ge van Geldorp.