Dentro il Sistema

All'interno del Kernel di Os/2

Claudio Umana
Versione originale in Inglese di David C. Zimmerli
apparso su  Edm2 
tradotto liberamente da Claudio Umana


Introduzione

Il mio scopo in questo articolo è quello di condurre gli utenti e sviluppatori Os/2 in un figurativo "viaggio al centro della terra" - una spedizione dentro i poco noti, ma fondamentali, meccanismi del kernel di sistema.

Il principale strumento usato nella ricerca per questo articolo è stato il Kernel Debugger (KDB), materiale utilmente fornito dalla IBM Corporation, e l'allegato volume 4 dell'"Os/2 Debugging Handbook". Nel mio precedente articolo, "Adventures in Kernel Debugging" (EDM/2 Nov. 1996, http://www.edm2.com/0410/kdb.html) , ho fornito una introduzione al setup e uso del KDB. Per ottenere il massimo dal presente articolo, dovreste conoscere questo materiale nonché avere una discreta conoscenza dell'architettura dell'Intel 80x86 e le caratteristiche della gestione della memoria.

Per una dettagliata visione del kernel è necessario, ovviamente, fare riferimento a una specifica versione di Os/2 dal momento che il codice del kernel è stato modificato e aggiornato, assieme con gli altri componenti del sistema, nelle varie versioni e nei livelli di fixpack. Ho scelto Os/2 2.1 per Windows (la 6.514), come il minimo denominatore comune dei sistemi che probabilmente sono ancora in uso. Dal momento che la maggior parte degli utenti staranno usando versioni più nuove, le locazioni della memoria, le dimensioni dei file e così via, potranno essere differenti da quelli mostrati qui. Ma la maggioranza delle strutture dati e i componenti dei codici non sono cambiati in modo apprezzabile.
 
 

Componenti del kernel

La maggior parte del codice del kernel è nel file OS2KRNL, il quale occupa nel sistema dell'esempio circa 730K (nella versione al dettaglio) o 1M (versione di debug). La restante parte del codice è costituito da diversi file più piccoli: OS2LDR (28K retail, 37K debug), SCREEN01.SYS, PRINT01.SYS, KBD01.SYS, e CLOCK01.SYS (ognuno da 3-30K). Dal momento che il codice sorgente per i device drivers di base sono forniti dall' IBM DDK Developer Connection CD-Roms, allora non indagheremo su essi.

Le DLL di sistema DOSCALL1.DLL, KBDCALLS.DLL, QUECALLS.DLL, SESMGR.DLL, OS2CHAR.DLL e così via, non sono parte del Presentation Manager ma non sono esattamente neanche parte del kernel. Molto di questo "codice" è semplicemente l'avanzato ingresso alle API Dos* del kernel. Il codice SESMGR merita alcuni commenti, ma verrà trattato in un futuro articolo per non appesantire quest'articolo di più.

L'OS2KRNL è un file eseguibile-segmentato a 32 bit nel formato standar "LX", e come tale può essere esaminato con un'utility di formattazione di un file EXE. Ma è più facile e più informativo usare il comando "lg" del KDB, che legge file di simboli forniti da IBM. Questo rivela i seguenti segmenti:
 
 

##lg os2krnl

os2krnl:
0400 : 00000000 DOSGROUP
1100 : 00000000 DOSCODE
0120 : 00000000 DBGCODE
0128 : 00000000 DBGDATA
0030 : 00000000 TASKAREA
0138 : 00000000 DOSGDTDATA
0140 : 00000000 DOSINITDATA
0148 : 00000000 DOSINITR3CODE
%00110000 DOSMVDMINSTDATA
%00120000 DOSSWAPINSTDATA
%FFEFF000 DGRROUP
0150 : 00000000 DOSHIGH2CODE
0158 : 00000000 DOSHIGH3CODE
0160 : 00000000 DOSHIGH4CODE
%fff3f000 DOSHIGH32CODE
##

 
 

Di questi segmenti, i segmenti di codice a 16 bit sono: DOSINITR3CODE, DOSCODE, DOSHIGH2CODE, DOSHIGH3CODE, DOSHIGH4CODE, e DBGCODE. I segmenti dati a 16 bit sono DOSGROUP, DOSINITDATA e DBGDATA. (I segmenti DBGCODE e DBGDATA esistono solo per supportare il KDB e non sono contenuti nel kernel della versione retail.) Il segmento codice a 32 bit è DOSHIGH32CODE e il segmento dati a 32 bit è il DGROUP. 

I segmenti "DOS" riguardano, naturalmente, non il DOS ma il Programma di Controllo, il successore del modo protetto del DOS. I segmenti "HIGH" sono quelli che saranno caricati in "alto", cioè oltre l'indirizzo di 1Mb della memoria fisica. 

Quattro segmenti richiedono un commento speciale. TASKAREA è una parte del segmento dati che semplicemente mappa il corrente PDTA (Area Dati per processo), come descritto nel Debugging Handbook".DOSGDTDATA mappa il GDT di sistema, che contiene porte di chiamata per gli accessi alle API del kernel e parametri di ingresso per i segmenti usati da alcune parti del codice del kernel a 16 bit. Il file di simboli ha nomi per la maggior parte di questi segmenti, che esprimono qualcosa circa la loro funzione (digita "ls dosgdtdata" al prompt del KDT). Il DOSMVDMINSTDATA e DOSSWA­PINSTDATA, come indicano i loro nomi, supportano multiple VDM (Virtual Dos Machines), come sarà discusso nella sezione apposita. 

I seguenti diagrammi forniscono un'idea delle relative dimensioni di questi segmenti. (Comunque i segmenti a 16 e 32 bit non sono rappresentati con la stessa scala.) Le aree in scuro rappresentano parti del codice scartate dal sistema dopo il completamento della sua inizializzazione.
 
 
 
 

I segmenti codice a 16 bit sembrano essere codificati a mano in assembler, mentre il segmento di codice a 32 bit è largamente compilato in codice C. Qui di seguito c'è un com­pendio per la maggior parte dei codici sorgente di ogni segmento codice.
 
 

 
 

 
 

La sequenza di start-up di Os/2

In questa sezione daremo uno sguardo da vicino al meccanismo di bootstrap del kernel. Questo tipo di analisi è spesso utile per la diagnosi del fallimento dell'hardware o della corruzione dei file che possono causare il fallimento della fase di boot di Os/2. Per avere più dettagli su questa fase del sistema, confronta anche la sezione "Remote IPL/Bootable IFSnel file "Installable File System" (IFS.INF) su Hobbes.

Come sa ogni scolaro inglese, quando un computer IBM compatibile viene riavvia­to, l'appropriato boot sector è letto nella locazione di memoria 07c0:0. L'esecuzione inizia in modo reale a questo indirizzo, che è giusto sotto i 32K. (Questa locazione venne scelta così che i primi IBM PC potessero teoricamente operare, sebbene ovviamente non possano eseguire Os/2 con così poca memoria.) 

Il boot sector inizia con 3 byte di istruzioni JMP, seguiti dalle strutture dati dei parametri dei drive che sono comuni sia a Os/2 che al DOS. Questa struttura dati è documentata in numerosi libri sul DOS e sulla struttura del PC e qui non saranno ulteriormente descritti. (Vedi per esempio, Ray Duncam, Advanced MSDOS Programming, 2d. ed. 1988, Microsoft Press, p.180.) Il boot sector carica un piccolo file (1K) chiamato OS2BOOT all'indirizzo 0800:0, e l'OS2BOOT a sua volta carica OS2LDR all'indirizzo 2000:0. Ovviamente il caricamento di questo file è fatto con le chiamate in modo reale del BIOS, dal momento che nessun file system è ancora disponibile. 

Il file OS2LDR è uno delle parti più oscure, e meno documentate, del kernel. Non ci sono file di simboli per questo codice, non possiamo neppure usufruire delle informazioni del Kernel Debugger, dal momento che questo risiede nella versione debug del OS2KRNL la quale non è stata ancora caricata. Comunque la versione debug di OS2LDR genera abbondanti dati nel terminale di debug, e attraverso l'analisi di questo output assieme al codice disassemblato, possiamo avere una buona idea delle attività di questo modulo. 

Iniziamo con il test dei chip e l'inizializzazione del sistema di base - interrogando la disponibili­tà della memoria e i drive installati, testando la velocità di clock della CPU e memorizzando i modi video disponibili. Durante ciò prenderemo alcune criptiche indicazioni di progresso sul terminale di debug:
 

IODel 000a
 

Il ritardo di I/O - il tempo di attesa tra le istruzioni di IN e OUT - è stato settato in base 10 sulla velocità di questo processore.
 

Int12 st 00000000 end 0009f7ff

Int1588 st 00100000 end 03ffffff

 

Il BIOS riporta 640K (approssimato a 9f7ff in esadecimale) della memoria in modo reale, e 64Mb (03ffffff in esadecimale) di memoria estesa.
 

CPUUsable = 00000001

CPUWeAre = 00000001

 

La CPU è un 80486 (0=396, 1=486, ecc.)
 

SLRrm len a 342
 

La lunghezza del segmento di OS2LDR, includendo lo stack, è 0a342 in esadecimale.
 

cgvi
 

Noi stiamo chiamando la "get video modes" routine.
 

cldr
 

E ora passiamo alla attività per la quale è stato nominato il loader: il caricamento dell'OS2KRNL, o se questo file non viene trovato, l'OS2KRNLI, l'install kernel.

La routine del loader per primo ci fornisce un inventario dei segmenti dell'OS2KRNL:
 

ob flags oi-flags paddr/sel glp laddr/fladdr msz/vsz Object name

01 rw--sfTLaA 00005063 004000/0400 0001 ffe00000/ffe00000 009000/00c5b3 DOSGROUP
02 r-x-sfTLa- 00001065 011000/1100 000a ffe0d000/ffe0d000 00c000/00bfb0 DOSCODE
03 r-x-sf-LaA 00005025 01d000/0120 0016 ffe19000/ffe19000 00b000/00aeea DBGCODE
04 rw--sf-LaA 00005023 028000/0128 0021 ffe24000/ffe24000 009000/0085c0 DBGDATA
05 rw--sN-LaA 0000d0a3 031000/0130 002a ffe2d000/ffe2d000 010000/010000 stack
06 rw--sN-LaA 0000d023 041000/0138 003a ffe3d000/ffe3d000 002000/001e50 DOSGDTDATA
07 rw--sf-LaA 00005023 043000/0140 003c ffe3f000/ffe3f000 002000/004b4e DOSINITDATA
08 r-x-sf-LaA 00005025 048000/0148 003e ffe44000/ffe44000 002000/001fe8 DOSINITR3CODE
09 rw-BPf-h-- 00002213 100000/0000 0040 ffefc000/00110000 001000/0001ac DOSMVDMINSTDATA
0a rw-BPf-h-- 00002013 101000/0000 0041 ffefd000/00120000 002000/001948 DOSSWAPINSTDATA
0b rw-Bsf-h-A 00006033 103000/0000 0043 ffeff000/ffeff000 012000/015326 DGROUP
0c r-x-sf-ha- 00001035 119000/0150 0055 fff15000/fff15000 010000/00fdcc DOSHIGH2CODE
0d r-x-sf-ha- 00001035 129000/0158 0065 fff25000/fff25000 00a000/009a08 DOSHIGH3CODE
0e r-x-sf-ha- 00001035 133000/0160 006f fff2f000/fff2f000 010000/00f304 DOSHIGH4CODE
0f r-xBsf-h-- 00002035 143000/0000 007f fff3f000/fff3f000 081000/080628 DOSHIGH32CODE

 

La colonna all'estrema destra della tabella qui sopra, contiene mie annotazioni, e correla gli oggetti in elenco con i segmenti discussi nella sezione precedente.

Per chiarezza, il termine "oggetto" qui indica l'equivalente in un modulo eseguibile a 32 bit di un "segmento" in un modulo a 16 bit. Un oggetto è più complesso di un segmento dal momento che consiste di pagine che possono essere lette e subire lo swap indipendentemente l'una dalle altre, e questo potrebbe essere il perché dell'uso del nuovo termine. Ma per la maggioranza dei propositi, un oggetto è semplicemente un segmento che non è limitato alla dimensione di 64K. Userò i due termini in maniera più o meno intercambiabile.

Ad ogni modo, la tabella qui sopra mostra attributi, indirizzi e selettori lineari e fisici e le dimensioni per ogni segmento nell'OS2KRNL. L'attuale lettura nel file procede per primo caricando il segmento "high" nella memoria bassa, poi posizionandoli nel loro proprio posto usando la funzione del BIOS Int 15h/87h "Move extended memory block". (Ricordiamo che ancora stiamo eseguendo in modalità reale!) Al secondo passo sono caricati i segmenti della memoria bassa.

Chiudendo il discorso, OS2LDR ci dà la mappa della memoria fisica:
 

pa=00000000 sz=00001000 va=00000000 sel=0000 fl=2000 of=00000003 ow=0000 Real mode IVT

pa=00001000 sz=00002300 va=ffef9000 sel=0100 fl=2014 of=00001004 ow=ff6d OS2LDR 32bit invio int
pa=00004000 sz=0000c5b3 va=ffe00000 sel=0400 fl=2144 of=00005063 ow=ffaa DOSGROUP
pa=00011000 sz=0000bfb0 va=ffe0d000 sel=1100 fl=2244 of=00001065 ow=ffaa DOSCODE
pa=0001d000 sz=0000aeea va=ffe19000 sel=0120 fl=2344 of=00005025 ow=ffaa DBGCODE
pa=00028000 sz=000085c0 va=ffe24000 sel=0128 fl=2444 of=00005023 ow=ffaa DBGDATA
pa=00031000 sz=00010000 va=ffe2d000 sel=0130 fl=2544 of=0000d0a3 ow=ffaa stack
pa=00041000 sz=00001e50 va=ffe3d000 sel=0138 fl=2644 of=0000d023 ow=ffaa DOSGDTDATA
pa=00043000 sz=00004b4e va=ffe3f000 sel=0140 fl=2744 of=00005023 ow=ffaa DOSINITDATA
pa=00048000 sz=00001fe8 va=ffe44000 sel=0148 fl=2844 of=00005025 ow=ffaa DOSINITR3CODE
pa=0004a000 sz=00000ac8 va=00000000 sel=4a00 fl=2001 of=00000000 ow=0000 OS2DUMP
pa=0004b000 sz=00049000 va=00000000 sel=0000 fl=2002 of=00000000 ow=0000 unused
pa=00094000 sz=0000a762 va=ffeee000 sel=0000 fl=2054 of=00001003 ow=ffab OS2LDR (relocated)
pa=0009f000 sz=00000800 va=00000000 sel=0000 fl=2002 of=00000000 ow=0000 unused
pa=0009f800 sz=00000800 va=ffeed800 sel=0000 fl=2004 of=00000000 ow=ff37 romdata
pa=000a0000 sz=00060000 va=00000000 sel=0000 fl=0001 of=00000000 ow=0000 video/BIOS area
pa=00100000 sz=000001ac va=ffefc000 sel=0000 fl=0944 of=00002213 ow=ffaa DOSMVDMINSTDATA
pa=00101000 sz=00001948 va=ffefd000 sel=0000 fl=0a44 of=00002013 ow=ffaa DOSSWAPINSTDATA
pa=00103000 sz=00015326 va=ffeff000 sel=0000 fl=0b44 of=00006033 ow=ffaa DGROUP
pa=00119000 sz=0000fdcc va=fff15000 sel=0150 fl=0c44 of=00001035 ow=ffaa DOSHIGH2CODE
pa=00129000 sz=00009a08 va=fff25000 sel=0158 fl=0d44 of=00001035 ow=ffaa DOSHIGH3CODE
pa=00133000 sz=0000f304 va=fff2f000 sel=0160 fl=0e44 of=00001035 ow=ffaa DOSHIGH4CODE
pa=00143000 sz=00080628 va=fff3f000 sel=0000 fl=0f44 of=00002035 ow=ffaa DOSHIGH32CODE
pa=001c4000 sz=00e3c000 va=00000000 sel=0000 fl=0002 of=00000000 ow=0000 unused
pa=01000000 sz=00000000 va=00000000 sel=0000 fl=0001 of=00000000 ow=0000 unused
pa=01000000 sz=03000000 va=00000000 sel=0000 fl=0002 of=00000000 ow=0000 unused
pa=04000000 sz=00000000 va=00000000 sel=0000 fl=4000 of=00000000 ow=0000 limite memoria fisica

 

Qui ho ancora aggiunto annotazioni sulla colonna più a destra. La seconda colonna partendo dalla fine è il "System Object Id" per il quale le informazioni possono essere reperite nella sezione 4.6 del Debugging Handbook, volume IV.

Abbiamo settato il chip 8259 PIC così che gli IRQ da 0 a 7 mappano gli interrupt 50h-57h, e gli IRQ da 8 a 0fh mappano gli interrupt 70h-77h:

rPIC

E saltiamo al sysInitializaOS2 nel segmento DOSCODE del OS2KRNL:

j syi

Ora che siamo propriamente nel kernel, noi possiamo procedere con la maggior parte del restante codice con il Kernel Debugger. Una speciale facilitazione del KDB ci permette di tenere premuto "R" , "P", o <space> sul terminale di debug per interrompere il codice di startup o prima della commutazione al modo protetto, dopo la commutazione al modo protetto ma prima del loader e dell'inizializzatore di pagina, o rispettivamente dopo di essi. (Assicurarsi di impostare la frequenza di lettura della tastiera sul terminale di debug ad un alto valore o altrimenti, i caratteri premuti, potrebbero andare perduti dalla routine polling della porta COM.)

La maggior parte del sistema init del DOSCODE consiste nell'analisi logica del CONFIG.SYS. C'è anche un altro componente chiamato "system init file system" (abbreviato con sifs), usato per leggere nel CONFIG.SYS così come i vari BASEDEV e altri file necessari finché la fase set up non sia terminata. Ogni componente principale del kernel (il loader, il paginatore, schedulatore, e così via) ha una routine di inizializzazione che viene chiamata a questo punto e noi rinviamo alla routine sysiProcess nel segmento DOSINITR3CODE.

Allora syiProcess carica e inizializza i regolari (non base) device drivers installabili, carica le DLL e avvia la shell. Questo è il primo posto dove troviamo disponibili i driver ADD usati dal sistema completamente inizializzato per accedere all'hard disk. Dal momento che una delle prime routine chiamate dal syiProcess è nel modulo inicp.asm (inizializza le codepage), è qui che noi possiamo ricevere l'infame messaggio di errore "Cannot find COUNTRY.SYS", anche se non ci sono problemi con il file COUNTRY.SYS, nel caso che i device drive di base non siano stati installati propriamente.
 

Il codice di accesso ai file

Voglio dare un piccolo sguardo alla sequenza di chiamate che connettono un file DOS* di I/O con il codice di accesso all'hardware. Per più dettagli si prega di consultare il "Storage Device Driver reference" trovato nel DDK e il file IFS.INF di Hobbes. 

Come prima situazione, chiameremo il DOS32READ per la manipolazione di un file sulla partizione FAT di un hard disk SCSI. Questo è un punto di ingresso nel DOSCALL1 il quale passa poco dopo attraverso una chiamata di accesso al kernel a 32 bit e invoca FS32IREAD. Per l'accesso alla FAT, FS32IREAD chiama la h_DOS_Read, una routine a 16 bit, la quale dopo aver accertato che il dato richiesto non è in una precedentemente lettura nel buffer, formula una "lista di richieste" e la invia al device driver OS2DASD.DMD. La lista di richieste è costruita nelle routine _BufReadExecute e _ReqListExecute del DOSHIGH32CODE, e consiste di singole richieste con il codice del comando esteso 1Eh for Read. 

La richiesta specifica il blocco di partenza, il numero di blocchi da leggere, gli indirizzi dei buffer per tenere i dati. OS2DASD.DMD allora chiama l'appropriato device driver *.ADD - per il sistema dell'esempio è il AHA152X.ADD - per accedere all'hardware fisico. 

La seconda situazione è la stessa eccetto il fatto che il file risiede su una partizione HPFS. In questo caso, FS32IREAD aggira le routine della FAT a 16 bit nel DOSHIGH4CODE e chiama invece la FS_READ, punto di accesso all'HPFS.IFS. Il file system HPFS allora si prende cura di bufferizzare i dati e interfacciarsi al modulo OS2DASD.DMD. 

La terza situazione l'abbiamo quando leggiamo un file da floppy disk. Per quanto riguarda il kernel questa situazione è uguale alla prima; comunque, il codice dell'OS2DASD.DMD passerà la richiesta all'IBM1FLPY.ADD (o IBM2FLPY.ADD per macchine micro channel), piuttosto che al AHA152X.ADD. 

Per ultimo, per leggere i dati da un CDRom SCSI, FS32IREAD chiama FS_READ del driver del file system per CDRom CDFS.IFS. CDFS ancora bufferizzerà i servizi, a questo punto invia la lista di richieste a OS2CDROM.DMD, il quale invocherà l'appropriato BASEDEV - nel sistema dell'esempio è il LMS206.ADD per i drive CdRom della Philips.
 
 

Il meccanismo della commutazione di contesto

Generalmente la commutazione di contesto ha luogo ai "margini" delle chiamate delle API di sistema. Prima di tornare dal modo supervisore (kernel) al modo utente, il sistema chiamerà una speciale routine chiamata KMExitKmodeEvents. Questa routine esamina la variabile globale Resched, che indica se altri thread di sufficiente priorità sono pronti per l'esecuzione. Se la Resched è non nulla, il prossimo passo è la routine _tkSchedNext. 

_tkSchedNext invoca lo schedulatore (_SCHGetNextRunner) per decidere quali tra i thread pronti saranno i prossimi a ricevere il timeslice. Alcuni aspetti dello schedulatore, con i suoi stati e transizioni, code di priorità, code di attesa, e così via sono documentate nel Debugging Handbook. Per ora notiamo semplicemente che il _SCHGetNextRunner ritorna nel registro EAX, un puntatore alla TCB del nuovo, o subentrante thread. Questo puntatore diventa il singolo argomento alla routine _PGSwichContext.

Il codice della _PGSwichContext occupa 559 byte nel DOSHIGH32CODE, e vale la pena esaminarlo più a fondo. 

Non possiamo procedere ulteriormente in questo codice nel Kernel Debugging, dal momento che le tabelle di pagina le strutture di sistema sono in stato di transizione di cui il debugger non può dare informazioni. Ma dall'analisi del codice prodotto dal disassemblaggio, possiamo capire le sue operazioni e ottenere significative intuizioni sull'architettura dei processi di OS/2. 

Il percorso che possiamo prendere dipende dal fatto se noi stiamo commutando tra differenti processi o semplicemente tra differenti thread appartenenti allo stesso processo. Se è una commutazione di processo, allora dobbiamo riscrivere la porzione della tabella di pagina corrispondente alla memoria utente (tipicamente sopra i 256Mb) per mostrare i nuovi indirizzi fisici. Una commutazione di processo richiede che anche il puntatore al LDTR sia aggiornato, dal momento che LDT può essere diverso per processi differenti. 

Sia per la commutazione tra thread e sia per quella tra processi, dobbiamo rimappare il segmento TASKAREA (selettore 30), dal momento questo selettore indirizza il TCB e TSD correnti così pure il PDTA. Noi dobbiamo anche aggiornate diverse variabili globali di sistema: _pPTDACur, _TKSSBase, _TKTCBBias, _pTCBCur, _pTSDCur e il puntatore allo stack per i ring 0 e ring 2. 

Per ulteriori dettagli sul meccanismo della commutazione di contesto, vedi pagina 339 del Debugging Handbook, Volume I. 

Ovviamente, è possibile per alcune applicazioni non fare nessuna chiamata al kernel per un lungo periodo di tempo. Forse il programma sta risolvendo una equazione differenziale, facendo una complessa ricerca su stringhe, o altrimenti si sta facendo gli affari propri senza la necessità di fare operazione di I/O o richiedere servizi al kernel. Sarà la routine KMExitKmodeEvents routine a essere aggirata e dobbiamo far sì che tutti gli altri thread stiano in attesa mentre aspettiamo che il programma finisca? 

La risposta, come ci aspettavamo, è no, grazie al 8254 PIT, o Programmable Interval Timer, un chip sulla scheda madre del PC. Al boot, il contatore 0 del chip 8254 è impostato per operare in modo 2 (rate generator mode) per causare un interrupt sull'IRQ0 approssimativamente 18.2 volte al secondo. Similmente agli altri IRQ, questo è manipolato da intIRQRouter nel DOSHIGH32CODE, e su ricezione dell'IRQ0, l'intIRQRouter invoca KMExitKmodeEvents, come sopra. Questo forza l'applicazione a subire la stessa minuziosa schedulazione come se fosse essa stessa a fare una chiamata direttamente al kernel.
 
 

Gestione della memoria

Quando una applicazione invoca la DosAllocMem, il sistema crea un "oggetto memoria" riservando un segmento continuo nello spazio di indirizzamento virtuale privato del processo. Il sistema alloca entrate alla tabella di pagina per l'oggetto e dal momento che ogni ingresso alla tabella di pagina controlla 4Kb di memoria, l'oggetto avrà una dimensione uguale alla dimensione richiesta arrotondata superiormente al più prossimo 4Kb. L'oggetto inizierà sul limite dei 64Kb per permettere che sia indirizzato da un selettore LDT, così ogni chiamata al DosAllocMem consumerà almeno 64Kb di spazio di indirizzamento virtuale. 

Comunque, nessuna memoria fisica o spazio di swap su disco è allocato dal codice del DosAllocMem. Il meccanismo usato è chiamato "commissione pigra": quando si fa un tentativo di leggere o scrivere nell'area della memoria virtuale in questione, sarà generata un'eccezione di pagina (trap 0eh), e le routine di gestione in DOSHIGH32CODE allocheranno memoria fisica e imposteranno il bit di "presenza" nel corrispondente indice della tabella di pagina. 

Un semplice esperimento mostra lo stato prima e dopo nella tabella di pagina come risultato di una chiamata al DosAllocMem. Qui di seguito riporto un programma che permette di fare una chiamata con la richiesta di allocare 00020000h, o 128Kb:
 

eax=0006eb03 ebx=000a0000 ecx=0006eb88 edx=0006ebb0 esi=00000000 edi=00019010

eip=000120f7 esp=0006eb7c ebp=0006ebd4 iopl=2 rf -- -- nv up ei pl nz na pe nc
cs=005b ss=0053 ds=0053 es=0053 fs=150b gs=0000 cr2=00093ffe cr3=001f6000
005b:000120f7 e8385a011a call DOS32ALLOCMEM (1a027b34)
##d ss:esp l 20
0053:0006eb7c 88 eb 06 00 00 00 02 00-13 00 00 00 03 00 00 00 .k..............
0053:0006eb8c 74 34 01 00 d0 eb 06 00-08 00 00 00 00 00 00 00 t4..Pk..........
##

 

E qui ci sono gli ingressi alla tabella di pagina per %120000 e %130000:
 

##dp %120000

linaddr frame pteframe state res Dc Au CD WT Us rW Pn state
%00120000* 02ec1 frame=02ec1 2 0 D A U W P resident
##dp %130000
linaddr frame pteframe state res Dc Au CD WT Us rW Pn state
%00130000* 02ec1 frame=02ec1 2 0 D A U W P resident
##

 

Digitiamo "p", e allora esaminiamo lo stack e ancora le tabelle di pagina:
 

##p

eax=00000000 ebx=000a0000 ecx=0006eb88 edx=0006ebb0 esi=00000000 edi=00019010
eip=000120fc esp=0006eb7c ebp=0006ebd4 iopl=2 -- -- -- nv up ei pl nz na pe nc
cs=005b ss=0053 ds=0053 es=0053 fs=150b gs=0000 cr2=00093ffe cr3=001f6000
005b:000120fc 83c40c add esp,+0c
##d %6eb88 l 20
%0006eb88 00 00 12 00 74 34 01 00-d0 eb 06 00 08 00 00 00 ....t4..Pk......
%0006eb98 00 00 00 00 00 00 00 00-00 00 00 00 e0 5d 01 00 ............`]..
##dp %120000
linaddr frame pteframe state res Dc Au CD WT Us rW Pn state
%00120000* 02ec1 frame=02ec1 2 0 D A U W P resident
%00120000 vp id=01608 0 0 c u U W n pageable
%00121000 vp id=01609 0 0 c u U W n pageable
%00122000 vp id=0160a 0 0 c u U W n pageable
%00123000 vp id=0160b 0 0 c u U W n pageable
%00124000 vp id=0160c 0 0 c u U W n pageable
%00125000 vp id=0160d 0 0 c u U W n pageable
%00126000 vp id=01635 0 0 c u U W n pageable
%00127000 vp id=01636 0 0 c u U W n pageable
%00128000 vp id=01637 0 0 c u U W n pageable
%00129000 vp id=01638 0 0 c u U W n pageable
%0012a000 vp id=01639 0 0 c u U W n pageable
%0012b000 vp id=0163a 0 0 c u U W n pageable
%0012c000 vp id=0163b 0 0 c u U W n pageable
%0012d000 vp id=0163c 0 0 c u U W n pageable
%0012e000 vp id=0163d 0 0 c u U W n pageable
%0012f000 vp id=0163e 0 0 c u U W n pageable
##dp %130000
linaddr frame pteframe state res Dc Au CD WT Us rW Pn state
%00130000* 02ec1 frame=02ec1 2 0 D A U W P resident
%00130000 vp id=0163f 0 0 c u U W n pageable
%00131000 vp id=01640 0 0 c u U W n pageable
%00132000 vp id=01641 0 0 c u U W n pageable
%00133000 vp id=01642 0 0 c u U W n pageable
%00134000 vp id=01643 0 0 c u U W n pageable
%00135000 vp id=01644 0 0 c u U W n pageable
%00136000 vp id=01645 0 0 c u U W n pageable
%00137000 vp id=01646 0 0 c u U W n pageable
%00138000 vp id=01647 0 0 c u U W n pageable
%00139000 vp id=01648 0 0 c u U W n pageable
%0013a000 vp id=01649 0 0 c u U W n pageable
%0013b000 vp id=0164a 0 0 c u U W n pageable
%0013c000 vp id=0164b 0 0 c u U W n pageable
%0013d000 vp id=0164c 0 0 c u U W n pageable
%0013e000 vp id=0164d 0 0 c u U W n pageable
%0013f000 vp id=0164e 0 0 c u U W n pageable
##

 

Il kernel ha allocato 128Kb di entrate alle tabelle di pagina inizianti all'indirizzo lineare %120000. 

Nel kernel il principale operatore che si occupa di questo è _VMAllocMem, che invoca la routine _VMReserve, _PGAlloc e _SELAlloc. 

Possiamo anche vedere cosa accade quando un programma veramente prova ad accedere alla memoria. Il comando del KDB "vsp e" intercetterà i fault di pagina prima che essi siano elaborati e questo può essere usato in congiunzione con la facilitazione "zs" (comando di cambio di default) per fare statistiche sul meccanismo dei fault di pagina e i suoi effetti sulle prestazioni del sistema. Per far questo, è più facile mettere un punto di blocco all'inizio della lunga _PGPageFault che tratta questa eccezione.
 
 

L'emulazione DOS del kernel

Il componente per l'emulazione DOS del sistema non è menzionato affatto in tutto il Debugging Handbook e tende a essere ignorato dagli sviluppatori perché esso esiste solo per la compatibilità dei vecchi programmi. Comunque, esso occupa oltre il 25% del codice nell'OS2KRNL ed è il suo esame vale anche solo per illustrare la versatilità dell'architettura x86. 

Ci sono essenzialmente tre parti nell'emulatore DOS in OS/2: il gestore MVDM, l'emulatore DOS propriamente detto e l'emulatore x86. Una quarta parte, i device driver virtuali necessari per eseguire la maggioranza dei programmi DOS, esistono all'esterno del kernel ma fanno uso delle chiamate alle API del Virtual DevHelp implementate nel gestore MVDM. 

Per avere un idea degli argomenti coinvolti nel tracciamento di una applicazione DOS nel Ker­nel Debugging, guardiamo un semplice "Hello, world" scritto in Assembly. Apriamo un finestra DOS, dopo di che il kernel ci dà una VDM con una copia "parziale del DOS virtuale del kernel" - il file \os2\mdos\doskrnl - caricato in memoria bassa per fornire i servizi dell'int 21h. Avviamo allora HELLO.EXE. Qui sotto è mostrato completamente disassemblato:
 

--u ac2:0 l 7

0ac2:00000000 b8c30a mov ax,0ac3
0ac2:00000003 8ed8 mov ds,ax
0ac2:00000005 b409 mov ah,09 ; output a string at ds:dx
0ac2:00000007 ba0000 mov dx,0000
0ac2:0000000a cd21 int 21
0ac2:0000000c b44c mov ah,4c
0ac2:0000000e cd21 int 21
--d ac3:0 l 10
0ac3:00000000 48 65 6c 6c 6f 2c 20 77-6f 72 6c 64 0d 0a 24 00 Hello, world..$.

 

Si noti che il KDB usa un doppia linietta ("--") per prompt invece dell'usuale "##" per indicare che stiamo eseguendo in modalità V86. Eravamo in grado di bloccare in questo codice l'esecuzione del programma, nel codice operativo CCh nel punto di ingresso di HELLO.EXE scrivendo nella memoria per sostituire il proprio codice operativo. (?) 

Dopo aver digitato varie volte la "t", arriviamo a una chiamata DOS di sistema:
 

--t

eax=000009c3 ebx=00000000 ecx=000000ff edx=00000000 esi=00000000 edi=00000100
eip=0000000a esp=00000100 ebp=0000091c iopl=3 -- vm -- nv up ei pl zr na pe nc
cs=0ac2 ss=0ac4 ds=0ac3 es=0ab2 fs=0000 gs=0000 cr2=01390000 cr3=001f6000
0ac2:0000000a cd21 int 21
--

 

Ma non siamo in grado di trattare "t" in questa chiamata. Questo perché questa istruzione causa un'Eccezione di Protezione Generale (Trap 0D), sebbene IOPL è 3, apparentemente perché l'ingresso all'IDT per int 21h non è valido o contiene un puntatore nullo:
 

--di 21

0021 TrapG Sel:Off=0000:00000000 DPL=3 P

 

Così noi mettiamo un brackpoint al trap0d nel kernel a 32bit, e continuiamo:
 

--br e trap0d

--g
Debug register hit
eax=000009c3 ebx=00000000 ecx=000000ff edx=00000000 esi=00000000 edi=00000100
eip=fff491bc esp=00006708 ebp=0000091c iopl=3 rf -- -- nv up ei pl zr na pe nc
cs=0170 ss=0030 ds=0000 es=0000 fs=0000 gs=0000 cr2=01390000 cr3=001f6000
os2krnl:DOSHIGH32CODE:trap0d:
0170:fff491bc 6a0d push +0d ;br0
##

 

Questo codice chiamerà em86opINTnn per simulare un'interruzione software, e noi ritorneremo in fretta in modo V86 con "iretd". La chiamata sarà trattata in memoria bassa dal DOSKRNL. 

Lasceremo per un altro giorno il resto della saga, per il DOSKRNL dobbiamo ancora fare le chiamate al BIOS, che ancora causeranno eccezioni di protezione generale e saranno guidate dai VDD come il VBIOS.SYS e il VVGA.SYS. Queste coopereranno con il SESMGR e i PDD per vi­sualizzare finalmente il messaggio sullo schermo. 

Alcune informazioni addizionali circa il lavoro dell'emulazione DOS in OS/2 può essere trovato in The Design of OS/2, 1992 Addison-Wesley, by H. M. Deitel e M. S. Kogan, pp. 290- 300. Questi sono solo accenni, per quanto, la correlazione fra la descrizione testuale offerta nel testo e cosa è osservabile con il KDB non è la stessa.
 
 

Le routine di shut-down

Dal momento che tutte le buone cose finiscono, le API del Programma di Controllo includono la routine di DosShutdown. L'operatore che lo esegue è al simbolo w_Shutdown nel DOSHIGH4CODE. 

Questa funzione disabilita i driver dei file system installati per mezzo di sovrascrittura di tutti i punti di ingresso con gli indirizzi della routine del ShutdownBlock nel DOSHIGH2CODE. Quindi qualsiasi thread che tenta di chiamare una FSD rimarrà bloccato. Alcune routine, quantomeno, rimangono intatte perché usate dal codice dello shutdown: FS_COMMIT, FS_DOPAGEIO, FS_FSCTLBUF, e FS_SHUTDOWN. Lo stesso vale per i driver dei file system usati dallo swap­per, alcuni punti di ingresso sono per primi preservati alle locazioni FS_SDCHGFILEPTR, FS_SDFSINFO, FS_SDREAD, e FS_SDWRITE. Questo abilita le routine di paginazione a continuare l'elaborazione della routine di shutdown, mentre tutti gli altri thread sono bloccati definitivamente. 

Noi allora iteriamo attraverso tutti i device driver installati, inviando la richiesta di "shutdown" (codice di comando 1Ch) a ognuno. Ogni driver è chiamato due volte, con parametri 0 e 1 per l'inizio e la fine del shutdown e, rispettivamente. Similmente, per ogni driver di sistema installato, la funzione FS_SHUTDOWN è chiamata due volte con i flag di shutdown di inizio e fine. Durante queste chiamate, le routine shutdown$FlushAllSFT e h_FSD_FlushBuf stabilizzano la porzione della cache in RAM dei file system.
 
 

Conclusioni

In un tempo in cui il supporto per Os/2 della IBM sembra diventare ogni giorno meno entusiasmante, diventa sempre più importante per gli utenti e sviluppatori capire profondamente il proprio sistema. Questa conoscenza può aiutare nello sviluppo dei driver e applicazioni, costruendo sistemi di help indipendenti, e ugualmente scrivendo patch di sistema se necessario. Con lo sforzo di supporto attivo dall'esterno della comunità, Os/2 può e continuerà a prosperare. Spero che il presente articolo abbia contribuito in qualche misura per la comprensione delle fondamenta di questo imponente edificio.
 

eax=00000000 ebx=7b22002b ecx=80010013 edx=7ba0fc0c esi=7ba093c0 edi=ffffffff

eip=fff46572 esp=00006688 ebp=00006688 iopl=2 -- -- -- nv up ei pl zr na pe nc
cs=0170 ss=0030 ds=0168 es=0168 fs=0000 gs=0000 cr2=111f3302 cr3=001f6000
0170:fff46572 833d7c1ff1ff00 cmp dword ptr [_ptcbPriQReady (fff11f7c)],+00
ds:fff11f7c=00000000
##

 
 

Copyright © 1998, David C. Zimmerli. All rights reserved. 
 

 
11
 

[Pagina precedente] [Sommario] [Pagina successiva]