[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()).
[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.
[10] A condizione che sia la parte transiente ad utilizzarle. E' bene che la parte residente non faccia uso di funzioni di libreria.
[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.
[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 CTRLBREAK o CTRLC 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).
[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.
[29] Una possibile eccezione è costituita dall'utilizzo dei vettori di interrupt come puntatori.
[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.
[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.