You are on page 1of 17

27

“Hard workers will become leaders.”

TSR Programming

TSR or “Terminate and Stay Resident” Programming is one of the interesting topics in DOS Programming. TSR programs are the one which seems to terminate, but remains resident in memory. So the resident program can be invoked at any time. Few TSR programs are written with the characteristic of TCR (Terminate Continue Running) i.e., TSR program seems to terminate, but continues to run in the background. TSR Programming is supposed to be an easy one, if you know the DOS internals. In this chapter, I have tried to explain the tough TSR Programming concept in a simpler manner.

27.1 DOS’s non-reentrancy Problem
If a function can be called before it is finished, it is called reentrant. Unfortunately, DOS functions are non-reentrant. That is, we should not call a DOS function when it executes the same. Now, our intuition suggests us to avoid the DOS functions in TSR programs!

27.2 Switching Programs
As we know, DOS is not a multitasking operating system. So DOS is not meant for running two or more programs simultaneously! One of the major problems we face in TSR programming is that DOS’s nature of switching programs. DOS handles switching programs, by simply saving the swapped-out program’s complete register set and replacing it with the swapped-in program’s registers. In DOS, if a program is put to sleep its registers are stored in an area called TCB (Task Control Block). We must finish one process before another is undertaken. The main idea behind it is that, whenever we switch between programs, DOS switches our program’s stack to its own internal set. And whatever that is pushed must be fully popped. For example, assume that we have a process currently running called previous-process, and we initiate another process in the meantime called current-process. In this case, the current-process will work fine, but when the previous-process just gets finished, it would find its stack data has been trashed by currentprocess. It is a serious injury! Everything will mess-up!

27.3 DOS Busy Flag
From the above discussion, we understand that before popping up our TSR program, we must check whether DOS is currently executing an internal routine (i.e., busy) or not. Surprisingly DOS also checks its status using a flag called “DOS Busy Flag”. This “DOS Busy Flag” feature is undocumented and some programmers refer this flag as “DOS Critical Flag”. We

we have to capture few interrupts. We have already seen that interrupt routines will be called whenever an interrupt is been generated. BIOS is not compatible and there is no guarantee for its reentrancy. So for professional TSR programming. BIOS disk interrupt (int . avoid BIOS functions too! 27. when to popup our TSR program? For that. we can make our TSR program “live”.4 BIOS Functions As BIOS functions are reentrant. Control-C interrupt (int 23h).5 Popping up TSR TSR programs can be made to reside in memory with the keep( ) function. Critical error interrupt (int 24h). we have to use undocumented DOS function 34h. some programmers use BIOS functions in TSR programs. But professional programmers don’t use BIOS functions. TSR programmers capture Keyboard interrupt (int 9h). as the implementation of BIOS functions is quite different from machine to machine. it is being requested by user? In other words. So if we replace the existing interrupt routine with our routine. 27. Control-break interrupt (int 1bh). For that. Then how does our TSR program understand. In other words.A to Z of C 123 can also use this flag in our TSR program to check whether DOS is busy or not. Watch keyboard for hot key Watch Video functions Watch Disk functions Block Control + C usage Block Critical error Watch timer interrupt Watch DOS idle interrupt for surfer popup Other programs TSR int 9h int 10h int 13h int 23h int 24h int 1Ch int 28h DOS Normally.

e. . If you are very particular to know more about this protocol. Control-break interrupt and Critical error interrupt. Otherwise. The idea is that we have to block Control-C interrupt. this protocol is meant for sharing hardware interrupts. and not that of the running process. It is especially useful for unloading TSR programs from memory. When DOS is busy waiting for console input. Use “signature” mechanism to check whether the TSR is already loaded or not. in order to unload any TSR at any time. avoid BIOS functions too! 2. IBM has suggested a protocol for sharing system interrupts. 8. DOS is executing interrupt 21h function. when you programming TSR: 1. it must be the last TSR loaded. For example. TSR programs should be compiled in Small memory model. And so prevent multiple copies. you can understand the concept better. if we follow this protocol standard. 5.7 Rules for TSR Programming It is wise to consider the following rules. and we have to chain them. And it will spoil everything! We must also monitor other interrupts—Keyboard interrupt. That is. BIOS disk interrupt. So I omit the discussion of this protocol. Timer interrupt and DOS Idle interrupt. Avoid DOS functions. Timer interrupt (int 1ch) and DOS Idle interrupt (int 28h). Indian TSR programmers often use int 8h as Timer interrupt. 7.. large or huge memory model if you use file operations with getdta( ) and setdta( ) functions. When DOS busy flag is non-zero.6 IBM’s Interrupt-Sharing Protocol Almost all TSR utilities came with the property of unloading itself from the memory. irrespective of its loading sequence. So we must also chain any interrupt vector that our program needs. So you should watch interrupt 28h. So we must wait and watch DOS busy flag.124 A to Z of C 13h). However you may need to compile in compact . Even though. TSR programs should be compiled with stack checking turned off. I hope by looking at the figure. Our TSR program must use its own stack. there is a chance that the control will pass onto another program when our TSR program is in action. we can disturb DOS regardless of the DOS busy flag setting. it can be used for software interrupts too. The problem here is that of sharing of interrupts by TSR programs. Other TSR programs might be chained to interrupts. But unfortunately. checkout the Intshare. “Y”. 27.doc file found on CD . But other international TSR programmers use int 1ch as Timer interrupt. we can unload only the last TSR loaded i. if we run TSR utilities namely “X” and “Y”. all the TSR programs must use this protocol. TSR programmers don’t use this standard. 9. we can unload any TSR at any time! So. 6. But in order to unload the TSR. 3. If possible. 4. 27.

/* Pointer to user's start routine */ static void (*InitRtn)(void).*/ #define DISK 0x13 #define INT28 0x28 #define KYBRD 0x9 #define CRIT 0x24 #define DOS 0x21 #define CTRLC 0x23 #define CTRLBRK 0x1b #define TIMER 0x1c typedef struct { int bp. I list the codes of Tsr. /* Pointer to user's initialization routine */ /* ---. int unloading. This file can be treated as a good TSR Template and it reduces the pain of TSR programming.si.c file.cx. . Source codes of PC-PILOT run up to several pages and so I have avoided listing the codes here.es.fl.h> 1 0 #include #include #include #include #define TRUE #define FALSE /* --. /* TSR unload flag */ static void (*UserRtn)(void). } IREGS.C by Tom Grubbe <stdio. unsigned scancode. unsigned keymask.ds.h> <stdlib.A to Z of C 125 27. However. static void interrupt (*oldctrlc)(void).bx.ax.h> <conio.vectors ---. /* */ TSR. PC-PILOT is a good substitute for the commercial Sidekick utility.cs. extern char signature[].8 TSR Template Tom Grubbe has written a utility called PC-PILOT Programmer's Pop-Up.di.dx.ip.h> <dos.interrupt vector chains ----.*/ static void interrupt (*oldbreak)(void). Full source code of PC-PILOT is available on the CD .

void resterm(void). static static static static static static static static static static void tsr_init(void).126 A to Z of C static static static static static void void void void void interrupt interrupt interrupt interrupt interrupt (*oldtimer)(void). void dores(void). void pspaddr(void). unsigned dosbusy. int diskflag. int resident(char *signature). static void interrupt newkb(void).*/ static void interrupt newtimer(void). void resinit(void). static void interrupt newdisk(IREGS). void interrupted_psp(void). (*olddisk)(void). /* -----. void resident_psp(void). signature. static static static static static static static static static static static static static unsigned sizeprogram. /* /* /* /* /* /* /* /* /* /* /* /* /* TSR's program size */ DOS segment address */ offset to InDos flag */ table of DOS PSP addresses */ # of DOS PSP addresses */ disk BIOS busy flag */ address of 1st DOS mcb */ TSR's DTA */ TSR's stack segment */ TSR's stack pointer */ Interrupted PSP address */ TSR running indicator */ Hotkey pressed flag */ /* -----. (*oldkb)(void). unsigned intpsp. static void interrupt newbreak(void). unsigned mysp. static void interrupt newcrit(IREGS). int pspctr. void unload(void). void (*InitFPtr)(void)).local prototypes ------. (*oldcrit)(void).*/ void tsr(void (*FPtr)(void). (*old28)(void). char far *mydta. unsigned dosseg. static void interrupt new28(void). #define signon(s) printf("\n%s %s". unsigned myss.ISRs fot the TSR --------. int test_hotkeys(int ky). unsigned psps[2]. int hotkey_flag. unsigned mcbseg. s). int running. .

if (resident(signature) == FALSE) { /* ------.A to Z of C 127 void tsr(void (*FPtr)(void). /* --------.initial load of TSR program -------. #endif } signon("is already installed.*/ (*InitRtn)(). } /* --------.*/ } /* --------.*/ addr of 1st DOS MCB --------. geninterrupt(DOS).get address mydta = getdta().establish & declare residency ---------. /* --------. dosbusy = _BX. #else /* ------.get address if (_osmajor < 3) pspaddr(). /* --------. InitRtn = InitFPtr.*/ static void resinit() { myss = _SS. mysp = _SP.get address _AH = 0x34.*/ static void tsr_init() { unsigned es.\n"). bx-2).Terminate and Stay Resident -------. geninterrupt(DOS). /* user's init function */ resinit().x --------. of DOS busy flag --------. void (*InitFPtr)(void)) { UserRtn = FPtr. mcbseg = peek(es.*/ of resident program's dta -------. .initialize TSR control values ---------. /* --------.*/ of PSP in DOS 2.get the seg _AH = 0x52. bx = _BX. dosseg = _ES. bx. return. es = _ES. tsr_init().*/ #ifdef DEBUG (*UserRtn)().

*/ sizeprogram = myss + ((mysp+50) / 16) . ir.compute program's size -------. newkb).attach vectors to resident program ------. oldkb = getvect(KYBRD). olddisk = getvect(DISK).*/ static int test_hotkeys(int ky) { static unsigned biosshift.*/ setvect(TIMER.BIOS disk functions ISR --------.critical error ISR --------. } /* --------.*/ static void interrupt newdisk(IREGS ir) { diskflag++. setvect(INT28. ir.fl = _FLAGS.ax = _AX._psp.cx = _CX. newtimer).128 A to Z of C oldtimer = getvect(TIMER). if (ky == scancode && (biosshift & keymask) == keymask) hotkey_flag = !running. /* ignore critical errors */ } /* -------.*/ static void interrupt newcrit(IREGS ir) { ir. 0x417). sizeprogram). --diskflag.break handler ----------.dx = _DX. (*olddisk)().*/ static void interrupt newbreak() { return.test for the hotkey --------.ax = 0. setvect(DISK. old28 = getvect(INT28). /* -------. ir. biosshift = peekb(0. /* for the register returns */ ir.*/ keep(0. setvect(KYBRD. } /* ---------.terminate and stay resident -------. newdisk). new28). /* -------. } /* -------. . /* ------.

if (hotkey_flag && peekb(dosseg. } /* --------. outportb(0x61. . outportb(0x61.switch psp context from interrupted to TSR -----. } } } /* ---------. } } /* -----. hotkey_flag = FALSE. dores(). kbval). } else (*oldkb)(). if (hotkey_flag && peekb(dosseg.0x28 ISR ---------. 0x20).keyboard ISR ---------. outportb(0x20. dosbusy) != 0) { hotkey_flag = FALSE.*/ static void resident_psp() { int pp.A to Z of C 129 return hotkey_flag.timer ISR ---------. dosbusy) == 0) { if (diskflag == 0) { outportb(0x20. if (test_hotkeys(inportb(0x60))) { /* reset the keyboard */ kbval = inportb(0x61). kbval | 0x80). 0x20). test_hotkeys(0).*/ static void interrupt new28() { (*old28)(). } /* --------.*/ static void interrupt newtimer() { (*oldtimer)(). dores().*/ static void interrupt newkb() { static int kbval.

geninterrupt(DOS).*/ static void dores() { static char far *intdta.*/ for (pp = 0. /* set TSR running metaphore */ . *psps).reset interrupted psp (DOS 2. geninterrupt(DOS). pp < pspctr.save interrupted program's psp (DOS 2.save interrupted program's psp -----. /* " stack segment */ static unsigned ctrl_break.*/ intpsp = getpsp(). } else { /* ----. pp++) poke(dosseg.130 A to Z of C if (_osmajor < 3) { /* --. disable(). pp++) poke(dosseg.*/ _AH = 0x50. /* -----. intsp = _SP. /* " stack pointer */ static unsigned intss. psps[pp]. pp < pspctr. psps[pp].*/ _AH = 0x50.reset interrupted psp ------. intpsp). /* Ctrl-Break setting */ running = TRUE. /* ------.*/ intpsp = peek(dosseg.*/ static void interrupted_psp() { int pp.set resident program's psp ------. _BX = intpsp. } else { /* -----.set resident program's psp ------. /* interrupted DTA */ static unsigned intsp.switch psp context from TSR to interrupted --------. if (_osmajor < 3) { /* --. _psp). _BX = _psp.*/ for (pp = 0. intss = _SS.x) ---.execute the resident program ---------. } } /* -------.x) ---. } } /* -------.

newcrit).walk through mcb chain & search for TSR --. setdta(mydta). oldcrit). setdta(intdta). 0) == 0x20cd) { /* ---. 0) == 0x4d) { blkseg = peek(mcbs. setcbrk(0). disable(). setvect(CTRLC. setvect(CTRLBRK. setcbrk(ctrl_break). ctrl_break = getcbrk(). unsigned df. enable(). df = _DS ._psp. if (unloading) unload(). unsigned blkseg. _SS = intss. setvect(CTRLBRK. newbreak). if (peek(blkseg.*/ static int resident(char *signature) { char *sg. (*UserRtn)(). oldctrlc = getvect(CTRLC). oldbreak). setvect(CTRLC.*/ while (peekb(mcbs. newbreak).A to Z of C 131 _SP = mysp. running = FALSE. resident_psp(). setvect(CRIT.test to see if the program is already resident -------. _SP = intsp. intdta = getdta(). mcbs = mcbseg.this is a psp ---. oldctrlc). oldbreak = getvect(CTRLBRK). /* /* /* /* /* get ctrl break setting */ turn off ctrl break logic */ get interrupted dta */ set resident dta */ swap psps */ /* call the TSR program here */ /* reset interrupted psp */ /* reset interrupted dta */ /* reset critical error */ /* reset ctrl break */ /* reset interrupted stack */ } /* -----. _SS = myss. interrupted_psp(). oldcrit = getvect(CRIT). setvect(CRIT. /* if the transient copy */ .*/ if (blkseg == _psp) break. /* --. 1). enable().

_BX = _psp + 1. } adr++.*/ static void unload() { if (getvect(DISK) == (void interrupt (*)()) newdisk) if (getvect(KYBRD) == newkb) if (getvect(INT28) == new28) if (getvect(TIMER) == newtimer) resterm(). adr) == _psp + 1) /*---. geninterrupt(DOS). set phoney psp ------.matches psp.this is a DOS 2.find address of PSP (DOS 2.*/ _AH = 0x50. /* ------. sg++) if (*sg != peekb(blkseg+df.reset the original psp -----. } mcbs += peek(mcbs.*/ _AH = 0x50.search for matches on the psp in dos -------. } enable().x psp placeholder ----*/ psps[pspctr++] = adr. if (*sg == '\0') /*.did matched psp change to the phoney? ----. { . 3) + 1. geninterrupt(DOS). /* ---. /* --------.*/ while (pspctr < 2 && (unsigned)((dosseg<<4) + adr) < (mcbseg<<4)) { if (peek(dosseg. _BX = _psp. /* ----. adr) == _psp) { /* -----.unload the rsident program --------.TSR is already resident -*/ return TRUE.*/ if (peek(dosseg. disable().*/ static void pspaddr() { unsigned adr = 0.132 A to Z of C for (sg = signature. } /* -------. (unsigned)sg)) break.x) ----------. *sg. } } return FALSE.

You can see how the TSR template (Tsr.9 PC-PILOT In the last section we have seen the TSR Template that will be very useful for writing any TSR software. olddisk). oldkb). TextClr = 0x0f.c) is used in Pcpilot main program. setvect(DISK.EXE. In this section. } /* --------. setvect(INT28.h> #include #include #include #include #include int int int int BorderClr = 0x09. I just present the main program only.*/ static void resterm() { unsigned mcbs = mcbseg.another TSR is above us.A to Z of C 133 return. setvect(KYBRD. oldtimer). <stdio. 1) == _psp) freemem(mcbs+1). /* restore the interrupted vectors */ setvect(TIMER. /* obliterate the signature */ *signature = '\0'. 3) + 1. } } 27. old28). TitleClr = 0x0c.TSR unload function ----------. 0) == 0x4d) { if (peek(mcbs. /* walk through mcb chain & release memory owned by the TSR */ while (peekb(mcbs. . /* */ PCPILOT.This is the main( ) module for PCPILOT.h> <dos. It should be compiled in the small or tiny memory model.h> <stdlib.h> <scr. } /* --. FooterClr = 0x0b. cannot unload --. mcbs += peek(mcbs.*/ putch(7).h> <kbd.C .

void TitleScreen(void). 11. /* To UnInstall TSR */ void main(int argc. static int videomode(void). } MENU. extern unsigned scancode[]. extern unsigned _stklen = 1024. void Initialize(void). " Hex/Dec/Binary ". } typedef struct { char *str. extern int unloading. tsr(PcPilot. int Row = 0. 14. /* Alt(8) . " Color Codes ". " Printer Setup ". int Code = 0. " Keyboard Codes ".134 A to Z of C int HighlightClr = 0x4f. keymask[]. unsigned long NumIdx = 0L. *scancode= 76. 13. return. void PcPilot(void). if (tolower(argv[0][1]) == 'x') { Initialize(). Col = 0. extern unsigned _heaplen = 12288. 10. " Box Characters ". " Ruler ". 8. int ClrIdx = 0x00. int y. } } Initialize(). int BoxIdx = 0. PcPilot(). char *argv[]) { while (--argc > 0) { ++argv. if (**argv != '-') break. /* #define DEBUG */ char signature[] = "PC-PILOT".'5'(76) on the keypad */ *keymask = 8. 9. TitleScreen). 12. MENU m[] = { " Ascii Table ". /* /* /* /* /* For For For For For Ascii() BoxCodes() ColorCodes() BaseConvert() Ruler() */ */ */ */ */ .

case DN: case RIGHT: HighLight(0). Idx = 0. oldy. static void HighLight(int code). int K. break. int oldx. ScrSetCur(oldx. &oldy. if (--Idx < 0) Idx = 8. 16 }. break. case PGUP: case HOME: HighLight(0). 15. switch (K = KbdGetC()) { case UP: case LEFT: HighLight(0). static void DrawMenu(void).) { HighLight(1). 0). break. static void ExecuteMenuOptions(int index). int Idx = 0. for (. break.A to Z of C 135 " Uninstall " Exit ". HideCur(). case PGDN: case END: HighLight(0). ScrPush(). case RET: if (Idx == 7 || Idx == 8) { if (Idx == 7) unloading = 1. ScrPop(1). void PcPilot() { ScrGetCur(&oldx. 0). oldy. } . if (++Idx > 8) Idx = 0. return. DrawMenu().. Idx = 8. ".

break. ScrPop(1). break. break. ScrPop(1). ScrPush(). break. } break. case 'e': Idx = 8. case 'u': Idx = 7. case 'k': Idx = 3. break. case 'c': Idx = 5. default: if ((K = K&0x00ff) != 0) { if (!strchr("abhkrcpue". case ESC: ScrPop(1). DrawMenu(). } HighLight(1). return. oldy.136 A to Z of C if (Idx == 4) { ScrPop(1). break. } } } . 0). Ruler(). ScrPush(). case 'b': Idx = 1. return. case 'r': Idx = 4. case 'p': Idx = 6. Ruler(). break. break. ExecuteMenuOptions(Idx). } ExecuteMenuOptions(Idx). unloading = 1. 0). switch (tolower(K)) { case 'a': Idx = 0. tolower(K))) break. ScrSetCur(oldx. oldy. ScrSetCur(oldx. DrawMenu(). default : continue. case 'h': Idx = 2. HighLight(0).

return.m[Idx]. "%s".17. case 1: BoxCodes(). TextClr. case 7: return. } } static void ExecuteMenuOptions(int index) { switch (index) { case 0: Ascii().str). case 6: PrintCodes()."╞════════════════╡"). return. FooterClr. break. FooterClr. } static void HighLight(int code) { switch (code) { case 0: PutStr(32. PutStr(33. i++) { PutStr(32. TitleClr. PutStr(31. m[i]. case 2: BaseConvert(). m[Idx].7.y.8+i. case 5: ColorCodes().25).BorderClr. return. " PC . for (i=0. m[Idx]. "%c". 2. } } . case 4: return.8+i. 24.5.y.FooterClr.str[1]). m[Idx]. "%s". PutStr(31. ~TextClr & 0x7f. m[Idx].str[1]).19. BorderClr). return. m[i]. break.48.PILOT ")." %c %c <Esc> exits". "%c". PutStr(33.str). return.m[Idx]. return. ~FooterClr & 0x7f. "%c". case 1: PutStr(32.A to Z of C 137 static void DrawMenu() { register int i. PutStr(32. PutStr(33.m[Idx]. case 3: KeyCodes().18.y. } HighLight(1).y.6.str[1]). BorderClr. i<9. PutStr(32.m[Idx]. TextClr.str). ShadowBox(31. "%s"."╞════════════════╡").

ah = 15. TextClr. PutStr(44. " PC . Write a Screen Thief utility. Suggested Projects 1. PutStr(19. } { } static int videomode() { union REGS r. ShadowBox(18." Alt-5 (keypad) PutStr(23.PILOT PutStr(19.FooterClr. &r) & 255.15. vmode = videomode().\n"). exit(1). . "To Activate"). r. TextClr = 0x07. } static void TitleScreen() { Cls(). Screen Thief will store the screen into BMP or GIF or JPEG.15.138 A to Z of C static void Initialize() { int vmode. Depending upon the mode you set. TitleClr. if (VideoMode == MONO) { BorderClr = 0x0f.16. } "). when you load the TSR.15. when a hotkey is pressed.0).18.9. ").h." Programmer's Pop-Up PutStr(19. TitleClr." FREEware written by Tom Grubbe PutStr(19. return int86(0x10. The Screen Thief will capture the screen.8. TextClr. 2. TextClr. if ((vmode != 2) && (vmode != 3) && (vmode != 7)) printf("Must be in 80 column text mode.13. ScrSetCur(0. BorderClr). "). &r.12.10. ").59. "Press"). "). HighlightClr = 0x70. } InitScr()." Released to the Public Domain 01-12-90 PutStr(19. FooterClr = 0x0f. TitleClr = 0x0f. TextClr.