Einmal wurde ich in einem Interview gefragt, was ich tun würde, wenn ich einen nicht funktionierenden Dienst entdecke, weil der Speicherplatz auf der Festplatte erschöpft ist.
Natürlich habe ich geantwortet, dass ich mir anschaue, was diesen Speicherplatz belegt, und wenn möglich, den Speicherplatz bereinigen würde.
Daraufhin fragte der Interviewer, was passiert, wenn auf dem Laufwerk kein freier Speicherplatz vorhanden ist, aber ich auch keine Dateien sehe, die den gesamten Speicherplatz belegen.
Darauf habe ich gesagt, dass man immer die offenen Dateideskriptoren überprüfen kann, zum Beispiel mit dem Befehl lsof, um herauszufinden, welche Anwendung den gesamten verfügbaren Speicherplatz belegt, und dann je nach Situation handeln kann, je nachdem, ob die Daten benötigt werden.
Der Interviewer unterbrach mich beim letzten Wort und ergänzte seine Frage: „Angenommen, die Daten sind uns nicht wichtig, es handelt sich nur um ein Debug-Log, aber die Anwendung funktioniert nicht, weil sie das Debug-Log nicht schreiben kann?“
„Okay“, antwortete ich, „wir können das Debugging in der Konfiguration der Anwendung deaktivieren und sie neu starten.“
Der Interviewer widersprach: „Nein, wir können die Anwendung nicht neu starten, wir haben noch wichtige Daten im Speicher und wichtige Kunden sind mit dem Dienst verbunden, die wir nicht zwingen können, sich erneut zu verbinden.“
„Nun gut“, sagte ich, „wenn wir die Anwendung nicht neu starten können und die Daten für uns nicht wichtig sind, können wir einfach diese offene Datei über den Dateideskriptor bereinigen, auch wenn wir sie in der Ausgabe des Befehls ls im Dateisystem nicht sehen.“
Der Interviewer war zufrieden, ich jedoch nicht.
Dann dachte ich, warum geht die Person, die meine Kenntnisse prüft, nicht tiefer? Was ist, wenn die Daten doch wichtig sind? Was ist, wenn wir den Prozess nicht neu starten können und gleichzeitig dieser Prozess auf das Dateisystem in ein Verzeichnis schreibt, in dem kein freier Speicherplatz mehr vorhanden ist? Was ist, wenn wir nicht nur die bereits geschriebenen Daten verlieren können, sondern auch die Daten, die dieser Prozess schreibt oder zu schreiben versucht?
Tuzik
Am Anfang meiner Karriere versuchte ich, eine kleine Anwendung zu erstellen, in der Informationen über Benutzer gespeichert werden sollten. Damals dachte ich darüber nach, wie ich den Benutzer seinen Daten zuordnen konnte. Zum Beispiel habe ich Ivanov Ivan Ivanovich, und er hat irgendwelche Daten, aber wie kann ich sie zusammenbringen? Ich kann direkt angeben, dass ein Hund namens „Tuzik“ diesem Ivan gehört. Aber was, wenn er seinen Namen ändert und anstelle von Ivan plötzlich Olya heißt? Dann würde unsere Olya Ivanovna Ivanova keinen Hund mehr haben, während unser Tuzik immer noch einem nicht existierenden Ivan gehört. Dieses Problem löste eine Datenbank, die jedem Benutzer eine eindeutige Identifikation (ID) gab, und mein Tuzik wurde mit dieser ID verknüpft, die im Grunde genommen einfach eine fortlaufende Nummer war. So hatte der Besitzer von Tuzik die ID mit der Nummer 2, und zu einem bestimmten Zeitpunkt war das ein Ivan, und dann wurde es unter derselben ID eine Olya. Das Problem der Menschheit und der Viehzucht war fast gelöst.
Dateibesitzer
Das Problem von Datei und Programm, das mit dieser Datei arbeitet, ist ähnlich wie das unserer Hund und Mensch. Angenommen, ich habe eine Datei mit dem Namen ivan.txt geöffnet und begann, das Wort tuzik darin zu schreiben, konnte aber nur den ersten Buchstaben „t“ in die Datei schreiben, und diese Datei wurde von jemandem umbenannt, beispielsweise in olya.txt. Aber die Datei blieb dieselbe, und ich möchte immer noch meinen Tuzik dort speichern. Jedes Mal, wenn ich die Datei mit einem Systemaufruf in einer beliebigen Programmiersprache öffne, erhalte ich eine eindeutige ID, die mich auf die Datei verweist; diese ID ist der Dateibesitzer. Es spielt überhaupt keine Rolle, was und wer mit dieser Datei später passiert, sie kann gelöscht, umbenannt, ihr Eigentümer kann geändert werden oder die Lese- und Schreibrechte können entzogen werden, ich werde trotzdem Zugriff darauf haben, denn zum Zeitpunkt des Öffnens der Datei hatte ich die Erlaubnis, sie zu lesen und/oder zu schreiben, und ich habe bereits begonnen, damit zu arbeiten, was bedeutet, dass ich das auch weiterhin tun sollte.
In Linux öffnet die libc-Bibliothek für jede laufende Anwendung (Prozess) 3 Dateibesitzer mit den Nummern 0, 1, 2. Weitere Informationen finden Sie unter den Links und
- Der Dateibesitzer 0 heißt STDIN und ist mit der Eingabedaten des Programms assoziiert.
- Die Datei Beschreibung 1 wird STDOUT genannt und von Anwendungen zur Ausgabe von Daten verwendet, beispielsweise von print-Befehlen.
- Die Datei Beschreibung 2 wird STDERR genannt und von Anwendungen zur Ausgabe von Daten verwendet, die Fehler melden.
Wenn Sie in Ihrem Programm eine Datei zum Lesen oder Schreiben öffnen, erhalten Sie höchstwahrscheinlich die erste freie ID und das wird die Nummer 3 sein.
Die Liste der Dateibeschreibungen kann bei jedem Prozess angezeigt werden, wenn Sie seine PID kennen.
Öffnen wir zum Beispiel eine Konsole mit bash und schauen wir uns die PID unseres Prozesses an.
[user@localhost ]$ echo $$
15771
In der zweiten Konsole starten wir
[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user 0 Okt 7 15:42 .
dr-xr-xr-x 9 user user 0 Okt 7 15:42 ..
lrwx------ 1 user user 64 Okt 7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Okt 7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Okt 7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Okt 7 15:42 255 -> /dev/pts/21
Die Dateibeschreibung mit der Nummer 255 können Sie in diesem Artikel getrost ignorieren, sie wurde schon für eigene Bedürfnisse von bash und nicht von einer verknüpften Bibliothek geöffnet.
Zurzeit sind alle 3 File Deskriptoren mit einem Pseudo-Terminal verbunden. , aber wir können trotzdem damit arbeiten, beispielsweise indem wir in der zweiten Konsole starten
[user@localhost ]$ echo "hello world" > /proc/15771/fd/0
Und in der ersten Konsole sehen wir
[user@localhost ]$ hello world
Redirect und Pipe
Sie können diese 3 Dateibeschreibungen in jedem Prozess, einschließlich bash, leicht überschreiben, beispielsweise durch eine Pipe, die zwei Prozesse verbindet, sehen wir uns das an.
[user@localhost ]$ cat /dev/zero | sleep 10000
Sie können diesen Befehl selbst mit strace -f ausführen und sehen, was im Inneren passiert, aber ich werde es kurz erklären.
Unser übergeordneter Prozess bash mit PID 15771 analysiert unseren Befehl und versteht, wie viele Befehle wir tatsächlich ausführen wollen; in unserem Fall sind es zwei: cat und sleep. Bash weiß, dass es zwei untergeordnete Prozesse erstellen und diese durch eine Pipe verbinden muss. Insgesamt benötigt bash 2 untergeordnete Prozesse und eine Pipe.
Vor der Erstellung der untergeordneten Prozesse führt bash einen Systemaufruf aus und erhält neue Dateibeschreibungen für den temporären Pufferspeicher der Pipe, aber dieser Puffer verbindet noch nicht unsere beiden untergeordneten Prozesse.
Für den übergeordneten Prozess sieht es so aus, als gäbe es die Pipe bereits, während die untergeordneten Prozesse noch nicht existieren:
PID command
15771 bash
lrwx------ 1 user user 64 Okt 7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Okt 7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Okt 7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Okt 7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Okt 7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Okt 7 15:42 255 -> /dev/pts/21
Dann erstellt der Systemaufruf bash zwei Kindprozesse, und unsere drei Prozesse sehen folgendermaßen aus:
PID Befehl
15771 bash
lrwx------ 1 user user 64 7. Okt 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 7. Okt 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 7. Okt 15:42 255 -> /dev/pts/21
PID Befehl
9004 bash
lrwx------ 1 user user 64 7. Okt 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 7. Okt 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 7. Okt 15:57 255 -> /dev/pts/21
PID Befehl
9005 bash
lrwx------ 1 user user 64 7. Okt 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 7. Okt 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 7. Okt 15:57 255 -> /dev/pts/21
Vergessen wir nicht, dass clone den Prozess zusammen mit allen Dateideskriptoren klont, weshalb sie im Elternprozess und in den Kindprozessen gleich sein werden. Die Aufgabe des Elternprozesses mit der PID 15771 besteht darin, die Kindprozesse zu überwachen, daher wartet er einfach auf eine Rückmeldung von den Kindprozessen.
Daher benötigt er kein pipe und schließt die Dateideskriptoren mit den Nummern 3 und 4.
Im ersten Kindprozess bash mit der PID 9004 ändert der Systemaufruf , unseren STDOUT-Dateideskriptor mit der Nummer 1 auf den Dateideskriptor, der auf das pipe verweist, in unserem Fall ist das die Nummer 3. Alles, was der erste Kindprozess mit der PID 9004 in STDOUT schreibt, gelangt somit automatisch in den Buffer des pipe.
Im zweiten Kindprozess mit der PID 9005 ändert bash mit dup2 den Dateideskriptor STDIN mit der Nummer 0. Jetzt wird alles, was unser zweiter bash mit der PID 9005 liest, aus dem pipe gelesen.
Danach werden auch in den Kindprozessen die Dateideskriptoren mit den Nummern 3 und 4 geschlossen, da sie nicht mehr benötigt werden.
Den Dateideskriptor 255 ignoriere ich absichtlich, er wird für interne Zwecke von bash verwendet und wird auch in den Kindprozessen geschlossen.
Anschließend startet der erste Kindprozess mit der PID 9004 bash mithilfe des Systemaufrufs die ausführbare Datei, die wir in der Befehlszeile angegeben haben, in unserem Fall ist das /usr/bin/cat.
Im zweiten Kindprozess mit der PID 9005 startet bash die zweite ausführbare Datei, die wir angegeben haben, in unserem Fall ist das /usr/bin/sleep.
Der systematische Aufruf exec schließt keine Dateideskriptoren, wenn sie während des Aufrufs open nicht mit dem Flag O_CLOEXEC geöffnet wurden. In unserem Fall bleiben nach dem Start der ausführbaren Dateien alle aktuellen Dateideskriptoren erhalten.
Überprüfen wir in der Konsole:
[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
Wie Sie sehen können, stimmt die eindeutige Nummer unseres Pipes in beiden Prozessen überein. Somit haben wir eine Verbindung zwischen zwei verschiedenen Prozessen mit einem gemeinsamen Elternprozess.
Für diejenigen, die nicht mit den Systemaufrufen, die Bash verwendet, vertraut sind, empfehle ich dringend, die Befehle über strace auszuführen und zu beobachten, was im Inneren passiert, zum Beispiel so:
strace -s 1024 -f bash -c "ls | grep hello"
Kommen wir zurück zu unserem Problem mit dem begrenzten Speicherplatz auf der Festplatte und dem Versuch, Daten zu speichern, ohne den Prozess neu zu starten. Lassen Sie uns ein kleines Programm schreiben, das ungefähr 1 Megabyte pro Sekunde auf die Festplatte schreibt. Wenn wir aus irgendeinem Grund keine Daten auf die Festplatte schreiben können, ignorieren wir dies einfach und versuchen es in einer Sekunde erneut. In diesem Beispiel verwende ich Python; Sie können eine andere Programmiersprache verwenden.
[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
Lassen Sie uns das Programm starten und die Dateideskriptoren überprüfen.
[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 Okt 7 16:29 .
dr-xr-xr-x 9 user user 0 Okt 7 16:29 ..
lrwx------ 1 user user 64 Okt 7 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 Okt 7 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 Okt 7 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 Okt 7 16:29 3 -> /home/user/123.txt
Wie wir sehen, haben wir unsere 3 Standard-Dateideskriptoren und einen weiteren, den wir geöffnet haben. Überprüfen wir die Größe der Datei:
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 117M Okt 7 16:30 123.txt
Daten werden geschrieben, versuchen wir die Berechtigungen für die Datei zu ändern:
[user@localhost ]$ sudo chown root: 123.txt
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 root root 168M Okt 7 16:31 123.txt
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 root root 172M Okt 7 16:31 123.txt
Wir sehen, dass Daten weiterhin geschrieben werden, obwohl unser Benutzer keine Berechtigung hat, in die Datei zu schreiben. Versuchen wir, sie zu löschen:
[user@localhost ]$ sudo rm 123.txt
[user@localhost ]$ ls 123.txt
ls: cannot access 123.txt: No such file or directory
Wohin werden die Daten geschrieben? Und werden sie überhaupt geschrieben? Überprüfen wir:
[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user 0 Okt 7 16:29 .
dr-xr-xr-x 9 user user 0 Okt 7 16:29 ..
lrwx------ 1 user user 64 Okt 7 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 Okt 7 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 Okt 7 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 Okt 7 16:29 3 -> /home/user/123.txt (deleted)
Ja, unser Dateideskriptor existiert weiterhin, und wir können mit diesem Dateideskriptor so arbeiten, als ob es unsere alte Datei wäre. Wir können sie lesen, leeren und kopieren.
Sehen wir uns die Größe der Datei an:
[user@localhost ]$ lsof | grep 123.txt
python 31083 user 3w REG 8,5 19923457 2621522 /home/user/123.txt
Die Dateigröße beträgt 19923457. Versuchen wir, die Datei zu leeren:
[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
Wie wir sehen, wächst die Dateigröße nur, und unser Truncate hat nicht funktioniert. Lassen Sie uns die Dokumentation zu dem Systemaufruf konsultieren. Wenn wir beim Öffnen der Datei das Flag O_APPEND verwenden, überprüft das Betriebssystem bei jedem Schreibvorgang die Dateigröße und schreibt die Daten ans Ende der Datei, und zwar atomar. Dies ermöglicht es mehreren Threads oder Prozessen, in dieselbe Datei zu schreiben. Aber in unserem Code verwenden wir dieses Flag nicht. Wir können eine andere Dateigröße in lsof nach dem Truncate nur sehen, wenn wir die Datei zum Anhängen öffnen, was bedeutet, dass wir in unserem Code anstelle von
with open("123.txt", "w") as f:
wir
with open("123.txt", "a") as f:
Überprüfen wir das mit dem "w"-Flag
[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
und mit dem Flag "a"
[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
Wir programmieren einen bereits laufenden Prozess
Oft verwenden Programmierer beim Erstellen und Testen von Programmen Debugger (zum Beispiel GDB) oder verschiedene Logging-Ebenen in der Anwendung. Linux bietet die Möglichkeit, ein bereits laufendes Programm tatsächlich zu schreiben und zu ändern, zum Beispiel Variablenwerte zu ändern, Breakpoints zu setzen usw.
Um auf die ursprüngliche Frage nach dem Platzmangel auf der Festplatte beim Schreiben einer Datei zurückzukommen, versuchen wir, das Problem zu simulieren.
Erstellen wir eine Datei für unseren Abschnitt, den wir als separates Laufwerk einbinden:
[user@localhost ~]$ dd if=/dev/zero of=~/tempfile_for_article.dd bs=1M count=10
10+0 Datensätze ein
10+0 Datensätze aus
10485760 Bytes (10 MB) kopiert, 0.00525929 s, 2.0 GB/s
[user@localhost ~]$
Erstellen wir ein Dateisystem:
[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd
mke2fs 1.42.9 (28-Dec-2013)
/home/user/tempfile_for_article.dd ist kein Block-Sondergerät.
Trotzdem fortfahren? (y,n) y
...
Superblöcke und Dateisystembuchhaltungsinformationen schreiben: erfolgt
[user@localhost ~]$
Binden wir das Dateisystem ein:
[user@localhost ~]$ sudo mount ~/tempfile_for_article.dd /mnt/
[sudo] Passwort für user:
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 172K 7.9M 3% /mnt
Erstellen wir ein Verzeichnis mit unserem Eigentümer:
[user@localhost ~]$ sudo mkdir /mnt/logs
[user@localhost ~]$ sudo chown user: /mnt/logs
Öffnen wir die Datei nur zum Schreiben in unserem Programm:
with open("/mnt/logs/123.txt", "w") as f:
Starten
[user@localhost ]$ python openforwrite.py
Warten wir ein paar Sekunden
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 8.0M 0 100% /mnt
Wir haben also das Problem, das zu Beginn dieses Artikels beschrieben wurde. Freier Speicher 0, belegter Speicher 100%.
Wir erinnern uns, dass wir versuchen, sehr wichtige Daten zu speichern, die nicht verloren gehen dürfen. Außerdem müssen wir den Dienst reparieren, ohne den Prozess neu zu starten.
Angenommen, wir haben dennoch Platz auf der Festplatte, aber in einem anderen Abschnitt, zum Beispiel in /home.
Versuchen wir, unseren Code „live“ umzuprogrammieren.
Schauen wir uns die PID unseres Prozesses an, der den ganzen Speicherplatz auf der Festplatte belegt:
[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
Verbinden wir uns mit dem Prozess über gdb
[user@localhost ~]$ gdb -p 10078
...
(gdb)
Schauen wir uns die offenen Dateideskriptoren an:
(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
Schauen wir uns die Informationen über den Dateideskriptor mit der Nummer 3 an, der uns interessiert
(gdb) shell cat /proc/10078/fdinfo/3
pos: 8189952
flags: 0100001
mnt_id: 482
Wenn wir uns daran erinnern, welcher Systemaufruf Python macht (siehe oben, wo wir strace ausgeführt und den Aufruf open gefunden haben), behandeln wir unseren Code zum Öffnen der Datei, indem wir dasselbe selbst im Namen unseres Prozesses tun, aber die Bits O_WRONLY|O_CREAT|O_TRUNC müssen wir durch einen numerischen Wert ersetzen. Dafür öffnen wir die Kernelquellen, zum Beispiel und sehen uns an, wofür die Flags verantwortlich sind
#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000
Wir fassen alle Werte zusammen und erhalten 00001101
Wir führen unseren Aufruf aus gdb aus
(gdb) call open("/home/user/123.txt", 00001101,0666)
$1 = 4
Also haben wir einen neuen Dateideskriptor mit der Nummer 4 und eine neue geöffnete Datei auf einem anderen Verzeichnis, überprüfen wir:
(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user 0 Okt 8 11:06 .
dr-xr-xr-x 9 user user 0 Okt 8 11:06 ..
lrwx------ 1 user user 64 Okt 8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Okt 8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Okt 8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Okt 8 11:09 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Okt 8 11:15 4 -> /home/user/123.txt
Wir erinnern uns an das Beispiel mit dem Pipe — wie bash die Dateideskriptoren ändert, und haben bereits den Systemaufruf dup2 gelernt.
Wir versuchen, einen Dateideskriptor durch einen anderen zu ersetzen
(gdb) call dup2(4,3)
$2 = 3
Überprüfen:
(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user 0 Okt 8 11:06 .
dr-xr-xr-x 9 user user 0 Okt 8 11:06 ..
lrwx------ 1 user user 64 Okt 8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Okt 8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Okt 8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Okt 8 11:09 3 -> /home/user/123.txt
l-wx------ 1 user user 64 Okt 8 11:15 4 -> /home/user/123.txt
Wir schließen den Dateideskriptor 4, da wir ihn nicht benötigen:
(gdb) call close (4)
$1 = 0
Und wir verlassen gdb
(gdb) quit
Eine Debugging-Sitzung ist aktiv.
Inferior 1 [Prozess 10078] wird getrennt.
Trotzdem beenden? (y oder n) y
Trennung vom Programm: /usr/bin/python2.7, Prozess 10078
Wir überprüfen die neue Datei:
[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 5.1M Okt 8 11:18 /home/user/123.txt
[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 7.1M Okt 8 11:18 /home/user/123.txt
Wie wir sehen, werden die Daten in die neue Datei geschrieben, überprüfen wir die alte:
[user@localhost ~]$ ls -lah /mnt/logs/123.txt
-rw-rw-r-- 1 user user 7.9M Okt 8 11:08 /mnt/logs/123.txt
Die Daten sind nicht verloren, die Anwendung funktioniert, die Logs werden an den neuen Speicherort geschrieben.
Lassen Sie uns die Aufgabe etwas komplizierter machen
Angenommen, die Daten sind uns wichtig, aber wir haben keinen Platz auf der Festplatte in einem der Verzeichnisse und können keine Festplatte anschließen.
Was wir tun können, ist, unsere Daten irgendwo umzuleiten, zum Beispiel in ein Pipe, und die Daten aus dem Pipe wiederum über ein Programm wie netcat ins Netzwerk umzuleiten.
Wir können ein benanntes Pipe mit dem Befehl mkfifo erstellen. Es wird eine Pseudodatei im Dateisystem erstellen, selbst wenn darauf kein Platz ist.
Wir starten die Anwendung neu und überprüfen:
[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
Es ist kein Speicherplatz auf der Festplatte vorhanden, aber wir erstellen dort erfolgreich ein benanntes Pipe:
[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
Jetzt müssen wir die Daten, die in diese Pipe gelangen, irgendwie über das Netzwerk an einen anderen Server weiterleiten. Dazu eignet sich erneut netcat.
Auf dem Server remote-server.example.com starten wir
[user@localhost ~]$ nc -l 7777 > 123.txt
Auf unserem problematischen Server starten wir in einem separaten Terminal
[user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe
Jetzt gelangen alle Daten, die in die Pipe fließen, automatisch auf stdin von netcat, das sie über das Netzwerk an Port 7777 sendet.
Alles, was wir nun noch tun müssen, ist, unsere Daten in dieses benannte Pipe zu schreiben.
Wir haben bereits eine laufende Anwendung:
[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
Von allen Flags benötigen wir nur O_WRONLY, da die Datei bereits existiert und wir sie nicht leeren müssen.
[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 8. Okt 11:27 .
dr-xr-xr-x 9 user user 0 8. Okt 11:27 ..
lrwx------ 1 user user 64 8. Okt 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 8. Okt 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 8. Okt 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 8. Okt 11:28 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 8. Okt 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 8. Okt 11:27 .
dr-xr-xr-x 9 user user 0 8. Okt 11:27 ..
lrwx------ 1 user user 64 8. Okt 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 8. Okt 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 8. Okt 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 8. Okt 11:28 3 -> /mnt/logs/megapipe
l-wx------ 1 user user 64 8. Okt 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 8. Okt 11:27 .
dr-xr-xr-x 9 user user 0 8. Okt 11:27 ..
lrwx------ 1 user user 64 8. Okt 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 8. Okt 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 8. Okt 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 8. Okt 11:28 3 -> /mnt/logs/megapipe
(gdb) quit
Eine Debugging-Sitzung ist aktiv.
Inferior 1 [Prozess 5946] wird getrennt.
Möchten Sie trotzdem beenden? (y oder n) y
Trennen von Programm: /usr/bin/python2.7, Prozess 5946
Überprüfen des Remote-Servers remote-server.example.com
[user@localhost ~]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 38M 8. Okt 14:21 123.txt
Die Daten fließen, wir überprüfen den problematischen Server
[user@localhost ~]$ ls -lah /mnt/logs/
total 7.9M
drwxr-xr-x 2 user user 1.0K 8. Okt 11:28 .
drwxr-xr-x 4 root root 1.0K 8. Okt 10:55 ..
-rw-rw-r-- 1 user user 7.9M 8. Okt 14:17 123.txt
prw-rw-r-- 1 user user 0 8. Okt 14:22 megapipe
Die Daten wurden gespeichert, das Problem ist gelöst.
Ich nutze die Gelegenheit, um meinen Kollegen von Degiro Grüße zu senden.
Hört die Podcasts von Radio-T.
Allen alles Gute.
Als Hausaufgabe schlage ich vor, darüber nachzudenken, was in den Dateideskriptoren der Prozesse cat und sleep stehen wird, wenn man diesen Befehl ausführt:
[user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000
Quelle: habr.com
