Path: utzoo!utgpu!utstat!jarvis.csri.toronto.edu!mailrus!cornell!batcomputer!itsgw!steinmetz!sagittarius!dixon From: dixon@sagittarius.steinmetz (walt dixon) Newsgroups: comp.sys.ibm.pc Subject: Re: Disk I/O in Resident MS-DOS Program Summary: TSR disk I/O is complicated Keywords: TSR,Background Processing,MS-DOS Reentrancy Message-ID: <13211@steinmetz.ge.com> Date: 21 Feb 89 15:19:13 GMT References: <1872@holos0.UUCP> <2506@sun.soe.clarkson.edu> Sender: news@steinmetz.ge.com Reply-To: dixon@sagittarius.steinmetz.ge.com (walt dixon) Organization: General Electric CRD, Schenectady, NY Lines: 154 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.