Στους υπολογιστές, για να είναι εκτελέσιμη μια διαδικασία, πρέπει να τοποθετηθεί στη μνήμη. Για αυτό, ένα πεδίο πρέπει να εκχωρηθεί σε μια διεργασία στη μνήμη. Η εκχώρηση μνήμης είναι ένα σημαντικό ζήτημα που πρέπει να γνωρίζετε, ειδικά στις αρχιτεκτονικές του πυρήνα και του συστήματος.
Ας ρίξουμε μια ματιά στην κατανομή μνήμης Linux λεπτομερώς και ας καταλάβουμε τι συμβαίνει στα παρασκήνια.
Πώς γίνεται η εκχώρηση μνήμης;
Οι περισσότεροι μηχανικοί λογισμικού δεν γνωρίζουν τις λεπτομέρειες αυτής της διαδικασίας. Αλλά αν είστε υποψήφιος προγραμματιστής συστήματος, θα πρέπει να μάθετε περισσότερα για αυτό. Όταν εξετάζετε τη διαδικασία κατανομής, είναι απαραίτητο να υπεισέλθουμε σε μια μικρή λεπτομέρεια για το Linux και το glibc βιβλιοθήκη.
Όταν οι εφαρμογές χρειάζονται μνήμη, πρέπει να τη ζητήσουν από το λειτουργικό σύστημα. Αυτό το αίτημα από τον πυρήνα θα απαιτήσει φυσικά μια κλήση συστήματος. Δεν μπορείτε να εκχωρήσετε μόνοι σας τη μνήμη σε λειτουργία χρήστη.
ο malloc() Η οικογένεια συναρτήσεων είναι υπεύθυνη για την εκχώρηση μνήμης στη γλώσσα C. Το ερώτημα που τίθεται εδώ είναι εάν η malloc(), ως συνάρτηση glibc, πραγματοποιεί μια άμεση κλήση συστήματος.
Δεν υπάρχει κλήση συστήματος που ονομάζεται malloc στον πυρήνα του Linux. Ωστόσο, υπάρχουν δύο κλήσεις συστήματος για απαιτήσεις μνήμης εφαρμογών, οι οποίες είναι brk και mmap.
Εφόσον θα ζητάτε μνήμη στην εφαρμογή σας μέσω των συναρτήσεων glibc, μπορεί να αναρωτιέστε ποια από αυτές τις κλήσεις συστήματος χρησιμοποιεί το glibc σε αυτό το σημείο. Η απάντηση είναι και τα δύο.
Η πρώτη κλήση συστήματος: brk
Κάθε διεργασία έχει ένα συνεχόμενο πεδίο δεδομένων. Με την κλήση συστήματος brk, η τιμή διακοπής προγράμματος, που καθορίζει το όριο του πεδίου δεδομένων, αυξάνεται και εκτελείται η διαδικασία εκχώρησης.
Αν και η εκχώρηση μνήμης με αυτήν τη μέθοδο είναι πολύ γρήγορη, δεν είναι πάντα δυνατή η επιστροφή αχρησιμοποίητου χώρου στο σύστημα.
Για παράδειγμα, θεωρήστε ότι εκχωρείτε πέντε πεδία, το καθένα μεγέθους 16 KB, με την κλήση συστήματος brk μέσω της συνάρτησης malloc(). Όταν τελειώσετε με το νούμερο δύο από αυτά τα πεδία, δεν είναι δυνατή η επιστροφή του σχετικού πόρου (deallocation) ώστε το σύστημα να μπορεί να τον χρησιμοποιήσει. Διότι εάν μειώσετε την τιμή της διεύθυνσης για να εμφανίσετε το σημείο από το οποίο ξεκινά το πεδίο με αριθμό δύο, με μια κλήση στο brk, θα έχετε κάνει κατανομή για τα πεδία αριθμούς τρία, τέσσερα και πέντε.
Για να αποφευχθεί η απώλεια μνήμης σε αυτό το σενάριο, η εφαρμογή malloc στο glibc παρακολουθεί τις θέσεις που έχουν εκχωρηθεί στο πεδίο δεδομένων διεργασίας και στη συνέχεια καθορίζει να το επιστρέψει στο σύστημα με τη συνάρτηση free(), έτσι ώστε το σύστημα να μπορεί να χρησιμοποιήσει τον ελεύθερο χώρο για περαιτέρω μνήμη κατανομές.
Με άλλα λόγια, μετά από πέντε εκχωρήσεις περιοχών 16 KB, εάν επιστραφεί η δεύτερη περιοχή με τη συνάρτηση free() και άλλη περιοχή 16 KB ζητείται ξανά μετά από λίγο, αντί να διευρυνθεί η περιοχή δεδομένων μέσω της κλήσης συστήματος brk, επιστρέφεται η προηγούμενη διεύθυνση.
Ωστόσο, εάν η περιοχή που ζητήθηκε πρόσφατα είναι μεγαλύτερη από 16 KB, τότε η περιοχή δεδομένων θα διευρυνθεί εκχωρώντας μια νέα περιοχή με την κλήση συστήματος brk, καθώς η περιοχή δύο δεν μπορεί να χρησιμοποιηθεί. Αν και η περιοχή νούμερο δύο δεν χρησιμοποιείται, η εφαρμογή δεν μπορεί να τη χρησιμοποιήσει λόγω της διαφοράς μεγέθους. Λόγω σεναρίων όπως αυτό, υπάρχει μια κατάσταση που ονομάζεται εσωτερικός κατακερματισμός και στην πραγματικότητα, σπάνια μπορείτε να χρησιμοποιήσετε στο έπακρο όλα τα μέρη της μνήμης.
Για καλύτερη κατανόηση, δοκιμάστε να μεταγλωττίσετε και να εκτελέσετε το ακόλουθο δείγμα εφαρμογής:
#περιλαμβάνω <stdio.h>
#περιλαμβάνω <stdlib.h>
#περιλαμβάνω <unistd.χ>
ενθκύριος(ενθ argc, απανθρακώνω* argv[])
{
απανθρακώνω *ptr[7];
ενθ n;
printf("Pid του %s: %d", argv[0], getpid());
printf("Αρχική διακοπή προγράμματος: %p", sbrk (0));
για (n=0; n<5; n++) ptr[n] = malloc (16 * 1024);
printf("Μετά από 5 x 16 kB malloc: %p", sbrk (0));
Ελεύθερος(πτρ[1]);
printf("Μετά από το δεύτερο 16kB: %p", sbrk (0));
ptr[5] = malloc (16 * 1024);
printf("Μετά την κατανομή του 6ου από 16 kB: %p", sbrk (0));
Ελεύθερος(πτρ[5]);
printf("Μετά την απελευθέρωση του τελευταίου μπλοκ: %p", sbrk (0));
ptr[6] = malloc (18 * 1024);
printf("Μετά την εκχώρηση ενός νέου 18 kB: %p", sbrk (0));
getchar();
ΕΠΙΣΤΡΟΦΗ0;
}
Όταν εκτελείτε την εφαρμογή, θα λάβετε ένα αποτέλεσμα παρόμοιο με το ακόλουθο αποτέλεσμα:
Pid of ./a.out: 31990
Αρχικό πρόγραμμα Διακοπή: 0x55ebcadf4000
Μετά από 5 x 16 kB malloc: 0x55ebcadf4000
Μετά από το δεύτερο 16kB: 0x55ebcadf4000
Μετά την εκχώρηση 6ου από 16 kB: 0x55ebcadf4000
Μετά την απελευθέρωση του τελευταίου μπλοκ: 0x55ebcadf4000
Μετά την κατανομή α νέος18kB: 0x55ebcadf4000
Η έξοδος για brk με strace θα είναι η εξής:
brk(ΜΗΔΕΝΙΚΟ) = 0x5608595b6000
brk (0x5608595d7000) = 0x5608595d7000
Οπως βλέπεις, 0x21000 έχει προστεθεί στην τελική διεύθυνση του πεδίου δεδομένων. Μπορείτε να το καταλάβετε αυτό από την αξία 0x5608595d7000. Άρα περίπου 0x21000, ή εκχωρήθηκαν 132 KB μνήμης.
Υπάρχουν δύο σημαντικά σημεία που πρέπει να ληφθούν υπόψη εδώ. Το πρώτο είναι η κατανομή ποσού μεγαλύτερου από το ποσό που καθορίζεται στο δείγμα κώδικα. Ένα άλλο είναι ποια γραμμή κώδικα προκάλεσε την κλήση brk που παρείχε την κατανομή.
Τυχαιοποίηση διάταξης χώρου διεύθυνσης: ASLR
Όταν εκτελείτε το παραπάνω παράδειγμα εφαρμογής το ένα μετά το άλλο, θα βλέπετε διαφορετικές τιμές διεύθυνσης κάθε φορά. Η τυχαία αλλαγή του χώρου διευθύνσεων με αυτόν τον τρόπο περιπλέκει σημαντικά τη δουλειά του επιτίθεται στην ασφάλεια και αυξάνει την ασφάλεια του λογισμικού.
Ωστόσο, στις αρχιτεκτονικές των 32 bit, γενικά χρησιμοποιούνται οκτώ bit για την τυχαιοποίηση του χώρου διευθύνσεων. Η αύξηση του αριθμού των bit δεν θα είναι κατάλληλη, καθώς η διευθυνσιοδοτούμενη περιοχή στα υπόλοιπα bit θα είναι πολύ χαμηλή. Επίσης, η χρήση μόνο συνδυασμών 8-bit δεν δυσκολεύει αρκετά τα πράγματα για τον επιτιθέμενο.
Σε αρχιτεκτονικές 64-bit, από την άλλη πλευρά, δεδομένου ότι υπάρχουν πάρα πολλά bit που μπορούν να εκχωρηθούν για λειτουργία ASLR, παρέχεται πολύ μεγαλύτερη τυχαιότητα και ο βαθμός ασφάλειας αυξάνεται.
Ο πυρήνας του Linux εξουσιοδοτείται επίσης Συσκευές που βασίζονται σε Android και η λειτουργία ASLR είναι πλήρως ενεργοποιημένη σε Android 4.0.3 και μεταγενέστερες εκδόσεις. Ακόμη και για αυτόν τον λόγο και μόνο, δεν θα ήταν λάθος να πούμε ότι ένα smartphone 64 bit παρέχει σημαντικό πλεονέκτημα ασφάλειας έναντι των εκδόσεων 32 bit.
Απενεργοποιώντας προσωρινά τη δυνατότητα ASLR με την ακόλουθη εντολή, θα φανεί ότι η προηγούμενη δοκιμαστική εφαρμογή επιστρέφει τις ίδιες τιμές διεύθυνσης κάθε φορά που εκτελείται:
ηχώ0 | sudo tee /proc/sys/kernel/randomize_va_space
Για να το επαναφέρετε στην προηγούμενη κατάσταση, αρκεί να γράψετε 2 αντί για 0 στο ίδιο αρχείο.
Η δεύτερη κλήση συστήματος: mmap
Το mmap είναι η δεύτερη κλήση συστήματος που χρησιμοποιείται για την εκχώρηση μνήμης στο Linux. Με την κλήση mmap, ο ελεύθερος χώρος σε οποιαδήποτε περιοχή της μνήμης αντιστοιχίζεται στο χώρο διευθύνσεων της διαδικασίας κλήσης.
Σε μια εκχώρηση μνήμης που γίνεται με αυτόν τον τρόπο, όταν θέλετε να επιστρέψετε το δεύτερο διαμέρισμα των 16 KB με τη συνάρτηση free() στο προηγούμενο παράδειγμα brk, δεν υπάρχει μηχανισμός που να εμποδίζει αυτήν τη λειτουργία. Το σχετικό τμήμα μνήμης αφαιρείται από τον χώρο διευθύνσεων της διεργασίας. Επισημαίνεται ότι δεν χρησιμοποιείται πλέον και επιστρέφεται στο σύστημα.
Επειδή οι εκχωρήσεις μνήμης με mmap είναι πολύ αργές σε σύγκριση με αυτές με brk, απαιτείται εκχώρηση brk.
Με το mmap, οποιαδήποτε ελεύθερη περιοχή της μνήμης αντιστοιχίζεται στον χώρο διευθύνσεων της διεργασίας, επομένως τα περιεχόμενα του εκχωρημένου χώρου επαναφέρονται πριν ολοκληρωθεί αυτή η διαδικασία. Εάν η επαναφορά δεν έγινε με αυτόν τον τρόπο, τα δεδομένα που ανήκουν στη διεργασία που χρησιμοποιούσε προηγουμένως τη σχετική περιοχή μνήμης θα μπορούσαν επίσης να προσπελαστούν από την επόμενη άσχετη διεργασία. Αυτό θα καθιστούσε αδύνατο να μιλήσουμε για ασφάλεια στα συστήματα.
Η σημασία της εκχώρησης μνήμης στο Linux
Η εκχώρηση μνήμης είναι πολύ σημαντική, ειδικά σε θέματα βελτιστοποίησης και ασφάλειας. Όπως φαίνεται στα παραπάνω παραδείγματα, η μη πλήρης κατανόηση αυτού του ζητήματος μπορεί να σημαίνει καταστροφή της ασφάλειας του συστήματός σας.
Ακόμη και έννοιες παρόμοιες με το push και το pop που υπάρχουν σε πολλές γλώσσες προγραμματισμού βασίζονται σε λειτουργίες εκχώρησης μνήμης. Η καλή χρήση και ο έλεγχος της μνήμης συστήματος είναι ζωτικής σημασίας τόσο στον προγραμματισμό του ενσωματωμένου συστήματος όσο και στην ανάπτυξη μιας ασφαλούς και βελτιστοποιημένης αρχιτεκτονικής συστήματος.
Εάν θέλετε επίσης να βυθίσετε τα δάχτυλα των ποδιών σας στην ανάπτυξη πυρήνα Linux, σκεφτείτε πρώτα να μάθετε τη γλώσσα προγραμματισμού C.
Μια σύντομη εισαγωγή στη γλώσσα προγραμματισμού C
Διαβάστε Επόμενο
Σχετικά θέματα
- Linux
- Μνήμη υπολογιστή
- Πυρήνας Linux
Σχετικά με τον Συγγραφέα
Ένας μηχανικός και προγραμματιστής λογισμικού που είναι λάτρης των μαθηματικών και της τεχνολογίας. Πάντα του άρεσαν οι υπολογιστές, τα μαθηματικά και η φυσική. Έχει αναπτύξει έργα μηχανών παιχνιδιών καθώς και μηχανική μάθηση, τεχνητά νευρωνικά δίκτυα και βιβλιοθήκες γραμμικής άλγεβρας. Επιπλέον, συνεχίζει να εργάζεται σε μηχανική μάθηση και γραμμικούς πίνακες.
Εγγραφείτε στο ενημερωτικό μας δελτίο
Εγγραφείτε στο ενημερωτικό μας δελτίο για συμβουλές τεχνολογίας, κριτικές, δωρεάν ebook και αποκλειστικές προσφορές!
Κάντε κλικ εδώ για να εγγραφείτε