ΠΕΡΙΕΧΟΜΕΝΑ
2.
16bit ΕΝΑΝΤΙΟΝ 24bit ΔΙΕΥΘΥΝΣΕΩΝ
α. Tiny Model
β. Small Model
γ. Medium Model
ε. Large Model
στ. Huge Model
4.
ΥΠΕΡΠΗΔΩΝΤΑΣ ΤΑ ΜΟΝΤΕΛΑ ΜΝΗΜΗΣ
ΚΕΦΑΛΑΙΟ 11ο
ΜΟΝΤΕΛΑ ΜΝΗΜΗΣ ΤΗΣ TURBO C
Για αρκετούς λόγους που θα ξεκαθαρίσουμε παρακάτω, μπορούμε να μεταφράσουμε ένα πρόγραμμα C κάνοντας χρήση κάποιου από τα έξι (6) διαφορετικά μοντέλα που ορίζει η οικογένεια 8086. Κάθε μοντέλο οργανώνει τη μνήμη διαφορετικά και ελέγχει το μέγεθος του κώδικα ή των δεδομένων (ή και των δύο) με άλλο τρόπο. Επίσης καθορίζει και την ταχύτητα με την οποία θα εκτελείται ένα πρόγραμμα.
Στο κεφάλαιο αυτό θα εξεταστεί η χρήση της C με τους επεξεργαστές της οικογένειας 8086 / 8088. Η θεωρία και τα παραδείγματα δεν έχουν εφαρμογή σε άλλους επεξεργαστές.
Πριν γίνει
αναφορά στα μοντέλα μνήμης θα πρέπει να γίνει κατανοητός ο τρόπος με τον οποίο
ο 8086 διευθυνσιοδοτεί τη μνήμη (από εδώ και στο εξής η CPU θα ονομάζεται 8086, αλλά οι πληροφορίες που θα
δοθούν έχουν εφαρμογή και στους 8088, 80186, 80286, 80386, 80486 και Pentium, αλλά και στους συμβατούς με
αυτούς πχ AMD, CYRIX, T.I. κτλ.).
O 8086 περιέχει 14 καταχωρητές όπου αποθηκεύονται
πληροφορίες για την επεξεργασία ή τον έλεγχο του προγράμματος. Οι καταχωρητές
διακρίνονται στις παρακάτω κατηγορίες:
-
Γενικής Χρήσεως Καταχωρητές
-
Base
Pointer και Index
Καταχωρητές
-
Segment
Καταχωρητές
-
Ειδικοί Καταχωρητές
Όλοι οι
καταχωρητές του 8086 έχουν εύρος 16 Bit, δηλαδή 2
Bytes.
Οι
καταχωρητές γενικής χρήσης είναι οι χαμάληδες της CPU. Τιμές καταχωρούνται σ’ αυτούς τους καταχωρητές για
επεξεργασία ή αριθμητικές πράξεις, όπως: προσθέσεις, πολλαπλασιασμοί,
συγκρίσεις, λογικές πράξεις, άλματα, κλπ. Κάθε ένας από αυτούς τους καταχωρητές
προσπελαύνεται είτε ως ένας 16-bit
καταχωρητής, είτε ως δύο 8-bit
καταχωρητές.
Ο Base Pointer και οι Intex Registers χρησιμοποιούνται σε καταστάσεις που έχουν σχέση με
σχετική διευθυνσιοδότηση, τη στοίβα ή εντολές μετακίνησης block.
Οι Segment Registers χρησιμοποιούνται για υποστήριξη
στο μοντέλο τμηματοποίησης της μνήμης από τον 8086. Ο CS κατά το τρέχον Code
Segment, ο DS κατά το
τρέχον Data Segment, ο ES κατά το τρέχον Extra Segment και ο SS το Stack Segment.
Ειδικός
καταχωρητής είναι ο Flag Register που
δείχνει ποια είναι η επόμενη προς εκτέλεση εντολή.
|
AX BX CX DX |
Accumulator Base Counter Data |
Συσσωρευτής Βάση Μετρητής Δεδομένα |
ΓΕΝΙΚΟΥ ΣΚΟΠΟΥ |
|
SI DI |
Source Index Dest. Index |
Πηγαίο Ευρετήριο Ευρ. Προορισμού |
ΕΥΡΕΤΗΡΙΟΥ |
|
SP BP |
Stack Pointer Base Pointer |
Δείκτης Σωρού Δείκτης Βάσης |
ΣΩΡΟΥ |
|
CS DS SS ES |
Code Segment Data Segment Stack Segment Extra Segment |
Τμήμα Κώδικα Τμήμα Δεδομένων Τμήμα Σωρού Τμήμα Extra |
ΤΜΗΜΑΤΟΣ |
|
IP FLAGS |
Instruction Register Flag Register |
Δείκτης Εντολών Σημάνσεις Λειτουργ. |
ΕΙΔΙΚΟΙ |
Οι
καταχωρητές (Registers) του 8086
|
Bit |
Ονομασία |
Λειτουργία |
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
CF Unused PF Unused AF Unused ZF SF TF IF PF OF Unused Unused Unused |
Σήμανση Υπολοίπου Σήμανση Ισοτιμίας Βοηθητική Σήμανση Υπολοίπου Σήμανση Μηδενός Σήμανση Πρόσημου Σήμανση Παγίδευσης Σήμανση Διακοπής Σήμανση Κατεύθυνσης Σήμανση Υπερχείλισης |
Οι σημαίες (FLAGS) του 8086
Ο 8086
έχει συνολικό μέγεθος διευθύνσεων μνήμης 1ΜΒ. Για να επιτευχθεί προσπέλαση 1ΜΒ RAM απαιτείται δίαυλος 20-bit. Πάντως, στον 8086 δεν υπάρχει καταχωρητής μεγαλύτερος από
16 bit. Αυτό σημαίνει ότι η 20-bit διεύθυνση διαιρείται μεταξύ δύο καταχωρητών.
Στον 8086
όλες οι διευθύνσεις αποτελούνται από ένα segment και ένα offset. Στην
πραγματικότητα ο τρόπος διευθυνσιοδότησης του 8086 καλείται μοντέλο segment : offset. Ένα segment είναι ένα
τμήμα της RAM, μεγέθους
64Κ το οποίο πρέπει να ξεκινά από ένα άρτιο πολλαπλάσιο του 16.
Στη γλώσσα
του 8086, 16 bytes
ονομάζονται paragraph. Έτσι, ο
όρος paragraph boundary χρησιμοποιείται πολλές φορές για
αναφορά σε τέτοια πολλαπλάσια των 16 bytes.
Ο 8086
έχει 4 τμήματα. Ένα για τον κώδικα, ένα για τα δεδομένα, ένα για τη στοίβα, και
ένα έξτρα. Η τοποθεσία ενός byte ανάμεσα
στο τμήμα λέγεται offset. H απόλυτη (πραγματική
διεύθυνση) ενός συγκεκριμένου byte μέσα στον
υπολογιστή είναι ο συνδυασμός του segment
και του offset.
Για να
υπολογίσουμε το πραγματικό byte που
αναφέρεται από τον συνδυασμό segment : offset πρέπει να εκτελέσουμε κύλιση της τιμής στον segment register κατά 4 bit αριστερά
και κατόπιν να προσθέσουμε το offset. Αυτό μας
επιστρέφει μία τιμή 20 bit. Π.χ. αν
ο καταχωρητής τμήματος περιλαμβάνει την τιμή 0020h και το offset έχει την
τιμή 0100h η απόλυτη
τιμή στον δίαυλο 20 bit θα είναι
00300h.
Segment Register : 0000 0000
0010 0000
Segment Shifted : 0000
0000 0010 0000
Offset : 0000 0001 0000
0000
Segment
+ Offset : 0000
0000 0011 0000
0000
Πάντως,
στην περίπτωση που προσπελαύνουμε διευθύνσεις μόνο μέσα στο τρέχον τμήμα, τότε
θα χρειαστεί να δουλέψουμε μόνο με το offset, οπότε
συνήθως το segment δε μας
απασχολεί. Σε γενικές γραμμές, μια διεύθυνση του τύπου segment : offset ενδέχεται
να έχει την ίδια τιμή με μία άλλη, μια και τα τμήματα αλληλοκαλύπτονται μεταξύ
τους. Π.χ. η διεύθυνση 0000:0010 έχει την ίδια τιμή με την 0001:0000.
i)
16-Bit Εναντίον 20-Bit Pointers
Όπως ήδη έχει
αναφερθεί θα χρειαστούμε μόνο 16-bit δ/νση για
να προσπελάσουμε μνήμη μέσα στο ίδιο τμήμα. Πάντως, εάν θέλουμε να αναφερθούμε
σε μνήμη έξω από το τρέχουν τμήμα, θα πρέπει να χρησιμοποιήσουμε και το segment και το offset με τις επιθυμητές τιμές στους αντίστοιχους καταχωρητές.
Αυτό σημαίνει ότι θα χρειαστούμε μία τιμή εύρους 20-bit. Η χρήση μιας 20-bit δ/νσης
συνεπάγεται τη φόρτωση και του 16-Bit
Segment και του
16-Bit Offset. Σε γενικές γραμμές οι 20-Βit δ/νσεις κάνουν τα προγράμματα αργά, επιτρέπουν όμως τη
δημιουργία μεγάλων προγραμμάτων ή τη χρήση πολλών δεδομένων.
To Tiny Model
μεταφράζει ένα πρόγραμμα θέτοντας όλους τους καταχωρητές τμήματος στην ίδια
τιμή κάνοντας χρήση διευθύνσεων μόνο 16-Βit. Αυτό σημαίνει ότι ο κώδικας, τα δεδομένα και η στοίβα, θα
βρίσκονται μέσα στο ίδιο τμήμα 64Κ. Η μέθοδος αυτή παράγει τα γρηγορότερα και
μικρότερα προγράμματα. Γενικά, προγράμματα μεταφρασμένα με αυτή τη μέθοδο
μπορούν να μετατραπούν σε .COM
χρησιμοποιώντας την εντολή EXE2BIN του DOS. Υπενθυμίζεται ότι τα .COM προγράμματα χρησιμοποιούν μόνο ένα segment για όλες τις λειτουργίες τους.
Η εντολή EXE2BIN
συντάσσεται:
ΕΧΕ2ΒΙΝ ProgName.EXE ProgName.COM
Το Small Model είναι το μοντέλο που εξ’ ορισμού χρησιμοποιείται από
τη C και είναι χρήσιμο σε μία μεγάλη γκάμα προβλημάτων. Παρ’ ότι η
διευθυνσιοδότηση γίνεται με χρήση διευθύνσεων μεγέθους 16-Bit (Offset), το
τμήμα κώδικα είναι ξεχωριστό από το τμήμα δεδομένων, της στοίβας, και το έξτρα
(που βρίσκονται στο δικό τους τμήμα). Το συνολικό μέγεθος ενός τέτοιου
προγράμματος είναι 128Κ, σπασμένο σε κώδικα και δεδομένα.
Ο χρόνος
διευθυνσιοδότησης παραμένει ίδιος όπως και στο Tiny Model, αλλά ο
κώδικας μπορεί να είναι διπλάσιος. Τα περισσότερα προγράμματα που γράφουμε,
χωρούν σ’ αυτό το μοντέλο. Το Small
Model παράγει Run-Times που
πλησιάζουν σε ταχύτητα αυτά του Tiny.
Το Medium Model προορίζεται για μεγάλα
προγράμματα όπου ο κώδικας ξεπερνά τον περιορισμό του ενός segment. Ο κώδικας μπορεί να χρησιμοποιεί
πολλά segments και
απαιτεί χρήση 20-bit Pointers, αλλά η στοίβα, τα δεδομένα και
το έξτρα, βρίσκονται στο δικό τους segment, κάνοντας
χρήση 16-Bit δ/σεων.
Αυτό είναι καλό για μεγάλα προγράμματα τα οποία χρησιμοποιούν λίγα δεδομένα.
Προγράμματα μεταφρασμένα σ’ αυτό το μοντέλο είναι σχετικά αργά όταν αναφέρονται
σε κώδικα και συναρτήσεις, αλλά σχετικά γρήγορα (όσο και στο Small Model) όταν αναφέρονται σε δεδομένα.
Το
συμπαγές μοντέλο αποτελεί συμπλήρωμα του μεσαίου. Σ’ αυτή την περίπτωση ο
κώδικας περιορίζεται στο ένα τμήμα, αλλά τα δεδομένα μπορεί να καταλαμβάνουν
πολλά segments. Αυτό
σημαίνει ότι όλες οι διευθύνσεις σε δεδομένα απαιτούν 20-Bit αλλά ο κώδικας χρησιμοποιεί 16-Bit διευθυνσιοδότηση. Αυτό είναι καλό για προγράμματα τα
οποία απαιτούν μεγάλες ποσότητες δεδομένων αλλά λίγο κώδικα. Το πρόγραμμα θα
είναι το ίδιο γρήγορο με το small
model, αλλά θα γίνεται αργό όταν
αναφέρεται σε δεδομένα.
Το μεγάλο
μοντέλο επιτρέπει στον κώδικα και τα δεδομένα να χρησιμοποιούν πολλά segments. Πάντως, τα συνολικά στατικά
δεδομένα όπως οι πίνακες, περιορίζονται στα 64Κ. Το μοντέλο αυτό
χρησιμοποιείται όταν έχουμε μεγάλες ποσότητες κώδικα και δεδομένων. Φυσικά τα
προγράμματα αυτού του είδους είναι αρκετά πιο αργά από τα υπόλοιπα μοντέλα.
Το
τεράστιο μοντέλο είναι το ίδιο με το Large με μόνη
διαφορά ότι δεν υπάρχει περιορισμός στο σύνολο των στατικών δεδομένων. Αυτό
κάνει τα Run-Time ακόμη πιο αργά.
Γενικά, θα
χρησιμοποιήσουμε το small model αν υπάρχει λόγος να συμβεί το αντίθετο.
Χρησιμοποιούμε το Medium αν έχουμε
λίγα δεδομένα και πολύ κώδικα. Το Compact αν έχουμε
πολλά δεδομένα και λίγο κώδικα. To
Large αν έχουμε πολλά δεδομένα
και κώδικα και τα στατικά μας δεδομένα δεν ξεπερνούν τα 64Κ. Αν ξεπερνούν τα
64Κ επιλέγουμε το Huge.
Υπενθυμίζεται πάντως ότι το Large και το Huge παράγουν αργό κώδικα.
iii)
Υπερπηδώντας
τα μοντέλα μνήμης
Θα ήταν
ατυχές να αναγκαστούμε να χρησιμοποιήσουμε το compact μοντέλο (το οποίο θεωρείται αργότερο του small) σε περίπτωση που θέλαμε να κάνουμε μία μόνο αναφορά
σε δεδομένα με 20-Bit διεύθυνση
(π.χ. Video RAM). Η λύση σε τέτοιου είδους προβλήματα παρέχεται από
την Turbo C κάνοντας χρήση των Segment Override
Modifiers
(Τροποποιητές Υπερπήδησης Τμήματος). Αυτοί είναι:
far, near, huge
Οι
τροποποιητές αυτοί προσαρμόζονται μόνο σε pointers ή συναρτήσεις. Όταν εφαρμόζονται σε pointers, επηρεάζουν τον τρόπο με τον
οποίο προσπελαύνονται τα δεδομένα. Όταν εφαρμόζονται σε συναρτήσεις επηρεάζουν
τον τρόπο κλήσης ή περιστροφής από συνάρτηση.
Οι
τροποποιητές ακολουθούν το βασικό τύπο και προηγούνται του ονόματος της
μεταβλητής. Π.χ.:
char
far *fp;
Ορίζει ένα far pointer με όνομα fp.
Ο
κυριότερος τροποποιητής αυτού του είδους είναι ο far, επειδή είναι κοινότυπο να θέλουμε να προσπελάσουμε ένα
τμήμα της μνήμης που βρίσκεται έξω από το τμήμα των δεδομένων. Φυσικά, όταν η
μετάφραση έχει γίνει για large ή huge μοντέλο η προσπέλαση δεδομένων είναι αργή. Η λύση σ’
αυτό το πρόβλημα είναι να δηλώσουμε pointers ακριβώς
στο σημείο που θέλουμε σαν far να
μεταφράσουμε χρησιμοποιώντας το small μοντέλο.
Μ’ αυτόν τον τρόπο μόνο αναφορές σε αντικείμενα έξω από το ορισμένο data segment χρησιμοποιούν την υπερπήδηση.
Η χρήση far συναρτήσεων είναι λιγότερο
κοινή και γενικά απαγορεύεται σε ειδικές προγραμματιστικές περιπτώσεις.
Συνήθως, χρησιμοποιείται για κλήση συναρτήσεων έξω από το τρέχον code segment, όπως ROM-Based ρουτίνες, κλπ. Σε τέτοιες περιπτώσεις η χρήση του far σιγουρεύει τη σωστή κλήση και επιστροφή.
Υπάρχουν
δύο σοβαρά πράγματα που πρέπει να θυμόμαστε γύρω από τους far pointers στη C.
1)
Η αριθμητική των pointers επηρεάζει μόνο το offset. Αυτό
σημαίνει ότι αν ένας pointer με τιμή
0000:FFFF αυξηθεί, η νέα του τιμή θα
είναι 0000:0000 και όχι 0000:FFF0. Η τιμή
του segment δεν
αλλάζει ποτέ.
2)
Δύο far
pointers δεν
πρέπει να χρησιμοποιούνται σε μία σχεσιακή έκφραση γιατί μόνο τα offset θα ελεγχθούν. Είναι πιθανό να έχουμε δύο
διαφορετικούς pointer που θα
δείχνουν την ίδια φυσική διεύθυνση (στο δίαυλο) αλλά θα έχουν διαφορετικό segment και offset. Αν θέλουμε να συγκρίνουμε 20-Bit pointers θα πρέπει
να χρησιμοποιήσουμε huge pointers. Πάντως, οι τελεστές == και !=
μπορούν να χρησιμοποιηθούν σε 20-Bit
διευθύνσεις οπότε και ένας null
pointer μπορεί να
ανιχνευθεί.
Ένας near pointer είναι ένα 16-bit
offset που χρησιμοποιεί την τιμή
του κατάλληλου segment για να
καθορίσει μία ακριβή διεύθυνση μνήμης.
Ο
τροποποιητής near αναγκάζει
την C να χειριστεί ένα pointer σαν ένα
16-bit offsset στο segment που δείχνει ο DS.
Χρησιμοποιούμε τον τροποποιητή near όταν το
πρόγραμμα έχει μεταφραστεί σε medium, large ή huge
model και θέλουμε να το
υπερπηδήσουμε. Εάν χρησιμοποιήσουμε τον τροποποιητή near σε συνάρτηση, αναγκάζουμε τη συνάρτηση να συμπεριφερθεί σα
να είχαμε χρησιμοποιήσει το small
model. Αν η συνάρτηση έχει
μεταφραστεί κάνοντας χρήση του Tiny, Small ή Compact μοντέλου,
όλες οι κλήσεις τοποθετούν μία τιμή 16-bit στη στοίβα. Μεταφράζοντας με Large μοντέλο, μία δ/νση του τύπου segment : offset εύρους
20-bit, θα γίνει push στη στοίβα.
O huge pointer είναι σαν
τον far με δύο διαφορές:
1.
To
segment του έχει
ομαλοποιηθεί έτσι ώστε οι συγκρίσεις μεταξύ huge pointers να έχουν
νόημα.
2.
Όταν ένας huge
pointer αυξάνεται
και το segment και το offset αλλάζουν. Οι huge pointers δεν
υποφέρουν από την ανακύκλωση που εμφανίζεται στους far.
Η Turbo C υποστηρίζει τέσσερις τροποποιητές διευθύνσεων,
τους: _cs, _ds, _ss, και _es. Όταν ένας από αυτούς δηλωθεί μπροστά από έναν pointer τότε ο pointer γίνεται ένα 16-bit
offset στο συγκεκριμένο segment. Π.χ.:
int_es *ptr;
Ο pointer ptr θα δείχνει ένα offset στο extra
segment. Γενικά,
δε χρησιμοποιούνται και πολύ, παρά μόνο στις πολύ “εξωτικές” εφαρμογές.
H παρακάτω εφαρμογή εμφανίζει ένα τμήμα της μνήμης RAM και μας επιτρέπει να το
δούμε ή να το αλλάξουμε. Το πρόγραμμα θα χρησιμοποιεί τον τροποποιητή far οπότε μπορούμε να αναφερθούμε σε οποιοδήποτε σημείο
της μνήμης.
Η πρώτη
συνάρτηση που θα δημιουργήσουμε είναι η display_mem( ) την
οποία θα χρησιμοποιήσουμε για να εμφανίσουμε τα περιεχόμενα της μνήμης στη
δ/νση που θα ζητήσει ο χρήστης. Στην αρχή ζητά να εισάγουμε μία τιμή 20-bit και εμφανίζει τα επόμενα 256 bytes.
Το
αποτέλεσμα δείχνει 16 γραμμές με 16 bytes η
καθεμία. Η διεύθυνση της κάθε γραμμής εμφανίζεται στα αριστερά της.
Η δεύτερη
συνάρτηση είναι η change_mem( ). Χρησιμοποιείται για να επιτευχθεί η αλλαγή του
περιεχομένου ενός συγκεκριμένου byte.
Λειτουργεί ζητώντας της 20-bit διεύθυνση
του byte και στη συνέχεια ζητά τη
νέα τιμή του.
Το κυρίως
πρόγραμμα ζητά ή την τιμή D ή την
τιμή C εμφανίζοντας και το promt <. Εάν πατηθεί Q η λειτουργία τερματίζεται.
/ * Display And / Or Change Memory Program * /
#include <stdlib.h>
#include <stdio.h>
void display_mem( ), change_mem( )
main( )
{
char ch;
for (;
;) {
printf
(“
< “); / * Display Prompt * /
ch
= getche ( ); / * Read Command * /
printf
(“ \ n”);
switch
(tolower (ch) ) {
case
‘d’ : display_mem( );
break;
case ‘c’ : change_mem( );
break;
case
‘q’ : exit(0);
}
}
}
void display_mem( )
{
register
int i;
unsigned
char ch;
unsigned
char far *p;
char
s[80];
printf(“Beginning
Address (in HEX) : “);
scanf (“%p%*c”, &p);
scanf (“%p
: “, p);
for (i=1 ; i<=256 ; i++) {
ch
= *p;
printf
(“%02x”, ch);
p++;
if
(! ( i%16) ) {
printf
(“ \ n “);
if
(i! = 256) printf(“%p :”, p);
}
}
}
void change_mem( )
{
unsigned
char far *p;
char s
[80];
char
value;
printf (“Enter
Addres To Change (in HEX) : “);
scanf (
“ %x”, &value);
*p =
(unsigned char) value;
}