ΠΑΝΕΠΙΣΤΗΜΙΟ ΠΑΤΡΩΝ ΤΜΗΜΑ ΗΛΕΚΤΡΟΛΟΓΩΝ ΜΗΧΑΝΙΚΩΝ ΚΑΙ ΤΕΧΝΟΛΟΓΙΑΣ ΥΠΟΛΟΓΙΣΤΩΝ ΤΟΜΕΑΣ: Ηλεκτρονικής και Υπολογιστών

Σχετικά έγγραφα
Writing kernels for fun and profit

Προηγμένοι Μικροεπεξεργαστές. Εργαστήριο 4 - Editor

Προηγμένοι Μικροεπεξεργαστές. Φροντιστήριο 4 Real Mode Interrupts

Προηγμένοι Μικροεπεξεργαστές. Paging & Segmentation

Προηγμένοι Μικροεπεξεργαστές. Εργαστήριο 3 Task Switching in PM

Εισαγωγή στην Πληροφορική

Εργαστήριο Λειτουργικών Συστημάτων 8o εξάμηνο, Ροή Υ, ΗΜΜΥ

Αρχιτεκτονική-ΙI Ενότητα 6 :

Προηγμένοι Μικροεπεξεργαστές. Εργαστήριο 6 C & Assembly

Διεργασίες (μοντέλο μνήμης & εκτέλεσης) Προγραμματισμός II 1

Υποστήριξη Λ.Σ. ΜΥΥ-106 Εισαγωγή στους Η/Υ και στην Πληροφορική

Αρχιτεκτονική υπολογιστών

Οργάνωση Υπολογιστών (IΙI)

Μάθημα 3.8 Τεχνικές μεταφοράς δεδομένων Λειτουργία τακτικής σάρωσης (Polling) Λειτουργία Διακοπών DMA (Direct Memory Access)

ΑΡΧΙΤΕΚΤΟΝΙΚΗ HARDWARE ΥΠΟΛΟΓΙΣΤΙΚΩΝ ΣΥΣΤΗΜΑΤΩΝ

Μικροεπεξεργαστές - Μικροελεγκτές Ψηφιακά Συστήματα

Εικονική Μνήμη (Virtual Μemory)

Μάθημα 8: Επικοινωνία Συσκευών με τον Επεξεργαστή

Keyboard. Ασσιούρας Ιωάννης 5593 Βούκας Ιωάννης 5001 Πρωτονοτάριος Ιωάννης 6072

Λιβανός Γιώργος Εξάμηνο 2017Β

Διεργασίες (μοντέλο μνήμης & εκτέλεσης) Προγραμματισμός II 1

- Εισαγωγή - Επίπεδα μνήμης - Ολοκληρωμένα κυκλώματα μνήμης - Συσκευασίες μνήμης προσωπικών υπολογιστών

Προηγμένοι Μικροεπεξεργαστές. Παρουσίαση Projects

Βασικές συσκευές Ε/Ε. Είσοδος Έξοδος στον υπολογιστή. Ένα τυπικό υπολογιστικό σύστημα σήμερα. Οργάνωση Υπολογιστών (IΙI) Μ.

ΠΡΟΗΓΜΕΝΟΙ ΜΙΚΡΟΕΠΕΞΕΡΓΑΣΤΕΣ PROJECT 2: MEMORY MANAGEMENT

Ιεραρχία Μνήμης. Εικονική μνήμη (virtual memory) Επεκτείνοντας την Ιεραρχία Μνήμης. Εικονική Μνήμη. Μ.Στεφανιδάκης

ΛΕΙΤΟΥΡΓΙΚΑ ΣΥΣΤΗΜΑΤΑ ΟΡΓΑΝΩΣΗ Η/Υ

Κεφάλαιο 4 Σύνδεση Μικροεπεξεργαστών και Μικροελεγκτών ΕΡΩΤΗΣΕΙΣ ΑΣΚΗΣΕΙΣ

Προηγμένοι Μικροεπεξεργαστές. Παρουσίαση Εργασιών

Ενσωµατωµένα Υπολογιστικά Συστήµατα (Embedded Computer Systems)

Διαφορές single-processor αρχιτεκτονικών και SoCs

Λειτουργικά Συστήματα Πραγματικού Χρόνου

Προηγμένοι Μικροεπεξεργαστές. Protected Mode & Multitasking

Εισαγωγή στην Πληροφορική

Εισαγωγή εκτελέσιμου κώδικα σε διεργασίες

Κεφάλαιο Το υπολογιστικό σύστημα Η εξέλιξη του ανθρώπου πραγματοποιήθηκε χάρη στην ικανότητά στον χειρισμό εργαλείων.

MIPS Interactive Learning Environment. MILE Simulator. Version 1.0. User's Manual

Λειτουργικά Συστήματα (Λ/Σ)

Μάθημα 8: Διαχείριση Μνήμης

Οργάνωση ενός σύγχρονου Υπολογιστικού Συστήματος ή Ηλεκτρονικού Υπολογιστή (Η/Υ) Τα σύγχρονα συστήματα Η/Υ έχουν την παρακάτω οργάνωση:

SIMATIC MANAGER SIMATIC MANAGER

Εικονική Μνήμη (Virtual Μemory)

Ιόνιο Πανεπιστήμιο Τμήμα Πληροφορικής Αρχιτεκτονική Υπολογιστών Εικονική Μνήμη. (και ο ρόλος της στην ιεραρχία μνήμης)

Εικονική Μνήμη (1/2)

Σχεδίαση και Υλοποίηση Μηχανισμού Μεταφοράς Δεδομένων από Συσκευές Αποθήκευσης σε Δίκτυο Myrinet, Χωρίς τη Μεσολάβηση της Ιεραρχίας Μνήμης

Dr. Kerneldev or: how I learned to stop worrying and love the pagefault

ΔΙΑΧΕΙΡΙΣΗ ΜΝΗΜΗΣ. Λειτουργικά Συστήματα Ι. Διδάσκων: Καθ. Κ. Λαμπρινουδάκης ΛΕΙΤΟΥΡΓΙΚΑ ΣΥΣΤΗΜΑΤΑ Ι

ΔΙΑΧΥΤΑ ΚΑΙ ΕΝΣΩΜΑΤΩΜΕΝΑ ΣΥΣΤΗΜΑΤΑ

Λειτουργικά Συστήματα (Λ/Σ)

Στοιχεία αρχιτεκτονικής μικροεπεξεργαστή

ΚΕΦΑΛΑΙΟ 3: Λειτουργικά Συστήµατα

ΜΥΥ- 402 Αρχιτεκτονική Υπολογιστών Μεταγλώτιση, σύνδεση

Σελίδα 1 από 11. Απαντήσεις στο φυλλάδιο 57 Ερώτηση: 1 η : Οι ακροδέκτες αυτοί χρησιµοποιούνται για:

Αρχιτεκτονική Υπολογιστών

Λειτουργικά Συστήματα (διαχείριση επεξεργαστή, μνήμης και Ε/Ε)

Τι είναι ένα λειτουργικό σύστημα (ΛΣ); Μια άλλη απεικόνιση. Το Λειτουργικό Σύστημα ως μέρος του υπολογιστή

Μάθημα 3: Αρχιτεκτονική Υπολογιστών

Μικροεπεξεργαστές. Σημειώσεις Μαθήματος Υπεύθυνος: Δρ Άρης Παπακώστας,

Αρχιτεκτονική Υπολογιστών II Ενδεικτικές απαντήσεις στα θέματα των εξετάσεων

Λειτουργικά Συστήματα Ι. Καθηγήτρια Παπαδάκη Αναστασία

2. Σκοποί και Λειτουργίες των ΛΣ. Λειτουργικά Συστήματα Η/Υ. Περίληψη. Ι. Προστασία Υλικού ΚΕΦΑΛΑΙΟ 2 - ΕΞΕΛΙΞΗ ΚΑΙ ΣΚΟΠΟΙ ΛΣ

Υπάρχουν δύο τύποι μνήμης, η μνήμη τυχαίας προσπέλασης (Random Access Memory RAM) και η μνήμη ανάγνωσης-μόνο (Read-Only Memory ROM).

Κεφάλαιο 3. Διδακτικοί Στόχοι

Οργάνωση και Αρχιτεκτονική Υπολογιστών. Κεφάλαιο 7.4

Προηγμένοι Μικροεπεξεργαστές. Έλεγχος Ροής Προγράμματος

Κεντρική Μονάδα Επεξεργασίας (ΚΜΕ) Τμήματα ΚΜΕ (CPU) Ένα τυπικό υπολογιστικό σύστημα σήμερα. Οργάνωση Υπολογιστών (Ι)

ΗΛΕΚΤΡΟΝΙΚΟΙ ΥΠΟΛΟΓΙΣΤΕΣ

Εισαγωγικά & Βασικές Έννοιες

ΗΜΥ 213. Εργαστήριο Οργάνωσης Η.Y. και Μικροεπεξεργαστών week 5. Διδάσκων: Δρ. Γιώργος Ζάγγουλος

Προγραμματισμός Ι. Δυναμική Διαχείριση Μνήμης. Δημήτρης Μιχαήλ. Ακ. Έτος Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

Προηγμένοι Μικροεπεξεργαστές. Παρουσίαση Projects

Οργάνωση Υπολογιστών (Ι)

Προγραμματισμός Ι. Προχωρημένα Θέματα. Δημήτρης Μιχαήλ. Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

Κεφάλαιο 4 ο. Ο Προσωπικός Υπολογιστής

ΛΕΙΤΟΥΡΓΙΚΑ ΣΥΣΤΗΜΑΤΑ ΙΙ - UNIX. Συστήματα Αρχείων. Διδάσκoντες: Καθ. Κ. Λαμπρινουδάκης Δρ. Α. Γαλάνη

Computer Setup Οδηγός χρήσης

Λειτουργικά Συστήματα. Τ.Ε.Ι. Ιονίων Νήσων Σχολή Διοίκησης και Οικονομίας - Λευκάδα

Δημήτρης Πρίτσος. ISLAB HACK: Βασικές Έννοιες της Αρχιτεκτονικής

1. Εισαγωγή. Λειτουργικά Συστήματα Η/Υ. Διεργασίες. Ορισμός ΚΕΦΑΛΑΙΟ 3 - ΔΙΕΡΓΑΣΙΕΣ. Κεφάλαιο 3 «Διεργασίες»

ΛΕΙΤΟΥΡΓΙΚΑ ΣΥΣΤΗΜΑΤΑ ΕΙΣΑΓΩΓΗ

Εισαγωγή στα Λειτουργικά

Αρχιτεκτονική υπολογιστών

Κατανεμημένα Συστήματα

Η ιεραρχία της μνήμης

Βοηθητικό πρόγραµµα Setup Οδηγός χρήσης

Διαχείριση Κύριας Μνήμης

Εικονικοποίηση. Αρχιτεκτονική Υπολογιστών 5ο Εξάμηνο,

Λειτουργικά Συστήματα (ΗΥ321)

ΛΕΙΤΟΥΡΓΙΚΑ ΣΥΣΤΗΜΑΤΑ Ι. Λειτουργικά Συστήματα Ι ΔΙΑΧΕΙΡΙΣΗ ΜΝΗΜΗΣ. Επ. Καθ. Κ. Λαμπρινουδάκης

Μικροεπεξεργαστές. Σημειώσεις Μαθήματος Υπεύθυνος: Δρ Άρης Παπακώστας,

ΣΥΣΚΕΥΕΣ ΑΠΟΘΗΚΕΥΣΗΣ (ΜΝΗΜΗ)

Λειτουργικά Συστήματα (Λ/Σ)

Προγραμματισμός Ι (HY120)

Λειτουργικά Συστήματα Η/Υ

Μετάφραση ενός Προγράμματος Εξαιρέσεις

Γενική οργάνωση υπολογιστή «ΑΒΑΚΑ»

ΕΡΓΑΣΤΗΡΙΟ ΑΡΧΙΤΕΚΤΟΝΙΚΗΣ Η/Υ

ΛΟΓΙΣΜΙΚΟ (software)

Λειτουργικά Συστήματα

ΛΕΙΤΟΥΡΓΙΚΑ ΣΥΣΤΗΜΑΤΑ. Διαχείριση μνήμης III

Transcript:

ΠΑΝΕΠΙΣΤΗΜΙΟ ΠΑΤΡΩΝ ΤΜΗΜΑ ΗΛΕΚΤΡΟΛΟΓΩΝ ΜΗΧΑΝΙΚΩΝ ΚΑΙ ΤΕΧΝΟΛΟΓΙΑΣ ΥΠΟΛΟΓΙΣΤΩΝ ΤΟΜΕΑΣ: Ηλεκτρονικής και Υπολογιστών Διπλωματική Εργασία του φοιτητή του Τμήματος Ηλεκτρολόγων Μηχανικών και Τεχνολογίας Υπολογιστών της Πολυτεχνικής Σχολής του Πανεπιστημίου Πατρών Αθανασίου Αντώνιου Μάριου του Μιχαήλ Αριθμός Μητρώου: 5578 Θέμα «Ανάπτυξη πλατφόρμας για τον προγραμματισμό προηγμένων λειτουργιών σε μοντέρνους επεξεργαστές» Επιβλέπων Καξίρας Στέφανος Αριθμός Διπλωματικής Εργασίας: Πάτρα, Μάρτιος 2010

Ε Υ Χ Α Ρ Ι Σ Τ Ι Ε Σ Με την ολοκλήρωση της παρούσης διπλωματικής εργασίας θα ήθελα να ευχαριστήσω τους ανθρώπους οι οποίοι βοήθησαν στην περάτωση αυτής. Κατά κύριο λόγο οφείλω να ευχαριστήσω τον επιβλέποντα καθηγητή Στέφανο Καξίρα και τον κ. Γιάννη Κωνσταντινίδη. Ιδιαίτερες ευχαριστίες εκφράζω στο μεταπτυχιακό φοιτητή Παύλο Πετούμενο του οποίου η καθοδήγηση ήταν πολύτιμη και καθοριστική. Τέλος, θέλω να ευχαριστήσω τους φίλους μου Θάλεια, Δημήτρη, Γιώργο, Κωνσταντίνο και Θέμη για την ψυχολογική τους υποστήριξη και συμμετοχή σε δοκιμές του κώδικα. 1

Π Ε Ρ Ι Λ Η Ψ Η... 4 1. ΕΙΣΑΓΩΓΗ... 5 2. Η Χ86 ΑΡΧΙΤΕΚΤΟΝΙΚΗ... 6 2.1 Ο καταχωρητές EFLAGS... 7 2.2 Οι καταχωρητές CR... 8 3. ΠΕΡΙΓΡΑΦΗ ΛΕΙΤΟΥΡΓΙΚΟΥ ΣΥΣΤΗΜΑΤΟΣ... 11 3.1 Εκκίνηση (boot)... 11 3.2 Αρχικοποίηση (Initialization)... 12 4. ΣΧΕΔΙΑΣΗ ΤΟΥ ΣΥΣΤΗΜΑΤΟΣ... 14 4.1 Περιβάλλον Σχεδίασης Εργαλεία... 14 4.2 Συμβατικός τρόπος λειτουργίας (Real Mode)... 15 4.3 Σχεδίαση Stage 1 Boot Loader... 16 4.4 Σχεδίαση Stage 2 Boot Loader... 17 4.4.1 Αρχικοποίηση Segment Registers... 19 4.4.2 Εγκατάσταση Global Descriptor Table... 19 4.4.3 Ενεργοποίηση γραμμής A20... 20 4.4.4 Εύρεση και αντιγραφή του Πυρήνα... 20 4.4.5 Χαρτογράφηση της μνήμης... 21 4.4.6 Πέρασμα σε Protected Mode... 21 4.4.7 Αντιγραφή του πυρήνα στη μόνιμή του θέση... 22 4.5.1 Αφαιρετική προσέγγιση... 22 4.5.2 Boot Information... 24 4.5.3 Physical Memory Manager... 25 4.5.4 Virtual Memory Manager... 26 4.5.6 Interrupt Descriptor Table... 27 4.5.6 Interrupt Service Routines... 27 4.5.7 IRQ και ο PIC... 27 4.5.8 Programmable Interval Timer... 28 4.5.9 Keyboard Driver... 28 4.5.10 Task Manager, Tasks Setup... 28 4.5.11 Command Line Interface... 29 5. ΑΝΑΛΥΤΙΚΗ ΠΕΡΙΓΡΑΦΗ ΤΗΣ ΑΡΧΙΚΟΠΟΙΗΣΗΣ ΤΟΥ ΠΥΡΗΝΑ... 30 5.1 Αρχικοποίηση Μνήμης... 30 5.1.1 Physical Memory Manager... 30 2

5.1.2 Virtual Memory Manager... 31 5.2 Αρχικοποίηση Exceptions και Interrupts... 32 5.2.1 Interrupt Descriptor Table... 32 5.2.2 Εγκατάσταση ISRS (Interrupts/Exceptions)... 33 5.2.3 Εγκατάσταση IRQ και PIC... 33 5.3 Εγκατάσταση PIT και πληκτρολογίου... 34 5.4 Αρχικοποίηση Διεργασιών... 35 6. ΑΝΑΛΥΣΗ ΤΟΥ ΣΥΣΤΗΜΑΤΟΣ... 36 6.1 Διαχείριση Μνήμης... 36 6.1.1 Το αρχείο physical_memory.c... 36 6.1.2 Τα αρχεία virtual_memory.c, virtual_pde.c και virtual_pte.c... 38 6.2 Διαχείριση διακοπών... 40 6.2.1 Το αρχείο idt.c... 40 6.2.1 Τα αρχεία isrs.c, irq.c και assembly.asm... 41 6.3 Διαχείριση Timer και πληκτρολογίου... 42 6.3.1 Το αρχείο timer.c... 43 6.3.1 Το αρχείο kb.c... 43 6.3 Διαχείριση Διεργασιών... 45 6.3.1 Το αρχείο task.c... 45 6.4 Η γραμμή εντολών... 48 6.4.1 Λειτουργία Γραμμής Εντολών... 48 6.4.2 Οι εντολές του CLI... 49 6.5 Συμπληρωματικές Λειτουργίες και συναρτήσεις... 50 6.5.1 Το αρχεία stdlib.c... 50 6.5.2 Τα αρχεία scr.c και kprintf.c... 51 6.5.3 Το αρχείο speaker.c... 52 6.6 Το αρχείο makefile... 53 6.6.1 CFLAGS... 53 6.6.2 kernel.o... 54 6.6.2 krnl.sys... 54 6.7 Χάρτης Μνήμης Συστήματος... 55 ΠΑΡΑΡΤΗΜΑ Α ΒΙΒΛΙΟΓΡΑΦΙΑ... 56 ΠΑΡΑΡΤΗΜΑ Β ΠΗΓΑΙΟΣ ΚΩΔΙΚΑΣ ΤΟΥ SHEEP OS... 57 3

Π Ε Ρ Ι Λ Η Ψ Η Το αντικείμενο της παρούσης εργασίας είναι η ανάλυση και η περιγραφή της προσπάθειας σχεδιασμού ενός λειτουργικού συστήματος χρησιμοποιώντας τις δυνατότητες που παρέχονται από την x86 αρχιτεκτονική υπολογιστών της Intel. Απώτερος σκοπός της διπλωματικής εργασίας θα μπορούσε να είναι η συμβολή στην εκπαίδευση και επιμόρφωση των προπτυχιακών φοιτητών που επιλέγουν το μάθημα των προηγμένων μικροεπεξεργαστών. Η ανάθεση ατομικών ή ομαδικών εργασιών στα πλαίσια του μαθήματος για τη βελτίωση του λειτουργικού συστήματος, θα μπορούσε να αποτελέσει βάση εκμάθησης των λειτουργιών των επεξεργαστών, και να συμβάλει στην ανάπτυξη της ομαδικής εργασίας και συνεργασίας των φοιτητών. Στο πρώτα δύο κεφάλαια γίνεται μια σύντομη περίληψη της x86 αρχιτεκτονικής και στο τρίτο κάνουμε μια εισαγωγή στην διαδικασία αρχικοποίησης. Στο τέταρτο κεφάλαιο γίνεται παρουσίαση του περιβάλλοντος ανάπτυξης και των χρησιμοποιηθέντων εργαλείων, ενώ αναλύεται ο κώδικας εκκίνησης του συστήματος και γίνεται μια πρώτη, αφαιρετική προσέγγιση στον πυρήνα σε υψηλό επίπεδο Στο πέμπτο κεφάλαιο εμβαθύνουμε το στάδιο αρχικοποίησης του πυρήνα ενώ στο έκτο γίνεται αναλυτική παρουσίαση της σχεδίασης σε επίπεδο αρχείων και συναρτήσεων. Τέλος, στο παράρτημα δίνεται ο πηγαίος κώδικας της διπλωματικής ανά αρχείο και η βιβλιογραφία. 4

1. Εισαγωγή Από τις αρχές της δεκαετίας του 1980 μέχρι σήμερα, η x86 αρχιτεκτονική υπολογιστών έχει παρουσιάσει σημαντική εξέλιξη στο χώρο των υπολογιστικών συστημάτων. Αρχής γενομένης από τον 8086 το 1978 και τον 80286 το 1982 ήδη βρισκόμαστε στη 10η γενιά της αρχιτεκτονικής με τους επεξεργαστές Core i7. Οι πιο σημαντικοί σταθμοί σε αυτή τη διαδρομή φαίνονται στον Πίνακα 1. Γενιά Έτος CPU Νέες λειτουργίες 2 1982 80286 Προστατευμένος τρόπος Λειτουργίας (Protected Mode) 3 1985 80386 Σελιδοποίηση (Paging), 32 bit instruction set (IA 32), Hardware Multitasking 6 1995 Pentium Pro Επέκταση Φυσικής Διεύθυνσης (Physical Address Extension) 8 2004 Pentium 4 Prescott Εισαγωγή x86 64 instruction set Πίνακας 1: Εξέλιξη της x86 αρχιτεκτονικής. Ξεκινώντας από τον 80286, διακρίνουμε λειτουργίες που χρησιμοποιούνται ευρέως μέχρι σήμερα, αν και ελαφρά παραλλαγμένες. Ο προστατευμένος τρόπος λειτουργίας (Protected Mode) είναι ο μόνος αποδεκτός για την πλήρη αξιοποίηση των λειτουργιών του επεξεργαστή. Η χρήση σελιδοποίησης (Paging) διευκολύνει τις αυξανόμενες ανάγκες σε μνήμη καθώς και τη θέσπιση προδιαγραφών για εκτελέσιμα προγράμματα σε ένα λειτουργικό σύστημα ενώ το Hardware Multitasking επιτρέπει μια ενιαία δομή διαχείρισης των διεργασιών προς διευκόλυνση του προγραμματισμού του συστήματος. 5

2. Η Χ86 Αρχιτεκτονική Η αρχιτεκτονική είναι γνωστή. Γίνεται μια σύντομη αναφορά σε καταχωρητές ειδικού σκοπού και δομές που έχουν χρησιμοποιηθεί στην παρούσα εργασία. Όπως είναι ήδη γνωστό, μπορούμε να απεικονίσουμε ένα σύστημα x86 σύμφωνα με το Διάγραμμα 2.1. Διάγραμμα 2.1: Καταχωρητές συστήματος και δομές δεδομένων. 6

2.1 Ο καταχωρητές EFLAGS Σε αυτό τον καταχωρητή περιέχονται πληροφορίες του επεξεργαστή για μια δεδομένη στιγμή. Η μορφή του καταχωρητή παρουσιάζεται στο Διάγραμμα 2.2: Διάγραμμα 2.2: Ο καταχωρητής EFLAGS. Τα στοιχεία που μας ενδιαφέρουν είναι τα εξής: NT: Η σημαία ένδειξης εμφωλιασμού της διεργασίας (Nested Task) κατά τον προστατευμένο τρόπο λειτουργίας, χρησιμοποιείται για να δείξει ότι η τρέχουσα διεργασία είναι ένθετη από το λογισμικό, μέσα σε μια άλλη διαδικασία. IOPL: Τα bits αυτά χρησιμοποιούνται στον προστατευμένο τρόπο λειτουργίας για να προσδιοριστεί το προνομιακό επίπεδο της τρέχουσας διεργασίας για τις συσκευές εισόδου εξόδου. Το επίπεδο της τρέχουσας 7

διεργασίας (CPL) θα πρέπει να είναι μικρότερο ή ίσο του IOPL για να προσπελάσει το χώρο των συσκευών. IF: Η σημαία αυτή προσδιορίζει την απόκριση του επεξεργαστή σε διακοπές (interrupts). Δεν έχει επίδραση σε software Interrupts, εξαιρέσεις (exceptions) και στο NMI. 2.2 Οι καταχωρητές CR Οι καταχωρητές CR0 έως CR4 (Control Register) περιέχουν σημαίες και δομές για έλεγχο διαδικασιών και λειτουργιών σε επίπεδο συστήματος. Η δομή των καταχωρητών παρουσιάζεται αναλυτικότερα στο Διάγραμμα 1.3. Διάγραμμα 2.3: Οι καταχωρητές Ελέγχου (Control Registers). 8

Ειδικότερα: CR0: Περιέχει σημαίες ελέγχου του συστήματος, καθώς και του τρόπου λειτουργίας του επεξεργαστή. Τα πιο σημαντικά στοιχεία του συγκεκριμένου καταχωρητή για εμάς είναι: - PG: Ενεργοποίηση/απενεργοποίηση του μηχανισμού σελιδοποίησης (Paging). - PE: Ενεργοποίηση/απενεργοποίηση του προστατευμένου τρόπου λειτουργίας (Protected Mode). CR1: Δεσμευμένος για μελλοντική χρήση. CR2: Περιέχει τη γραμμική διεύθυνση μνήμη (Linear Address) όταν προκύπτει σφάλμα σελίδας (Page Fault). CR3: Περιέχει τη φυσική διεύθυνση της βάσης της δομής για τον μηχανισμό σελιδοποίησης. Αναφέρεται και ως PDBR (Page Directory Base Register). CR4: Περιέχει σημαίες που ενεργοποιούν διάφορες δυνατότητες της αρχιτεκτονικής, όπως: - PSE: Page size Extensions. Διαμορφώνει το μέγεθος σελίδας στα 4kb ή στα 4mb. 9

- PAE: Physical Address Extension. Όταν τεθεί, επιτρέπει στο μηχανισμό σελιδοποίησης τη διευθυνσιοδότηση φυσικής μνήμης με εύρος 36 bit. 10

3. Περιγραφή Λειτουργικού Συστήματος Ακολουθεί η γενική περιγραφή του Λειτουργικού Συστήματος τη λειτουργία του οποίου μπορούμε να χωρίσουμε σε δύο μέρη: εκκίνηση και αρχικοποίηση του συστήματος. 3.1 Εκκίνηση (boot) Η διαδικασία εκκίνησης ενός υπολογιστή x86 είναι συγκεκριμένη και κατά ένα μεγάλο μέρος τυποποιημένη κυρίως για λόγους συμβατότητας. Γι αυτό το λόγο ένα παλαιό λειτουργικό σύστημα μπορεί να εκκινήσει σε σύγχρονο υπολογιστή και (μερικές φορές) το αντίστροφο. Όταν γίνεται εκκίνηση του υπολογιστή, ο επεξεργαστής ξεκινάει την εκτέλεσή του από την διεύθυνση F000:FFF0 και από εκεί, γίνεται η εκτέλεση του BIOS. Μετά την εκτέλεση των απαραίτητων ελέγχων (Power on Self Test ή POST), το BIOS αναζητεί συσκευή από την οποία μπορεί να γίνει η εκκίνηση λειτουργικού συστήματος, δηλαδή bootable device. Ένα bootable device καθορίζεται από την «υπογραφή» ΑΑ55 στα δύο τελευταία bytes του πρώτου sector. O assembly κώδικας της διαδικασίας εκκίνησης ή bootstrap μπορεί να είναι το πολύ 510 bytes. Η τελευταία ενέργεια που κάνει το BIOS για εμάς, είναι να αντιγράψει αυτό το sector στη διεύθυνση 7C00. Τα περισσότερα λειτουργικά συστήματα αποκαλούν τον κώδικα της παραπάνω διαδικασίας boot loader ή Stage1 boot loader. Επειδή 510 bytes συνήθως δεν είναι αρκετά για να γίνουν οι κατάλληλες αρχικοποιήσεις, ο boot 11

loader φορτώνει συμπληρωματικό κώδικα γνωστός και ως Stage2 χωρίς να υπάρχει περιορισμός στο μέγεθος του κώδικα αυτού. Η εκκίνηση του λειτουργικού μας συστήματος (Sheep OS) στηρίζεται στα παραπάνω. Ο Stage1 boot loader αναλαμβάνει μονάχα την εύρεση, την αντιγραφή στη μνήμη και την εκτέλεση του Stage2 Boot loader. Εν συνεχεία, γίνονται οι κατάλληλες ενέργειες για την αρχικοποίηση του περιβάλλοντος εκτέλεσης του πυρήνα (Kernel) πριν περάσουμε στην εκτέλεση αυτού. Η κυριότερη από αυτές, είναι η ενεργοποίηση του προστατευμένου τρόπου λειτουργίας. 3.2 Αρχικοποίηση (Initialization) Ο πυρήνας είναι το πιο σημαντικό κομμάτι του λειτουργικού συστήματος. Γεφυρώνει τις εφαρμογές με τις συσκευές εισόδου/εξόδου, και αναλαμβάνει την επεξεργασία των δεδομένων σε επίπεδο υλικού. Είναι υπεύθυνος για τη διαχείριση των πόρων και τη σταθερότητα του συστήματος. Το μοντέλο που υιοθετήθηκε στο Sheep OS είναι μονολιθικός πυρήνας (Monolithic Kernel). Η βασική αρχιτεκτονική σχεδίαση αυτού του είδους ορίζει ότι ο πυρήνας εκτελείται στο υψηλότερο επίπεδο προτεραιότητας (Ring 0), οι υπηρεσίες που παρέχονται σε χρήστες ή εφαρμογές πραγματοποιούνται μέσω κλήσεων συστήματος (System Calls) και ότι αποτελούν αποκλειστικά μέρος του πυρήνα. Πλεονεκτήματα αυτής της σχεδίασης είναι η ταχύτητα εκτέλεσης, η αξιοπιστία, καθώς και οι στενά συνδεδεμένες δομές του συστήματος. Από την άλλη πλευρά, εάν υπάρξει κάποιο πρόβλημα με οδηγό συσκευής ή υπηρεσία του συστήματος, μπορεί να καταρρεύσει ολόκληρο το σύστημα. Η δομή ενός Monolithic Kernel φαίνεται στο Διάγραμμα 3.1: 12

Applications User Mode System Call File System, Memory Manager, Task Manager Kernel Mode Device Drivers, Interrupt Handlers Hardware Διάγραμμα 3.1: μονολιθικός πυρήνας. Η πρώτη δουλειά του πυρήνα, είναι η αρχικοποίηση των δομών και των υπηρεσιών που θα χρησιμοποιηθούν αργότερα από το λειτουργικό. Μερικά από αυτά είναι ο πίνακας διακοπών (IDT), ο διαχειριστής φυσικής μνήμης (Physical Memory Manager), ο διαχειριστής εικονικής μνήμης (Virtual Memory Manager) καθώς και συναρτήσεις για εμφάνιση πληροφοριών στην οθόνη. Περιφερειακές συσκευές όπως ο Programmable Interrupt Controller (PIC) ή ο PIT (Programmable Interval Timer) χρειάζονται εκ νέου προγραμματισμό. Τέλος, περνάμε στην εκτέλεση της γραμμής εντολών (Command Line Interface ή CLI) του συστήματος όπου πραγματοποιείται η αλληλεπίδραση με το χρήστη. 13

4. Σχεδίαση του συστήματος Στις παρακάτω ενότητες παρουσιάζεται μια λεπτομερής περιγραφή του σχεδιασμού και της υλοποίησης του λειτουργικού συστήματος, δυσκολίες που προέκυψαν και πώς αυτές αντιμετωπίστηκαν. 4.1 Περιβάλλον Σχεδίασης Εργαλεία Η σχεδίαση του Sheep OS έγινε σε περιβάλλον Linux. Η απόφαση αυτή ελήφθη λόγω της ελευθερίας και ευκολίας στη χρήση των εργαλείων που παρέχει το λειτουργικό Linux (gcc, ld, objdump). Τα απαιτούμενα προγράμματα είναι τα εξής: Nasm: Assembler κώδικα assembly για τη δημιουργία binary αρχείων και τύπου object. Gcc: Compiler κώδικα C προς δημιουργία αρχείων τύπου object. Ld: Linker που αναλαμβάνει τη σύνδεση των αρχείων τύπου object. Objcopy: Δημιουργία αρχείων binary από εκτελέσιμα που παράγει ο ld. Make: Παρέχει αυτοματοποιημένη διαδικασία δημιουργίας εκτελέσιμων αρχείων ή τύπου object. Dd: Ανάγνωση/εγγραφή εικονικών αρχείων δίσκων (Image Files) προς και από τη δισκέτα. 14

Πέρα από τη σχεδίαση, χρειάζεται και ένα περιβάλλον αποσφαλμάτωσης (Debugging) για τον έλεγχο ροής και την εύρεση σφαλμάτων του Sheep OS. Λόγω προσωπικής προτίμησης, επελέχθησαν τα Microsoft Windows με τη βοήθεια των προγραμμάτων Bochs και Vmware Server. Το Bochs είναι ένας εξομοιωτής x86 συστημάτων με εξελιγμένες δυνατότητες debugging, ενώ το Vmware Server είναι λογισμικό Virtualization, επιτρέποντας την παράλληλη εκτέλεση πολλών λειτουργικών συστημάτων σε ένα μηχάνημα. Τέλος, δοκιμές του Sheep OS έγιναν και εκτός εικονικού περιβάλλοντος σε αρκετούς υπολογιστές για τον κατά δυνατό μέγιστο έλεγχο συμβατότητας. 4.2 Συμβατικός τρόπος λειτουργίας (Real Mode) Κατά την έναρξη του υπολογιστή, ο επεξεργαστής δουλεύει στο λεγόμενο συμβατικό τρόπο λειτουργίας ή Real Mode. Θα πρέπει να δοθεί ιδιαίτερη προσοχή στη χρήση της μνήμης, καθώς υπάρχουν αρκετές memory mapped συσκευές, ο Interrupt Vector Table (IVT) είναι σε χρήση και αρκετές περιοχές είναι δεσμευμένες από της συναρτήσεις του BIOS. Αν και τα παραπάνω δεν είναι επακριβώς καθορισμένα, έχουμε επαρκή στοιχεία για να δούμε ποιές θέσεις στη RAM μπορούμε να χρησιμοποιήσουμε με ασφάλεια, όπως φαίνεται στον Πίνακα 2: Αρχή Τέλος Μέγεθος Τύπος Περιγραφή 00000 003FF 400 (1kbyte) RAM (σε χρήση) IVT 00400 004FF 100 RAM (σε χρήση) BIOS Data Area 00500 07BFF 7700 (30kbyte) RAM (ελεύθερη) Συμβατική μνήμη 07C00 07DFF 200 RAM (ελεύθερη) Boot Sector 07E00 7FFFF 78200 (480kbyte) RAM (ελεύθερη) Συμβατική μνήμη 80000 9BFFF 1fc00 RAM (ελεύθερη) Συμβατική μνήμη 9FC00 9FFFF 400 RAM (σε χρήση) Extended Bios Data Area A0000 FFFFF 60000 ROM Πίνακας 2: Χάρτης μνήμης στην έναρξη του υπολογιστή (τιμές σε hex). 15

4.3 Σχεδίαση Stage 1 Boot Loader Πρόκειται μάλλον για την πιο απλή δομή του Sheep OS λόγω του περιορισμένου μήκους του. Πέρα από την υποχρεωτική υπογραφή στα τελευταία δύο bytes του πρώτου sector (AA55), στα πρώτα 62 bytes σχηματίζουμε έναν πίνακα, γνωστό και ως BIOS Parameter Block. Επειδή η εύκολη αντιγραφή αρχείων προς τη δισκέτα ήταν επιβεβλημένη, επιλέξαμε την υιοθέτηση του συστήματος αρχείων FAT12. Η δομή του πίνακα, καθώς και οι τιμές που χρησιμοποιήθηκαν για το Sheep OS παρουσιάζεται στον Πίνακα 3. Πεδίο Μέγεθος Offset Περιγραφή Τιμή (Bytes) (Bytes) OEM 8 3 SOS Bytes per sector 2 11 Bytes ανά τομέα 512 Sectors per cluster 1 13 Τομέα ανά τομέα 1 Reserved sectors 2 14 Δεσμευμένοι τομείς 1 Number of FATs 1 16 Αριθμός FAT 2 Root Entries 2 17 Εγγραφές ρίζας 224 Total Sectors 2 19 Αριθμός τομέων 2880 Media 1 21 Περιγραφή μέσου 0xf0 Sectors per FAT 2 22 Τομείς ανά FAT 9 Sectors per Track 2 24 Τομείς ανά τροχιά 18 Heads per 2 26 Αριθμός κεφαλών 2 Cylinder Hidden Sectors 4 28 Κρυφοί τομείς 0 Total Sectors Big 4 32 Σύνολο τομέων 0 Driver Number 1 36 Αριθμός οδηγού 0 Flags (WinNT) 1 37 Σημαία (για WinNT) 0 Boot Signature 1 38 Υπογραφή boot 0x29 Serial Number 4 39 Σειριακός αριθμός a0a1a2a3 Volume Label 11 43 Ετικέτα SOS FLOPPY File System 8 54 File System FAT12 Πίνακας 3: Bios Parameter Block. Τα πρώτα 3 bytes του τομέα εκκίνησης (Boot Sector) δεσμεύονται για να γίνει Far Jump στον κώδικα εκκίνησης. Κατόπιν, θέτουμε τους καταχωρητές τμήματος, προσδιορίζουμε την θέση του αρχείου Stage2.sys στο FAT και το αντιγράφουμε σε προκαθορισμένη θέση στη μνήμη. Τέλος, με Far Jump γίνεται η 16

μετάβαση στον Stage 2 Boot Loader. Το διάγραμμα ροής του αρχείου boot.asm εμφανίζεται στο Διάγραμμα 4.1. Start Set Segment registers to 0x7c00, Create Stack Load root directory to 7c00:0200 Find Stage2.sys Copy FAT to 7c00:0200 Copy Stage2.sys to 0x500 Failure halt Far Jump to 0x500 Διάγραμμα 4.1: Διάγραμμα ροής boot.asm. 4.4 Σχεδίαση Stage 2 Boot Loader Όπως ειπώθηκε και προηγουμένως σε αυτό το στάδιο γίνονται οι κατάλληλες αρχικοποιήσεις πριν περάσουμε στην εκτέλεση του πυρήνα. Συγκεκριμένα πραγματοποιούνται τα εξής: 1. Αρχικοποίηση Segment Registers. 2. Εγκατάσταση του Global Descriptor Table. 3. Ενεργοποίηση γραμμής A20. 17

4. Εύρεση και αντιγραφή του πυρήνα σε προσωρινή θέση στη μνήμη. 5. Χαρτογράφηση της μνήμης. 6. Πέρασμα σε Protected Mode. 7. Αντιγραφή του πυρήνα στη μόνιμη του θέση στη μνήμη. Το διάγραμμα ροής του Stage 2 απεικονίζεται στο Διάγραμμα 4.2: Διάγραμμα 4.2: Διάγραμμα ροής Stage2. 18

4.4.1 Αρχικοποίηση Segment Registers Από τη στιγμή που ο κώδικας του Stage2.asm έχει την εντολή org 0x500 θέτουμε τους Segment Registers στο μηδέν. 4.4.2 Εγκατάσταση Global Descriptor Table Η ρουτίνα εγκατάστασης του GDT βρίσκεται στο αρχείο Gdt.asm. Η δομή του πίνακα παρουσιάζεται στον Πίνακα 4: Εγγραφή Selector Περιγραφή 0 0x0 Null 1 0x8 Code 2 0x10 Data 3 0x18 TSS 4 0x20 LDT Πίνακας 4: GDT του Sheep OS. Η εγγραφή 0 οφείλει να είναι κενή, ενώ οι 3 και 4 θα συμπληρωθούν αργότερα από τον πυρήνα. Ο Code Descriptor περιγράφεται στον Πίνακα 5: Πεδίο Τιμή Παρατηρήσεις Base 0x0 Αρχή από το 0 Limit 0xFFFFF Μέγιστη τιμή Granularity 1 4kb steps D/B 1 32 bit Segment L 0 Not IA 32e mode AVL 0 Available to OS P 1 Segment is present DPL 00 Ring 0 S 1 Code segment Type 1010 Read/execute Πίνακας 5: Code Descriptor. 19

Το Data Descriptor είναι όμοιο με το Code, με μοναδική διαφορά το πεδίο type, όπου τα δικαιώματα είναι read/write. Η βάση και το όριο και των δύο descriptors επιτρέπει στους selectors να προσπελάσουν όλη την περιοχή της φυσικής μνήμης (Physical Address Space), χωρίς να γνωρίζουμε εάν αυτή υπάρχει. Επιπλέον, λόγω του DPL, o χώρος αυτός φαίνεται να είναι προσπελάσιμος μόνο από κώδικα με μέγιστα δικαιώματα (Ring 0). Αυτά τα θέματα θα λυθούν αργότερα από τον πυρήνα του συστήματος. Η δομή του GDT είναι αποθηκευμένη στη μνήμη κάπου στην περιοχή 0x500, αφού αντιγράφτηκε μαζί με το Stage2.sys εκεί. Επειδή πρέπει να γνωρίζει ο πυρήνας ακριβώς τη θέση του, πρώτα τον αντιγράφουμε σε μια καλώς καθορισμένη θέση (0x80800) και στη συνέχεια καλούμε την εντολή lgdt για να αποθηκευτεί η βάση και το limit του GDT στον GDTR. 4.4.3 Ενεργοποίηση γραμμής A20 Για λόγους συμβατότητας, όταν το BIOS δίνει τον έλεγχο του συστήματος στον Boot loader, διατηρεί τη γραμμή 20 του διαύλου διευθύνσεων κλειστή. Έτσι, το σύστημα μπορεί να προσπελάσει μονάχα 1Mbyte μνήμης. Ενεργοποίηση της πύλης στην γραμμή Α20 συνεπάγεται την προσπέλαση μνήμης πέρα από το 1Mbyte. Υπάρχουν διάφοροι τρόποι για να επιτευχθεί αυτό (BIOS, Keyboard Controller, System Control Port A). Ο πιο συμβατός φαίνεται να είναι μέσω του Output Port. Ο κώδικας για την ενεργοποίηση περιέχεται στο αρχείο A20.asm. 4.4.4 Εύρεση και αντιγραφή του Πυρήνα Όπως και στο Stage 1, ο ίδιος κώδικας αναλαμβάνει την αρχικοποίηση του συστήματος αρχείων FAT12. Όταν εντοπιστεί το αρχείο kernel.sys στη δισκέτα αντιγράφεται στην θέση 0x9000 και αποθηκεύεται το μέγεθός του. Το 20

μέγεθος του πυρήνα θα χρειαστεί αργότερα για τη δέσμευση του χώρου που καταλαμβάνει στη μνήμη από το Διαχειριστή Φυσικής Μνήμης. 4.4.5 Χαρτογράφηση της μνήμης Χρησιμοποιώντας την υπηρεσία INT 0x15 του BIOS μπορούμε να εξασφαλίσουμε πληροφορίες για τη μνήμη του συστήματος. Στην αρχή χρησιμοποιούμε τη λειτουργία 0xE801 για να πάρουμε το ποσό της μνήμης ανάμεσα από την περιοχή 1Mbyte 16Mbyte καθώς και πάνω από τα 16MByte σε blocks των 64Kbyte. Αποθηκεύουμε τις πληροφορίες αυτές στη δομή που θα χρησιμοποιήσουμε αργότερα στον πυρήνα (Boot_Info) σε καθορισμένη τοποθεσία (0x1000) και στη συνέχεια χρησιμοποιούμε τη λειτουργία 0xE820 για να αποκτήσουμε την πλήρη χαρτογράφηση της μνήμης από το BIOS. Οι πληροφορίες αυτές αποθηκεύονται στην τοποθεσία 0x1100 για μετέπειτα χρήση τους από τον πυρήνα. 4.4.6 Πέρασμα σε Protected Mode Η ενεργοποίηση του Protected Mode είναι απλή διαδικασία, αφού αρκεί να αλλάξουμε το bit 0 του καταχωρητή CR0 από μηδέν σε ένα. Προσοχή πρέπει να δοθεί σε δύο σημεία: 1) Οι διακοπές πρέπει να απενεργοποιηθούν καθώς δεν υπάρχει ακόμα ο Interrupt Descriptor Table. Ακόμα και ένα IRQ να έρθει στο σύστημα, αυτό θα οδηγήσει σε double fault και στη συνέχεια σε triple fault, δηλαδή σε κύκλο Shutdown. 2) Χρειάζεται far jump αμέσως μετά από την ενεργοποίηση του Protected Mode για να «καθαριστεί» το pipeline από εναπομείνασες 16 bit εντολές. 21

Τέλος, θα χρειαστεί να θέσουμε τους καταχωρητές τμήματος στον Selector του Data Descriptor, καθώς και να ορίσουμε το δείκτη στοίβαξης (ESP). 4.4.7 Αντιγραφή του πυρήνα στη μόνιμή του θέση Τώρα που το σύστημα μπορεί να προσπελάσει μνήμη πάνω από το 1Mbyte, μπορούμε με ασφάλεια να μεταφέρουμε το αρχείο kernel.sys στη θέση όπου πρέπει να εκτελεστεί. Αυτή καθορίστηκε να είναι η διεύθυνση 0x100000, δηλαδή ακριβώς στο 1Mbyte. Η δουλειά του Stage2 τελειώνει με ένα call της παραπάνω διεύθυνσης και πλέον ο έλεγχος του συστήματος περνάει στον πυρήνα. 4.5 Σχεδίαση Πυρήνα (kernel) Ο πυρήνας του Sheep OS περιέχει το 75% του κώδικα ολόκληρου του λειτουργικού. Προκειμένου να γίνει πιο εύκολα η κατανόησή του, θα γίνει μια αφαιρετική περιγραφή σε υψηλό επίπεδο και στη συνέχεια θα εμβαθύνουμε στα κυριότερα κομμάτια, σε επίπεδο κώδικα. 4.5.1 Αφαιρετική προσέγγιση Στο Διάγραμμα 4.3 μπορούμε να κάνουμε την πρώτη προσέγγιση στη λειτουργία του πυρήνα: 22

Διάγραμμα 4.3: Αφαιρετική προσέγγιση πυρήνα. Κατά την εκτέλεση του πυρήνα, αρχικά αποθηκεύονται οι πληροφορίες που συλλέχτηκαν κατά το Stage 2. Βάσει αυτών των πληροφοριών, εκτελείται ο διαχειριστής φυσικής μνήμης (Physical Memory Manager) και στη συνέχεια ο διαχειριστής εικονικής μνήμης (Virtual Memory Manager) για να 23

ολοκληρωθεί η αρχικοποίηση της μνήμης. Στο επόμενο στάδιο, γίνεται η σύσταση του πίνακα διακοπών (Interrupt Descriptor Table ή IDT), η εγκατάσταση των κατάλληλων ρουτινών εξυπηρέτησης αυτών καθώς και ο προγραμματισμός του Programmable Interrupt Controller (PIC). Ακολουθεί η εγκατάσταση του προγράμματος οδήγησης του πληκτρολογίου και του ολοκληρωμένου κυκλώματος PIT (Programmable Interval Timer). Τέλος, αρχικοποιούμε τις δομές για το hardware multitasking και ο έλεγχος μεταβαίνει στο CLI (Command Line Interface). 4.5.2 Boot Information Οι πληροφορίες που συλλέχτηκαν κατά το Stage2, θα χρησιμεύσουν στους διαχειριστές μνήμης του Sheep OS. Αναφερόμαστε κυρίως στη συνολική ποσότητα μνήμης και στο χάρτη μνήμης. Θεωρείται ο πιο ασφαλής τρόπος για τον εντοπισμό της μνήμης και γίνεται αναγκαστικά κατά το συμβατικό τρόπο λειτουργίας διότι μόλις το σύστημα εισέλθει στον προστατευμένο, ο Interrupt Vector Table παύει να έχει ισχύ, οπότε δεν μπορούμε να χρησιμοποιήσουμε τα BIOS Services. Εναλλακτικός τρόπος είναι η μέθοδος direct probing, αλλά είναι ιδιαίτερα αργός όσο και επίφοβος, καθώς θα μπορούσε να προκαλέσει ζημιά σε περιφερειακές συσκευές. Υπάρχουν τρεις τρόποι να περάσουμε τις άνωθεν πληροφορίες από το Stage2 στον πυρήνα: 1) Τοποθέτηση των πληροφοριών στη στοίβα, χωρίς να τις αποθηκεύσουμε στη μνήμη. 2) Τοποθέτηση δεικτών στη στοίβα, με ταυτόχρονη αποθήκευσή τους στη μνήμη. 24

3) Απλή αποθήκευση σε καλώς καθορισμένη τοποθεσία στη μνήμη, την οποία ήδη γνωρίζει ο πυρήνας. Ενώ οι τρόποι 1 και 2 μοιάζουν πιο κομψοί και δελεαστικοί, το Sheep OS χρησιμοποιεί τον τρίτο διότι είναι ο πιο ασφαλής. Τα πηγαία αρχεία του Stage2 και του πυρήνα είναι ξεχωριστά και δεν έχουν καμία σύνδεση μεταξύ τους με αποτέλεσμα η χρησιμοποίηση ορισμάτων ως κλήση συναρτήσεων να μην λογαριάζεται από τον compiler. Ενώ λοιπόν είναι πολύ πιθανό να δουλέψει, δεν υπάρχει καμία εγγύηση ότι μια μικρή αλλαγή στον κώδικα του πυρήνα δεν θα αλλάξει τον τρόπο σύνδεσής του με το Stage2. Γι αυτό είναι προτιμότερο η «σύνδεση» ανάμεσά τους να γίνει στο Design Time. Με τον τρίτο τρόπο, γνωρίζουμε κατά τη σχεδίαση ακριβώς που αποθηκεύονται οι πληροφορίες κατά την εκτέλεση του Stage2, με συνέπεια η συλλογή τους από τον πυρήνα να είναι απλή. Τέλος, οι πληροφορίες που λαμβάνουμε είναι οι εξής: 1) Μνήμη ανάμεσα στο 1 ο και το 16 ο Mbyte. 2) Μνήμη (σε blocks των 64Kbyte) άνω του 16 ου Mbyte. 512Byte). 3) Μέγεθος εκτελέσιμου αρχείου του πυρήνα (σε blocks των 4) Εγγραφές στο χάρτη μνήμης που σχεδιάστηκε από το BIOS. 4.5.3 Physical Memory Manager Ο διαχειριστής φυσικής μνήμης λειτουργεί με τη μέθοδο που γνωστή και ως bitmap, δηλαδή αντιστοίχηση bit. Συγκεκριμένα, γίνεται αναπαράσταση όλου του χώρου μνήμης (4Gbyte) μέσω ενός χάρτη από bit. Για κάθε ένα bit 25

αντιστοιχίζεται ένα block των 4Kbyte, ενώ συνολικά έχουμε 1048576 blocks. Χρειαζόμαστε δηλαδή μόλις 131072 Bytes για να περιγράψουμε και τα 4Gbytes. Η μέθοδος αυτή είναι αρκετά αποτελεσματική λόγω του μικρού μεγέθους της, είναι όμως και αργή, αφού πρέπει κάθε φορά να ψάχνουμε bit προς bit για την εύρεση ελεύθερου block. Η πρώτη αρχικοποίηση γίνεται αξιοποιώντας την πληροφορία για το συνολικό ποσό της μνήμης. Αρχικά δεσμεύουμε όλη τη διαθέσιμη μνήμη και τοποθετούμε το bitmap ακριβώς στο τέλος του πυρήνα. Στη συνέχεια, χρησιμοποιώντας το χάρτη μνήμης που σχεδιάστηκε από το BIOS, αποδεσμεύουμε σταδιακά τις περιοχές που έχουν χαρακτηριστεί (από το BIOS) ως διαθέσιμες. Tέλος, δεσμεύουμε το 1ο Mbyte μνήμης και το χώρο του πυρήνα, για να σιγουρέψουμε ότι οι περιοχές αυτές δεν έχουν αποδεσμευτεί στην παραπάνω διαδικασία. 4.5.4 Virtual Memory Manager Περνάμε στο διαχειριστή εικονικής μνήμης. Αρχικά δημιουργούμε έναν πίνακα 1024 εγγραφών τύπου page table entry, απεικονίζοντας τα πρώτα 4Mbyte της μνήμης. Στόχος μας είναι η φυσική διεύθυνση του πυρήνα να απεικονίζεται στην ίδια εικονική διεύθυνση. Η διαδικασία αυτή είναι γνωστή και ως identity mapping. Με αυτό τον τρόπο, όταν ενεργοποιηθεί ο μηχανισμός σελιδοποίησης, ο τρέχων κώδικας θα παραμείνει στη θέση που θα έπρεπε να είναι. Στη συνέχεια, δημιουργούμε ένα κενό κατάλογο σελίδων (Page Directory) και θέτουμε την πρώτη του εγγραφή να δείχνει στην πρώτη εγγραφή του πίνακα σελίδων (Page Table). Τέλος, ενεργοποιούμε το μηχανισμό σελιδοποίησης. Από αυτή τη στιγμή, κάθε διευθυνσιοδότηση που περνάει από την MMU (Memory Management Unit), αντιμετωπίζεται ως εικονική. Με τα παραπάνω 26

επιτύχαμε να ενεργοποιήσουμε το μηχανισμό σελιδοποίησης χωρίς να χρειαστεί να κάνουμε αλλαγές στον κώδικα άλλων φυσικών οντοτήτων του πυρήνα. 4.5.6 Interrupt Descriptor Table Ο πίνακας περιγραφέων διακοπών χρησιμοποιείται μόνο στον προστατευμένο τρόπο λειτουργίας, και έχει ακριβώς 256 εγγραφές των 8 bytes. Τοποθετούμε τον πίνακα ακριβώς κάτω από τον GDT, με διεύθυνση της πρώτης εγγραφής να είναι 0x80000. Η αρχικοποίησή του γίνεται απλά, μηδενίζοντας την περιοχή της μνήμης που καταλαμβάνει ο IDT και δηλώνοντάς τον στον καταχωρητή IDTR. 4.5.6 Interrupt Service Routines Σε αυτό το στάδιο, καθορίζουμε τις πρώτες 32 εγγραφές του IDT που αφορούν τις εξαιρέσεις (Exceptions) του συστήματος. Η απόκριση του Sheep OS σε κάποια εξαίρεση είναι η εμφάνιση όλων των δυνατών πληροφοριών που οδήγησαν σε αυτή. 4.5.7 IRQ και ο PIC Οι αιτήσεις διακοπών (Interrupt ReQuest) και ο προγραμματιζόμενος ελεγκτής διακοπών (Programmable Interrupt Controller ή PIC), είναι το τελευταίο στάδιο αρχικοποίησης των διακοπών. Αρχικά, είναι απαραίτητος ο ανά προγραμματισμός του PIC, γιατί είχε ρυθμιστεί για λειτουργία με τον IVT που δεν είναι πλέον σε ισχύ. Αντιστοιχούμε λοιπόν τα IRQ0 IRQ15 στις εγγραφές 32 47 του IDT και στη συνέχεια ανανεώνουμε τον πίνακα καταλλήλως. 27

4.5.8 Programmable Interval Timer Ο προγραμματισμός του PIT είναι αρκετά απλός. Χρησιμοποιείται παραδοσιακά το κανάλι 0 για το χρονοπρογραμματισμό του λειτουργικού. Σε αυτό το σημείο γίνεται και η αντιστοίχηση του handler με το IRQ0, που είναι και η αντίστοιχη διακοπή. Η υλοποίηση του στο Sheep OS έχει τη δυνατότητα για μεταβολή της συχνότητας λειτουργίας. 4.5.9 Keyboard Driver Η πρώτη κίνηση για το χειρισμό του πληκτρολογίου, είναι η αντιστοίχηση του σχετικού handler με το IRQ1. Όταν πατηθεί (ή απελευθερωθεί ένα πλήκτρο), γίνεται μια ταυτοποίηση ανάλογα με τον τύπο του πλήκτρου που έχει πατηθεί πχ. εκτυπώσιμος χαρακτήρας. Υπάρχει άμεση σύνδεση του οδηγού του πληκτρολογίου με το Command Line Interface, καθώς όλοι οι εκτυπώσιμοι χαρακτήρες στέλνονται σε αυτό για περαιτέρω επεξεργασία. 4.5.10 Task Manager, Tasks Setup Το Sheep OS μεταχειρίζεται το Hardware multitasking με ιδιαίτερο τρόπο. Αντί η κάθε διεργασία να έχει το δικό του Task State Segment (TSS) και Local Descriptor Table (LDT), χρησιμοποιούμε τις δύο κενές εγγραφές στον GDT, και εναλλάσσουμε τα περιεχόμενα της εκάστοτε διεργασίας, κάθε φορά που γίνεται εναλλαγή διεργασιών. Με αυτό τον τρόπο μπορούμε να έχουμε απεριόριστες στον αριθμό διεργασίες, διότι ο GDT έχει περιορισμένο αριθμό εγγραφών. Η πρώτη δουλειά του Task Manager είναι να θέσει τον υπό εκτέλεση κώδικα του πυρήνα ως αρχική διεργασία και να μεταβεί σε αυτόν. Στη συνέχεια, 28

αρχικοποιεί τον πίνακα των διεργασιών (Task List) και δημιουργεί 4 δοκιμαστικές διεργασίες για επίδειξη του multitasking. 4.5.11 Command Line Interface Έχοντας τελειώσει με τις απαραίτητες αρχικοποιήσεις του συστήματος ο έλεγχος του συστήματος περνάει στο Command Line Interface (CLI). Η λειτουργία του είναι άμεσα συνδεδεμένη με τον οδηγό του πληκτρολογίου, καθώς κάθε χαρακτήρας που δημιουργείται επεξεργάζεται από τον επιλογέα εντολών. Το έργο του επιλογέα είναι να μεταφράζει τις εντολές του χρήστη σε συναρτήσεις και να τις εκτελεί. Το CLI δεν ήταν μέρος της αρχικής σχεδίασης του λειτουργικού, και υπάρχει κυρίως για λόγους αποσφαλμάτωσης. 29

5. Αναλυτική περιγραφή της αρχικοποίησης του Πυρήνα Έχοντας σχηματίσει μια γενικότερη εικόνα των κυρίων κομματιών που απαρτίζουν τον πυρήνα, είμαστε πλέον σε θέση να τα αναλύσουμε σε περισσότερο βάθος. 5.1 Αρχικοποίηση Μνήμης 5.1.1 Physical Memory Manager Σε αυτή την ενότητα θα αναλύσουμε επακριβώς την αρχικοποίηση της διαχείριση μνήμης στο Sheep OS. Αρχικά ανακτούμε τις πληροφορίες που συλλέξαμε κατά το Stage2. Αυτές εντοπίζονται στη φυσική διεύθυνση 0x1000 και έχουν την ακόλουθη μορφή: struct boot_info{ unsigned long int memorylo; /* Μνήμη 1 16MB*/ unsigned long int memoryhi; /*Μνήμη πάνω από τα 16MB (Block των 64Kb)*/ unsigned long int BootDevice; /*Συσκευή εκκίνησης*/ unsigned long int ImageSize; /*Μέγεθος πυρήνα, σε τομείς των 512B*/ unsigned long int MemoryEntries; /*Εγγραφές στον Χάρτη Μνήμης του BIOS*/ ; 30

Πρωτίστως, τα ηνία παίρνει η συνάρτηση pmmngr_init (physical_addr memsize, physical_addr bitmap). Ο τύπος physical_addr είναι τύπου unsigned long int. Το όρισμα memsize περιέχει το σύνολο της φυσικής μνήμης που έχει ανιχνευτεί από το BIOS, ενώ το bitmap δείχνει το τέλος του αρχείου του πυρήνα, με σκοπό την τοποθέτηση του Bitmap σε αυτή τη διεύθυνση. Η συνάρτηση θέτει όλα τα bits του bitmap σε άσους, δηλαδή θεωρεί πως όλη η μνήμη είναι δεσμευμένη. Στη συνέχεια εκτελείται ένας βρόχος με τον οποίο βρίσκουμε τις περιοχές μνήμης που έχουν χαρακτηριστεί από το BIOS ως Available, δηλαδή διαθέσιμες. Ο βρόχος εκτελείται τόσες φορές όσες εγγραφές περιέχει το MemoryEntries. Κάθε φορά που βρίσκουμε διαθέσιμη περιοχή, καλούμε τη συνάρτηση pmmngr_init_region για να ανανεωθεί το bitmap. Στο τέλος του βρόχου δεσμεύουμε το πρώτο Mb καθώς σε αυτό υπάρχουν απαραίτητες δομές του συστήματος (GDT, IDT) και Memory mapped συσκευές. Επιπλέον, δεσμεύουμε και το χώρο που καταλαμβάνει ο πυρήνας και το bitmap. 5.1.2 Virtual Memory Manager Σειρά έχει η διαχείριση της εικονικής μνήμης. Η αρχικοποίησή της γίνεται μέσω της συνάρτησης vmmngr_initialize(). Δημιουργούμε ένα κενό Page Table χρησιμοποιώντας τη συνάρτηση pmmngr_alloc_block() του διαχειριστή φυσικής μνήμης. Η κάθε εγγραφή στον πίνακα είναι 4 bytes, άρα χρειαζόμαστε ακριβώς 4Kbytes. Η βάση του πίνακα πρέπει να είναι ευθυγραμμισμένη (Aligned) κατά 4Kbytes, αλλά αυτό δε μας ανησυχεί καθώς οι συναρτήσεις δέσμευσης μνήμης επιστρέφουν μόνο τέτοιες εγγραφές. Έπειτα, κάνουμε το λεγόμενο identity mapping. Έτσι, ολόκληρος ο πίνακας χρησιμοποιείται και τα πρώτα 4Mbytes της εικονικής μνήμης δείχνουν στην αντίστοιχη φυσική. Επόμενο βήμα είναι η δημιουργία του καταλόγου 31

σελίδων. Δεσμεύουμε πάλι 1 block μνήμης, θέτουμε όλες τις εγγραφές του καταλόγου ίσες με το μηδέν εκτός από την πρώτη που δείχνει στον πίνακα σελίδων που δημιουργήσαμε με ιδιότητες user mode, present και writable. Τέλος, φορτώνουμε τη βάση του καταλόγου σελίδων στον CR3 και ενεργοποιούμε το μηχανισμό σελιδοποίησης. 5.2 Αρχικοποίηση Exceptions και Interrupts 5.2.1 Interrupt Descriptor Table Συνέχεια στην αρχικοποίηση έχει η συνάρτηση idt_install(). Η συνάρτηση αυτή χρησιμοποιεί την ακόλουθη δομή: struct idt_ptr { unsigned short limit; unsigned long int base; attribute ((packed)); Η δομή ακολουθεί τα πρότυπα για άμεση φόρτωση του IDTR. Αρχικά ενεργοποιούμε το limit να έχει ακριβώς το μέγεθος των 256 εγγραφών (idt_entry). Θέτουμε το base στη θέση μνήμης που έχουμε καθορίσει να ξεκινάει ο IDT (0x80000) και μηδενίζουμε όλη την περιοχή που καταλαμβάνει αυτός με τη βοήθεια της memset. Τέλος, φορτώνουμε τη δομή στον IDTR. 32

5.2.2 Εγκατάσταση ISRS (Interrupts/Exceptions) δομή: Σειρά έχουν τα πρώτα 32 interrupts. Εδώ χρησιμοποιούμε την παρακάτω struct idt_entry { /* bytes 0-3 */ unsigned short base_low; /* bits 0-15 */ unsigned short selector; /* bits 16-31 */ /* bytes 4-7 */ unsigned char always0; /* bits 0-7 */ unsigned char flags; /* bits 8-15 */ unsigned short base_high; /* bits 16-31 */ attribute ((packed)); που περιγράφει ακριβώς έναν IDT Gate Descriptor. Εφαρμόζουμε τη συνάρτηση idt_set_gate(unsigned char num, unsigned long base, unsigned short sel, unsigned char flags), όπου num ο αριθμός του exception/interrupt, base η αντίστοιχη συνάρτηση εξυπηρέτησης, sel ο selector και flags τα διάφορα bits που μπορούν να παραμετροποιήσουν τον Descriptor. Ο τρόπος που καλούμε τη συνάρτηση είναι ο εξής: idt_set_gate(χ, (unsigned)isrχ, CODE_SEL, 0x8E); όπου Χ ο αριθμός του interrupt/exception. Τα flags θέτουν το Interrupt Gate σε present, DPL ίσο με το 0 και 32bit size. Οι συναρτήσεις isr0 έως isr32 ορίζονται σε ξεχωριστό αρχείο και θα αναλυθούν αργότερα. 5.2.3 Εγκατάσταση IRQ και PIC Όπως έχει αναφερθεί, είναι απαραίτητος ο επαναπρογραμματισμός του PIC, για την ακρίβεια και των 2 PIC που υπάρχουν στο σύστημα. Αυτό πραγματοποιείται με 10 outb εντολές: 33

Data Port Περιγραφή 1 0x11 0x20 Init sequence του Master PIC 2 0x11 0xa0 Init sequence του Slave PIC 3 0x20 0x21 Offset στον IDT για το Master PIC 4 0x28 0xa1 Offset στον IDT για το Slave PIC 5 0x04 0x21 Slave συνδεδεμένο στην γραμμή 2 6 0x02 0xa1 Slave συνδεδεμένο στην γραμμή 2 7 0x01 0x21 8086 mode (Master) 8 0x01 0xa1 8086 mode (Slave) 9 0xff 0x21 Mask all Interrupts (Master) 10 0xff 0xa1 Mask all Interrupts (Slave) Μέχρι στιγμής επιτύχαμε την ανακατεύθυνση των interrupts 0 έως 15 στις εγγραφές 32 έως 47 του IDT. Χρησιμοποιούμε ξανά τη συνάρτηση idt_set_gate για να ορίσουμε τις κατάλληλες συναρτήσεις εξυπηρέτησης αυτών των interrupts. 5.3 Εγκατάσταση PIT και πληκτρολογίου Ο προγραμματισμός του PIT είναι αρκετά απλός. Καλούμε τη συνάρτηση timer_install(int hz) όπου hz η επιθυμητή συχνότητα του timer σε hertz. Στέλνοντας στη διεύθυνση 0x43 (όπου βρίσκεται το command word του ολοκληρωμένου) τη λέξη 0x36 θέτουμε σε λειτουργία το channel 0 σε mode 3 (Square Wave Mode) 16 bit μετρητή. Κατόπιν, καθαρίζουμε τη μάσκα του IRQ0 που είναι συνδεδεμένος ο PIT με το Master PIC, διαφορετικά ο επεξεργαστής δε θα ενημερωθεί ποτέ για timer interrupt. Τέλος, γίνεται κλήση της συνάρτησης irq_install_handler(0, timer_handler) για να προσδιορισθεί ότι το IRQ0 θα εξυπηρετείται από τη συνάρτηση timer_handler. Η εγκατάσταση του πληκτρολογίου είναι ακόμα πιο εύκολη, καλώντας τη συνάρτηση irq_install_handler(1, do_kb), ορίζοντας ότι το IRQ1 (που εντοπίζεται ο ελεγκτής του πληκτρολογίου) θα εξυπηρετείται από τη συνάρτηση do_kb. Εν κατακλείδι, με την εντολή outb(inb(0x21)&0xfd, 0x21) καθαρίζεται η μάσκα του IRQ1 ενώ με τις outb(0xed,0x60) και outb(0x0,0x60) σβήνουμε τα LED. 34

5.4 Αρχικοποίηση Διεργασιών Τελευταίο κομμάτι της εκκίνησης του πυρήνα είναι η αρχικοποίηση τεσσάρων δοκιμαστικών διεργασιών για επίδειξη του hardware multitasking και το πέρασμα του νήματος του πυρήνα ως διεργασία του συστήματος. Αρχικά αποθηκεύουμε στον GDT τους selectors που έχει ο πυρήνας (Task0) για τα TSS και LDT, κατόπιν τους περνάμε στους καταχωρητές Task Register και LDTR. Η δημιουργία των διεργασιών γίνεται μέσω της συνάρτησης new_task η οποία θα αναλυθεί ενδελεχώς σε επόμενη ενότητα. Συνοψίζοντας, για να περάσουμε από το Ring0 (Supervisor Mode) στο Ring3 (User Mode) υπάρχει μόνο ένας τρόπος, μέσω της εντολής IRET. Αρχικά θέτουμε τους Segment Registers στις κατάλληλες τιμές τους (USER_CODE_SEL) και στην πορεία φτιάχνουμε το Stack στην ακόλουθη μορφή για να τρέξουμε την εντολή IRET: EIP LDT Code Selector EFLAGS ESP LDT Stack Selector H εντολή: pushl $1f τοποθετεί στη στοίβα τη διεύθυνση του label 1 κοιτάζοντας μπροστά (f orward). Κατά συνέπεια, η εντολή που εκτελείται αμέσως μετά το IRET είναι η επόμενη γραμμή στον κώδικά μας. Εν τέλει, μόλις τοποθετηθεί ο EFLAGS στη στοίβα, ενεργοποιούμε τα Interrupts μέσω της εντολής orl $0x200,(%%esp), η οποία ενεργοποιεί τη σημαία IF. Οπότε, με την εκτέλεση της IRET, οι διακοπές είναι ενεργοποιημένες. Η διαδικασία αυτή γίνεται διότι η εντολή sti μπορεί να εκτελεστεί μόνο στο supervisor mode ενώ παράλληλα, δε θέλουμε να διακόψουμε την είσοδο στο user mode. Συνοψίζοντας, ο πυρήνας έχει εισέλθει στο Ring3 μέσω της διεργασίας TASK0, η οποία σχεδιάζει μία περιστρεφόμενη γραμμή κάτω αριστερά στην οθόνη. 35

6. Ανάλυση του συστήματος Τώρα που η αρχικοποίηση των υπηρεσιών του συστήματος έχει τελειώσει, μπορούμε να παρατηρήσουμε με περισσότερη λεπτομέρεια τη λειτουργία του καθώς και τη διασύνδεση των στοιχείων του σε επίπεδο αρχείων και συναρτήσεων. 6.1 Διαχείριση Μνήμης Σκόπιμο είναι να γίνει λεπτομερής επισκόπηση της διαχείρισης της μνήμης αφού αρκετά στοιχεία του Sheep OS χρειάζονται δυναμική δέσμευση μνήμης για να λειτουργήσουν. Τα αρχεία που είναι υπεύθυνα για αυτό το έργο είναι τα physical_memory.c, virtual_memory.c, virtual_pde.c, virtual_pte.c καθώς και τα αντίστοιχα header files. Το αρχείο physical_memory.c περιέχει όλες τις συναρτήσεις και τις δομές που χρειαζόμαστε για το χειρισμό της φυσικής μνήμης ενώ το virtual_memory.c για την εικονική μνήμη. Τα αρχεία virtual_pde.c και virtual_pte.c περιέχουν συναρτήσεις που χειρίζονται τις εγγραφές στον κατάλογο σελίδων και τον πίνακα σελίδων αντίστοιχα. 6.1.1 Το αρχείο physical_memory.c Οι μεταβλητές που χρησιμοποιούνται σε αυτό το αρχείο είναι οι εξής: static unsigned long int static unsigned long int static unsigned long int static unsigned long int _mmngr_memory_size=0; _mmngr_used_blocks=0; _mmngr_max_blocks=0; *_mmngr_memory_map=0; 36

Η _mmngr_memory_size αποθηκεύει το συνολικό μέγεθος της μνήμης σε Kbyte, πληροφορία που προκύπτει από κλήση της συνάρτησης 0xe801 του BIOS μέσω του Stage2. Η _mmngr_used_blocks αποθηκεύει τον αριθμό blocks που είναι σε χρήση (δεσμευμένα). Η _mmngr_max_blocks διατηρεί το μέγιστο αριθμό block που μπορεί να έχει το σύστημα. Η *_mmngr_memory_map δείχνει τη θέση μνήμης που έχει αποθηκευτεί το bitmap. Ενώ οι μεταβλητές _mmngr_memory_size και _mmngr_max_blocks χρησιμεύουν κυρίως κατά την αρχικοποίηση του συστήματος, οι υπόλοιπες δύο χρησιμοποιούνται συνέχεια από τις συναρτήσεις του αρχείου. H συνάρτηση μεγαλύτερης σημασίας που συναντάμε είναι η void* pmmngr_alloc_block(). Η λειτουργία της μπορεί να συνοψιστεί στα παρακάτω: Έλεγχος εάν υπάρχουν ελεύθερα block μέσω της συνάρτησης pmmngr_get_free_block_count(). Αντιστοίχηση του ελεύθερου block μέσω της συνάρτησης mmap_first_free(). Ανάθεση του ανάλογου bit στο bitmap ως δεσμευμένο. Αύξηση της μεταβλητής _mmngr_used_blocks κατά ένα. Επιστροφή της διεύθυνσης έναρξης του block στη μνήμη. 37

Ανάλογη λειτουργία έχει και η συνάρτηση void* pmmngr_alloc_blocks (size_t size), η οποία δεσμεύει περισσότερα του ενός block. Οι υπόλοιπες συναρτήσεις του αρχείου αποτελούν απλές διαδικασίες εύρεσης block στο bitmap και ανάθεσης αυτού ως δεσμευμένο ή ελεύθερο. Στο τέλος εντοπίζουμε και τις διαδικασίες ενεργοποίησης/απενεργοποίησης του μηχανισμού σελιδοποίησης μέσω inline assembly. 6.1.2 Τα αρχεία virtual_memory.c, virtual_pde.c και virtual_pte.c Σειρά έχει η διαχείριση της εικονικής μνήμης. Στο στάδιο υλοποίησης του Sheep OS, οι περισσότερες συναρτήσεις δε χρησιμοποιούνται, αλλά δίνουν τη βάση για πλήρη χειρισμό της εικονικής μνήμης. Τα αρχεία virtual_pde.c και virtual_pte.c αξιοποιούνται για την τροποποίηση των εγγραφών στον κατάλογο σελίδων και τον πίνακα αντίστοιχα. Έχουν οριστεί στα header τους κατάλληλα flags για να ταιριάξουν τη μορφή τους όταν χρησιμοποιούμε σελίδες 4Kbyte: 38

Στο αρχείο virtual_memory.c η κύρια συνάρτηση είναι η void vmmngr_initialize() που εφαρμόζεται στην αρχικοποίηση του πυρήνα. Χρησιμοποιεί τις ρουτίνες που προαναφέρθηκαν για να φέρει τις εγγραφές στους πίνακες στην κατάλληλη μορφή και κατόπιν ενεργοποιεί το μηχανισμό σελιδοποίησης. Τέλος, διακρίνουμε τις δύο μεταβλητές struct pdirectory* _cur_directory και physical_addr _cur_pdbr για άμεση πρόσβαση στον τρέχοντα κατάλογο σελίδων. 39

6.2 Διαχείριση διακοπών Η διαχείριση των διακοπών αποτελεί ένα από τα σημαντικότερα κομμάτια του Sheep OS. Δόθηκε ιδιαίτερη έμφαση στη δημιουργία απλού και κατανοητού κώδικα ο οποίος θα είναι επίσης εύκολος στην παραμετροποίηση του. Τα αρχεία που εμπλέκονται με τη διαχείριση των διακοπών είναι τα idt.c, isrs.c, irq.c και assembly.asm. 6.2.1 Το αρχείο idt.c Πέρα από τη συνάρτηση idt_install() που έχει ήδη αναλυθεί, διακρίνουμε τη συνάρτηση void idt_set_gate(unsigned char num, unsigned long base, unsigned short sel, unsigned char flags). Τα ορίσματά της έχουν ως εξής: num: ο αριθμός του interrupt στον IDT (0 255), base: η διεύθυνση της ρουτίνας εξυπηρέτησης της διακοπής, sel: ο selector στον GDT, Gate. flags: καθορίζει τα Present, DPL, Size καθώς και τον τύπο του Ασκώντας την κατάλληλη δομή (idt_entry), η παραπάνω συνάρτηση εκτελείται εύκολα χρησιμοποιώντας απλή ανάθεση και bitwise εντολές. Επιπρόσθετα μας δίνει τη δυνατότητα να καθορίσουμε οποιονδήποτε από τους τρεις τύπους IDT Descriptors (Task gate, Interrupt Gate, Trap Gate) παρόλο που στο Sheep OS χρησιμοποιούμε μόνο τον τύπο Interrupt Gate. Η κλήση της συνάρτησης στην τρέχουσα φάση της υλοποίησης πραγματοποιείται από τα 40

αρχεία isrs.c και irq.c για τον καθορισμό των exceptions και των διακοπών των PIC αντίστοιχα. 6.2.1 Τα αρχεία isrs.c, irq.c και assembly.asm Επειδή τα δύο πρώτα αρχεία έχουν παρόμοιες συναρτήσεις θα εξεταστούν παράλληλα. Η πλήρωση του IDT γίνεται μέσω της συνάρτησης idt_set_gate όπως έχει ήδη αναφερθεί. Η μορφή τόσο των exceptions (interrupts 0 έως 31) όσο και των interrupts του PIC είναι η ίδια. Ως selector τίθεται ο CODE_SEL (Ring 0) ενώ το DPL είναι ίσο με 3 (Ring 3). Επειδή ο χειρισμός των διακοπών χρειάζεται ιδιαίτερη προσοχή, όλες οι ρουτίνες εξυπηρέτησης δείχνουν στο αρχείο assembly.asm όπου και υπάρχει assembly κώδικας. Ξεκινούμε με την κατασκευή ενός πανομοιότυπου stack ανεξάρτητα τον τύπο της διακοπής που λαμβάνει χώρα. Όπως είναι ήδη γνωστό από τη θεωρία, μερικά από τα exception δίνουν error code, ενώ άλλα όχι. Φροντίζουμε να βάλουμε στη στοίβα ένα πρόσθετο μηδέν στα exceptions χωρίς error code. Στη συνέχεια σώζουμε την κατάσταση του προγράμματος που διακόπηκε (γενικοί και ειδικοί καταχωρητές) και φορτώνουμε τον DATA_SEL στους καταχωρητές τμήματος. Τέλος, καλούμε τη συνάρτηση fault_handler (irq_handler για τα interrupts του PIC) για την πραγματική εξυπηρέτηση της διακοπής. Η συνάρτηση void fault_handler(struct regs *r) δεν κάνει τίποτα περισσότερο από απλή απεικόνιση της κατάστασης των καταχωρητών τη στιγμή που έγινε το exception. Από την άλλη πλευρά, η void irq_handler(struct regs *r) αναλαμβάνει να ξεχωρίσει ποιο interrupt συνέβη και να καλέσει την αντίστοιχη συνάρτηση για την εξυπηρέτηση της διακοπής. Η αντιστοίχιση της διακοπής με συνάρτηση γίνεται με χρήση function pointers που έχουν ανατεθεί κατά την αρχικοποίηση των ανάλογων συσκευών. Προς το παρόν χρησιμοποιούμε μόλις δύο συσκευές 41

στον PIC, τον timer (PIT) και το πληκτρολόγιο. Όταν εξυπηρετηθεί η διακοπή, ο έλεγχος επιστρέφει στο σημείο που έγινε η κλήση της irq_handler(struct regs *r) όπου και αποκαθίσταται η κατάσταση του συστήματος πριν τη διακοπή και επιστρέφει στο πρόγραμμα που διακόπηκε. Η διασύνδεση σε επίπεδο αρχείων/συναρτήσεων παρουσιάζεται διεξοδικά στο παρακάτω Διάγραμμα. Διάγραμμα 6.1: Διασύνδεση και εξυπηρέτηση διακοπών. 6.3 Διαχείριση Timer και πληκτρολογίου Στην ενότητα αυτή θα εξεταστεί λεπτομερώς η λειτουργία του timer και του πληκτρολογίου. 42

6.3.1 Το αρχείο timer.c Το αρχείο timer.c περιέχει μόλις δύο συναρτήσεις. Τη συνάρτηση αρχικοποίησης timer_install (int hz) που αναλύθηκε στο στάδιο αρχικοποίησης και τη ρουτίνα εξυπηρέτησης του timer, timer_handler. Η τελευταία τυπώνει στο κάτω δεξί άκρο της οθόνης τα ticks του συστήματος ως ένδειξη του χρόνου λειτουργίας. Τα ticks αποθηκεύονται στη μεταβλητή timer_ticks. Στη συνέχεια, γράφει τη λέξη 0x20 στη θύρα 0x20 που βρίσκεται ο πρώτος PIC για να τον ειδοποιήσει για το τέλος της εξυπηρέτησης της διακοπής. Επιπλέον, καλεί (υπό συνθήκη) τη συνάρτηση scheduler() για πραγματοποίηση αλλαγής διεργασίας. Η συνθήκη ορίζεται κατάλληλα με σκοπό η κάθε διεργασία να έχει αρκετό χρόνο για την εκτέλεσή της και να μη γίνεται πολύ συχνά το task switch για λόγους απόδοσης. 6.3.1 Το αρχείο kb.c Το παρόν αρχείο αποτελεί στοιχειώδη υλοποίηση οδηγού για το πληκτρολόγιο. Η κύρια συνάρτηση είναι η do_kb η οποία καλείται όταν το πληκτρολόγιο παράγει τη διακοπή 33 (IRQ 1). Αρχικά χρησιμοποιούμε τον πίνακα key_way ο οποίος αποτελείται από function pointers ανάλογα με τον τύπο του πλήκτρου. Αυτοί διακρίνονται σε unimplemented, printable characters, function keys κλπ. Για να γίνει η εξακρίβωση του πλήκτρου διαβάζουμε από τη θύρα 0x60 που βρίσκεται ο ελεγκτής του πληκτρολογίου. Μόλις εξυπηρετηθεί η διακοπή, πρέπει να ειδοποιήσουμε τον ελεγκτή ότι «λάβαμε» το πλήκτρο. Αυτό γίνεται εύκολα εάν θέσουμε το bit 7 της θύρας 0x61 σε άσσο και κατόπιν σε μηδέν. Τέλος, στέλνουμε το συνηθισμένο 0x20 στο πρώτο PIC για να ειδοποιηθεί για το πέρας της εξυπηρέτησης της διακοπής. 43

Υπάρχουν τέσσερις μεταβλητές στο αρχείο: static unsigned char shf_p = 0; static unsigned char ctl_p = 0; static unsigned char alt_p = 0; static unsigned char scan_code; static unsigned char leds=0; Οι πρώτες τρεις αποθηκεύουν την κατάσταση των πλήκτρων shift, control και alt. Η scan_code αποθηκεύει την τιμή του υπο επεξεργασία πλήκτρου, ενώ η leds κρατάει την κατάσταση των 3 Led του πληκτρολογίου Caps lock, Num lock και Scroll lock. Τα scan codes που στέλνει ο ελεγκτής έχουν κάποια ιδιαιτερότητα. Όταν πατηθεί ένα πλήκτρο λαμβάνουμε την τιμή που υπάρχει στο keymap (έστω XY). Όταν απελευθερωθεί το πλήκτρο, ο ελεγκτής στέλνει την τιμή XY OR 0x80. Γι αυτό το λόγο, σε κάθε συνάρτηση key_way ελέγχουμε την παραπάνω κατάσταση. Εάν το πλήκτρο έχει πατηθεί, η διαδικασία συνεχίζεται κανονικά, διαφορετικά επιστρέφουμε στην do_kb(). Τέλος, η συνάρτηση pln() που αναλαμβάνει την εξυπηρέτηση των εκτυπώσιμων χαρακτήρων έχει έναν πίνακα με 116 τιμές, 58 για τους χαρακτήρες χωρίς πατημένο το shift (ή το Caps lock ενεργοποιημένο) και 58 για τις υπόλοιπες για να μπορούμε γρήγορα να ξεχωρίσουμε τα πεζά από τα κεφαλαία. Η συνάρτηση στέλνει όλα τα πατημένα πλήκτρα στη συνάρτηση get_command_char (char c) που ανήκει στη διεπαφή του Command Line Interface. 44

6.3 Διαχείριση Διεργασιών 6.3.1 Το αρχείο task.c Όλες οι λειτουργίες που περιλαμβάνουν το χειρισμό των διεργασιών βρίσκονται στο αρχείο task.c. Για να μη γίνει σύγχυση των εννοιών, θα επεξηγηθούν επιμελώς τα χαρακτηριστικά της σχεδίασης. Πιο συγκεκριμένα: Η TASK_STRUCT αποτελεί πρωτίστης σημασίας δομή του συστήματος. Περιλαμβάνει τη δομή του TSS (Task State Segment) που απαιτείται από την αρχιτεκτονική x86, καθώς και συμπληρωματικές πληροφορίες, όπως την κατάσταση της διεργασίας, τον PID (Process Identifier), τις εγγραφές του LDT κλπ. Η μεταβλητή TASK_STRUCT *current δείχνει πάντοτε στην τρέχουσα εκτελούμενη διεργασία. Η δομή TASK_STRUCT TASK0 είναι η πρότυπη (από αυτή δημιουργούνται οι υπόλοιπες) και αποτελεί τη διεργασία του πυρήνα, όταν αυτός εισέλθει στο user mode. Η δομή TASK_LIST_STRUCT task_list περιέχει μια λίστα με τις διεργασίες που τρέχουν στο σύστημα. Η σχεδίαση θα μπορούσε να γίνει ελλείψει αυτής, αλλά υλοποιήθηκε για πιο κατανοητό κώδικα. Η μεταβλητή ldt της δομής TSS που εμπεριέχεται στη δομή TASK_STRUCT δείχνει τον descriptor του LDT στον GDT (δηλαδή την τιμή 0x20). 45

Η μεταβλητή ldt_entry της δομής TASK_STRUCT περιέχει τα δεδομένα που τοποθετούνται στον descriptor του LDT στον GDT, και μεταβάλλεται για κάθε διεργασία. Η μεταβλητή ldt[2] της δομής TASK_STRUCT περιέχει τους selectors για τον κώδικα και τα δεδομένα στον IDT (δηλαδή 0x7 και 0xf). Αρχικά διακρίνουμε την πιο σημαντική συνάρτηση, την void new_task (unsigned long int eip, char* task_name). Τα ορίσματα που δέχεται είναι το entry point της διεργασίας, δηλαδή η τιμή του καταχωρητή EIP και το όνομα της διεργασίας. Η συνάρτηση αρχικά δεσμεύει χώρο για τη δομή TASK_STRUCT μέσω της συνάρτησης pmmngr_alloc_block(). Στη συνέχεια αντιγράφει τα περιεχόμενα της δομής από το πρότυπο TASK0, και δεσμεύει άλλο ένα block μνήμης για το stack. Αφότου γίνουν οι αναθέσεις των υπόλοιπων τιμών, η διεργασία λαμβάνει PID αριθμό μέσω της συνάρτησης task_list_add και μπαίνει στην διπλά συνδεδεμένη λίστα. Έπειτα έχουμε τη συνάρτηση που πραγματοποιεί το task switching, την void scheduler (void) χωρίς ορίσματα. Τα βήματα που ακολουθεί είναι τα εξής: 1) Δημιουργία δείκτη στην εισερχόμενη διεργασία. 2) Ανάκτηση των δεδομένων TSS και LDT που βρίσκονται στον GDT της εξερχόμενης διεργασίας (σε περίπτωση που ο επεξεργαστής έχει αλλάξει τις τιμές). στον GDT. 3) Ανάθεση των τιμών του TSS και LDT της εισερχόμενης διεργασίας 4) Αλλαγή της κατάστασης των δύο διεργασιών. 5) Ανάθεση της μεταβλητής current στην εξερχόμενη διαδικασία. 46