Ֆայլի նկարագրիչ Linux-ում օրինակներով

Մի անգամ հարցազրույցի ժամանակ ինձ հարցրին՝ ի՞նչ եք անելու, եթե հայտնաբերեք, որ ծառայությունը չի աշխատում սկավառակի տարածքի սպառվելու պատճառով:

Ես, իհարկե, պատասխանեցի, որ կտեսնեմ, թե ինչ է զբաղեցրել այս տեղը, և հնարավորության դեպքում կմաքրեմ այդ տեղը։
Այնուհետև հարցազրուցավարը հարցրեց՝ ի՞նչ անել, եթե բաժանման վրա ազատ տարածք չկա, բայց դուք նաև չեք տեսնում որևէ ֆայլ, որը կզբաղեցնի ամբողջ տարածքը:

Սրա համար ես ասացի, որ դուք միշտ կարող եք դիտել բաց ֆայլերի նկարագրիչները, օրինակ՝ lsof հրամանով, և հասկանալ, թե որ հավելվածն է զբաղեցրել ողջ հասանելի տարածքը, այնուհետև կարող եք գործել ըստ հանգամանքների՝ կախված տվյալների անհրաժեշտությունից: .

Հարցազրուցավարն ընդհատեց ինձ վերջին բառի ժամանակ՝ ավելացնելով իր հարցին. «Ենթադրենք, մեզ պետք չեն տվյալները, դա պարզապես վրիպազերծման մատյան է, բայց հավելվածը չի աշխատում, քանի որ չի կարող վրիպազերծում գրել»:

«Լավ,- պատասխանեցի ես,- մենք կարող ենք անջատել վրիպազերծումը հավելվածի կազմաձևում և վերագործարկել այն»:
Հարցազրուցավարն առարկեց. «Ոչ, մենք չենք կարող վերագործարկել հավելվածը, մենք դեռ կարևոր տվյալներ ունենք պահված հիշողության մեջ, և կարևոր հաճախորդները միացված են հենց ծառայությանը, որը մենք չենք կարող ստիպել նորից միացնել»:

«Լավ», - ասացի ես, - եթե մենք չենք կարող վերագործարկել հավելվածը, և տվյալները մեզ համար կարևոր չեն, ապա մենք կարող ենք պարզապես մաքրել այս բաց ֆայլը ֆայլի նկարագրիչի միջոցով, նույնիսկ եթե այն չենք տեսնում ls հրամանում: ֆայլային համակարգի վրա»:

Հարցազրուցավարը գոհ էր, իսկ ես՝ ոչ:

Հետո մտածեցի՝ ինչո՞ւ իմ գիտելիքները ստուգողն ավելի խորը չի փորում: Բայց ի՞նչ, եթե տվյալներն ի վերջո կարևոր են: Ի՞նչ անել, եթե մենք չկարողանանք վերագործարկել գործընթացը, և գործընթացը գրի ֆայլային համակարգին մի բաժանման վրա, որը ազատ տարածություն չունի: Իսկ եթե մենք չկարողանանք կորցնել ոչ միայն արդեն գրված տվյալները, այլև այն տվյալները, որոնք այս գործընթացը գրում կամ փորձում է գրել:

Տուզիկ

Իմ կարիերայի սկզբում ես փորձեցի ստեղծել մի փոքրիկ հավելված, որը պետք է պահպաներ օգտատերերի տեղեկատվությունը: Եվ հետո ես մտածեցի, թե ինչպես կարող եմ համապատասխանեցնել օգտվողին իր տվյալների հետ: Օրինակ, ես ունեմ Իվանով Իվան Իվանովիչ, և նա որոշակի տեղեկություններ ունի, բայց ինչպե՞ս կարող եմ ընկերանալ նրանց հետ: Կարող եմ ուղղակիորեն նշել, որ «Տուզիկ» անունով շունը հենց այս Իվանին է պատկանում։ Բայց եթե նա փոխի իր անունը և Իվանի փոխարեն դառնա, օրինակ, Օլյա՞ն։ Հետո կպարզվի, որ մեր Օլյա Իվանովնա Իվանովան այլեւս շուն չի ունենա, իսկ մեր Տուզիկը դեռ գոյություն չունեցող Իվանին է պատկանում։ Տվյալների բազան, որը յուրաքանչյուր օգտվողին տալիս էր եզակի նույնացուցիչ (ID) օգնեց լուծել այս խնդիրը, և իմ Tuzik-ը կապված էր այս ID-ի հետ, որն իրականում ընդամենը սերիական համար էր: Այսպիսով, էսի սեփականատերը ուներ ID համար 2, և ժամանակի ինչ-որ պահի Իվանը գտնվում էր այս ID-ի տակ, իսկ հետո Օլյան դարձավ նույն ID-ի տակ: Մարդկության ու անասնապահության խնդիրը գործնականում լուծվեց։

Ֆայլի նկարագրիչ

Ֆայլի և այս ֆայլի հետ աշխատող ծրագրի խնդիրը մոտավորապես նույնն է, ինչ մեր շան և մարդու խնդիրը: Ենթադրենք, ես բացեցի ivan.txt անունով մի ֆայլ և սկսեցի դրա մեջ գրել tuzik բառը, բայց միայն հաջողվեց գրել առաջին «t» տառը ֆայլում, և այս ֆայլը ինչ-որ մեկի կողմից վերանվանվեց, օրինակ, olya.txt: Բայց ֆայլը մնում է նույնը, և ես դեռ ուզում եմ դրանում գրանցել իմ ace-ը: Ամեն անգամ, երբ ֆայլը բացվում է համակարգային զանգի միջոցով բացել Ցանկացած ծրագրավորման լեզվով ես ստանում եմ եզակի ID, որն ինձ ուղղորդում է դեպի ֆայլ, այս ID-ն ֆայլի նկարագրիչն է: Եվ ամենևին էլ կարևոր չէ, թե հաջորդը ինչ և ով է անում այս ֆայլը, այն կարելի է ջնջել, անվանափոխել, սեփականատերը փոխել, կամ կարդալու և գրելու իրավունքը խլել, ես դեռ մուտք կունենամ։ դրան, քանի որ ֆայլը բացելու պահին ես իրավունք ունեի կարդալու և/կամ գրելու այն և կարողացա սկսել աշխատել դրա հետ, ինչը նշանակում է, որ ես պետք է շարունակեմ դա անել:

Linux-ում libc գրադարանը յուրաքանչյուր գործող հավելվածի (գործընթացի) համար բացում է 3 նկարագրող ֆայլ՝ 0,1,2 համարներով։ Լրացուցիչ տեղեկություններ կարելի է գտնել հղումներով մարդ stdio и մարդ stdout

  • Ֆայլի նկարագրիչը 0 կոչվում է STDIN և կապված է հավելվածի մուտքագրման հետ
  • Ֆայլի նկարագրիչը 1 կոչվում է STDOUT և օգտագործվում է հավելվածների կողմից տվյալների ելքի համար, ինչպիսիք են տպման հրամանները
  • Ֆայլի նկարագրիչ 2-ը կոչվում է STDERR և օգտագործվում է հավելվածների կողմից՝ սխալի հաղորդագրություններ ուղարկելու համար:

Եթե ​​ձեր ծրագրում բացեք որևէ ֆայլ՝ կարդալու կամ գրելու համար, ապա, ամենայն հավանականությամբ, կստանաք առաջին անվճար ID-ն և այն կլինի համարը 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

Այնուհետև օգտագործելով համակարգային զանգը clone 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 թվով ֆայլերի նկարագրիչները:

PID 9004-ով առաջին երեխայի բախման գործընթացում համակարգային զանգը dup2, փոխում է մեր STDOUT ֆայլի նկարագրիչը 1-ին դեպի խողովակը մատնանշող ֆայլի նկարագրիչ, մեր դեպքում դա թիվ 3 է: Այսպիսով, այն ամենը, ինչ PID 9004-ով առաջին երեխայի մշակումը գրում է STDOUT-ին, ինքնաբերաբար կհայտնվի խողովակի բուֆերում:

PID 9005-ով երկրորդ երեխայի գործընթացում bash-ը օգտագործում է dup2՝ փոխելու ֆայլի նկարագրիչ STDIN համարը 0: Այժմ այն ​​ամենը, ինչ կկարդա մեր երկրորդ bash-ը PID 9005-ով, կկարդացվի խողովակից:

Դրանից հետո 3 և 4 համարներով ֆայլերի նկարագրիչները նույնպես փակվում են երեխայի գործընթացներում, քանի որ դրանք այլևս չեն օգտագործվում:

Ես միտումնավոր անտեսում եմ ֆայլի նկարագրիչ 255-ը, այն օգտագործվում է ներքին նպատակների համար բաշի կողմից և կփակվի նաև երեխայի գործընթացներում:

Հաջորդը, 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 մեգաբայթ վայրկյանում։ Ավելին, եթե ինչ-ինչ պատճառներով մենք չկարողացանք տվյալներ գրել սկավառակի վրա, մենք պարզապես անտեսելու ենք դա և կփորձենք նորից գրել տվյալները մեկ վայրկյանում: Օրինակում, որ ես օգտագործում եմ 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 դրոշը ֆայլ բացելիս, ապա յուրաքանչյուր գրելու հետ օպերացիոն համակարգը ստուգում է ֆայլի չափը և գրում տվյալներ ֆայլի ամենավերջում և դա անում է ատոմային եղանակով: Սա թույլ է տալիս մի քանի թելերի կամ գործընթացների գրել նույն ֆայլում: Բայց մեր օրենսգրքում մենք չենք օգտագործում այս դրոշը: «

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-ում:

Փորձենք «վերածրագրավորել» մեր կոդը:

Եկեք նայենք մեր գործընթացի 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| O_TRUNC բիթերը փոխարինվում են թվային արժեքով: Դա անելու համար, օրինակ, բացեք միջուկի աղբյուրները այստեղ ու տեսեք, թե որ դրոշներն ինչի համար են պատասխանատու

#սահմանել O_WRONLY 00000001
#սահմանել O_CREAT 00000100
#սահմանել 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

Մենք հիշում ենք խողովակի օրինակը՝ ինչպես է 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

Ոչ մի տվյալ չի կորչում, հավելվածն աշխատում է, տեղեկամատյանները գրվում են նոր վայրում:

Եկեք մի փոքր բարդացնենք խնդիրը

Եկեք պատկերացնենք, որ տվյալները մեզ համար կարևոր են, բայց մենք բաժանմունքներից որևէ մեկում սկավառակի տարածություն չունենք և չենք կարող միացնել սկավառակը:

Այն, ինչ մենք կարող ենք անել, մեր տվյալները վերահղելն է ինչ-որ տեղ, օրինակ՝ խողովակին, և իր հերթին տվյալները խողովակից դեպի ցանց վերահղել ինչ-որ ծրագրի միջոցով, օրինակ՝ 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

Տվյալները պահպանված են, խնդիրը լուծված է։

Օգտվելով առիթից՝ ողջունում եմ Դեժիրոյի իմ գործընկերներին:
Լսեք Radio T podcasts:

Ամենայն բարիք։

Որպես տնային աշխատանք, ես առաջարկում եմ ձեզ մտածել այն մասին, թե ինչ կլինի գործընթացի ֆայլի նկարագրիչներ cat and sleep, եթե գործարկեք հետևյալ հրամանը.

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

Source: www.habr.com

Добавить комментарий