[1] Significa che lo stato dello hardware, del DOS e del programma in foreground non è conosciuto al momento dell'attivazione.

[2] Considerazioni analighe valgono con riferimento ai device driver.

[3] Tale valore può essere utilizzato da un programma batch mediante l'istruzione DOS "IF ERRORLEVEL..." (vedere quanto detto circa la funzione main()).

[4] Le argomentazioni esposte sono valide per tutte le variabili globali utilizzate dalle routine residenti: non solo, dunque, quelle gestite in comune con le routine transienti.

[5] Questo algoritmo, a differenza del precedente, è applicabile anche ai .COM.

[6] Questo campo dello header, spesso chiamato maxalloc, esiste, presumibilmente, in previsione di future versioni multitask/multiutente del DOS. Attualmente esso è poco utilizzato: il linker gli assegna per default il valore (fittizio) di 0FFFFh, cioè 65535 paragrafi. Se si desidera che l'area di RAM allocata al programma dal DOS sia limitata all'indispensabile è sufficiente scrivere il valore 1 nel campo maxalloc. Il Microsoft Linker dispone, allo scopo, dell'opzione /CPARMAXALLOC: o /CP:. Il comando

link my_prog.obj /CP:1

raggiunge lo scopo. In TURBO C occorre agire dall'interno del codice, limitando al valore desiderato la dimensione in byte dello heap attraverso la variabile globale _heaplen:

    ....
    _heaplen = 8000;       // e' un valore di esempio
    ....

Inoltre maxalloc può essere modificato in qualunque file .EXE con il programma EXEMOD.EXE:

exemod my_prog.exe /MAX 1

consente di ottenere il risultato voluto. Vi è infine, per gli amanti del brivido, la possibilità di intervenire sul file eseguibile con un programma in grado di effettuare l'editing esadecimale del codice compilato.

[7] La dichiarazione del prototipo di Jolly() e l'opzione ­Tm2 non sono necessari se la Jolly() è definita prima di tutte le funzioni residenti che ne utilizzano il nome come puntatore ai dati residenti: in tal caso è necessario calcolare il numero di paragrafi residenti basandosi sull'indirizzo della prima funzione non residente definita nel codice. La semplificazione così introdotta rende però impossibili alcune ottimizzazioni. Si pensi al caso in cui lo spazio necessario ai dati residenti non sia noto al momento della compilazione, ma sia determinato in fase di installazione: la Jolly() deve riservare tutto lo spazio necessario nel caso di maggiore "ingombro" possibile dei dati. Solo se essa è definita dopo tutte le funzioni residenti è possibile limitare la RAM allocata al minimo indispensabile.

[8] Tra queste ultime, per citarne una forse tra le più utilizzate, la fopen(). Si veda la gestione dei file nei TSR per alcuni particolari interessanti.

[9] Analoghe considerazioni valgono per blocchi di memoria allocati durante le fasi di popup del TSR: essi non vengono protetti quando il controllo è ceduto all'applicazione interrotta.

[10] A condizione che sia la parte transiente ad utilizzarle. E' bene che la parte residente non faccia uso di funzioni di libreria.

[11] In realtà il DOS mette a disposizione di ogni programma una copia dell'environment originale (quello generato dall'interprete dei comandi).

[12] Lo stack, tanto vale ripeterlo ancora una volta, è sempre gestito secondo la modalità LIFO (Last In, First Out): l'ultimo dato o, per meglio dire, l'ultima word spinta su di esso è la prima a esserne estratta. Di qui il nome, che significa "pila".

[13] O, almeno, in modo tale che funzioni.

[14] I due registri, infatti, devono essere salvati senza usare lo stack, perché prima del loro salvataggio è ancora utilizzato quello del processo interrotto: in questo caso, l'obiettivo principale non è tanto quello di evitare l'uso di risorse che non appartengono al TSR, ma quello di ripristinare i valori originali in uscita dalla new1Ch(). Infatti, se SS e SP venissero salvati sullo stack, dovrebbero essere estratti dal medesimo mediante l'istruzione POP, la quale, però, non farebbe altro che prelevare una word dall'indirizzo espresso proprio dalla coppia SS:SP: essendo stata quest'ultima modificata per puntare allo stack del TSR, l'estrazione avverrebbe ad un indirizzo diverso da quello al quale si trovano i due valori salvati.

[15] Non solo una ricorsione: è facile immaginare il caso in cui l'int 1Ch, chiamato in modo asincrono dall'int 08h (interrupt hardware ad elevata priorità) interrompa un'altra routine residente: se questa utilizza il medesimo stack locale, la frittata è fatta.

[16] Supponiamo di avere scritto il sorgente di un TSR, che per comodità battezziamo MY_TSR.C, le routine residenti del quale utilizzano la funzione di libreria int86(). Con l'opzione "­c" del compilatore si disattiva l'invocazione automatica del linker da parte di BCC.EXE; il seguente comando produce pertanto il solo file oggetto MY_TSR.OBJ, per il modello di memoria small (default):

bcc -c my_tsr

Dal momento che la int86() invoca, a sua volta, la funzione __IOerror() occorre estrarre entrambi i moduli dalla libreria CS.LIB:

tlib cs.lib * int86 ioerror

Si ottengono così INT86.OBJ e IOERROR.OBJ; poiché il modulo di startup per il modello small è C0S.OBJ, possiamo effettuare il linking con il comando:

tlink c0s int86 ioerror my_tsr,my_tsr,my_tsr,cs

Il linker consolida, nell'ordine, il modulo di startup, quelli relativi alle funzioni di libreria e il codice di MY_TSR, producendo il file eseguibile MY_TSR.EXE e il map file (file contenente l'elenco degli indirizzi e delle dimensioni dei simboli pubblici che compongono il codice) MY_TSR.MAP: le funzioni esterne ai moduli oggetto sono ricercate nella libreria CS.LIB. Si noti che è indispensabile specificare al linker tutte le librerie da utilizzare.

[17] Il contenuto delle locazioni di memoria originariamente riservate a quelle variabili è, in pratica, casuale. Inoltre, se una funzione di libreria invocata dalle routine residenti tentasse di modificarlo, rischierebbe di "obliterare" il codice e/o i dati di altre applicazioni. Tra le variabili globali definite dallo startup code vi è, ad esempio, errno.

[18] Il prefisso N_ indica che il frammento di codice è stato compilato per un modello di memoria "a codice piccolo": compilando con i modelli medium, large e huge i puntatori (far) sarebbero F_LMUL@, F_LDIV@ e F_LMOD@.

[19] Molte funzioni di libreria si servono dell'int 21h: tra esse quelle relative alla gestione dello I/O con gli streams, i files e la console (printf(), fopen(), open(), getch(), etc.).

[20] Notizie di carattere generale sugli interrupt si trovano nel capitolo dedicato al loro utilizzo.

[21] Il servizio 51h (dell'int 21h) è identico, ma non documentato.

[22] Comprendenti, ovviamente, gestori per i due interrupt citati.

[23] Attenzione, però: eventuali sequenze CTRL­BREAK o CTRL­C digitate durante l'attività in foreground del TSR si "scaricano" sull'applicazione interrotta non appena le viene restituito il controllo.

[24] In realtà, a condizione che la RAM ad esso allocata non sia stata liberata, la disattivazione di un TSR potrebbe anche essere realizzata mediante la sospensione integrale di ogni sua attività: in tal caso la riattivazione (reinstallazione dei vettori) dovrebbe essere effettuata dalla parte transiente (il programma deve essere nuovamente invocato al prompt del DOS e comunicare in modo opportuno, ad esempio via  int2Fh, con la parte residente).

[25] Si pensi, per esempio, ad un programma di redirezione dell'output di altre applicazioni, progettato per visualizzare sul monitor tutto ciò che è diretto alla stampante, fino al verificarsi di un evento particolare (come la pressione di uno hotkey prestabilito) che ne interrompa l'azione: da quel momento esso dovrebbe limitarsi a monitorare la tastiera, per riprendere l'attività di redirezione quando intercetti il medesimo hotkey o un altro evento prescelto dal programmatore.

[26] Un listato di _restorezero() è presentato con riferimento ai device driver.

[27] Per la precisione, si tratta dei vettori 00h, 04h, 05h e 06h. Gli ultimi tre possono essere modificati dalle funzioni appartenenti al gruppo della signal(), se utilizzate nel programma. Ne consegue, tra l'altro, che se le routine residenti fanno uso di tali funzioni devono provvedere autonomamente a salvare e a rispristinare i vettori al momento opportuno.

[28] D'altra parte dovrebbe essere ormai evidente che gli interrupt sono il solo mezzo che i TSR hanno a disposizione per interagire con l'ambiente.

[29] Una possibile eccezione è costituita dall'utilizzo dei vettori di interrupt come puntatori.

[30] Va però osservato che disattivazione e riattivazione possono essere gestite senza modifiche ai vettori di interrupt: può essere sufficiente un flag opportunamente controllato in testa ai gestori di interrupt che devono essere attivi o inattivi: con un piccolo sacrificio in termini di eleganza, si evitano i problemi di cui sopra.

[31] Meglio ripetere ancora una volta che si tratta di dati salvati in fase di installazione nello spazio riservato da una o più funzioni fittizie. Va ricordato che la parte del TSR residente in memoria e la parte transiente invocata in un secondo tempo, pur essendo due parti di un medesimo programma, si comportano in realtà come se fossero due programmi separati e indipendenti.

[32] I puntatori huge sono automaticamente normalizzati dal compilatore. Ciò significa che la loro parte offset (i 16 bit meno significativi) contiene esclusivamente lo spiazzamento all'interno del paragrafo referenziato dalla parte segmento (i 16 bit più significativi).

[33] I blocchi possono essere più di uno qualora la parte residente gestisca dei buffer.

[34] Anche il DOS, a partire dalla versione 5.0, è dotato di tale capacità.

[35] La parte segmento di un puntatore far, costruito mediante due registri a 16 bit, esprime indirizzi di paragrafo (ogni unità conta 16 byte). L'indirizzo FFFF:000F può dunque essere scritto FFFFFh, cifra equivalente al Mb. L'offset non può essere ulteriormente incrementato, poiché un offset di 10h (16 decimale) rappresenta in realtà un incremento di uno della parte segmento, con offset zero.

[36] La keep() invoca a sua volta la _restorezero(), la quale copia nuovamente i vettori originali nella tavola: l'operazione è inutile e potrebbe risultare dannosa, nel caso in cui il programma TSR abbia installato nuovi vettori per gli interrupt 00h, 04h, 05h e 06h.

[37] Un listato di _restorezero()è presentato con riferimento ai device driver.

[38] FINDFIRST mediante File Control Block. Modificando questo servizio si modifica, tra l'altro, il comportamento del comando DIR, che lo utilizza.

[39] I 15 esplicitamente definiti dall'istruzione DB, più i 5 di codice generato automaticamente dal compilatore (opcodes per PUSH BP; MOV BP,SP; POP BP). L'ordine di memorizzazione dei dati è: vettore originale dell'int 21h (doubleword); vettore originale dell' int2Fh (doubleword); indirizzo di new21h() (doubleword); indirizzo di new2Fh() (doubleword); indirizzo di segmento del PSP del TSR (word); byte di stato (attivato/disattivato) del TSR (word).

[40] La tavola dei vettori si trova all'indirizzo 0:0. Dal momento che ogni vettore è un indirizzo far, ed occupa pertanto 4 byte, per ottenere l'offset di un vettore all'interno della tavola è sufficiente moltiplicare il numero dell'interrupt per 4.

[41] Backwords è un termine nato dall'assonanza con backwards (all'indietro) e significa, letteralmente, a parole rovesciate. In effetti, tutti i dati numerici sono scritti in RAM a rovescio, in modo tale, cioè, che a indirizzo inferiore corrisponda la parte meno significativa della cifra. Ad esempio, il numero 0x11FF024A (che potrebbe rappresentare un puntatore far a 11FF:024A) viene scritto in RAM in modo da occupare 4 byte nel seguente ordine: 4A 02 FF 11.