Περιγραφέας αρχείων σε Linux με παραδείγματα

Κάποτε, κατά τη διάρκεια μιας συνέντευξης, με ρώτησαν, τι θα κάνετε αν βρείτε μια υπηρεσία που δεν λειτουργεί επειδή ο δίσκος έχει εξαντληθεί;

Φυσικά, του απάντησα ότι θα δω τι κατείχε αυτό το μέρος και, αν είναι δυνατόν, θα καθαρίσω τον χώρο.
Στη συνέχεια, ο ερευνητής ρώτησε, τι θα συμβεί αν δεν υπάρχει ελεύθερος χώρος στο διαμέρισμα, αλλά δεν βλέπετε επίσης αρχεία που θα καταλάμβαναν όλο το χώρο;

Σε αυτό είπα ότι μπορείτε πάντα να κοιτάξετε τους περιγραφείς ανοιχτών αρχείων, για παράδειγμα με την εντολή lsof, και να κατανοήσετε ποια εφαρμογή έχει καταλάβει όλο τον διαθέσιμο χώρο και, στη συνέχεια, μπορείτε να ενεργήσετε ανάλογα με τις περιστάσεις, ανάλογα με το αν χρειάζονται τα δεδομένα .

Ο συνεντευκτής με διέκοψε στην τελευταία λέξη, προσθέτοντας στην ερώτησή του: «Ας υποθέσουμε ότι δεν χρειαζόμαστε τα δεδομένα, είναι απλώς ένα αρχείο καταγραφής εντοπισμού σφαλμάτων, αλλά η εφαρμογή δεν λειτουργεί επειδή δεν μπορεί να γράψει έναν εντοπισμό σφαλμάτων»;

«Εντάξει», απάντησα, «μπορούμε να απενεργοποιήσουμε τον εντοπισμό σφαλμάτων στη διαμόρφωση της εφαρμογής και να την επανεκκινήσουμε».
Ο ερευνητής αντέτεινε: «Όχι, δεν μπορούμε να επανεκκινήσουμε την εφαρμογή, έχουμε ακόμα σημαντικά δεδομένα αποθηκευμένα στη μνήμη και σημαντικοί πελάτες είναι συνδεδεμένοι στην ίδια την υπηρεσία, την οποία δεν μπορούμε να αναγκάσουμε να επανασυνδεθεί ξανά».

"Εντάξει", είπα, "αν δεν μπορούμε να επανεκκινήσουμε την εφαρμογή και τα δεδομένα δεν είναι σημαντικά για εμάς, τότε μπορούμε απλά να διαγράψουμε αυτό το ανοιχτό αρχείο μέσω του περιγραφέα αρχείου, ακόμα κι αν δεν το δούμε στην εντολή ls στο σύστημα αρχείων."

Ο συνεντευκτής ήταν ευχαριστημένος, αλλά εγώ όχι.

Τότε σκέφτηκα, γιατί το άτομο που δοκιμάζει τις γνώσεις μου δεν σκάβει βαθύτερα; Τι γίνεται όμως αν τα δεδομένα είναι τελικά σημαντικά; Τι γίνεται αν δεν μπορούμε να επανεκκινήσουμε μια διαδικασία και η διαδικασία γράψει στο σύστημα αρχείων σε ένα διαμέρισμα που δεν έχει ελεύθερο χώρο; Τι γίνεται αν δεν μπορούμε να χάσουμε όχι μόνο τα δεδομένα που έχουν ήδη γραφτεί, αλλά και τα δεδομένα που γράφει ή προσπαθεί να γράψει αυτή η διαδικασία;

Τούζικ

Στην αρχή της καριέρας μου, προσπάθησα να δημιουργήσω μια μικρή εφαρμογή που χρειαζόταν να αποθηκεύει πληροφορίες χρήστη. Και μετά σκέφτηκα πώς μπορώ να αντιστοιχίσω τον χρήστη με τα δεδομένα του. Για παράδειγμα, έχω τον Ivanov Ivan Ivanovich και έχει κάποιες πληροφορίες, αλλά πώς μπορώ να κάνω φίλους μαζί τους; Μπορώ να επισημάνω ευθέως ότι ο σκύλος με το όνομα "Tuzik" ανήκει σε αυτόν ακριβώς τον Ιβάν. Τι γίνεται όμως αν αλλάξει το όνομά του και αντί για Ιβάν γίνει, για παράδειγμα, Olya; Τότε θα αποδειχθεί ότι η δική μας Olya Ivanovna Ivanova δεν θα έχει πλέον σκύλο και ο Tuzik μας θα εξακολουθεί να ανήκει στον ανύπαρκτο Ιβάν. Μια βάση δεδομένων που έδινε σε κάθε χρήστη ένα μοναδικό αναγνωριστικό (ID) βοήθησε στην επίλυση αυτού του προβλήματος και το Tuzik μου συνδέθηκε με αυτό το αναγνωριστικό, το οποίο, στην πραγματικότητα, ήταν απλώς ένας σειριακός αριθμός. Έτσι, ο ιδιοκτήτης του άσου είχε αναγνωριστικό αριθμό 2 και κάποια στιγμή ο Ιβάν ήταν κάτω από αυτό το αναγνωριστικό και στη συνέχεια η Olya έγινε κάτω από την ίδια ταυτότητα. Το πρόβλημα της ανθρωπότητας και της κτηνοτροφίας λύθηκε πρακτικά.

Περιγραφέας αρχείου

Το πρόβλημα του αρχείου και του προγράμματος που λειτουργεί με αυτό το αρχείο είναι περίπου το ίδιο με αυτό του σκύλου και του ανθρώπου μας. Ας υποθέσουμε ότι άνοιξα ένα αρχείο που ονομάζεται ivan.txt και άρχισα να γράφω τη λέξη tuzik σε αυτό, αλλά κατάφερα να γράψω μόνο το πρώτο γράμμα "t" στο αρχείο και αυτό το αρχείο μετονομάστηκε από κάποιον, για παράδειγμα, σε olya.txt. Αλλά το αρχείο παραμένει το ίδιο, και θέλω ακόμα να καταγράψω τον άσο μου σε αυτό. Κάθε φορά που ανοίγει ένα αρχείο με κλήση συστήματος ανοίξτε Σε οποιαδήποτε γλώσσα προγραμματισμού λαμβάνω ένα μοναδικό αναγνωριστικό που με οδηγεί σε ένα αρχείο, αυτό το αναγνωριστικό είναι ο περιγραφέας αρχείου. Και δεν έχει καθόλου σημασία τι και ποιος κάνει με αυτό το αρχείο στη συνέχεια, μπορεί να διαγραφεί, να μετονομαστεί, να αλλάξει ο κάτοχος ή να αφαιρεθούν τα δικαιώματα ανάγνωσης και εγγραφής, θα έχω ακόμα πρόσβαση σε αυτό, γιατί τη στιγμή που άνοιξα το αρχείο, είχα τα δικαιώματα να το διαβάσω ή/και να το γράψω και κατάφερα να ξεκινήσω να δουλεύω μαζί του, πράγμα που σημαίνει ότι πρέπει να συνεχίσω να το κάνω.

Στο Linux, η βιβλιοθήκη libc ανοίγει 3 αρχεία περιγραφής για κάθε εφαρμογή που εκτελείται (διαδικασία), με αριθμό 0,1,2. Περισσότερες πληροφορίες μπορείτε να βρείτε στους συνδέσμους άνθρωπος stdio и άντρας stdout

  • Ο περιγραφέας αρχείου 0 ονομάζεται STDIN και σχετίζεται με την είσοδο της εφαρμογής
  • Ο περιγραφέας αρχείου 1 ονομάζεται STDOUT και χρησιμοποιείται από εφαρμογές για την έξοδο δεδομένων, όπως εντολές εκτύπωσης
  • Ο περιγραφέας αρχείου 2 ονομάζεται STDERR και χρησιμοποιείται από εφαρμογές για την έξοδο μηνυμάτων σφάλματος.

Εάν στο πρόγραμμά σας ανοίξετε οποιοδήποτε αρχείο για ανάγνωση ή γραφή, τότε πιθανότατα θα πάρετε το πρώτο δωρεάν αναγνωριστικό και θα είναι το νούμερο 3.

Η λίστα των περιγραφικών αρχείων μπορεί να προβληθεί για οποιαδήποτε διαδικασία, εάν γνωρίζετε το PID της.

Για παράδειγμα, ας ανοίξουμε την κονσόλα bash και ας δούμε το PID της διαδικασίας μας

[user@localhost ]$ echo $$
15771

Στη δεύτερη κονσόλα ας τρέξουμε

[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user  0 Oct  7 15:42 .
dr-xr-xr-x 9 user user  0 Oct  7 15:42 ..
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21

Μπορείτε να αγνοήσετε με ασφάλεια τον περιγραφικό αριθμό αρχείου 255 για τους σκοπούς αυτού του άρθρου. Ανοίχθηκε για τις ανάγκες του από το ίδιο το bash και όχι από τη συνδεδεμένη βιβλιοθήκη.

Τώρα και τα 3 αρχεία περιγραφής συσχετίζονται με την ψευδοτερματική συσκευή /dev/pts, αλλά μπορούμε ακόμα να τα χειριστούμε, για παράδειγμα, να τα εκτελέσουμε σε μια δεύτερη κονσόλα

[user@localhost ]$ echo "hello world" > /proc/15771/fd/0

Και στην πρώτη κονσόλα θα δούμε

[user@localhost ]$ hello world

Ανακατεύθυνση και Σωλήνας

Μπορείτε εύκολα να παρακάμψετε αυτά τα 3 αρχεία περιγραφής σε οποιαδήποτε διαδικασία, συμπεριλαμβανομένου του bash, για παράδειγμα μέσω ενός σωλήνα που συνδέει δύο διεργασίες, βλ.

[user@localhost ]$ cat /dev/zero | sleep 10000

Μπορείτε να εκτελέσετε αυτήν την εντολή μόνοι σας με strace -f και να δεις τι γίνεται μέσα, αλλά θα σου πω εν συντομία.

Η γονική μας διαδικασία bash με PID 15771 αναλύει την εντολή μας και κατανοεί ακριβώς πόσες εντολές θέλουμε να εκτελέσουμε, στην περίπτωσή μας υπάρχουν δύο από αυτές: cat και sleep. Το Bash γνωρίζει ότι πρέπει να δημιουργήσει δύο θυγατρικές διεργασίες και να τις συγχωνεύσει σε έναν σωλήνα. Συνολικά, το bash θα χρειαστεί 2 διεργασίες παιδιών και ένα σωλήνα.

Το Bash εκτελεί μια κλήση συστήματος πριν δημιουργήσει θυγατρικές διεργασίες σωλήνας και λαμβάνει νέους περιγραφείς αρχείων στην προσωρινή προσωρινή μνήμη σωλήνα, αλλά αυτή η προσωρινή μνήμη δεν συνδέει ακόμη τις δύο θυγατρικές διεργασίες μας.

Για τη γονική διαδικασία, φαίνεται ότι υπάρχει ήδη ένας σωλήνας, αλλά δεν υπάρχουν ακόμη θυγατρικές διεργασίες:

PID    command
15771  bash
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21

Στη συνέχεια, χρησιμοποιώντας την κλήση συστήματος κλωνοποίηση Το bash δημιουργεί δύο θυγατρικές διαδικασίες και οι τρεις διεργασίες μας θα μοιάζουν με αυτό:

PID    command
15771  bash
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21
PID    command
9004  bash
lrwx------ 1 user user 64 Oct  7 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 255 -> /dev/pts/21
PID    command
9005  bash
lrwx------ 1 user user 64 Oct  7 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 255 -> /dev/pts/21

Μην ξεχνάτε ότι ο κλώνος κλωνοποιεί τη διαδικασία μαζί με όλους τους περιγραφείς αρχείων, έτσι θα είναι ίδιοι στη γονική διαδικασία και στις θυγατρικές. Η δουλειά της γονικής διαδικασίας με το PID 15771 είναι να παρακολουθεί τις θυγατρικές διαδικασίες, επομένως απλώς περιμένει μια απάντηση από τα παιδιά.

Επομένως, δεν χρειάζεται σωλήνα και κλείνει τους περιγραφείς αρχείων με αριθμό 3 και 4.

Στην πρώτη διαδικασία παιδικού bash με PID 9004, η κλήση συστήματος dup2, αλλάζει τον περιγραφικό αριθμό 1 του αρχείου STDOUT σε έναν περιγραφέα αρχείου που δείχνει προς τον σωλήνα, στην περίπτωσή μας είναι ο αριθμός 3. Έτσι, ό,τι γράφει η πρώτη θυγατρική διαδικασία με PID 9004 στο STDOUT θα καταλήγει αυτόματα στην προσωρινή μνήμη σωλήνα.

Στη δεύτερη θυγατρική διαδικασία με το PID 9005, το bash χρησιμοποιεί το dup2 για να αλλάξει τον περιγραφέα αρχείου STDIN αριθμό 0. Τώρα όλα όσα θα διαβάσει το δεύτερο bash μας με PID 9005 θα διαβαστούν από το σωλήνα.

Μετά από αυτό, οι περιγραφείς αρχείων με αριθμό 3 και 4 κλείνουν επίσης στις θυγατρικές διεργασίες, καθώς δεν χρησιμοποιούνται πλέον.

Παραβλέπω εσκεμμένα τον περιγραφέα αρχείου 255· χρησιμοποιείται για εσωτερικούς σκοπούς από το ίδιο το bash και θα κλείσει επίσης σε θυγατρικές διεργασίες.

Στη συνέχεια, στην πρώτη θυγατρική διαδικασία με το PID 9004, το bash ξεκινά να χρησιμοποιεί μια κλήση συστήματος exec το εκτελέσιμο αρχείο που καθορίσαμε στη γραμμή εντολών, στην περίπτωσή μας είναι /usr/bin/cat.

Στη δεύτερη θυγατρική διαδικασία με PID 9005, το bash εκτελεί το δεύτερο εκτελέσιμο που καθορίσαμε, στην περίπτωσή μας /usr/bin/sleep.

Η κλήση συστήματος exec δεν κλείνει τις λαβές αρχείων εκτός και αν είχαν ανοίξει με τη σημαία O_CLOEXEC τη στιγμή που έγινε η ανοιχτή κλήση. Στην περίπτωσή μας, μετά την εκκίνηση των εκτελέσιμων αρχείων, όλοι οι τρέχοντες περιγραφείς αρχείων θα αποθηκευτούν.

Ελέγξτε στην κονσόλα:

[user@localhost ]$ pgrep -P 15771
9004
9005
[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user  0 Oct  7 15:42 .
dr-xr-xr-x 9 user user  0 Oct  7 15:42 ..
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21
[user@localhost ]$ ls -lah /proc/9004/fd
total 0
dr-x------ 2 user user  0 Oct  7 15:57 .
dr-xr-xr-x 9 user user  0 Oct  7 15:57 ..
lrwx------ 1 user user 64 Oct  7 15:57 0 -> /dev/pts/21
l-wx------ 1 user user 64 Oct  7 15:57 1 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
lr-x------ 1 user user 64 Oct  7 15:57 3 -> /dev/zero
[user@localhost ]$ ls -lah /proc/9005/fd
total 0
dr-x------ 2 user user  0 Oct  7 15:57 .
dr-xr-xr-x 9 user user  0 Oct  7 15:57 ..
lr-x------ 1 user user 64 Oct  7 15:57 0 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
[user@localhost ]$ ps -up 9004
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
user  9004  0.0  0.0 107972   620 pts/21   S+   15:57   0:00 cat /dev/zero
[user@localhost ]$ ps -up 9005
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
user  9005  0.0  0.0 107952   360 pts/21   S+   15:57   0:00 sleep 10000

Όπως μπορείτε να δείτε, ο μοναδικός αριθμός του σωλήνα μας είναι ο ίδιος και στις δύο διαδικασίες. Έτσι έχουμε μια σύνδεση μεταξύ δύο διαφορετικών διεργασιών με τον ίδιο γονέα.

Για όσους δεν είναι εξοικειωμένοι με τις κλήσεις συστήματος που χρησιμοποιεί το bash, συνιστώ ανεπιφύλακτα να εκτελέσετε τις εντολές μέσω του strace και να δείτε τι συμβαίνει εσωτερικά, για παράδειγμα ως εξής:

strace -s 1024 -f bash -c "ls | grep hello"

Ας επιστρέψουμε στο πρόβλημά μας με το χαμηλό χώρο στο δίσκο και την προσπάθεια αποθήκευσης δεδομένων χωρίς επανεκκίνηση της διαδικασίας. Ας γράψουμε ένα μικρό πρόγραμμα που θα γράφει περίπου 1 megabyte ανά δευτερόλεπτο στο δίσκο. Επιπλέον, εάν για κάποιο λόγο δεν μπορέσαμε να γράψουμε δεδομένα στο δίσκο, απλώς θα το αγνοήσουμε και θα προσπαθήσουμε να γράψουμε ξανά τα δεδομένα σε ένα δευτερόλεπτο. Στο παράδειγμα που χρησιμοποιώ την Python, μπορείτε να χρησιμοποιήσετε οποιαδήποτε άλλη γλώσσα προγραμματισμού.

[user@localhost ]$ cat openforwrite.py 
import datetime
import time

mystr="a"*1024*1024+"n"
with open("123.txt", "w") as f:
    while True:
        try:
            f.write(str(datetime.datetime.now()))
            f.write(mystr)
            f.flush()
            time.sleep(1)
        except:
            pass

Ας τρέξουμε το πρόγραμμα και ας δούμε τους περιγραφείς αρχείων

[user@localhost ]$ python openforwrite.py &
[1] 3762
[user@localhost ]$ ps axuf | grep [o]penforwrite
user  3762  0.0  0.0 128600  5744 pts/22   S+   16:28   0:00  |   _ python openforwrite.py
[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user  0 Oct  7 16:29 .
dr-xr-xr-x 9 user user  0 Oct  7 16:29 ..
lrwx------ 1 user user 64 Oct  7 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  7 16:29 3 -> /home/user/123.txt

Όπως μπορείτε να δείτε, έχουμε τους 3 τυπικούς περιγραφείς αρχείων μας και έναν ακόμη που ανοίξαμε. Ας ελέγξουμε το μέγεθος του αρχείου:

[user@localhost ]$ ls -lah 123.txt 
-rw-rw-r-- 1 user user 117M Oct  7 16:30 123.txt

Τα δεδομένα γράφονται, προσπαθούμε να αλλάξουμε τα δικαιώματα στο αρχείο:

[user@localhost ]$ sudo chown root: 123.txt
[user@localhost ]$ ls -lah 123.txt 
-rw-rw-r-- 1 root root 168M Oct  7 16:31 123.txt
[user@localhost ]$ ls -lah 123.txt 
-rw-rw-r-- 1 root root 172M Oct  7 16:31 123.txt

Βλέπουμε ότι τα δεδομένα γράφονται ακόμη, αν και ο χρήστης μας δεν έχει άδεια εγγραφής στο αρχείο. Ας προσπαθήσουμε να το αφαιρέσουμε:

[user@localhost ]$ sudo rm 123.txt 
[user@localhost ]$ ls 123.txt
ls: cannot access 123.txt: No such file or directory

Πού είναι γραμμένα τα δεδομένα; Και γράφονται καθόλου; Ελέγχουμε:

[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user  0 Oct  7 16:29 .
dr-xr-xr-x 9 user user  0 Oct  7 16:29 ..
lrwx------ 1 user user 64 Oct  7 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  7 16:29 3 -> /home/user/123.txt (deleted)

Ναι, ο περιγραφέας αρχείου μας εξακολουθεί να υπάρχει και μπορούμε να αντιμετωπίσουμε αυτόν τον περιγραφικό αρχείου όπως το παλιό μας αρχείο, μπορούμε να το διαβάσουμε, να το διαγράψουμε και να το αντιγράψουμε.

Ας δούμε το μέγεθος του αρχείου:

[user@localhost ]$ lsof | grep 123.txt
python    31083             user    3w      REG                8,5   19923457   2621522 /home/user/123.txt

Το μέγεθος του αρχείου είναι 19923457. Ας προσπαθήσουμε να διαγράψουμε το αρχείο:

[user@localhost ]$ truncate -s 0 /proc/31083/fd/3
[user@localhost ]$ lsof | grep 123.txt
python    31083             user    3w      REG                8,5  136318390   2621522 /home/user/123.txt

Όπως μπορείτε να δείτε, το μέγεθος του αρχείου αυξάνεται μόνο και ο κορμός μας δεν λειτούργησε. Ας δούμε την τεκμηρίωση κλήσεων συστήματος ανοίξτε. Εάν χρησιμοποιήσουμε τη σημαία O_APPEND όταν ανοίγουμε ένα αρχείο, τότε με κάθε εγγραφή, το λειτουργικό σύστημα ελέγχει το μέγεθος του αρχείου και εγγράφει δεδομένα στο τέλος του αρχείου και το κάνει ατομικά. Αυτό επιτρέπει σε πολλαπλά νήματα ή διεργασίες να γράψουν στο ίδιο αρχείο. Αλλά στον κώδικά μας δεν χρησιμοποιούμε αυτή τη σημαία. Μπορούμε να δούμε διαφορετικό μέγεθος αρχείου στο lsof after trunk μόνο αν ανοίξουμε το αρχείο για επιπλέον εγγραφή, πράγμα που σημαίνει στον κώδικά μας

with open("123.txt", "w") as f:

πρέπει να βάλουμε

with open("123.txt", "a") as f:

Έλεγχος με τη σημαία "w".

[user@localhost ]$ strace -e trace=open python openforwrite.py 2>&1| grep 123.txt
open("123.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

και με τη σημαία «α».

[user@localhost ]$ strace -e trace=open python openforwrite.py 2>&1| grep 123.txt
open("123.txt", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3

Προγραμματισμός μιας ήδη εκτελούμενης διαδικασίας

Συχνά οι προγραμματιστές, όταν δημιουργούν και δοκιμάζουν προγράμματα, χρησιμοποιούν προγράμματα εντοπισμού σφαλμάτων (για παράδειγμα GDB) ή διάφορα επίπεδα καταγραφής στην εφαρμογή. Το Linux παρέχει τη δυνατότητα να γράφετε και να αλλάξετε πραγματικά ένα πρόγραμμα που εκτελείται ήδη, για παράδειγμα, να αλλάξετε τις τιμές των μεταβλητών, να ορίσετε ένα σημείο διακοπής, κ.λπ., κ.λπ.

Επιστρέφοντας στην αρχική ερώτηση σχετικά με τον ανεπαρκή χώρο στο δίσκο για τη σύνταξη ενός αρχείου, ας προσπαθήσουμε να προσομοιώσουμε το πρόβλημα.

Ας δημιουργήσουμε ένα αρχείο για το διαμέρισμα μας, το οποίο θα προσαρτήσουμε ως ξεχωριστό δίσκο:

[user@localhost ~]$ dd if=/dev/zero of=~/tempfile_for_article.dd bs=1M count=10
10+0 records in
10+0 records out
10485760 bytes (10 MB) copied, 0.00525929 s, 2.0 GB/s
[user@localhost ~]$

Ας δημιουργήσουμε ένα σύστημα αρχείων:

[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd
mke2fs 1.42.9 (28-Dec-2013)
/home/user/tempfile_for_article.dd is not a block special device.
Proceed anyway? (y,n) y
...
Writing superblocks and filesystem accounting information: done
[user@localhost ~]$

Προσαρτήστε το σύστημα αρχείων:

[user@localhost ~]$ sudo mount ~/tempfile_for_article.dd /mnt/
[sudo] password for user: 
[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  172K  7.9M   3% /mnt

Δημιουργούμε έναν κατάλογο με τον κάτοχό μας:

[user@localhost ~]$ sudo mkdir /mnt/logs
[user@localhost ~]$ sudo chown user: /mnt/logs

Ας ανοίξουμε το αρχείο για εγγραφή μόνο στο πρόγραμμά μας:

with open("/mnt/logs/123.txt", "w") as f:

Εκκίνηση

[user@localhost ]$ python openforwrite.py 

Περιμένουμε μερικά δευτερόλεπτα

[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  8.0M     0 100% /mnt

Έτσι, έχουμε το πρόβλημα που περιγράφεται στην αρχή αυτού του άρθρου. Ελεύθερος χώρος 0, 100% κατειλημμένος.

Θυμόμαστε ότι σύμφωνα με τις συνθήκες της εργασίας, προσπαθούμε να καταγράψουμε πολύ σημαντικά δεδομένα που δεν μπορούν να χαθούν. Και ταυτόχρονα, πρέπει να διορθώσουμε την υπηρεσία χωρίς επανεκκίνηση της διαδικασίας.

Ας υποθέσουμε ότι έχουμε ακόμα χώρο στο δίσκο, αλλά σε διαφορετικό διαμέρισμα, για παράδειγμα στο /home.

Ας προσπαθήσουμε να "επαναπρογραμματίσουμε on the fly" τον κώδικα μας.

Ας δούμε το PID της διαδικασίας μας, το οποίο έχει καταναλώσει όλο το χώρο στο δίσκο:

[user@localhost ~]$ ps axuf | grep [o]penfor
user 10078 27.2  0.0 128600  5744 pts/22   R+   11:06   0:02  |   _ python openforwrite.py

Συνδεθείτε στη διαδικασία μέσω gdb

[user@localhost ~]$ gdb -p 10078
...
(gdb) 

Ας δούμε τους περιγραφείς ανοιχτών αρχείων:

(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user  0 Oct  8 11:06 .
dr-xr-xr-x 9 user user  0 Oct  8 11:06 ..
lrwx------ 1 user user 64 Oct  8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:09 3 -> /mnt/logs/123.txt

Εξετάζουμε τις πληροφορίες σχετικά με τον περιγραφικό αριθμό αρχείου 3, που μας ενδιαφέρουν

(gdb) shell cat /proc/10078/fdinfo/3
pos:    8189952
flags:  0100001
mnt_id: 482

Έχοντας κατά νου την κλήση συστήματος που κάνει η Python (δείτε παραπάνω πού εκτελέσαμε το strace και βρήκαμε την ανοιχτή κλήση), όταν επεξεργαζόμαστε τον κώδικά μας για να ανοίξουμε ένα αρχείο, κάνουμε το ίδιο για λογαριασμό της διεργασίας μας, αλλά χρειαζόμαστε το O_WRONLY|O_CREAT| Τα bit O_TRUNC αντικαθίστανται με μια αριθμητική τιμή. Για να το κάνετε αυτό, ανοίξτε για παράδειγμα τις πηγές του πυρήνα εδώ και δείτε ποιες σημαίες είναι υπεύθυνες για τι

#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000

Συνδυάζουμε όλες τις τιμές σε μία, παίρνουμε 00001101

Εκτελούμε την κλήση μας από το gdb

(gdb) call open("/home/user/123.txt", 00001101,0666)
$1 = 4

Λοιπόν, πήραμε έναν νέο περιγραφέα αρχείου με αριθμό 4 και ένα νέο ανοιχτό αρχείο σε άλλο διαμέρισμα, ελέγχουμε:

(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user  0 Oct  8 11:06 .
dr-xr-xr-x 9 user user  0 Oct  8 11:06 ..
lrwx------ 1 user user 64 Oct  8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:09 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Oct  8 11:15 4 -> /home/user/123.txt

Θυμόμαστε το παράδειγμα με το pipe - πώς το bash αλλάζει τους περιγραφείς αρχείων και έχουμε ήδη μάθει την κλήση συστήματος dup2.

Προσπαθούμε να αντικαταστήσουμε έναν περιγραφέα αρχείου με έναν άλλο

(gdb) call dup2(4,3)
$2 = 3

Ελέγχουμε:

(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user  0 Oct  8 11:06 .
dr-xr-xr-x 9 user user  0 Oct  8 11:06 ..
lrwx------ 1 user user 64 Oct  8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:09 3 -> /home/user/123.txt
l-wx------ 1 user user 64 Oct  8 11:15 4 -> /home/user/123.txt

Κλείνουμε το αρχείο περιγραφής 4, αφού δεν το χρειαζόμαστε:

(gdb) call close (4)
$1 = 0

Και βγείτε από το gdb

(gdb) quit
A debugging session is active.

    Inferior 1 [process 10078] will be detached.

Quit anyway? (y or n) y
Detaching from program: /usr/bin/python2.7, process 10078

Έλεγχος του νέου αρχείου:

[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 5.1M Oct  8 11:18 /home/user/123.txt
[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 7.1M Oct  8 11:18 /home/user/123.txt

Όπως μπορείτε να δείτε, τα δεδομένα εγγράφονται σε ένα νέο αρχείο, ας ελέγξουμε το παλιό:

[user@localhost ~]$ ls -lah /mnt/logs/123.txt 
-rw-rw-r-- 1 user user 7.9M Oct  8 11:08 /mnt/logs/123.txt

Δεν χάνονται δεδομένα, η εφαρμογή λειτουργεί, τα αρχεία καταγραφής εγγράφονται σε μια νέα τοποθεσία.

Ας περιπλέκουμε λίγο το έργο

Ας φανταστούμε ότι τα δεδομένα είναι σημαντικά για εμάς, αλλά δεν έχουμε χώρο στο δίσκο σε κανένα από τα διαμερίσματα και δεν μπορούμε να συνδέσουμε το δίσκο.

Αυτό που μπορούμε να κάνουμε είναι να ανακατευθύνουμε τα δεδομένα μας κάπου, για παράδειγμα στο pipe, και με τη σειρά μας να ανακατευθύνουμε δεδομένα από το σωλήνα στο δίκτυο μέσω κάποιου προγράμματος, για παράδειγμα netcat.
Μπορούμε να δημιουργήσουμε ένα σωλήνα με όνομα με την εντολή mkfifo. Θα δημιουργήσει ένα ψευδοαρχείο στο σύστημα αρχείων ακόμα κι αν δεν υπάρχει ελεύθερος χώρος σε αυτό.

Επανεκκινήστε την εφαρμογή και ελέγξτε:

[user@localhost ]$ python openforwrite.py 
[user@localhost ~]$ ps axuf | grep [o]pen
user  5946 72.9  0.0 128600  5744 pts/22   R+   11:27   0:20  |   _ python openforwrite.py
[user@localhost ~]$ ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/123.txt
[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  8.0M     0 100% /mnt

Δεν υπάρχει χώρος στο δίσκο, αλλά δημιουργούμε επιτυχώς έναν επώνυμο σωλήνα εκεί:

[user@localhost ~]$ mkfifo /mnt/logs/megapipe
[user@localhost ~]$ ls -lah /mnt/logs/megapipe 
prw-rw-r-- 1 user user 0 Oct  8 11:28 /mnt/logs/megapipe

Τώρα πρέπει να τυλίξουμε με κάποιο τρόπο όλα τα δεδομένα που πηγαίνουν σε αυτόν τον σωλήνα σε έναν άλλο διακομιστή μέσω του δικτύου· το ίδιο netcat είναι κατάλληλο για αυτό.

Στον διακομιστή remote-server.example.com τρέχουμε

[user@localhost ~]$ nc -l 7777 > 123.txt 

Στον προβληματικό διακομιστή μας εκκινούμε σε ξεχωριστό τερματικό

[user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe 

Τώρα όλα τα δεδομένα που καταλήγουν στο σωλήνα θα πάνε αυτόματα στο stdin στο netcat, το οποίο θα τα στείλει στο δίκτυο στη θύρα 7777.

Το μόνο που έχουμε να κάνουμε είναι να αρχίσουμε να γράφουμε τα δεδομένα μας σε αυτόν τον ονομαζόμενο σωλήνα.

Έχουμε ήδη την εφαρμογή σε λειτουργία:

[user@localhost ~]$ ps axuf | grep [o]pen
user  5946 99.8  0.0 128600  5744 pts/22   R+   11:27 169:27  |   _ python openforwrite.py
[user@localhost ~]$ ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/123.txt

Από όλες τις σημαίες, χρειαζόμαστε μόνο O_WRONLY καθώς το αρχείο υπάρχει ήδη και δεν χρειάζεται να το διαγράψουμε

[user@localhost ~]$ gdb -p 5946
...
(gdb) call open("/mnt/logs/megapipe", 00000001,0666)
$1 = 4
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Oct  8 14:20 4 -> /mnt/logs/megapipe
(gdb) call dup2(4,3)
$2 = 3
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/megapipe
l-wx------ 1 user user 64 Oct  8 14:20 4 -> /mnt/logs/megapipe
(gdb) call close(4)
$3 = 0
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/megapipe
(gdb) quit
A debugging session is active.

    Inferior 1 [process 5946] will be detached.

Quit anyway? (y or n) y
Detaching from program: /usr/bin/python2.7, process 5946

Έλεγχος του απομακρυσμένου διακομιστή remote-server.example.com

[user@localhost ~]$ ls -lah 123.txt 
-rw-rw-r-- 1 user user 38M Oct  8 14:21 123.txt

Τα δεδομένα έρχονται, ελέγχουμε τον προβληματικό διακομιστή

[user@localhost ~]$ ls -lah /mnt/logs/
total 7.9M
drwxr-xr-x 2 user user 1.0K Oct  8 11:28 .
drwxr-xr-x 4 root     root     1.0K Oct  8 10:55 ..
-rw-rw-r-- 1 user user 7.9M Oct  8 14:17 123.txt
prw-rw-r-- 1 user user    0 Oct  8 14:22 megapipe

Τα δεδομένα αποθηκεύονται, το πρόβλημα λύνεται.

Δράττομαι αυτής της ευκαιρίας για να πω ένα γεια στους συναδέλφους μου από το Degiro.
Ακούστε podcast του Radio T.

Ολα καλά.

Ως εργασία για το σπίτι, σας προτείνω να σκεφτείτε τι θα περιγραφεί στο αρχείο διαδικασίας περιγραφής cat and sleep εάν εκτελέσετε την ακόλουθη εντολή:

[user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο