Përshkruesi i skedarit në Linux me shembuj

Një herë, në një intervistë, më pyetën se çfarë do të bëja nëse zbuloja një shërbim që nuk funksionon sepse në disk ka mbaruar hapësira?

Sigurisht, u përgjigja se do të kontrolloja çfarë e zë këtë hapësirë dhe, nëse është e mundur, do ta liroja.
Pastaj intervistuesi pyeti: po sikur në particion të mos ketë hapësirë të lirë, por as të mos shohësh skedarë që e zënë gjithë atë hapësirë?

Unë iu përgjigja se gjithmonë mund të kontrollosh përshkruesit e hapur të skedarëve, për shembull me komandën lsof, dhe të kuptosh cili aplikacion e ka zënë gjithë hapësirën e disponueshme; pastaj vepron sipas situatës, në varësi të faktit nëse të dhënat janë të nevojshme apo jo.

Intervistuesi më ndërpreu te fjala e fundit, duke e plotësuar pyetjen e tij: «Supozojmë se të dhënat nuk na duhen, është thjesht një log debug, por aplikacioni nuk punon sepse nuk mund të shkruajë debug-un?»

«Në rregull», u përgjigja unë, «mund ta çaktivizojmë debug-un në konfigurimin e aplikacionit dhe ta rinisim».
Intervistuesi kundĂ«rshtoi: «Jo, aplikacionin nuk mund ta rinisim, sepse nĂ« memorie ruhen ende tĂ« dhĂ«na tĂ« rĂ«ndĂ«sishme, dhe nĂ« vetĂ« shĂ«rbimin janĂ« tĂ« lidhur klientĂ« tĂ« rĂ«ndĂ«sishĂ«m, tĂ« cilĂ«ve nuk mund t’u kĂ«rkojmĂ« tĂ« rilidhen nga e para».

«Mirë atëherë», thashë unë, «nëse nuk mund ta rinisim aplikacionin dhe të dhënat nuk kanë rëndësi për ne, atëherë mund ta pastrojmë thjesht këtë skedar të hapur përmes përshkruesit të skedarit, edhe nëse nuk e shohim me komandën ls në sistemin e skedarëve».

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

AtĂ«herĂ« mendova: pse personi qĂ« po testonte njohuritĂ« e mia nuk po thellohej mĂ« shumĂ«? Po sikur tĂ« dhĂ«nat tĂ« jenĂ« gjithsesi tĂ« rĂ«ndĂ«sishme? Po sikur tĂ« mos mund ta rinisim procesin, ndĂ«rkohĂ« qĂ« ky proces shkruan nĂ« njĂ« particion tĂ« sistemit tĂ« skedarĂ«ve ku nuk ka mĂ« hapĂ«sirĂ« tĂ« lirĂ«? Po sikur tĂ« mos mund tĂ« humbasim jo vetĂ«m tĂ« dhĂ«nat qĂ« janĂ« shkruar tashmĂ«, por as ato qĂ« ky proces po i shkruan ose po pĂ«rpiqet t’i shkruajĂ«?

Tuzik

NĂ« fillim tĂ« karrierĂ«s sime, po pĂ«rpiqesha tĂ« krijoja njĂ« aplikacion tĂ« vogĂ«l ku duhej tĂ« ruheshin tĂ« dhĂ«nat e pĂ«rdoruesve. AtĂ«herĂ« mendoja: si ta lidh pĂ«rdoruesin me tĂ« dhĂ«nat e tij? PĂ«r shembull, kam Ivanov Ivan Ivaniç dhe ai ka disa tĂ« dhĂ«na, por si t’i lidh mes tyre? Mund tĂ« tregoj drejtpĂ«rdrejt qĂ« qeni me emrin «Tuzik» i pĂ«rket pikĂ«risht kĂ«tij Ivani. Por çfarĂ« ndodh nĂ«se ai ndryshon emrin dhe nĂ« vend tĂ« Ivanit bĂ«het, pĂ«r shembull, Olya? AtĂ«herĂ« del qĂ« Olya Ivanovna Ivanova nuk do ta ketĂ« mĂ« qenin, ndĂ«rsa Tuzik ende do t’i pĂ«rkasĂ« njĂ« Ivani qĂ« nuk ekziston. KĂ«tĂ« problem e zgjidhi baza e tĂ« dhĂ«nave, e cila i jepte çdo pĂ«rdoruesi njĂ« identifikues unik (ID), dhe Tuzik-u im lidhej me kĂ«tĂ« ID, qĂ« nĂ« thelb ishte thjesht njĂ« numĂ«r rendor. KĂ«shtu, pronari i Tuzik-ut kishte ID me numrin 2: pĂ«r njĂ« periudhĂ« kohe nĂ«n kĂ«tĂ« ID ishte Ivani, e mĂ« pas nĂ«n tĂ« njĂ«jtin ID u bĂ« Olya. Problemi i njerĂ«zimit dhe i blegtorisĂ« ishte pothuajse i zgjidhur.

Përshkruesi i skedarit

Problemi i skedarit dhe i programit qĂ« punon me kĂ«tĂ« skedar Ă«shtĂ« pothuajse i njĂ«jtĂ« si ai i qenit dhe njeriut nĂ« shembullin tonĂ«. Le tĂ« supozojmĂ« se hapa njĂ« skedar me emrin ivan.txt dhe nisa tĂ« shkruaj nĂ« tĂ« fjalĂ«n tuzik, por arrita tĂ« shkruaj vetĂ«m shkronjĂ«n e parĂ« «t», dhe ndĂ«rkohĂ« dikush e riemĂ«rtoi skedarin, pĂ«r shembull nĂ« olya.txt. Por skedari mbeti i njĂ«jti, dhe unĂ« ende dua tĂ« shkruaj nĂ« tĂ« Tuzik-un tim. Sa herĂ« qĂ« hap njĂ« skedar me thirrjen e sistemit open nĂ« çdo gjuhĂ« programimi marr njĂ« ID unik qĂ« mĂ« tregon skedarin; ky ID Ă«shtĂ« pikĂ«risht pĂ«rshkruesi i skedarit. Dhe nuk ka aspak rĂ«ndĂ«si çfarĂ« dhe kush bĂ«n me kĂ«tĂ« skedar mĂ« pas: mund ta fshijnĂ«, mund ta riemĂ«rtojnĂ«, mund t’i ndryshojnĂ« pronarin ose t’ia heqin tĂ« drejtat e leximit dhe shkrimit, unĂ« gjithsesi do tĂ« kem qasje nĂ« tĂ«, sepse nĂ« momentin kur e hapa skedarin kisha tĂ« drejta pĂ«r lexim dhe/ose shkrim dhe tashmĂ« kisha nisur tĂ« punoja me tĂ«, prandaj duhet tĂ« jem nĂ« gjendje tĂ« vazhdoj ta bĂ«j kĂ«tĂ«.

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

  • PĂ«rshkruesi i skedarit 0 quhet STDIN dhe lidhet me hyrjen e tĂ« dhĂ«nave te aplikacioni
  • File descriptor 1 quhet STDOUT dhe pĂ«rdoret nga aplikacionet pĂ«r nxjerrjen e tĂ« dhĂ«nave, pĂ«r shembull nga komandat print
  • File descriptor 2 quhet STDERR dhe pĂ«rdoret nga aplikacionet pĂ«r tĂ« nxjerrĂ« tĂ« dhĂ«na qĂ« sinjalizojnĂ« njĂ« gabim

Nëse në programin tuaj hapni ndonjë skedar për lexim ose shkrim, ka shumë të ngjarë të merrni ID-në e parë të lirë, dhe ai do të jetë numri 3.

Listën e file descriptor-ve mund ta shihni te çdo proces, nëse e dini PID-në e tij.

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

[user@localhost ]$ echo $$
15771

Në konzolën e dytë ekzekutojmë

[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

File descriptor-in me numrin 255 mund ta injoroni pa problem në kuadër të këtij artikulli; ai është hapur për nevojat e vetë bash-it, jo nga biblioteka e linkuar.

Tani tĂ« 3 file descriptor-at janĂ« tĂ« lidhur me pajisjen e pseudoterminalit /dev/pts, por prapĂ«seprapĂ« mund t’i manipulojmĂ«, pĂ«r shembull le tĂ« ekzekutojmĂ« nĂ« konzolĂ«n e dytĂ«

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

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

[user@localhost ]$ hello world

Redirect dhe Pipe

KĂ«ta 3 file descriptor-a mund t’i ripĂ«rcaktoni lehtĂ«sisht nĂ« çdo proces, pĂ«rfshirĂ« edhe nĂ« bash, pĂ«r shembull pĂ«rmes njĂ« pipe qĂ« lidh dy procese; le ta shohim

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

Këtë komandë mund ta ekzekutoni vetë me strace -f dhe të shihni çfarë ndodh brenda, por unë do ta shpjegoj shkurtimisht.

Procesi ynĂ« prind bash me PID 15771 e analizon komandĂ«n tonĂ« dhe kupton saktĂ«sisht sa komanda duam tĂ« ekzekutojmĂ«; nĂ« rastin tonĂ« janĂ« dy: cat dhe sleep. Bash e di qĂ« duhet tĂ« krijojĂ« dy procese bijĂ« dhe t’i lidhĂ« me njĂ« pipe. Pra, bash-it do t’i nevojiten 2 procese bijĂ« dhe njĂ« pipe.

Para krijimit të proceseve bijë, bash ekzekuton thirrjen e sistemit pipe dhe merr file descriptor-a të rinj për buffer-in e përkohshëm të pipe, por ky buffer ende nuk i lidh në asnjë mënyrë dy proceset tona bijë.

Për procesin prind kjo duket sikur pipe tashmë ekziston, ndërsa proceset bijë ende nuk janë krijuar:

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

Më pas, me ndihmën e thirrjes së sistemit clone bash krijon dy procese fëmijë, 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

Të mos harrojmë se clone e klonon procesin së bashku me të gjithë file descriptor-at, prandaj në procesin prind dhe në proceset fëmijë ato do të jenë të njëjta. Detyra e procesit prind me PID 15771 është të mbikëqyrë proceset fëmijë, prandaj ai thjesht pret përgjigje nga proceset fëmijë.

Për rrjedhojë, pipe nuk i nevojitet dhe ai mbyll file descriptor-at me numrat 3 dhe 4.

Në procesin e parë fëmijë bash me PID 9004, me anë të thirrjes së sistemit dup2, ndryshon file descriptor-in e STDOUT me numrin 1 në një file descriptor që tregon te pipe, në rastin tonë ky është numri 3. Kështu, gjithçka që procesi i parë fëmijë me PID 9004 do të shkruajë në STDOUT, do të përfundojë automatikisht në buffer-in e pipe.

Në procesin e dytë fëmijë me PID 9005, bash ndryshon me ndihmën e dup2 file descriptor-in e STDIN me numrin 0. Tani gjithçka që bash-i ynë i dytë me PID 9005 do të lexojë, do ta lexojë nga pipe.

Pas kësaj, në proceset fëmijë mbyllen gjithashtu file descriptor-at me numrat 3 dhe 4, pasi nuk përdoren më.

File descriptor-in 255 e injoroj qëllimisht; ai përdoret për nevojat e brendshme të vetë bash-it dhe në proceset fëmijë gjithashtu do të mbyllet.

Më tej, në procesin e parë fëmijë me PID 9004, bash nis me anë të thirrjes së sistemit exec skedarin e ekzekutueshëm që kemi treguar në rreshtin e komandës, në rastin tonë është /usr/bin/cat.

Në procesin e dytë fëmijë me PID 9005, bash nis skedarin e dytë të ekzekutueshëm që kemi treguar, në rastin tonë është /usr/bin/sleep.

Thirrja e sistemit exec nuk i mbyll deskriptorët e skedarëve nëse ata nuk janë hapur me flamurin O_CLOEXEC gjatë ekzekutimit të thirrjes open. Në rastin tonë, pas nisjes së skedarëve të ekzekutueshëm, të gjithë deskriptorët aktualë të skedarëve do të ruhen.

E kontrollojmë në konsolë:

[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 pipe-it tonë përputhet në të dy proceset. Kështu, 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, rekomandoj fuqimisht t’i ekzekutoni komandat pĂ«rmes strace dhe tĂ« shikoni çfarĂ« ndodh brenda, pĂ«r shembull kĂ«shtu:

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

T’i kthehemi problemit tonĂ« me mungesĂ«n e hapĂ«sirĂ«s nĂ« disk dhe pĂ«rpjekjes pĂ«r tĂ« ruajtur tĂ« dhĂ«nat pa rinisur procesin. Do tĂ« shkruajmĂ« njĂ« program tĂ« vogĂ«l qĂ« do tĂ« shkruajĂ« nĂ« disk rreth 1 megabajt nĂ« sekondĂ«. NĂ«se pĂ«r çfarĂ«do arsye nuk arrijmĂ« t’i shkruajmĂ« tĂ« dhĂ«nat nĂ« disk, thjesht do ta injorojmĂ« kĂ«tĂ« dhe do tĂ« provojmĂ« t’i shkruajmĂ« sĂ«rish pas njĂ« sekonde. NĂ« shembull pĂ«rdor Python, por 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

Le ta nisim programin dhe të shohim deskriptorët 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ç e shohim, kemi 3 file descriptor-ët standardë dhe edhe një tjetër që e 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 po shkruhen, le të provojmë të ndryshojmë të drejtat e skedarit:

[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

Shohim që të dhënat vazhdojnë të shkruhen, megjithëse përdoruesi ynë nuk ka të drejtë të shkruajë në skedar. Le të provojmë ta fshijmë:

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

Ku po shkruhen të dhënat? Dhe a po shkruhen fare? Le ta 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, file descriptor-i ynë ekziston ende dhe ne mund të punojmë me të si me skedarin tonë të vjetër: mund ta lexojmë, ta zbrazim dhe ta kopjojmë.

Le të shohim 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. Le të provojmë ta zbrazim 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ç e shohim, madhësia e skedarit vetëm po rritet dhe truncate ynë nuk funksionoi. Le t'i referohemi dokumentacionit për sistem call open. Nëse gjatë hapjes së skedarit përdorim flamurin O_APPEND, atëherë në çdo shkrim sistemi operativ kontrollon madhësinë e skedarit dhe i shkruan të dhënat në fund të tij, duke e bërë këtë në mënyrë atomike. Kjo u lejon disa thread-eve ose proceseve të shkruajnë në të njëjtin skedar. Por në kodin tonë nuk e përdorim këtë flamur. Mund të shohim një madhësi tjetër të skedarit në lsof pas truncate vetëm nëse e hapim skedarin për shtim të të dhënave, 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:

Po e kontrollojmë 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

Programojmë një proces që tashmë është në ekzekutim

Shpesh programuesit, gjatë krijimit dhe testimit të një programi, përdorin debugger-a (për shembull GDB) ose nivele të ndryshme logimi në aplikacion. Linux ofron mundësinë që praktikisht të shkruani dhe të ndryshoni një program që është tashmë në ekzekutim, për shembull të ndryshoni vlerat e variablave, të vendosni breakpoint etj.

Duke iu rikthyer pyetjes fillestare për mungesën e hapësirës në disk gjatë shkrimit të një skedari, le të përpiqemi ta simulojmë problemin.

Le të krijojmë një skedar për ndarjen tonë, të cilin do ta montojmë si disk më vete:

[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ë sistemin e skedarëve:

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

Krijojmë një direktori me pronarin tonë:

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

Le ta hapim skedarin vetëm për shkrim në programin tonë:

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

Po e nisim

[user@localhost ]$ python openforwrite.py 

Presim 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ësira e lirë është 0, e zëna 100%.

Mbajmë mend se, sipas kushteve të detyrës, po përpiqemi të shkruajmë të dhëna shumë të rëndësishme që nuk mund të humben. Dhe njëkohësisht duhet të rregullojmë shërbimin pa e rinisur procesin.

Le të supozojmë se gjithsesi kemi hapësirë në disk, por në një ndarje tjetër, për shembull në /home.

Le të përpiqemi ta «riprogramojmë në fluturim» kodin tonë.

Shikojmë PID-in e procesit tonë, i cili e ka mbushur të gjithë hapësirën në disk:

[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

Lidhemi me procesin përmes gdb

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

Shikojmë deskriptorët e hapur të skedarëve:

(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

Shikojmë informacionin për deskriptorin 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 cilĂ«n thirrje sistemi bĂ«n Python (shihni mĂ« sipĂ«r, ku ekzekutuam strace dhe gjetĂ«m thirrjen open), gjatĂ« pĂ«rpunimit tĂ« kodit tonĂ« pĂ«r hapjen e skedarit, bĂ«jmĂ« tĂ« njĂ«jtĂ«n gjĂ« vetĂ« nĂ« emĂ«r tĂ« procesit tonĂ«, por bitĂ«t O_WRONLY|O_CREAT|O_TRUNC duhet t’i zĂ«vendĂ«sojmĂ« me njĂ« vlerĂ« numerike. PĂ«r kĂ«tĂ« hapim burimet e kernel-it, pĂ«r shembull kĂ«tu dhe shohim se cilĂ«t flamuj pĂ«r çfarĂ« shĂ«rbejnĂ«

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

I bashkojmë të gjitha vlerat në një dhe marrim 00001101

Ekzekutojmë thirrjen tonë nga gdb

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

Pra, morëm një file descriptor të ri me numrin 4 dhe një skedar të ri të hapur në një ndarje tjetër, e kontrollojmë:

(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

MbajmĂ« mend shembullin me pipe — si bash ndryshon file descriptor-at, dhe tashmĂ« e kemi mĂ«suar thirrjen e sistemit dup2.

Provojmë të zëvendësojmë një file descriptor me një tjetër

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

Po kontrollojmë:

(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

Mbyllim file descriptor-in 4, sepse nuk na duhet:

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

Dhe dalim 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

Kontrollojmë skedarin e 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ç e shohim, të dhënat po shkruhen në skedarin e ri, kontrollojmë të vjetrin:

[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 kanë humbur, aplikacioni po funksionon, log-et po shkruhen në vendin e ri.

Le ta ndërlikojmë pak detyrën

Le të supozojmë se të dhënat janë të rëndësishme për ne, por nuk kemi hapësirë në disk në asnjërën prej ndarjeve dhe nuk mund të lidhim një disk tjetër.

Ajo qĂ« mund tĂ« bĂ«jmĂ« Ă«shtĂ« t’i ridrejtojmĂ« diku tĂ« dhĂ«nat tona, pĂ«r shembull nĂ« njĂ« pipe, dhe mĂ« pas tĂ« dhĂ«nat nga pipe t’i ridrejtojmĂ« nĂ« rrjet pĂ«rmes ndonjĂ« programi, pĂ«r shembull netcat.
Mund të krijojmë një pipe me emër me komandën mkfifo. Ajo do të krijojë një pseudofajll në sistemin e skedarëve, edhe nëse aty nuk ka hapësirë të lirë.

Rinisim aplikacionin dhe kontrollojmë:

[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 më hapësirë në disk, por ne arrijmë ta krijojmë me sukses një pipe të emërtuar aty:

[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ë ridrejtojmë të gjitha të dhënat që futen në këtë pipe drejt një serveri tjetër përmes rrjetit; për këtë na shërben po ai netcat.

Në serverin remote-server.example.com ekzekutojmë

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

Në serverin tonë problematik ekzekutojmë në një terminal të veçantë

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

Tani të gjitha të dhënat që do të futen në pipe do të kalojnë automatikisht në stdin të netcat, i cili do t'i dërgojë në rrjet drejt portës 7777.

Gjithçka që na mbetet të bëjmë është të fillojmë të shkruajmë të dhënat tona në këtë pipe të emërtuar.

Ne tashmë kemi një aplikacion në ekzekutim:

[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 duhet vetëm O_WRONLY, sepse skedari tashmë ekziston dhe nuk kemi nevojë ta pastrojmë

[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
Një sesion debugimi është aktiv.

    Inferior 1 [process 5946] do të shkëputet.

TĂ« dilet gjithsesi? (y or n) y
Po shkëputet nga programi: /usr/bin/python2.7, process 5946

Po kontrollojmë serverin e largët 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 kalojnë, po kontrollojmë serverin problematik

[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 u ruajtën, problemi u zgjidh.

Meqë ra fjala, u dërgoj përshëndetje kolegëve nga kompania Degiro.
Dëgjoni podcast-et e Radio-T.

Gjithë të mirat.

Si detyrë shtëpie, ju propozoj të mendoni se çfarë do të ndodhë me file descriptor-ët e proceseve cat dhe sleep nëse ekzekutohet kjo komandë:

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

Burimi: habr.com

Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster