Përshkruesi i skedarit në Linux me shembuj

Një herë, në një intervistë, më pyetën se çfarë do të bënit nëse gjeni një shërbim të prishur për faktin se disku i ka mbaruar hapësira?

Natyrisht, u përgjigja se do të shihja se çfarë po bën ky vend dhe, nëse ishte e mundur, do ta pastroja vendin.
Pastaj intervistuesi pyeti, po sikur të mos ketë hapësirë ​​të lirë në ndarje, por gjithashtu nuk i shihni skedarët që do të zënë të gjithë hapësirën?

Për këtë, thashë që gjithmonë mund të shikoni përshkruesit e skedarëve të hapur, për shembull, me komandën lsof dhe të kuptoni se cili aplikacion ka zënë të gjithë hapësirën në dispozicion, dhe më pas mund të veproni sipas rrethanave, varësisht nëse nevojiten të dhënat .

Intervistuesi më ndërpreu në fjalën e fundit, duke i shtuar pyetjes së tij: "Të supozojmë se nuk na duhen të dhënat, është thjesht një regjistër i korrigjimit, por aplikacioni nuk funksionon sepse nuk mund të shkruajë korrigjimin"?

"OK," u përgjigja, "mund të çaktivizojmë korrigjimin në konfigurimin e aplikacionit dhe ta rifillojmë atë."
Intervistuesi kundërshtoi: "Jo, ne nuk mund ta rinisim aplikacionin, ne kemi ende të dhëna të rëndësishme në memorie dhe klientët e rëndësishëm janë të lidhur me vetë shërbimin, të cilin nuk mund ta detyrojmë ta rilidhëm."

"Në rregull," thashë, "nëse nuk mund ta rinisim aplikacionin dhe nuk na interesojnë të dhënat, atëherë mund ta pastrojmë këtë skedar të hapur nëpërmjet përshkruesit të skedarit, edhe nëse nuk e shohim atë në ls komandë në sistemin e skedarëve."

Intervistuesi ishte i kënaqur, por unë jo.

Pastaj mendova, pse personi që teston njohuritë e mia nuk gërmon më thellë? Por, çka nëse të dhënat janë të rëndësishme në fund të fundit? Po sikur të mos mund ta rifillojmë procesin, dhe në të njëjtën kohë ky proces shkruan në sistemin e skedarëve në një ndarje që nuk ka hapësirë ​​të lirë? Po sikur të mos humbasim jo vetëm të dhënat e shkruara tashmë, por edhe të dhënat që ky proces po shkruan ose po përpiqet të shkruajë?

Tuzik

Në fillim të karrierës sime, po përpiqesha të krijoja një aplikacion të vogël që duhej të ruante informacione rreth përdoruesve. Dhe pastaj mendova, si mund ta përputh përdoruesin me të dhënat e tij. Për shembull, unë kam Ivanov Ivan Ivanovich, dhe ai ka disa të dhëna, por si të miqësohemi me ta? Mund të theksoj drejtpërdrejt se qeni me emrin "Tuzik" i përket të njëjtit Ivan. Por, çka nëse ai ndryshon emrin e tij dhe në vend të Ivan bëhet, për shembull, Olya? Atëherë do të rezultojë se Olya Ivanovna Ivanova jonë nuk do të ketë më qen, dhe Tuzik ynë do t'i përkasë akoma Ivanit inekzistent. Baza e të dhënave ndihmoi në zgjidhjen e këtij problemi, i cili i dha çdo përdoruesi një identifikues unik (ID), dhe Tuzik im ishte i lidhur me këtë ID, i cili, në fakt, ishte vetëm një numër serial. Kështu, pronari i tuzik ishte me numër ID 2, dhe në një moment në kohë Ivan ishte nën këtë ID, dhe më pas Olya u bë nën të njëjtën ID. Problemi i njerëzimit dhe blegtorisë u zgjidh praktikisht.

Përshkruesi i skedarit

Problemi i një skedari dhe një programi që funksionon me këtë skedar është pothuajse i njëjtë me qenin dhe njeriun tonë. Supozoni se hapa një skedar të quajtur ivan.txt dhe fillova të shkruaj fjalën tuzik në të, por arrita të shkruaj vetëm shkronjën e parë "t" në skedar, dhe ky skedar u riemërua nga dikush, për shembull, në olya.txt. Por skedari është i njëjtë dhe unë ende dua t'i shkruaj asin tim. Sa herë që hapni një skedar me një thirrje sistemi hapur në çdo gjuhë programimi, unë marr një ID unike që më drejton te një skedar, ky ID është përshkruesi i skedarit. Dhe nuk ka fare rëndësi se çfarë dhe kush bën më pas me këtë skedar, ai mund të fshihet, mund të riemërohet, mund të ndryshojë pronarin e tij ose të heqë të drejtat për të lexuar dhe shkruar, unë do të kem akoma akses në të. sepse në momentin e hapjes së dosjes kisha të drejtën ta lexoja dhe/ose ta shkruaja dhe arrita të filloj të punoj me të, që do të thotë se duhet të vazhdoj ta bëj këtë.

Në Linux, biblioteka libc hap 3 skedarë përshkrues për çdo aplikacion (proces) në ekzekutim, me numrat 0,1,2. Më shumë informacion mund të gjeni në lidhjet njeri stdio и njeri stdout

  • Përshkruesi i skedarit 0 quhet STDIN dhe shoqërohet me hyrjen e aplikacionit.
  • Përshkruesi i skedarit 1 quhet STDOUT dhe përdoret nga aplikacionet e daljes siç janë komandat e printimit.
  • Përshkruesi i skedarit 2 quhet STDERR dhe përdoret nga aplikacionet për të nxjerrë mesazhe gabimi.

Nëse në programin tuaj hapni ndonjë skedar për lexim ose shkrim, atëherë me shumë mundësi do të merrni ID-në e parë falas dhe do të jetë numri 3.

Ju mund të shihni listën e përshkruesve të skedarëve për çdo proces nëse e dini PID-në e tij.

Për shembull, le të hapim një tastierë me bash dhe të shohim PID-in e procesit tonë

[user@localhost ]$ echo $$
15771

Në tastierën e dytë, ekzekutoni

[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

Ju mund të injoroni me siguri përshkruesin e skedarit me numër 255 në kuadrin e këtij artikulli, ai u hap për nevojat tuaja nga vetë bash, dhe jo nga biblioteka e lidhur.

Tani të 3 skedarët përshkrues janë të lidhur me pajisjen pseudo-terminale /dev/pts, por ne ende mund t'i manipulojmë ato, për shembull, të ekzekutojmë në tastierën e dytë

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

Dhe në tastierën e parë do të shohim

[user@localhost ]$ hello world

Redirect and Pipe

Ju mund t'i anashkaloni lehtësisht këta 3 skedarë përshkrues në çdo proces, duke përfshirë në bash, për shembull, përmes një tubi (tub) që lidh dy procese, shih

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

Ju mund ta ekzekutoni vetë këtë komandë strace -f dhe shikoni se çfarë po ndodh brenda, por unë do ta bëj shkurt.

Procesi ynë bash prind me PID 15771 analizon komandën tonë dhe kupton saktësisht se sa komanda duam të ekzekutojmë, në rastin tonë ka dy prej tyre: cat dhe gjumë. Bash e di se duhet të krijojë dy procese fëmijësh dhe t'i bashkojë ato në një tub. Në total, bash do të ketë nevojë për 2 procese fëmijësh dhe një tub.

Përpara krijimit të proceseve të fëmijëve, bash kryen një thirrje sistemi tub dhe merr përshkrues të rinj skedarësh në një buffer të përkohshëm tubash, por ky buffer ende nuk i lidh në asnjë mënyrë dy proceset tona fëmijë.

Për procesin prind, duket sikur tubi është tashmë aty, por nuk ka ende procese fëmijë:

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

Pastaj përdorni thirrjen e sistemit klon bash krijon dy procese fëmijësh, dhe tre proceset tona do të duken kështu:

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

Mos harroni se kloni klonon procesin së bashku me të gjithë përshkruesit e skedarëve, kështu që ata do të jenë të njëjtë në procesin prind dhe në ato fëmijë. Detyra e procesit prind me PID 15771 është të monitorojë proceset e fëmijës, kështu që ai vetëm pret një përgjigje nga fëmijët.

Prandaj, ai nuk ka nevojë për një tub, dhe ai mbyll përshkruesit e skedarëve me numrat 3 dhe 4.

Në procesin e parë bash fëmijë me PID 9004, thirrja e sistemit dup2, ndryshon përshkruesin tonë të skedarit STDOUT numër 1 në një përshkrues skedari që tregon një tub, në rastin tonë është numri 3. Kështu, gjithçka që procesi i parë i fëmijës me PID 9004 shkruan në STDOUT do të bjerë automatikisht në buferin e tubit.

Në procesin e dytë të fëmijës me PID 9005, bash dup2s skedarin në përshkruesin STDIN numër 0. Tani gjithçka që bash-i ynë i dytë me PID 9005 do të lexojë do të lexohet nga tubacioni.

Pas kësaj, përshkruesit e skedarëve me numrat 3 dhe 4 mbyllen gjithashtu në proceset fëmijë, pasi ato nuk përdoren më.

Unë e shpërfill qëllimisht përshkruesin e skedarit 255, ai përdoret nga brenda nga vetë bash dhe gjithashtu do të mbyllet në proceset e fëmijëve.

Më pas, në procesin e parë të fëmijës me PID 9004, bash fillon me një thirrje sistemi Exec skedari i ekzekutueshëm që kemi specifikuar në vijën e komandës, në rastin tonë është /usr/bin/cat.

Në procesin e dytë fëmijë me PID 9005, bash ekzekuton ekzekutuesin e dytë që kemi specifikuar, në rastin tonë /usr/bin/sleep.

Thirrja e sistemit exec nuk i mbyll përshkruesit e skedarëve nëse nuk janë hapur me flamurin O_CLOEXEC në kohën kur u ekzekutua thirrja e hapur. Në rastin tonë, pas ekzekutimit të skedarëve të ekzekutueshëm, të gjithë përshkruesit aktual të skedarëve do të ruhen.

Ne kontrollojmë në tastierë:

[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

Siç mund ta shihni, numri unik i tubit tonë është i njëjtë në të dy proceset. Kështu, ne kemi një lidhje midis dy proceseve të ndryshme me të njëjtin prind.

Për ata që nuk janë të njohur me thirrjet e sistemit që përdor bash, unë rekomandoj shumë të ekzekutoni komandat përmes strace dhe të shihni se çfarë ndodh brenda, për shembull, si kjo:

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

Le të kthehemi te problemi ynë me mbarimin e hapësirës në disk dhe përpjekjen për të ruajtur të dhënat pa rifilluar procesin. Le të shkruajmë një program të vogël që do të shkruajë në disk rreth 1 megabajt për sekondë. Për më tepër, nëse për ndonjë arsye nuk mund të shkruanim të dhëna në disk, thjesht do ta injorojmë këtë dhe do të përpiqemi t'i shkruajmë të dhënat përsëri në një sekondë. Në shembullin që unë jam duke përdorur Python, ju mund të përdorni çdo gjuhë tjetër programimi.

[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

Drejtoni programin dhe shikoni përshkruesit e skedarëve

[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

Siç mund ta shihni, ne kemi 3 përshkruesit tanë standardë të skedarëve dhe një tjetër që kemi hapur. Le të kontrollojmë madhësinë e skedarit:

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

të dhënat janë shkruar, ne përpiqemi të ndryshojmë të drejtat për skedarin:

[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

Ne shohim që të dhënat janë ende duke u shkruar, megjithëse përdoruesi ynë nuk ka të drejtë të shkruajë në skedar. Le të përpiqemi ta heqim atë:

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

Ku shkruhen të dhënat? Dhe a janë shkruar fare? Ne kontrollojmë:

[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)

Po, përshkruesi ynë i skedarit ekziston ende, dhe ne mund të punojmë me këtë përshkrues skedari si skedari ynë i vjetër, mund ta lexojmë, ta pastrojmë dhe ta kopjojmë.

Shikoni madhësinë e skedarit:

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

Madhësia e skedarit është 19923457. Përpjekja për të pastruar skedarin:

[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

Siç mund ta shihni, madhësia e skedarit rritet vetëm dhe trungu ynë nuk funksionoi. Le të kthehemi te dokumentacioni për thirrjen e sistemit hapur. Nëse përdorim flamurin O_APPEND kur hapim një skedar, atëherë me çdo shkrim, sistemi operativ kontrollon madhësinë e skedarit dhe shkruan të dhënat deri në fund të skedarit dhe e bën atë në mënyrë atomike. Kjo lejon që threads ose procese të shumta të shkruajnë në të njëjtin skedar. Por në kodin tonë ne nuk e përdorim këtë flamur. Ne mund të shohim një madhësi të ndryshme skedari në lsof pas trunkut vetëm nëse hapim skedarin për shkrim, që do të thotë se në kodin tonë, në vend të

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

duhet të vendosim

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

Kontrollimi me flamurin "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

dhe me flamurin "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

Programimi i një procesi tashmë të ekzekutuar

Shpesh, kur krijojnë dhe testojnë një program, programuesit përdorin korrigjues (për shembull, GDB) ose nivele të ndryshme të regjistrimit në aplikacion. Linux ofron mundësinë për të shkruar dhe ndryshuar në të vërtetë një program tashmë të ekzekutuar, të tilla si ndryshimi i vlerave të variablave, vendosja e një pikë ndërprerjeje, e kështu me radhë e kështu me radhë.

Duke iu rikthyer pyetjes origjinale në lidhje me mungesën e hapësirës në disk për të shkruar një skedar, le të përpiqemi të simulojmë problemin.

Le të krijojmë një skedar për ndarjen tonë, të cilën do ta montojmë si një disk të veçantë:

[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 ~]$

Le të krijojmë një sistem skedari:

[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 ~]$

Le të montojmë sistemin e skedarëve:

[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

Krijoni një drejtori me pronarin tonë:

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

Le të hapim skedarin për të shkruar vetëm në programin tonë:

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

Nisja

[user@localhost ]$ python openforwrite.py 

Në pritje për disa sekonda

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

Pra, morëm problemin e përshkruar në fillim të këtij artikulli. Hapësirë ​​e lirë 0, e zënë 100%.

Kujtojmë se sipas kushteve të problemit po mundohemi të regjistrojmë të dhëna shumë të rëndësishme që nuk mund të humbasin. Dhe duke e bërë këtë, ne duhet të rregullojmë shërbimin pa rifilluar procesin.

Le të themi se kemi ende hapësirë ​​​​në disk, por në një ndarje tjetër, për shembull, në / shtëpi.

Le të përpiqemi të "riprogramojmë në fluturim" kodin tonë.

Ne shikojmë PID-in e procesit tonë, i cili hante të gjithë hapësirën e diskut:

[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

Lidhja me një proces me gdb

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

Ne shikojmë përshkruesit e skedarëve të hapur:

(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

Ne shikojmë informacionin për përshkruesin e skedarit me numrin 3, i cili na intereson

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

Duke pasur parasysh se çfarë bën thirrja e sistemit Python (shih më lart se ku kemi drejtuar strace dhe kemi gjetur një thirrje të hapur), ndërsa përpunojmë kodin tonë për të hapur një skedar, ne bëjmë të njëjtën gjë vetë në emër të procesit tonë, por na duhet O_WRONLY|O_CREAT| Bitët O_TRUNC zëvendësohen me një vlerë numerike. Për ta bërë këtë, për shembull, hapni burimet e kernelit këtu dhe shikoni se cilët flamuj janë përgjegjës për çfarë

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

Ne kombinojmë të gjitha vlerat në një, marrim 00001101

Kryerja e thirrjes sonë nga gdb

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

Pra, ne morëm një përshkrues të ri skedari me numrin 4 dhe një skedar të ri të hapur në një ndarje tjetër, kontrolloni:

(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

Ne e mbajmë mend shembullin me tub - se si bash ndryshon përshkruesit e skedarëve dhe kemi mësuar tashmë thirrjen e sistemit dup2.

Përpjekja për të zëvendësuar një përshkrues skedari me një tjetër

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

kontrolloni:

(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

Mbyllni përshkruesin e skedarit 4, pasi nuk na nevojitet:

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

Dhe dilni nga 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

Kontrollimi i skedarit të ri:

[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

Siç mund ta shihni, të dhënat janë shkruar në një skedar të ri, ne kontrollojmë atë të vjetër:

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

Të dhënat nuk humbasin, aplikacioni funksionon, regjistrat shkruhen në një vend të ri.

Le t'i bëjmë gjërat pak më të vështira

Imagjinoni që të dhënat janë të rëndësishme për ne, por ne nuk kemi hapësirë ​​në disk në asnjë nga ndarjet dhe nuk mund ta lidhim diskun.

Ajo që mund të bëjmë është të ridrejtojmë të dhënat tona diku, për shembull, në një tub, dhe t'i ridrejtojmë të dhënat nga tubacioni në rrjet përmes ndonjë programi, siç është netcat.
Mund të krijojmë një tub me emër me komandën mkfifo. Do të krijojë një pseudo skedar në sistemin e skedarëve, edhe nëse nuk ka hapësirë ​​​​të lirë në të.

Rinisni aplikacionin dhe kontrolloni:

[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

Nuk ka hapësirë ​​​​në disk, por ne krijojmë me sukses një tub me emër atje:

[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

Tani duhet të mbështjellim disi të gjitha të dhënat që futen në këtë tub në një server tjetër përmes rrjetit, i njëjti netcat është i përshtatshëm për këtë.

Në serverin remote-server.example.com, ekzekutoni

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

Në serverin tonë të problemeve, ekzekutoni në një terminal të veçantë

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

Tani të gjitha të dhënat që futen në tub do të shkojnë automatikisht në stdin në netcat, i cili do t'i dërgojë ato në rrjet në portin 7777.

Gjithçka që duhet të bëjmë është të fillojmë të shkruajmë të dhënat tona në këtë tub me emër.

Ne tashmë kemi një aplikacion që funksionon:

[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

Nga të gjithë flamujt, na duhen vetëm O_WRONLY pasi skedari tashmë ekziston dhe nuk kemi nevojë ta pastrojmë atë

[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

Po kontrollon serverin në distancë remote-server.example.com

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

Të dhënat po vijnë, ne kontrollojmë serverin e problemit

[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

Të dhënat ruhen, problemi zgjidhet.

Gjej rastin të përshëndes kolegët nga Degiro.
Dëgjoni podkastet e Radio T.

Mirë për të gjithë.

Si detyrë shtëpie, unë propozoj të mendoni se çfarë do të jenë në përshkruesit e skedarëve të procesit të maceve dhe gjumit nëse ekzekutoni komandën e mëposhtme:

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

Burimi: www.habr.com

Shto një koment