توصیف کننده فایل در لینوکس با مثال

یک بار در حین مصاحبه از من پرسیده شد که اگر متوجه شوید سرویسی به دلیل خالی شدن فضای دیسک کار نمی کند، چه خواهید کرد؟

البته من پاسخ دادم که ببینم این مکان چه چیزی را اشغال کرده و در صورت امکان محل را تمیز می کنم.
سپس مصاحبه کننده پرسید، اگر فضای خالی روی پارتیشن وجود نداشته باشد، اما هیچ فایلی را که تمام فضا را اشغال کند، نمی بینید، چه می شود؟

برای این گفتم که شما همیشه می توانید به توصیف کننده های فایل باز نگاه کنید، مثلاً با دستور lsof و بفهمید که کدام برنامه تمام فضای موجود را اشغال کرده است و سپس بسته به نیاز به داده ها می توانید با توجه به شرایط عمل کنید. .

مصاحبه کننده در آخرین کلمه حرف من را قطع کرد و به سوالش اضافه کرد: "فرض کنید ما به داده ها نیاز نداریم، فقط یک گزارش اشکال زدایی است، اما برنامه کار نمی کند زیرا نمی تواند اشکال زدایی بنویسد"؟

پاسخ دادم: «خوب، می‌توانیم اشکال‌زدایی را در پیکربندی برنامه خاموش کرده و آن را مجدداً راه‌اندازی کنیم.»
مصاحبه‌کننده مخالفت کرد: «نه، ما نمی‌توانیم برنامه را مجدداً راه‌اندازی کنیم، ما هنوز داده‌های مهمی را در حافظه ذخیره می‌کنیم، و کلاینت‌های مهم به خود سرویس متصل هستند، که نمی‌توانیم آنها را مجبور به اتصال دوباره کنیم.»

گفتم: «بسیار خوب، اگر نمی‌توانیم برنامه را مجدداً راه‌اندازی کنیم و داده‌ها برای ما مهم نیستند، می‌توانیم به سادگی این فایل باز شده را از طریق توصیفگر فایل پاک کنیم، حتی اگر آن را در دستور ls نبینیم. در سیستم فایل."

مصاحبه کننده راضی بود، اما من نه.

سپس فکر کردم، چرا کسی که دانش من را آزمایش می کند عمیق تر نمی کند؟ اما اگر در نهایت داده ها مهم باشند چه؟ اگر نتوانیم یک فرآیند را مجدداً راه اندازی کنیم و فرآیند در پارتیشنی که فضای خالی ندارد در سیستم فایل بنویسد چه؟ اگر نتوانیم نه تنها داده‌هایی را که قبلاً نوشته شده‌اند، بلکه داده‌هایی را که این فرآیند می‌نویسد یا می‌خواهد بنویسد را نیز از دست بدهیم، چه؟

توزیک

در اوایل کارم، سعی کردم یک اپلیکیشن کوچک ایجاد کنم که نیاز به ذخیره اطلاعات کاربر داشت. و سپس فکر کردم، چگونه می توانم کاربر را با داده های او مطابقت دهم. به عنوان مثال، من ایوانف ایوان ایوانوویچ را دارم، و او اطلاعاتی دارد، اما چگونه می توانم با آنها دوست شوم؟ می توانم مستقیماً اشاره کنم که سگی به نام «توزیک» متعلق به همین ایوان است. اما اگر اسمش را عوض کند و به جای ایوان مثلاً علیا شود چه؟ سپس معلوم می شود که اولیا ایوانونا ایوانووا ما دیگر سگی نخواهد داشت و توزیک ما همچنان متعلق به ایوان موجود نیست. پایگاه داده ای که به هر کاربر یک شناسه (ID) منحصر به فرد می داد به حل این مشکل کمک کرد و توزیک من به این شناسه گره خورده بود که در واقع فقط یک شماره سریال بود. بنابراین ، صاحب آس دارای شناسه شماره 2 بود و در مقطعی از زمان ایوان تحت این شناسه قرار گرفت و سپس علیا نیز تحت همان شناسه قرار گرفت. مشکل انسانیت و دامداری عملا حل شد.

توصیف کننده فایل

مشکل فایل و برنامه ای که با این فایل کار میکنه تقریبا مثل سگ و انسان ماست. فرض کنید من فایلی به نام ivan.txt را باز کردم و شروع به نوشتن کلمه tuzik در آن کردم، اما فقط توانستم حرف اول "t" را در فایل بنویسم و ​​این فایل توسط شخصی به عنوان مثال به olya.txt تغییر نام داده است. اما فایل به همان شکل باقی می ماند و من همچنان می خواهم آس خود را در آن ثبت کنم. هر بار که یک فایل با یک تماس سیستمی باز می شود باز کن در هر زبان برنامه نویسی یک شناسه منحصر به فرد دریافت می کنم که من را به یک فایل راهنمایی می کند، این شناسه توصیف کننده فایل است. و اصلاً مهم نیست که بعداً با این پرونده چه کاری و چه کسی انجام می دهد ، می توان آن را حذف کرد ، می توان آن را تغییر نام داد ، می توان مالک را تغییر داد یا حقوق خواندن و نوشتن را می توان سلب کرد ، من همچنان دسترسی خواهم داشت. به آن، زیرا در زمان باز کردن فایل، من حق خواندن و/یا نوشتن آن را داشتم و موفق به شروع کار با آن شدم، یعنی باید به این کار ادامه دهم.

در لینوکس، کتابخانه libc 3 فایل توصیفگر را برای هر برنامه (فرآیند) در حال اجرا با شماره 0,1,2،XNUMX،XNUMX باز می کند. اطلاعات بیشتر را می توان در لینک ها یافت مرد stdio и مرد بی وقفه

  • توصیف کننده فایل 0 STDIN نامیده می شود و با ورودی برنامه مرتبط است
  • توصیفگر فایل 1 STDOUT نامیده می شود و توسط برنامه های کاربردی برای خروجی داده ها مانند دستورات چاپ استفاده می شود.
  • توصیفگر فایل 2 STDERR نامیده می شود و توسط برنامه های کاربردی برای خروجی پیام های خطا استفاده می شود.

اگر در برنامه خود هر فایلی را برای خواندن یا نوشتن باز کنید، به احتمال زیاد اولین شناسه رایگان را دریافت خواهید کرد و شماره 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

سپس با استفاده از تماس سیستمی کلون کردن 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 را می بندد.

در اولین فرآیند bash فرزند با PID 9004، سیستم تماس می گیرد dup2، توصیف کننده فایل STDOUT شماره 1 ما را به یک توصیفگر فایل که به لوله اشاره می کند تغییر می دهد، در مورد ما عدد 3 است. بنابراین، هر چیزی که اولین پردازش فرزند با PID 9004 به STDOUT می نویسد به طور خودکار در بافر لوله ختم می شود.

در پردازش فرزند دوم با PID 9005، bash از dup2 برای تغییر توصیفگر فایل STDIN شماره 0 استفاده می کند. اکنون هر چیزی که bash دوم ما با PID 9005 خواهد خواند، از لوله خوانده می شود.

پس از این، توصیفگرهای پرونده شماره 3 و 4 نیز در پردازش های فرزند بسته می شوند، زیرا دیگر استفاده نمی شوند.

من عمداً توصیفگر فایل 255 را نادیده می‌گیرم؛ این توصیفگر فایل برای اهداف داخلی توسط خود bash استفاده می‌شود و همچنین در فرآیندهای فرزند بسته می‌شود.

سپس، در اولین پردازش فرزند با 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 مگابایت در ثانیه روی دیسک بنویسد. علاوه بر این، اگر به دلایلی نتوانستیم داده ها را روی دیسک بنویسیم، به سادگی از این موضوع چشم پوشی می کنیم و سعی می کنیم در یک ثانیه دوباره داده ها را بنویسیم. در مثالی که من از پایتون استفاده می کنم، می توانید از هر زبان برنامه نویسی دیگری استفاده کنید.

[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

و با پرچم "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

برنامه نویسی یک فرآیند در حال اجرا

اغلب برنامه نویسان، هنگام ایجاد و آزمایش برنامه ها، از اشکال زدا (به عنوان مثال GDB) یا سطوح مختلف ورود به سیستم در برنامه استفاده می کنند. لینوکس امکان نوشتن و تغییر یک برنامه از قبل در حال اجرا را فراهم می کند، به عنوان مثال، تغییر مقادیر متغیرها، تعیین نقطه شکست و غیره و غیره.

با بازگشت به سوال اصلی در مورد کمبود فضای دیسک برای نوشتن یک فایل، بیایید سعی کنیم مشکل را شبیه سازی کنیم.

بیایید یک فایل برای پارتیشن خود ایجاد کنیم که آن را به عنوان یک دیسک جداگانه سوار می کنیم:

[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

با در نظر گرفتن اینکه پایتون چه فراخوانی سیستمی ایجاد می کند (به قسمت بالا مراجعه کنید که در آن 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

مثال با pipe را به خاطر می آوریم - چگونه 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 گوش دهید.

همه خوب است

به‌عنوان تکلیف، پیشنهاد می‌کنم اگر دستور زیر را اجرا کنید، به این فکر کنید که در فایل‌های توصیف گر cat and sleep چه چیزی وجود دارد:

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

منبع: www.habr.com

اضافه کردن نظر