उदाहरण के साथ लिनक्स में फ़ाइल डिस्क्रिप्टर

एक बार, एक साक्षात्कार में, मुझसे पूछा गया था कि यदि डिस्क में स्थान समाप्त हो जाने के कारण आपको कोई टूटी हुई सेवा मिले तो आप क्या करेंगे?

बेशक, मैंने जवाब दिया कि मैं देखूंगा कि यह जगह क्या कर रही है और यदि संभव हो तो मैं इस जगह को साफ कर दूंगा।
तब साक्षात्कारकर्ता ने पूछा, क्या होगा यदि विभाजन पर कोई खाली जगह नहीं है, लेकिन आपको वे फ़ाइलें भी नहीं दिख रही हैं जो सारी जगह ले लेंगी?

इसके लिए, मैंने कहा कि आप हमेशा खुले फ़ाइल डिस्क्रिप्टर को देख सकते हैं, उदाहरण के लिए, lsof कमांड के साथ और समझ सकते हैं कि किस एप्लिकेशन ने सभी उपलब्ध स्थान ले लिया है, और फिर आप परिस्थितियों के अनुसार कार्य कर सकते हैं, यह इस पर निर्भर करता है कि डेटा की आवश्यकता है या नहीं .

साक्षात्कारकर्ता ने आखिरी शब्द में मुझे टोकते हुए अपने प्रश्न में जोड़ा: "मान लीजिए कि हमें डेटा की आवश्यकता नहीं है, यह सिर्फ एक डिबग लॉग है, लेकिन एप्लिकेशन डाउन है क्योंकि यह डिबग नहीं लिख सकता है"?

"ठीक है," मैंने उत्तर दिया, "हम एप्लिकेशन कॉन्फ़िगरेशन में डिबगिंग बंद कर सकते हैं और इसे पुनः आरंभ कर सकते हैं।"
साक्षात्कारकर्ता ने आपत्ति जताई: "नहीं, हम एप्लिकेशन को पुनरारंभ नहीं कर सकते, हमारे पास अभी भी मेमोरी में महत्वपूर्ण डेटा है, और महत्वपूर्ण क्लाइंट सेवा से ही जुड़े हुए हैं, जिन्हें हम पुनः कनेक्ट करने के लिए बाध्य नहीं कर सकते।"

"ठीक है," मैंने कहा, "अगर हम एप्लिकेशन को पुनरारंभ नहीं कर सकते हैं और हमें डेटा की परवाह नहीं है, तो हम फ़ाइल डिस्क्रिप्टर के माध्यम से इस खुली फ़ाइल को साफ़ कर सकते हैं, भले ही हम इसे एलएस में न देखें फ़ाइल सिस्टम पर कमांड।"

साक्षात्कारकर्ता संतुष्ट था, लेकिन मैं नहीं था।

फिर मैंने सोचा, मेरे ज्ञान का परीक्षण करने वाला व्यक्ति गहराई तक क्यों नहीं जाता? लेकिन क्या होगा यदि डेटा आख़िरकार महत्वपूर्ण है? क्या होगा यदि हम प्रक्रिया को पुनः आरंभ नहीं कर सकते हैं, और साथ ही यह प्रक्रिया फ़ाइल सिस्टम को एक ऐसे विभाजन पर लिखती है जिसमें कोई खाली स्थान नहीं है? क्या होगा यदि हम न केवल पहले से लिखे गए डेटा को खो सकते हैं, बल्कि उस डेटा को भी खो सकते हैं जिसे यह प्रक्रिया लिख ​​रही है या लिखने का प्रयास कर रही है?

बड़ी टोपी

अपने करियर की शुरुआत में, मैं एक छोटा एप्लिकेशन बनाने की कोशिश कर रहा था जिसमें उपयोगकर्ताओं के बारे में जानकारी संग्रहीत करने की आवश्यकता थी। और फिर मैंने सोचा, मैं उपयोगकर्ता का उसके डेटा से मिलान कैसे कर सकता हूं। उदाहरण के लिए, मेरे पास इवानोव इवान इवानोविच है, और उसके पास कुछ डेटा है, लेकिन उनसे दोस्ती कैसे करें? मैं सीधे बता सकता हूं कि "तुज़िक" नाम का कुत्ता इसी इवान का है। लेकिन क्या होगा अगर वह अपना नाम बदल ले और इवान की जगह, उदाहरण के लिए, ओलेया बन जाए? तब यह पता चलेगा कि हमारी ओलेआ इवानोव्ना इवानोवा के पास अब कोई कुत्ता नहीं होगा, और हमारा तुज़िक अभी भी अस्तित्वहीन इवान का होगा। डेटाबेस ने इस समस्या को हल करने में मदद की, जिसने प्रत्येक उपयोगकर्ता को एक विशिष्ट पहचानकर्ता (आईडी) दिया, और मेरा तुज़िक इस आईडी से जुड़ा हुआ था, जो वास्तव में, सिर्फ एक सीरियल नंबर था। इस प्रकार, तुज़िक का मालिक आईडी नंबर 2 के साथ था, और किसी समय इवान इस आईडी के तहत था, और फिर ओला उसी आईडी के तहत बन गया। मानव जाति एवं पशुपालन की समस्या व्यावहारिक रूप से हल हो गई।

फ़ाइल विवरणक

इस फ़ाइल के साथ काम करने वाली फ़ाइल और प्रोग्राम की समस्या लगभग हमारे कुत्ते और इंसान जैसी ही है। मान लीजिए कि मैंने ivan.txt नाम की एक फ़ाइल खोली और उसमें तुज़िक शब्द लिखना शुरू किया, लेकिन फ़ाइल में केवल पहला अक्षर "t" लिखने में कामयाब रहा, और इस फ़ाइल का नाम किसी ने बदल दिया, उदाहरण के लिए, olya.txt। लेकिन फ़ाइल वही है और मैं अभी भी उस पर अपना ऐस लिखना चाहता हूँ। हर बार जब आप सिस्टम कॉल के साथ कोई फ़ाइल खोलते हैं खुला किसी भी प्रोग्रामिंग भाषा में, मुझे एक अद्वितीय आईडी मिलती है जो मुझे एक फ़ाइल की ओर इंगित करती है, यह आईडी फ़ाइल डिस्क्रिप्टर है। और इससे कोई फर्क नहीं पड़ता कि इस फ़ाइल के साथ आगे क्या और कौन करता है, इसे हटाया जा सकता है, इसका नाम बदला जा सकता है, यह इसके मालिक को बदल सकता है या पढ़ने और लिखने के अधिकार छीन सकता है, मेरे पास अभी भी इस तक पहुंच होगी, क्योंकि फ़ाइल खोलने के समय मेरे पास इसे पढ़ने और/या लिखने का अधिकार था और मैं इसके साथ काम करना शुरू करने में कामयाब रहा, जिसका मतलब है कि मुझे ऐसा करना जारी रखना होगा।

लिनक्स पर, libc लाइब्रेरी प्रत्येक चल रहे एप्लिकेशन (प्रक्रिया) के लिए 3 डिस्क्रिप्टर फ़ाइलें खोलती है, जिनकी संख्या 0,1,2 है। अधिक जानकारी आप लिंक पर पा सकते हैं मैन स्टूडियो и आदमी stdout

  • फ़ाइल डिस्क्रिप्टर 0 को STDIN कहा जाता है और यह एप्लिकेशन इनपुट से जुड़ा होता है।
  • फ़ाइल डिस्क्रिप्टर 1 को STDOUT कहा जाता है और इसका उपयोग प्रिंट कमांड जैसे आउटपुट अनुप्रयोगों द्वारा किया जाता है।
  • फ़ाइल डिस्क्रिप्टर 2 को STDERR नाम दिया गया है और इसका उपयोग अनुप्रयोगों द्वारा त्रुटि संदेशों को आउटपुट करने के लिए किया जाता है।

यदि आप अपने प्रोग्राम में कोई फ़ाइल पढ़ने या लिखने के लिए खोलते हैं, तो सबसे अधिक संभावना है कि आपको पहली निःशुल्क आईडी मिलेगी और वह नंबर 3 होगी।

यदि आप किसी भी प्रक्रिया की पीआईडी ​​जानते हैं तो आप उसके फ़ाइल डिस्क्रिप्टर की सूची देख सकते हैं।

उदाहरण के लिए, आइए बैश के साथ एक कंसोल खोलें और हमारी प्रक्रिया का पीआईडी ​​देखें

[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 के साथ फ़ाइल डिस्क्रिप्टर को सुरक्षित रूप से अनदेखा कर सकते हैं, इसे आपकी आवश्यकताओं के लिए बैश द्वारा ही खोला गया था, न कि लिंक की गई लाइब्रेरी द्वारा।

अब सभी 3 डिस्क्रिप्टर फ़ाइलें छद्म-टर्मिनल डिवाइस से संबद्ध हैं /देव/पीटीएस, लेकिन हम अभी भी उनमें हेरफेर कर सकते हैं, उदाहरण के लिए, दूसरे कंसोल में चला सकते हैं

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

और पहले कंसोल में हम देखेंगे

[user@localhost ]$ hello world

रीडायरेक्ट और पाइप

आप बैश सहित किसी भी प्रक्रिया में इन 3 डिस्क्रिप्टर फ़ाइलों को आसानी से ओवरराइड कर सकते हैं, उदाहरण के लिए, दो प्रक्रियाओं को जोड़ने वाले पाइप (पाइप) के माध्यम से, देखें

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

आप इस कमांड को स्वयं चला सकते हैं स्ट्रेस -एफ और देखो अंदर क्या चल रहा है, लेकिन मैं इसे संक्षेप में बताऊंगा।

पीआईडी ​​15771 के साथ हमारी मूल बैश प्रक्रिया हमारे कमांड को पार्स करती है और समझती है कि हम कितने कमांड चलाना चाहते हैं, हमारे मामले में उनमें से दो हैं: बिल्ली और नींद। बैश जानता है कि उसे दो चाइल्ड प्रोसेस बनाने और उन्हें एक पाइप में मर्ज करने की आवश्यकता है। कुल मिलाकर, बैश को 2 चाइल्ड प्रोसेस और एक पाइप की आवश्यकता होगी।

चाइल्ड प्रोसेस बनाने से पहले, बैश एक सिस्टम कॉल चलाता है पाइप और एक अस्थायी पाइप बफ़र पर नए फ़ाइल डिस्क्रिप्टर प्राप्त करता है, लेकिन यह बफ़र अभी तक हमारी दो चाइल्ड प्रक्रियाओं को किसी भी तरह से कनेक्ट नहीं करता है।

मूल प्रक्रिया के लिए, ऐसा लगता है कि पाइप पहले से ही मौजूद है, लेकिन अभी तक कोई चाइल्ड प्रक्रिया नहीं है:

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

यह न भूलें कि क्लोन सभी फ़ाइल डिस्क्रिप्टरों के साथ प्रक्रिया को भी क्लोन करता है, इसलिए वे मूल प्रक्रिया और चाइल्ड प्रक्रिया में समान होंगे। पीआईडी ​​15771 के साथ मूल प्रक्रिया का कार्य बाल प्रक्रियाओं की निगरानी करना है, इसलिए यह केवल बच्चों की प्रतिक्रिया की प्रतीक्षा करता है।

इसलिए, उसे पाइप की आवश्यकता नहीं है, और वह फ़ाइल डिस्क्रिप्टर को संख्या 3 और 4 के साथ बंद कर देता है।

पीआईडी ​​9004 के साथ पहली बैश चाइल्ड प्रक्रिया में, सिस्टम कॉल डुप2, हमारे STDOUT फ़ाइल डिस्क्रिप्टर नंबर 1 को एक पाइप की ओर इंगित करने वाले फ़ाइल डिस्क्रिप्टर में बदल देता है, हमारे मामले में यह नंबर 3 है। इस प्रकार, PID 9004 के साथ पहली चाइल्ड प्रक्रिया STDOUT को लिखने वाली हर चीज स्वचालित रूप से पाइप बफर में आ जाएगी।

पीआईडी ​​9005 के साथ दूसरी चाइल्ड प्रक्रिया में, फ़ाइल को एसटीडीआईएन डिस्क्रिप्टर नंबर 2 पर बैश डुप्लिकेट करें। अब पीआईडी ​​0 के साथ हमारा दूसरा बैश जो कुछ भी पढ़ेगा वह पाइप से पढ़ा जाएगा।

उसके बाद, संख्या 3 और 4 वाले फ़ाइल डिस्क्रिप्टर भी चाइल्ड प्रक्रियाओं में बंद कर दिए जाते हैं, क्योंकि अब उनका उपयोग नहीं किया जाता है।

मैं जानबूझकर फ़ाइल डिस्क्रिप्टर 255 को अनदेखा करता हूं, इसका उपयोग आंतरिक रूप से बैश द्वारा ही किया जाता है और चाइल्ड प्रक्रियाओं में भी बंद कर दिया जाएगा।

अगला, पीआईडी ​​9004 के साथ पहली चाइल्ड प्रक्रिया में, बैश एक सिस्टम कॉल के साथ शुरू होता है कार्यकारी निष्पादन योग्य फ़ाइल जिसे हमने कमांड लाइन पर निर्दिष्ट किया है, हमारे मामले में यह /usr/bin/cat है।

पीआईडी ​​9005 के साथ दूसरी चाइल्ड प्रक्रिया में, बैश हमारे द्वारा निर्दिष्ट दूसरे निष्पादन योग्य को चलाता है, हमारे मामले में /usr/bin/sleep।

निष्पादन सिस्टम कॉल फ़ाइल डिस्क्रिप्टर को बंद नहीं करता है जब तक कि ओपन कॉल निष्पादित होने के समय उन्हें 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

जैसा कि आप देख सकते हैं, हमारे पाइप की अद्वितीय संख्या दोनों प्रक्रियाओं में समान है। इस प्रकार, हमारे पास एक ही मूल के साथ दो अलग-अलग प्रक्रियाओं के बीच संबंध है।

उन लोगों के लिए जो बैश द्वारा उपयोग किए जाने वाले सिस्टम कॉल से परिचित नहीं हैं, मैं अत्यधिक स्ट्रेस के माध्यम से कमांड चलाने की सलाह देता हूं और देखता हूं कि अंदर क्या होता है, उदाहरण के लिए, इस तरह:

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

आइए डिस्क स्थान ख़त्म होने और प्रक्रिया को पुनः आरंभ किए बिना डेटा सहेजने की कोशिश से जुड़ी अपनी समस्या पर वापस जाएँ। आइए एक छोटा प्रोग्राम लिखें जो डिस्क पर लगभग 1 मेगाबाइट प्रति सेकंड लिखेगा। इसके अलावा, यदि किसी कारण से हम डिस्क पर डेटा नहीं लिख पाते हैं, तो हम इसे अनदेखा कर देंगे और एक सेकंड में डेटा को फिर से लिखने का प्रयास करेंगे। उदाहरण में मैं पायथन का उपयोग कर रहा हूं, आप किसी अन्य प्रोग्रामिंग भाषा का उपयोग कर सकते हैं।

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

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

प्रोग्राम चलाएँ और फ़ाइल डिस्क्रिप्टर देखें

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

जैसा कि आप देख सकते हैं, हमारे पास हमारे 3 मानक फ़ाइल डिस्क्रिप्टर हैं और एक अन्य जिसे हमने खोला है। आइए फ़ाइल का आकार जांचें:

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

डेटा लिखा है, हम फ़ाइल के अधिकार बदलने का प्रयास करते हैं:

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

हम देखते हैं कि डेटा अभी भी लिखा जा रहा है, हालाँकि हमारे उपयोगकर्ता को फ़ाइल पर लिखने का अधिकार नहीं है। आइए इसे हटाने का प्रयास करें:

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

डेटा कहाँ लिखा गया है? और क्या वे बिल्कुल लिखे गए हैं? हम जाँच:

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

हाँ, हमारा फ़ाइल डिस्क्रिप्टर अभी भी मौजूद है, और हम अपनी पुरानी फ़ाइल की तरह इस फ़ाइल डिस्क्रिप्टर के साथ काम कर सकते हैं, हम इसे पढ़ सकते हैं, साफ़ कर सकते हैं और कॉपी कर सकते हैं।

फ़ाइल का आकार देखें:

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

फ़ाइल का आकार 19923457 है। फ़ाइल को साफ़ करने का प्रयास किया जा रहा है:

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

जैसा कि आप देख सकते हैं, फ़ाइल का आकार केवल बढ़ता है और हमारा ट्रंक काम नहीं करता है। आइए सिस्टम कॉल पर दस्तावेज़ीकरण की ओर मुड़ें खुला. यदि हम फ़ाइल खोलते समय O_APPEND ध्वज का उपयोग करते हैं, तो प्रत्येक लेखन के साथ, ऑपरेटिंग सिस्टम फ़ाइल आकार की जांच करता है और फ़ाइल के बिल्कुल अंत तक डेटा लिखता है, और यह परमाणु रूप से करता है। यह एक ही फ़ाइल में एकाधिक थ्रेड या प्रक्रियाओं को लिखने की अनुमति देता है। लेकिन हमारे कोड में हम इस ध्वज का उपयोग नहीं करते हैं। हम ट्रंक के बाद lsof में एक अलग फ़ाइल आकार तभी देख सकते हैं जब हम फ़ाइल को लिखने के लिए खोलते हैं, जिसका अर्थ है कि हमारे कोड में, के बजाय

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

प्रोग्रामिंग एक पहले से चल रही प्रक्रिया है

अक्सर, प्रोग्राम बनाते और परीक्षण करते समय, प्रोग्रामर डिबगर्स (उदाहरण के लिए, जीडीबी) या एप्लिकेशन में लॉगिंग के विभिन्न स्तरों का उपयोग करते हैं। लिनक्स वास्तव में पहले से चल रहे प्रोग्राम को लिखने और बदलने की क्षमता प्रदान करता है, जैसे कि वेरिएबल्स के मूल्यों को बदलना, ब्रेकपॉइंट सेट करना, इत्यादि।

फ़ाइल लिखने के लिए डिस्क स्थान की कमी के बारे में मूल प्रश्न पर लौटते हुए, आइए समस्या का अनुकरण करने का प्रयास करें।

आइए अपने विभाजन के लिए एक फ़ाइल बनाएं, जिसे हम एक अलग ड्राइव के रूप में माउंट करेंगे:

[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%।

हमें याद है कि समस्या की स्थितियों के अनुसार, हम बहुत महत्वपूर्ण डेटा रिकॉर्ड करने का प्रयास कर रहे हैं जिसे खोया नहीं जा सकता। और ऐसा करने में, हमें प्रक्रिया को पुनरारंभ किए बिना सेवा को ठीक करने की आवश्यकता है।

मान लीजिए कि हमारे पास अभी भी डिस्क स्थान है, लेकिन एक अलग विभाजन में, उदाहरण के लिए, / होम में।

आइए अपने कोड को "फ़्लाई पर रीप्रोग्राम" करने का प्रयास करें।

हम अपनी प्रक्रिया की पीआईडी ​​​​को देखते हैं, जिसने सभी डिस्क स्थान को खा लिया:

[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

जीडीबी के साथ एक प्रक्रिया से जुड़ना

[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

इस बात को ध्यान में रखते हुए कि पाइथॉन कौन सा सिस्टम कॉल करता है (ऊपर देखें जहां हमने स्ट्रेस चलाया और एक खुली कॉल पाई), फ़ाइल खोलने के लिए अपने कोड को संसाधित करते समय, हम अपनी प्रक्रिया की ओर से स्वयं भी ऐसा करते हैं, लेकिन हमें O_WRONLY|O_CREAT| O_TRUNC बिट्स को संख्यात्मक मान से प्रतिस्थापित किया जाता है। ऐसा करने के लिए, उदाहरण के लिए, कर्नेल स्रोत खोलें यहां और देखें कि कौन से झंडे किसके लिए ज़िम्मेदार हैं

#O_WRONLY 00000001 को परिभाषित करें
#O_CREAT 00000100 को परिभाषित करें
#O_TRUNC 00001000 को परिभाषित करें

हम सभी मानों को एक में जोड़ते हैं, हमें 00001101 मिलता है

जीडीबी से हमारी कॉल चल रही है

(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

हमें पाइप वाला उदाहरण याद है - बैश फ़ाइल डिस्क्रिप्टर को कैसे बदलता है, और हम पहले ही डुप2 सिस्टम कॉल सीख चुके हैं।

एक फ़ाइल डिस्क्रिप्टर को दूसरे से बदलने का प्रयास किया जा रहा है

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

डेटा नष्ट नहीं होता है, एप्लिकेशन काम करता है, लॉग एक नए स्थान पर लिखे जाते हैं।

आइए चीजों को थोड़ा और कठिन बनाएं

कल्पना करें कि डेटा हमारे लिए महत्वपूर्ण है, लेकिन हमारे पास किसी भी विभाजन में डिस्क स्थान नहीं है और हम डिस्क को कनेक्ट नहीं कर सकते हैं।

हम अपने डेटा को कहीं रीडायरेक्ट कर सकते हैं, उदाहरण के लिए, एक पाइप पर, और पाइप से डेटा को किसी प्रोग्राम, जैसे नेटकैट के माध्यम से नेटवर्क पर रीडायरेक्ट कर सकते हैं।
हम 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

अब हमें इस पाइप में आने वाले सभी डेटा को किसी तरह नेटवर्क के माध्यम से दूसरे सर्वर पर लपेटने की जरूरत है, वही नेटकैट इसके लिए उपयुक्त है।

रिमोट-server.example.com सर्वर पर, चलाएँ

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

हमारे समस्या सर्वर पर, एक अलग टर्मिनल में चलाएँ

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

अब पाइप में आने वाला सारा डेटा स्वचालित रूप से नेटकैट में stdin पर चला जाएगा, जो इसे पोर्ट 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

रिमोट सर्वर रिमोट-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

डेटा सहेजा गया है, समस्या हल हो गई है।

मैं इस अवसर पर डेगिरो के अपने सहकर्मियों को नमस्ते कहता हूँ।
रेडियो-टी पॉडकास्ट सुनें।

सभी के लिए अच्छा है।

होमवर्क के रूप में, मैं यह सोचने का प्रस्ताव करता हूं कि यदि आप निम्नलिखित कमांड चलाते हैं तो कैट एंड स्लीप प्रक्रिया के फ़ाइल डिस्क्रिप्टर में क्या होगा:

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

स्रोत: www.habr.com

एक टिप्पणी जोड़ें