program1 Assaggio: Costruiamo una applicazione windows utilizzando nvda Donato taddei su uictech, 07\02\2013, h. 22.08. Prerequisiti: nessuno. Per questo messaggio non è richiesto aver installato nvda, nè tanto meno conoscerlo. Se vi è interesse o consenso ditelo che continuo in questo tono, e soprattutto l'invito è a chi sa di approfondire, articolare, riproporre in altra salsa i concetti qui esposti, come una sorta di gioco del domino. Qualche domanda: vi è mai capitato, aprendo un programma da "esplora risorse" di vedere come programmi grossi come diciamo potrebbe essere outlook express, con una frega di funzionalità, abbiano un eseguibile, il file *.exe di pochi kilobytes? Altra domanda: Avete mai sentito parlare di dll? Pensate che siano dei programmi? La risposta è: sì e no. Sì: l'acronimo dll sta per "dynamically linkable library" vale a dire una libreria che viene caricata dinamicamente al momento dell'esecuzione del programma che la utilizza. Pertanto le dll possono essere definite come dei files contenenti collezioni di Segmenti di codice compilato che assolvono a determinate funzioni e che quindi sono costituite a loro volta da un complesso di variabili e istruzioni che eseguono un dato compito, una funzionalità e che dunque chiameremo sinteticamente funzioni. Una dll contiene quindi una collezione di funzioni. Quindi più software di così. Anzi diciamo che le dll sono la "struttura portante" del software non solo del sistema operativo ma delle singole applicazioni. No: Non sono dei programmi in quanto nel linguaggio comune col termine programma ci si riferisce quasi sempre al sistema che ci permette di interagire col computer per compiere una data operazione, (i menu, le finestre, le caselle, le icone, i drivers, le combinazioni di tasti ecc. Ogni programma ha quindi una serie di componenti: dll, file, finestre, che tecnicamente vengono chiamate "risorse" associate al programma. Chiamasi "interfaccia" quel componente che permette l'interazione con l'utente, ovvero permette all'utente di dare ordini al computer e di gestirne i risultati dell'elaborazione. Altra domanda: Cosa succede quando diamo invio per aprire un file eseguibile o, il che è lo stesso, quando lanciamo un programma dal relativo menu? Si dice che Attiviamo un evento, quello appunto di lanciare il tal programma, che viene intercettato dal sistema operativo che così si accorge di cosa vogliamo e provvede si dice tecnicamente ad "allocare le relative risorse. Che significa? Significa che il sistema operativo caica nella memoria il programma e tutto quanto di cui ha bisogno per funzionare (al solito files, finestre, icone, drivers ecc.) essendo la memoria centinaia di volte più veloce rispetto per esempio agli hard disk, in modo tale che il programma possa lavorare completamente in memoria e non aver bisogno di altro. Ma quale memoria domanderete, visto che sapete tutti che esiste una memoria reale e una virtuale? Se c'è memoria libera in quella reale, viceversa parte in quella reale e parte in quella virtuale e tanto più la memoria reale sarà occupata da altri programmi tanto più sarà la parte caricata in memoria virtuale, con relativo degrado delle prestazioni, perchè la memoria virtuale in realtà è basata sulla scrittura del suo contenuto sull'hard disk che appunto abbiamo detto essere molto più lento della memoria. Chiaro allora perchè a caricarci programmi tropo pesanti o troppi programmi si rallenta il sistema? Prima di cedere il controllo della macchina al programma il sistema operativo gli assegna un nemerino detto identificatore di processo (processId) col quale identificarlo univocamente, perchè ad esempio potremmo avere lanciato più istanza dello stesso programma contemporaneamente: per esempio aprendo due differenti finestre del browser, che sarà sempre IE o Firefox o Chrome, Attraverso il nemerino identificatore di processo il sistema saprà da quale finestra del browser proviene una determinata richiesta e conseguentemente indirizzare alla stessa il risultato della richiesta. Tornando al caricamento del nostro programma Infine si dice che "gli cede il controllo", vale a dire che carica nei registri il punto di inizio del programma e questi comincia il suo lavoro. Tornando al fatto che i file exe sembrano spesso straordinariamente piccoli rispetto alle loro funzionalità ciò è perchè di solito questi, come primissima cosa che fanno è richiamare, cioè caricare in memoria, le dll di cui hanno bisogno e in cui è contenuto la stragrandissima maggiorannza di quello che fanno. Sarà quindi tra i primissimi compiti del programmatore specificare al sistema operativo le dll di cui il programma ha bisogno. Altra domanda: avete mai fatto caso che ad esempio piccole utility a riga di comando sembrano comportarsi diversamente dal resto dei programmi? Quando le lanciate fanno il loro mestiere e finiscono automaticamente dopo aver prodotto i risultati, ma se invece lanciate il browser o il programma di posta o media player bisogna ammazzarlo con alt+f4, altrimenti rimane lì, sempre a vostra disposizione. Ci sono quindi due tipi di programmi: - quelli che una volta lanciati fanno quello che devono fare e passano subito la mano - e quelli che una volta lanciati non terminano, non passano la mano. Occupiamoci di questi ultimi. Cosa fa il nostro programma di posta dopo che lo abbiamo aperto e lasciato lì aperto dopo aver scaricato la posta, mentre per esempio siamo occupati in un'altra finestra a scrivere il messaggio di risposta, a navigare o a parlare al telefono? Risposta: Niente. si gratta le bale in attesa che ci venga in mente di inviare la posta, di chiuderlo, di bloccare un certo mittente ecc.. Però, a differenza di quanto per esempio avveniva col dos quando veniva tassativamente eseguito un programma alla volta e il successivo poteva essere lanciato solo dopo che il precedente avesse finito e liberato la memoria, qui siamo come si dice in un ambiente "multitasking" vale a dire che la memoria è condivisa da tutti i programmi che sono in esecuzione in un dato momento. Allora quale di questi sarà eseguito? Tutti, un po per uno. ma quanto poco e con quale ordine? Intanto avranno precedenza quelli con una priorità più alta, poi quelli che consumano meno risorse, ma toccherà un po per uno a ciascuno. Come realizza ciò il sistema operativo? Come un maestro di musica che indica con la bacchetta il memento di attacco a ciascun orchestrale. O meglio ancora: manda un sms al programma. (c'è posta per te). Il programma lo riceve, vede dal mittente chi glielo ha mandato, cosa vuole (per esempio una sua finestra gli fa sapere che è stato premuto un certo pulsante, un certto driver gli comunica che ha finito di suonare un certo file, è stata fatta una determinata scelta da un menù, ecc.) fa quando gli si richiede e ritorna al sistema operativo che cede il controllo al prossimo in attesa. Chiameremo "evento" ogni modifica dell'ambiente software: l'apertura di una finestra, la pressione di un tasto, l'ingrandimento di una finestra, la selezione di un pulsante radio, l'avveenuta connessione o disconnessione da internet o da un dispositivo, insomma ogni modifica dell'ambiente causata dall'utente o da altro programma, locale o remoto. Diremo allora che siamo su un sistema "ev ent-driven", guidato dagli eventi che in esso si manifestano, eventi che normalmente generano a loro volta "eventi di risposta come per esempio la chiusura di una finestra, la chiusura e scaricamento dalla memoria di un programma, o qualsiasi altra cosa in risposta a un determinato evento. Dunque il nucleo centrale di un qualsiasi programma di questo secondo tipo, la funzioone principale che ha avuto per prima il controllo al momento del lancio e che in molti linguaggi viene appunto chiamata "main", anzicchè essere come quelli del primo tipo (le utility a riga di comando di cui sopra) che sono costituiti da una sequenza di operazioni seguiti dalla fine del programma stesso e relativo scaricamento dalla memoria, questi sono costituiti da una funzione chiamata solitamente WinMain nel linguaggio c, che, effettuate le operazioni preliminari come caricamento di dll, inizializzazioni ecc. si limita a processare gli sms a lei diretti ripassando il controllo al sistema operativo fino a che non riceve un sms di chiusura causato per esempio dalla pressione di alt+f4, e in tal caso farà le operazioni finali come rilascio della memoria, scaricamento di file sull'hard disk, e toglierà il disturbo. Da quanto detto emerge che il lavoro del programmatore in una applicazione di questo tipo, consiste nel predisporre la risposta adeguata a tutti gli sms che possono essere inviati al programma dai dispositivi, dalle finestre, dalle altre applicazioni che segnalano un evento. Ho detto sms perchè in effetti questi messaggi del sistema operativo sono il più breve possibile ergo short: contengono - il nemerino del processo che ha inviato il messaggio, il nemerino che è stato assegnato all'interno di quel processo a ciascuna risorsa utilizzata dal programma come la finestra in cui è stato attivato quel tal pulsante, o il numerino relativo a un dato file aperto, o il numerino associato ad una icona o ad un oggetto grafico, che poi è lo stesso numerino che jaws vi dice quando attivate i grafici, _ il messaggio vero e proprio, sempre un numerino da cui si capisce ad esempio se è stato premuto un tasto o cliccato il mouse, o è passato un dato tempo - altri due numerini supplementari utilizzati per esempio per specificare quale tasto è stato premuto o quale operazione è stata richiesta. Come anticipato python e nvda qui non c'entrano. Tuttavia al solo scopo di dare una idea, sapete come si fa a caricare una dll in python? windll.kernel32 e avremo caricato in memoria una copia la dll kernel32.dll contenente molte funzioni base del sistema operativo: Adesso vogliamo ottenere un numerino da associare a un determinato componente, per esempio una dll, utilizzando la funzione GetModuleHandleA contenuta in kernel32.dll: print windll.kernel32.GetModuleHandleA Se poi vogliamo verificare che non ho detto sciocchezze, perchè detto così sarebbe troppo facile, nonostante le leggende metropolitane sulle indentazoini del pitone, apro il menu di nvda scelgo strumenti e poi console python e scrivo queste tre righe. from ctypes import * windll.kernel32 windll.kernel32.GetModuleHandleA vedrò il numerino associato alla dll e quello restituito dalla funzione GetModuleHandleA E come assaggio può bastare. Se Andrea o altri pù esperti vorranno integrare, chiarire, approfondire, insomma continuare il domino faranno cosa utile e a me gradita. Francesco Melis: A commento di quanto detto da Donato aggiungo qualche nota relativa ai dubbi e curiosità che potrebbero sorgere. Intanto un incoraggiamento a Donato a continuare ed ai suoi "discepoli" ad avere costanza e non scoraggiarsi. Quando compare qualche citazione che non si capisce, dai sù! non ci vuole molto a fare una ricerchina su internet per chiarirsi qualche concetto poco conosciuto. Tanto tempo fa a me venne un dubbio. Posto che il sistema operativo è un software come tutti gli altri, quantunque speciale, essendo il software fondamentale senza il quale non potrebbero girare gli altri programmi, mi chiesi come mai quando il processore sta eseguendo un programma applicativo che richiede molte risorse di calcolo, cioè ci mette molto a terminare, il sistema operativo che in quel momento non è in esecuzione può requisire la CPU al programma applicativo, eseguire se stesso e passare l'esecuzione ad un altro processo in attesa? Cioè, a livello di linguaggio macchina, (che tra l'altro è la parte dell'informatica che a me interessa maggiormente), quando è in esecuzione un programma, la Cpu esegue le sue istruzioni e non ha la minima percezione, (è troppo stupida), che quelle istruzioni non sono del sistema operativo. Come mai un programma o modulo che dir si voglia, dovrebbe smetterla di eseguire le sue istruzioni e lasciare il controllo ad altri? Lo stesso sistema operativo, quando manda in esecuzione un programma fra i vari che può avere allocato in memoria Ram, come fa ad intervenire se la cpu che dovrebbe eseguire le sue stesse istruzioni è impegnata con le istruzioni di un altro processo? La tecnica che viene usata sfrutta le possibilità offerte dallo stesso harware. Esiste una sorta di sveglia nell'hardware delle macchine chiamata "watch dog timer" che tradotta dovrebbe essere più o meno "temporizzatore del cane da guardia". Bene, le cose funzionano più o meno così. Quando il sistema operativo lancia un programma associa ad esso, o al modulo, il descrittore che ha ben descritto Donato, cioè, detto alla buona, "la sua carta di identità". Dopo di che carica una sveglia hardware (il watch dog timer" con il tempo che ritiene di destinare a quel programma. Mettiamo tanto per fare un esempio numerico 1 millisecondo. Come dire: "svegliami fra un millesimo di secondo". Dopo di che il sistema operativo si distacca nel senso che cede la cpu facendo eseguire ad essa la prima delle istruzioni del programma applicativo. Trascorso il millesimo di secondo della carica della sveglia il sistema operativo sarebbe bello che morto e non potrebbe mica riintervenire se non avvenisse qualcosa che lo risvegliasse. Ci pensa la sveglia che una volta suonata manda un segnale hardware alla cpu chiamata "interrupt" che richiama la sua attenzione interrompendola e qualificandosi come "la sveglia". La cpu non può ignorare questa chiamata hardware poiché stavolta è corrente elettrica che gli arriva ad un piedino e lei è progettata per rispondere a questa chiamata. La cpu allora, automaticamente, è costretta ad eseguire una routine di gestione dell'interruzione la quale salva il contesto operativo nel quale si trovava il processo in esecuzione in quel momento per poterlo riprendere più tardi esattamente da dove era arrivato, e finalmente richiama in esecuzione le istruzioni del sistema operativo. Bene, una volta ripreso il controllo della cpu, risvegliatosi, il sistema operativo va a vedere le sue carte e cartoncelle (descrittori dei processi) e vede a chi tocca di mandare in esecuzione fra i vari processi in attesa. Basandosi sui descrittori che egli conosce perfettamente quindi manda in esecuzione un altro processo secondo una sua politica di priorità. La cosa fantastica è che i sistemi operativi seri sono in grado di gestire delle "priorità dinamiche" cioè in altri termini che anche i processi con bassa priorità, che potrebbero essere sistematicamente prevaricati da altri processi a priorità più elevata, con il trascorrere del tempo cambiano la loro priorità aumentando di livello fino a portarsi alla priorità che consente loro di essere presi in considerazione dal sistema operativo per essere mandati in esecuzione. *** Donato Taddei: per fare una ripassatina veloce di alcuni concetti ribaditi da Francesco nella sua integrazione al mio messaggio di diri nonchè per fissare meglio concetti affrontati in precedenza come i numeri binari ho ripescato su pcciechi un mio messaggio pensate del 2001 che proponeva uno scritto del 1998 in cui veniva brevemente presentato il linguaggio assembler Intel perchè l'assembler, per definizione, è il linguaggio che più si avvicina al linguaggio macchina. Dunque può fornire quache elemento sul funzionamento a più basso livello dell'hardware. Ho tagliato qualcosa e fatto qualche piccola annotazione per attualizzare il tutto. Poi ho da assegnare un compitino a qualche volenteroso, per esempio Francesco. Ieri abbiamo detto che il sistema operativo, all'atto di lanciare un programma, anzi prima di lanciarlo, crea il relativo processo con la funzione pensate un po che fantasia "CreateProcess" e che indi passa il controllo alla funzione principale del programma che nei programmi c si chiama WinMain: bene queste sono delle funzioni cui vengono passati dei dati sotto forma di parametri e restituiscono strutture di dati come quelle sommariamente descritte da me ieri. Il compitino consiste nello specificare più precisamente questi dati e di farlo, ciò è tassativo, in maniera indipendente dal linguaggio perchè un'altra cosa va chiarita: se un programma usa funzioni contenute nella dll kernel32.dll per esempio per cancellare un file, la struttura dei dati da passare come parametri alla funzione DeleteFileA dove la a finale sta per ascii poichè per la maggior parte delle funzioni esiste una versione ascii compatibile ed una unicode compatibile della stessa funzione Dicevamo che quando un programma invoca, chiama, una funzione contenuta nella dll i dati passati come parametri alla funzione e la struttura dei dati restituiti in output dalla stessa sono sempre quelli indipendentemente dal fatto se il programma che la usa sia scritto in c, in delphi, in visualbasic o altro, e non potrebbe essere diversamente perchè si statta sempre della stessa dll, il file è sempre lo stesso. Procedendo in tal modo si fanno due servizi buoni: si dà la possibilità a chi si sta impelagando in uno specifico linguaggio di non rimanerne completamente invischiato e a volte disorientato e soprattutto si dà la possibilità a chi ancora non si è imbarcato o non intende imbarcarsi di trarre profitto da questi thread, evitando la molto probabile torre di babele conseguente al fatto che ciascuno parla con la terminologia specifica del proprio linguaggio. Se si riesce ad approcciare il problema nella maniera più eclettica possibile sarà un vantaggio per tutti. Don l'assembler intel :introduzione ai concetti generali di programmazione di donato taddei alle volte si impara anche da un pazzo o da uno scemo! Questo scritto e' del 1998 ma niente paura: la logica Booleanasu cui sono basati gli attuali e i futuri computerse' nata nell'800! --------------------------------------- l'assembler intel in 4 sedute di Donato Taddei Introduzione bit, byte, sistemi di numerazione: binaria, ascii, esadecimale. Clock, registri, stack, interrupt, segment prefix (psp) Nessuno si sogna di volervi insegnare l'assembler! sarei oltre tutto la persona meno indicata a farlo: io infatti programmo in c. Questo scritto si propone di dare una "infarinatura" sui concetti e gli elementi caratteristici di questo linguaggio, il piu' vicino all'hardware, tale da permettervi teoricamente di scrivere voi un programmino in assembler, alla fine di queste sedute, servendovi del materiale a corredo: -il set completo delle istruzioni assembler intel -"programmer technical reference for msdos and ibm pc". Questo scritto si propone altresi' di soddisfare la curiosita' di chiunque voglia farsi magari solo una pallida idea della materia. nei capitoli seguenti, tratti da PCGPE, "personal computers games programmers encyclopedia", da me tradotti, si assume che voi sappiate cosa sia un interrupt, e amenita' del genere. percio' nel prosieguo di questo capitolo verranno tratteggiati alcuni concetti basilari Cos'e' il sistema binario? cos'e' la memoria? come si rappresentano i dati? Qual'e' il cuore di un computer? Cosa sono i registri, lo stack e gli interrupt? l'industria dei computers si e' potuta sviluppare grazie all'applicazione su vasta scala di tecnologie connesse ai semiconduttori: si tratta di derivati del silicio, uno degli elementi piu' diffusi sulla crosta terrestre, che si comportanoin modo particolare quando sono attraversati da una corrente elettrica: normalmente sono conduttori fino a caricarsi; raggiunto un quantum divengono isolanti, interrompendo il flusso; si dice cioè che "scattano": Esempio classico, il transistor. bit, bytes e sistemi di rappresentazione: binaria, ascii, esadecimale. La memoria di un computere' costituita da una enorme serie di bit, cioè' di punti magnetici che possono essere magnetizzati o no, caricati o no, on o off. un singolo bit, quindi, puo' assumere solo due stati 1 (carico) e 0. Quando un bit viene sollecitato dal passaggio di una corrente cambia il suo stato: se e' scarico si carica, passando da 0 a 1; viceversa si scarica, passando da 1 a 0. L'unico sistema di numerazione che utilizza le cifre 1 e 0 per rappresentare tutti i numeri e' quello in base 2, detto appunto sistema di numerazione binario. Per inciso va detto che molti matematici e studiosi ritengono che il sistema binario sia il piu' antico sistema di numerazione in quanto permette un approccio qualitativo e non quantitativo nella rappresentazione dei fenomeni: si'-no, bene-male, e' cioè basato sulle antinomie: 4.000 anni fa i cinesi utilizzavano rappresentazioni binarie con tratti interi o spezzati (come ancora si ritrova in allegorie orientali), per rappresentare esplosioni combinatorie per scopi di previsione e di divinazione. La rappresentazione binaria delle cifre decimali da 0 a 9 in ordine crescente e': 0, 1, 10 11,100, 101, 110, 111, 1000, 1001. Da ciò segue che bisogna utilizzare una sequenza di bit via via crescente col crescere dei numeri: 2 elevato a n (dove n e' il numero di bit considerati) esprime il numero di possibili rappresentazioni: 2 alla prima = 2, alla seconda = 4, alla terza = 8, alla quarta = 16, alla ottava = 256, alla sedicesima = 65536, alla 32 = 4.294.836.225, etc. Per rappresentarele lettere dell'alfabeto, i numeri, la punteggiatura, i simboli matematici, le lettere dell'alfabeto greco, etc. si e' stabilito di consideraregruppidi 8 bits per volta: 8 bits permettono256 possibilita' dirappresentazione. Questi sono dette bytes, sono le celle di memoria elementari, contengono normalmente un carattere. Ad ogni caratterrecorrisponde quindi un numero binario da 0 a 255, la rappresentazione ascii (american standard computer interchange information) non e' altro che la rappresentazione decimale di tale numero binario: ad esempio, un byte contenente il carattere "A" e' una sequenza binaria 01000001, corrispondente al valore decimale (o ascii) 65. Dunque per rappresentare in ascii (decimale) molti caratteri sono necessarie tre cifre: es. 122 per il carattere "z". Fin dall'inizio pero' si e' preferito ricorrere a una rappresentazione piu' compatta che riduce a 2 soli simboli la rappresentazione di un byte di memoria: questa e' basata su un sistema di numerazione in base 16, perciò detta esadecimale, in cui oltre alle normali cifre da 0 a 9 vengono anche utilizzate le lettere a, b, c, d, e e f per i numeri da 10 a 15. Cosi':1= 1, a = 10, f = 15, 10 = 16,20 = 32, 80 = 128,ff = 255, 100 = 256, 200 = 512, 400 = 1024 fff = 4095, 1000 = 4096, a000 =40960, ffff = 65535, f0000 = 65536. Nellinguaggio assembler si fa spesso ricorso alla rappresentazione esadecimale perchè, essendo in base 16 = 2 alla quarta, permette di riferirsi a un gruppo di 4 bits, cioè a un semibyte (8 / 2 bits). quando si utilizza una notazione esadecimale si pospone una "h": cosi' il carattere "a" (ascii 65, come visto sopra), si rappresenta in assembler come 41h. Il timer o clock e i registri. Il cuore pulsante del computer e' costituito dal timer o clock. Attenzione! non si trattadell'orologino che vedete disegnato sullo schermo in molte applicazioni; quello viene aggiornato, cambia, 18.2 volte al secondo. Il clock hardware invece scatta da decine a centinaia di milioni di volte al seconto, determinandola velocita' del processore. Quando si dice che un processore e' a 166 MHZ (megahertz) si dice che il suo clock e i suoi circuiti logici scattano 166 milioni di volte al secondo. E per chi legge nel 2013 vale la stessa identica cosa per i gigahertz degli attuali processori. Ad ogni tic del timer il controllo ritorna al processore. Il processoresi accorge di quello che deve fare o che ha fatto esaminando il contenuto di speciali circuiti detti registri. Nell'architetturaintel vi sono due sets di registri: un set a 16 bit (2 bytes), e uno a 32 bit, usato sostanzialmente per la modalita' protetta (es. ms windows95). e ovviamente, per chi legge nel 2013 tutti i window succwessivi. Potete trovare una descrizione di tali registri nel "programmers technical reference for msdos and ibm pc. Nel linguaggio assembler, come anche del resto nei linguaggi di alto livello come il c o il pascal, e' possibile variare e manipolare il contenuto dei registri riferendosi loro con dei nomi simbolici: (qualcuno sicuramente ricorda R1, ... r16 dell'assembler 360 ibm ad esempio!) -normalmente usati dalle routines del sistema: ax, bx, cx, dx; -contengono dati relativi al segmento: cs, ds, es, ss; -indici: si, di (source index e destination index); -puntatori: bp, ip, sp (stack pointer); -contiene notizie sullo stato delle operazioni fatte: flags. per riferirsi al set di registri a 32 bytes si premette una "e" al nome: eax, ebx, esi, ebp. Per chi legge nel 2013 vi sarà capitato di vedere generalmente nelle segnalazini di erore o nei log di trovare robe tipo eax, ebx, ecc.: non è altro che la trascrizine del contenuto di tali registri al memento del verificarsi dell'errore sicchè ci ci capisce, per es. la microsoft cui queste segnalazioni vengono eventualmente girate. per quanto riguarda il set a 16 bit e' anche possibile riferirsial singolo byte,di sinistra o di destra: cosi ah, bh, ch, dh indicano rispettivamente il byte di sinistra (high, alto) dei registri ax, bx, cx, dx mentre al, bl, cl, dl si riferiscono al byte di destra (low, basso). Ma questa è roba che si usava ai tempi del comando debug del dos prima che anche questo passasse ad usare registri a 32 bit, come nel caso delle moderne versioni del command.com e cmd.exe. Nei registri si trovano gli indirizzi di memoria in cui sono memorizzati i dati e il codice, vale a dire la sequenza delle azioni che il processore deve eseguire. Ma cosa sono queste azioni? Un programma e' costituito da una sequenza di istruzioni macchina, costituite da un codice operazione e dai relativi operandi: cosi' l'istruzione assembler mov ax, 1 (metti 1 nel registro ax) vienetradotta dal compilatorein una sequenza binaria: il codice mnemonico "mov" viene tradotto nel corrispondente codice binario che attiva una sequenza di operazioni sui circuiti il cui risultato e' quello appunto di impostare 1 nel registro ax. ogni istruzione macchina si sviluppa in una precisa sequenza di operazioni sui circuiti hardware che impiega alcuni cicli di macchina, scanditi dai tics (scatti) del timer. Come potete vedere nel set di istruzioni, dove vengono riportati i cicli di macchina a fronte di ciascuna istruzione, si va da 3-4 fino a 10, in funzione della complessita' delle istruzioni stesse. Qui va solo detto che si parla impropriamente dell'assembler come linguaggio macchina: nel linguaggio macchina le istruzioni sono composte da un codice binario e dai relativi operandi; invece l'assembler e' un vero e proprio linguaggio di programmazione: -utilizza codici mnemonici per richiamare i relativi codici operazione; -permette di riferirsi a indirizzi di memoriamediante nomi simbolici; -permettedi associare nomi simbolici a sequenze di istruzioni (si parla allora di macroistruzioni o macros). - permette di includere nel sorgente altri segmenti di codice, standard o precostruite (librerie); - permette di scrivere un programma in moduli separati e di richiamare routines esterne. Malgrado in un semplice programma assembler vi sia corrispondenza 1 a 1 tra le istruzioni del sorgente e le relative istruzioni in linguaggio macchina,l'output di una compilazione assembler non e' ancora eseguibile:sara' il successivo passaggio al linker che lo rendera' eseguibile, risolvendo i collegamenti con i moduli esterni e con le routines del sistema operativo. Lo stack. L'architettura dei computers intel implementa uno "stack" hardware che contiene gli indirizzi che puntano alle istruzioni che devono essere eseguite: il registro SP (stack pointer punta alla istruzione da eseguire, il cui indirizzo sitrova nello "stack", che pure contiene gli operandi, i dati su cui si deve effettuare l'operazione. Sotto questo profilo sono fondamentali due istruzioniassembler: PUSH e POP. La prima "push" spinge nello stack, all'indirizzo puntato in quel momento dal registtro SP, l'istruzione da eseguire con i relativi registri in cui sono memorizzati i dati e i relativi indirizzi di memoria. Inoltre lo stack pointer (registro SP) viene incrementato in modo da poter caricare la prossima istruzione. l'istruzione "pop" effettua l'operazione contraria, decrementando lo stack pointer, punta alla istruzione precedentemente eseguita. Gli interrupt. Quando si accende un computer la prima operazione che viene eseguita automaticamente e' il caricamento del sistema operativo. Oltre all'interprete dei comandi esso contiene una serie di routines in linguaggio macchina che effettuano le operazioni base del sistema, richiamabili dall'interno dei programmi. Tali routines sono dette "interrupt": esse infatti, terminata l'esecuzione, interrompendo il flusso del programma, ritornano al sistema operativo, con un codice di errore (errorlevel, normalmente nel registro ax). il primo k della memoria (1024 bytes) , dopo il caricamento del sistema, contiene un set di indirizzi (256), ciascuno di 4 bytes, appunto di tali routines. Percio' e' noto come "vettore di interruzione". l'istruzione assembler int 5h cede il controllo all'indirizzo contenuto nel quinto elemento del vettore di interruzione, vale a dire al byte 20 (5 * 4). Di solito in questa locazione di memoria si trova l'indirizzo della routine che stampa il contenuto del video. Se un programma imposta in questa locazione un'altra routine che per esempio suona un allarme, quando si premera' il tasto "printscreen" udrete l'allarme, invecedistampare il video. Quanti sono gli interrupt? Come detto il vettore di interruzione contiene 256 indirizzi, ma non tutti sono utilizzati. I programmatori hanno quindi la possibilita' di definire degli interrupt a loro piacere, ed anche di ridefinirequelli che il sistema ha caricato all'accensione. Gli interrupt possono essere divisi in quattro categorie: -interrupt di sistema: il timer, (interrupt 8h, "printscreen 5h, exx, sono sempre attivi - rom bios interrupt: gestiscono la comunicazione con le periferiche: il video 10h, le porte seriali 14h, la tastiera 16h, la stampante 17h, i floppyes, l'hard disk etc. - dos interrupt: sostanzialmente l'interrupt 21hcui si possono richiedere quasi tutti i servizi, fatti da altri interrupt. -altri interrupt vari. Prima di chiamare un interrupt bisognera' impostare correttamente il contenuto di alcuni registri: si rimanda al "programmer technical reference", gia citato, per la documentazione del loro uso. Gli indirizzi o le locazioni di memoria. Abbiamo visto che per riferirsi agli indirizzi di memoria il sistema utilizza i registri. Abbiamo visto che il set normale di registri e' a 16 bit = 2 bytes. Con 2 bytes si puo' rappresentare un numero binariomassimo: 2** 16 = 65536. A differenza di altri sistemi operativi quelli Microsoft dividono la memoria in segmenti ciascuno di 65536 bytes (64 kbytes, 64 * 1024). Quando all'interno di un programma ci si vuole riferire a una locazione di memoria e' possibile farlo in due modi: -in modo relativo, a partire dall'inizio del segmento, contenuto nel registro SS (start segment): per far cio' basta utilizzare un solo registro che al massimo puo' contenere in binario 65536. -in modo assoluto, servendosi di una coppia di registri, di cui il primo contiene il numero che identifica il segmento e il secondo contiene l'indirizzo relativo al suo interno (offset). Al riguardo vanno fatte due osservazioni molto importanti: 1) - ai registri (segmento) (cs, ss, es, ds) si puo' accedere solo attraverso altri registri, e quindi per impostare 512 nel registro es si fara: mov ax, 200h mov es, ax 2) - quando e' stato creato l'assembler intel si riteneva che la memoria utilizzata dai computer non potesse superare il mega: cosicche' bastavano 20 bits per ciascun indirizzo. Ovviamente oggi le cose non stanno piu' cosi' mail problema rimane: l'inizio di ciascun segmento viene allineato al paragrafo; ogni paragrafo occupa 16 bytes di memoria. negli indirizzamenti assoluti attraverso coppia di registri (segment:offset), quindi, il registro segment conterra' il numero di paragrafi, a partire dall'inizio della memoria, vale a dire : indirizzo assoluto del primo byte del segmento diviso 16. se ad esempiosi vuole utilizzare la coppia es:di per puntare al dodicesimo byte del segmento che inizia all'indirizzo assoluto 2048 (800h), si fara': mov di, 12 mov ax, 128 ;2048 / 16 mov es, ax IL PSP "program segment prefix".Torna all'indice