SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor

علت پر نشان دادن دیسک در df و خالی بودن در du

اگر دستور df فضای دیسک را پر نشان می‌دهد اما du فضا را خالی می‌بیند، احتمالا فایلی حذف شده که هنوز توسط یک پردازش باز است. با lsof فایل‌های باز را پیدا و آزاد کنید.

چرا df فضای دیسک را پر نشان می‌دهد اما du خیر

df فضای دیسک را پر گزارش می‌کند، در حالی که du نمی‌تواند این فضا را پیدا کند؛ دلیل این است که یک پردازش همچنان فایلی را که حذف شده است، باز نگه داشته است. حذف یک فایل، نام آن را از دایرکتوری پاک می‌کند. بلوک‌های داده تنها زمانی آزاد می‌شوند که آخرین file descriptor که به آن inode اشاره دارد، بسته شود. du نام فایل‌ها را پیمایش می‌کند، بنابراین فایلی که نام ندارد را نمی‌شمارد. df از فایل‌سیستم می‌پرسد که چه تعداد بلوک تخصیص داده شده است، بنابراین همچنان فایلی را که دیگر نامی ندارد، در محاسبات خود لحاظ می‌کند.

این راهنما این وضعیت را روی یک Ubuntu VPS معمولی با ابزارهای از پیش نصب‌شده بازسازی می‌کند، پردازش نگهدارنده فایل را از طریق /proc پیدا می‌کند و فضا را بدون نیاز به reboot آزاد می‌کند. سایر دلایل بروز این نشانه عبارتند از: جدول inode بدون ورودی آزاد، فایل‌هایی که زیر یک mount point پنهان شده‌اند، و بلوک‌هایی که برای root رزرو شده‌اند.

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

تفاوت آنچه df و du محاسبه می‌کنند

df (disk free) از هر فایل‌سیستمِ mount شده، آمار خودش را می‌پرسد: چه تعداد بلاک وجود دارد، چه تعداد تخصیص یافته و چه تعداد آزاد است. این دستور هرگز دایرکتوری‌ها را باز نمی‌کند. پاسخ ارائه‌شده شامل تمام بلاک‌های تخصیص‌یافته است، حتی بلاک‌هایی که متعلق به فایلی هستند که هیچ ورودی دایرکتوری به آن اشاره نمی‌کند.

du (disk usage) دقیقاً برعکس عمل می‌کند. این دستور از مسیری که به آن می‌دهید شروع می‌کند، دایرکتوری‌ها را می‌خواند، وضعیت (stat) هر ورودی که پیدا می‌کند را بررسی کرده و بلاک‌ها را با هم جمع می‌زند. فایلی که نامی ندارد برای این دستور نامرئی است. همچنین هر دایرکتوری که اجازه خواندن آن را نداشته باشد نیز نادیده گرفته می‌شود؛ به همین دلیل است که یک کاربر معمولی مجموع کمتری نسبت به root دریافت می‌کند. پیش از آنکه از مقایسه این دو نتیجه‌گیری کنید، du را با دسترسی sudo اجرا کنید.

هنگام مقایسه این دو، همواره دو گزینه اهمیت دارند:

  • -x باعث می‌شود du فقط روی یک فایل‌سیستم باقی بماند. بدون این گزینه، du / وارد تمام فایل‌سیستم‌های mount شده در زیرمجموعه / می‌شود و مجموعه‌ای تولید می‌کند که df / هرگز آن را اندازه‌گیری نمی‌کرده است.
  • -s به جای نمایش یک خط برای هر دایرکتوری، تنها یک خط خلاصه برای هر آرگومان چاپ می‌کند.

این همان جفتی است که باید برای فایل‌سیستم مورد نظرتان به صورت هم‌زمان اجرا کنید.

df -h /
sudo du -xhs / 2>/dev/null

df بلافاصله پاسخ می‌دهد. du در فایل‌سیستم‌های بزرگ ممکن است دقایق زیادی طول بکشد، زیرا در مسیر خود وضعیت تک‌تک فایل‌ها را بررسی می‌کند. زمانی که مجموع این دو عدد تفاوت زیادی با هم دارند و du با دسترسی root و گزینه -x اجرا شده است، فضای گمشده به چیزی تخصیص یافته که نامی ندارد.

بازتولید عمدی عدم تطابق

این کار را روی یک VPS آزمایشی انجام دهید. تمام موارد زیر از bash و coreutils استفاده می‌کنند، بنابراین نیازی به نصب هیچ بسته‌ای نیست.

وضعیت اولیه فایل‌سیستمی که /var/tmp را در خود جای داده است، ثبت کنید.

cd /var/tmp
df -h .
df --output=used -B1 .

دستور دوم، تعداد بایت‌های استفاده‌شده را بدون گرد کردن نمایش می‌دهد که باعث می‌شود بررسی نهایی دقیق باشد.

اکنون یک فایل ایجاد کنید. اندازه آن از فضای خالی که خود ماشین گزارش می‌دهد گرفته می‌شود، بنابراین این نمایش با هر دیسکی که دارید سازگار است.

free=$(df --output=avail -B1 . | tail -n 1)
fallocate -l $((free / 10)) ghost.bin
ls -l ghost.bin
df -h .

$(...) جایگزینی دستور (command substitution) است: شل دستور داخل آن را اجرا می‌کند و خروجی به مقدار free تبدیل می‌شود. اگر این نحو برای شما جدید است، جایگزینی دستور در bash آن را به‌طور کامل پوشش می‌دهد. fallocate بلوک‌های واقعی را بدون نوشتن داده در آن‌ها رزرو می‌کند، به همین دلیل است که بلافاصله تمام می‌شود. در فایل‌سیستمی که از این قابلیت پشتیبانی نمی‌کند، دستور با خطا مواجه می‌شود و head -c $((free / 10)) /dev/zero > ghost.bin همان کار را با نوشتن بایت‌ها انجام می‌دهد.

این df -h . را با موردی که ثبت کردید مقایسه کنید. ستون used افزایش یافته و ستون available کاهش یافته است.

اکنون فایل را از طریق یک پردازش دیگر باز نگه دارید و سپس آن را حذف کنید.

sleep infinity < ghost.bin &
holder=$!
rm ghost.bin
ls -l ghost.bin
df -h .
sudo du -xhs . 2>/dev/null

این redirection تمام ترفند کار است. sleep infinity < ghost.bin & یک پردازش پس‌زمینه را شروع می‌کند که ورودی استاندارد آن همان فایل است، بنابراین شل فایل را باز کرده و توصیف‌گر (descriptor) آن را به sleep می‌دهد که آن را باز نگه می‌دارد. $! شناسه پردازش (PID) آن کار پس‌زمینه را نگه می‌دارد. سپس rm نام فایل را حذف می‌کند در حالی که توصیف‌گر همچنان باز است.

خروجی را بخوانید. ls نمی‌تواند فایل را پیدا کند، زیرا نام آن از بین رفته است. du به نزدیکی وضعیت اولیه خود بازگشته است، زیرا این دستور نام‌ها را پیمایش می‌کند. df تغییری نکرده است، زیرا بلوک‌ها همچنان تخصیص‌یافته باقی مانده‌اند. فایل‌سیستم و درخت دایرکتوری اکنون با هم اختلاف دارند و شکاف بین آن‌ها، همان فایلی است که به‌تازگی حذف کردید.

یافتن پردازشی که فایل حذف‌شده را در اختیار دارد

هر توصیف‌گر فایل (file descriptor) باز، در مسیر /proc/<pid>/fd/ به شکل یک پیوند نمادین (symbolic link) به فایلی که به آن اشاره دارد، ظاهر می‌شود. هنگامی که فایل unlinked می‌شود، هسته سیستم‌عامل مقصد آن پیوند را به عنوان حذف‌شده علامت‌گذاری می‌کند. بنابراین، یافتن پردازش درگیر به معنای یافتن پیوندی است که مقصد آن دارای این نشانگر باشد.

sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null

find /proc//fd -lname ' (deleted)' 2>/dev/null

دستور -lname با مقصد پیوند نمادین مطابقت دارد و نه نام آن، %p مسیر توصیف‌گر را چاپ می‌کند و %l مقصدی که به آن اشاره دارد را نمایش می‌دهد. شناسه پردازش (PID) دومین عنصر در مسیری است که چاپ می‌شود. این دستور را با sudo اجرا کنید، زیرا در غیر این صورت فقط می‌توانید /proc/<pid>/fd را برای پردازش‌های خودتان بخوانید. تغییر مسیر stderr (خطای استاندارد) باعث حذف پیام‌های مزاحم از پردازش‌هایی می‌شود که در حین جستجوی find خاتمه می‌یابند.

یک سرور شلوغ در هر لحظه چندین فایل حذف‌شده را در اختیار دارد که اکثر آن‌ها کوچک و بی‌خطر هستند. آن‌ها را بر اساس اندازه مرتب کنید تا فقط موارد مهم در بالا باقی بمانند.

sudo bash -c 'for fd in /proc/[0-9]*/fd/*; do
  target=$(readlink "$fd" 2>/dev/null) || continue
  case "$target" in
    *"(deleted)") echo "$(stat -Lc %s "$fd" 2>/dev/null) $fd $target" ;;
  esac
done' | sort -rn | head

find /proc//fd -lname ' (deleted)' -printf '%p\n' 2>/dev/null | xargs ls -l | sort -k 5 -n

دستور stat -L پیوند را تا خود inode دنبال می‌کند، بنابراین %s اندازه فایلی که دیگر نامی ندارد را گزارش می‌دهد. مرتب‌سازی بر اساس این عدد، بزرگ‌ترین فایل را در ابتدا قرار می‌دهد.

سپس پردازش پشت توصیف‌گر برتر را شناسایی کنید. مسیری که در بالای آن لیست قرار دارد، هر دو عددی که نیاز دارید را در خود دارد؛ بنابراین آن‌ها را ابتدا در متغیرها قرار دهید و PID و N را با مقادیری که لیست خودتان چاپ کرده است، جایگزین کنید.

pid=PID
n=N
ps -o pid,user,etime,args -p "$pid"
sudo stat -L "/proc/$pid/fd/$n"

ps -p PID -o comm=; ls -l /proc/PID/fd/N

دستور ps نام برنامه را مشخص می‌کند و نشان می‌دهد چه مدت در حال اجرا بوده است. stat -L اندازه و تعداد بلوک‌های تخصیص‌یافته به inode حذف‌شده را چاپ می‌کند. این دو در کنار هم به سوال اصلی پاسخ می‌دهند: کدام سرویس این فایل را زنده نگه داشته است.

اگر ماشین از قبل دارای lsof باشد، sudo lsof +L1 فایل‌های بازی را که تعداد پیوند آن‌ها به صفر رسیده است فهرست می‌کند و اندازه‌ها را در یک جدول نشان می‌دهد. این ابزار در یک ایمیج حداقلی Ubuntu موجود نیست و نصب یک بسته روی فایل‌سیستمی که فضای خالی ندارد خود می‌تواند با شکست مواجه شود، بنابراین جستجوی /proc نسخه‌ای است که همیشه کار می‌کند.

آزادسازی فضا بدون راه‌اندازی مجدد (Reboot)

راه‌اندازی مجدد سیستم مشکل را حل می‌کند، اما اولین اقدام اشتباهی است: این کار باعث از دسترس خارج شدن سرویس شده و شواهد را از بین می‌برد. چهار گزینه ملایم‌تر وجود دارد که باید به ترتیب امتحان شوند.

ابتدا، اگر هنوز به داده‌ها نیاز دارید، آن‌ها را کپی کنید. خواندن مسیر descriptor، همان inode زنده را می‌خواند.

sudo cp /proc/<pid>/fd/<n> /root/recovered.log

این تنها موردی است که بازیابی فایل حذف‌شده آسان است؛ به همین دلیل بازیابی فایل‌های حذف‌شده با rm -rf با پرسش درباره اینکه آیا فرآیندی هنوز فایل را باز نگه داشته است یا خیر، شروع می‌شود. به محض بسته شدن آخرین descriptor، این مسیر از بین می‌رود.

دوم، فایل را از طریق descriptor خالی کنید. مسیر /proc به همان inode اشاره می‌کند، بنابراین truncating (کوتاه کردن) آن، بلوک‌ها را آزاد می‌کند در حالی که فرآیند همچنان در حال اجراست.

sudo truncate -s 0 "/proc/$pid/fd/$n"
df -h /

این روش زمانی به خوبی کار می‌کند که نویسنده، فایل را در حالت append باز کرده باشد، زیرا هر عملیات نوشتن به انتهای فعلی فایل می‌رود. اگر این‌طور نباشد، فرآیند offset نوشتن قدیمی خود را حفظ می‌کند، بنابراین نوشتن بعدی آن در عمق فایل انجام شده و فایل را با یک حفره در ابتدا بازسازی می‌کند. حفره تخصیص داده نمی‌شود، بنابراین بلوک‌ها آزاد می‌مانند و df فضایی را که به‌تازگی بازگردانده است، حفظ می‌کند. آنچه بازمی‌گردد فقط اندازه فایل است: پس از اینکه فرآیند دوباره نوشت، sudo stat -L "/proc/$pid/fd/$n" را اجرا کنید؛ این دستور اندازه قدیمی را در کنار تعداد بلوکی که دیگر با آن مطابقت ندارد، گزارش می‌دهد. اگر می‌خواهید اندازه فایل نیز از صفر شروع شود، فرآیند را restart کنید.

سوم، از سرویس بخواهید لاگ‌های خود را دوباره باز کند. دیمونی که فایل لاگ آن در زیرمجموعه‌اش حذف شده، نسخه واقعی و رایج این مشکل است. بسیاری از دیمون‌ها فایل‌های لاگ خود را با دریافت یک سیگنال دوباره باز می‌کنند: nginx از SIGUSR1 و rsyslog از SIGHUP استفاده می‌کند. به جای حدس زدن، مستندات دیمون مربوطه را بررسی کنید، زیرا ارسال سیگنال اشتباه به دیمون اشتباه، باعث توقف آن می‌شود.

sudo systemctl kill -s USR1 nginx

چهارم، unit را restart کنید. sudo systemctl restart <unit> تمام descriptorهایی را که فرآیند قدیمی نگه داشته بود می‌بندد، بنابراین بلوک‌ها با قطعیت بازمی‌گردند. برای نمایش بالا، نگهدارنده یک sleep است که خودتان شروع کرده‌اید، بنابراین پایان دادن به آن کافی است.

kill $holder
df -h .
df --output=used -B1 .
sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null

بایت‌های استفاده‌شده را با مقداری که پیش از ایجاد فایل ثبت کرده بودید مقایسه کنید. آن‌ها دوباره مطابقت دارند و find دیگر descriptor شما را گزارش نمی‌کند. تایید با همان دستوری که مشکل را پیدا کرد، عادتی است که ارزش حفظ کردن دارد.

مشاهده تغییر آن مقدار، آسان‌تر از اجرای دستی و مکرر df است. watch یک دستور را در فواصل زمانی ثابت تکرار می‌کند و خروجی را در همان محل چاپ می‌کند، بنابراین watch df -h / تغییر ستون استفاده‌شده را همزمان با بازگشت فضا نشان می‌دهد.

زمانی که مجموع مقادیر همخوانی دارند اما دیسک همچنان پر است

اگر df و یک root du -x با یکدیگر همخوانی داشته باشند، هیچ فایل حذف‌شده‌ای در میان نیست. دلایل باقی‌مانده از نوع دیگری هستند و هر کدام بررسی خاص خود را دارند.

اتمام اینودها (inodes) و نه بلوک‌ها

یک اینود (inode) فراداده‌های مربوط به یک فایل را در خود نگه می‌دارد. سیستم فایل ext4 هنگام ایجاد، تعداد ثابتی اینود می‌سازد؛ بنابراین ممکن است یک سیستم فایل با وجود داشتن بلوک‌های آزاد، با کمبود اینود مواجه شود. در این حالت، ایجاد فایل‌های جدید با خطا مواجه می‌شود، حتی اگر df -h فضای خالی را نشان دهد.

df -h /
df -i /

دستور اول بلوک‌ها و دستور دوم اینودها را می‌شمارد. ستون use در هر کدام را مقایسه کنید. اگر میزان استفاده از بلوک پایین باشد اما استفاده از اینود به حد نهایی رسیده باشد، مشکل وجود تعداد بسیار زیادی فایل بسیار کوچک است.

df اجازه استفاده همزمان از -i و --output را در یک فراخوانی نمی‌دهد؛ بنابراین زمانی که می‌خواهید مقادیر خام را برای خواندن یا ارسال به دستور دیگری داشته باشید، فیلدهای اینود را با نام انتخاب کنید و -i را حذف کنید.

df --output=itotal,iused,iavail,ipcent /

این ستون‌ها همان حساب‌وکتابی را ارائه می‌دهند که df -i چاپ می‌کند، با این تفاوت که به شکلی است که می‌توانید آن را تفکیک کنید.

فایل‌ها را با شمارش ورودی‌ها به جای بایت‌ها پیدا کنید.

sudo du --inodes -x -d 1 / 2>/dev/null | sort -rn | head

همین دستور را یک سطح پایین‌تر در دایرکتوری که بیشترین تعداد را داشته است تکرار کنید تا به شاخه‌ای برسید که فایل‌ها را ایجاد می‌کند. اگر du شما از --inodes پشتیبانی نمی‌کند، sudo find /var -xdev -type f | wc -l یک زیرشاخه را به روشی کندتر می‌شمارد.

راه حل، حذف یا انتقال آن فایل‌ها است. شما نمی‌توانید به یک سیستم فایل ext4 موجود، اینود اضافه کنید، زیرا تعداد آن‌ها در زمان mkfs ثابت شده است؛ بنابراین افزایش آن مستلزم ایجاد مجدد سیستم فایل و بازیابی از نسخه پشتیبان است. سیستم فایل XFS اینودها را در صورت نیاز تخصیص می‌دهد، بنابراین با چنین محدودیت ثابتی مواجه نمی‌شود. ماشینی که کانتینرها را اجرا می‌کند، سریع‌تر از سایرین به هر دو محدودیت می‌رسد، زیرا لایه‌های ایمیج حاوی فایل‌های کوچک بسیاری هستند. در چنین ماشینی، پاک‌سازی فضای دیسک Docker در یک VPS راه حل اختصاصی است و فضای بسیار بیشتری نسبت به یک جستجوی کلی در سیستم فایل آزاد می‌کند.

فضای پنهان‌شده زیر یک mount point

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

این موضوع را با tmpfs نشان می‌دهیم که به دیسک اضافی نیاز ندارد. این بخش به ماشینی نیاز دارد که در آن اجازه mount کردن داشته باشید، بنابراین روی یک KVM VPS کار می‌کند.

sudo mkdir -p /srv/covered
sudo cp /etc/services /srv/covered/
ls /srv/covered
sudo mount -t tmpfs tmpfs /srv/covered
ls /srv/covered
sudo umount /srv/covered
ls /srv/covered

بخش میانی ls یک دایرکتوری خالی را نشان می‌دهد. کپی انجام‌شده هرگز به جایی نرفته است: همچنان روی فایل‌سیستم ریشه (root) قرار دارد و لحظه‌ای که unmount کنید، دوباره ظاهر می‌شود. اکنون سرویسی را تصور کنید که یک ماه در آن مسیر لاگ نوشته است، پیش از آنکه کسی یک volume را روی آن mount کند.

برای پیدا کردن فایل‌های واقعی روی یک سرور در حال اجرا، فایل‌سیستم ریشه را برای بار دوم در جای دیگری mount کنید. یک bind mount، یک فایل‌سیستم را بدون فایل‌سیستم‌هایی که درون آن mount شده‌اند، نمایش می‌دهد.

sudo mkdir -p /mnt/rootcheck
sudo mount --bind / /mnt/rootcheck
sudo du -xhs /mnt/rootcheck/* 2>/dev/null | sort -h
sudo umount /mnt/rootcheck

هر چیزی که در آن لیست ظاهر شود اما در مسیر عادی دیده نشود، زیر یک mount point دفن شده است. پس از پایان کار، bind mount را unmount کنید، در غیر این صورت یک du بعدی بدون -x، فایل‌های مشابه را دو بار می‌شمارد.

بلوک‌های رزرو شده برای root

فایل‌سیستم ext4 بخشی از بلوک‌های خود را برای کاربر root کنار می‌گذارد تا در صورت پر شدن دیسک، امکان ورود root به سیستم و تعمیر آن سلب نشود. فرآیندی که با دسترسی یک کاربر معمولی اجرا می‌شود، پیش از پر شدن کامل دیسک به این محدودیت برخورد می‌کند، در حالی که df همچنان فضای کمی را آزاد نشان می‌دهد. به‌جای فرض کردن مقادیر پیش‌فرض، تنظیمات فایل‌سیستم خود را بررسی کنید.

dev=$(df --output=source / | tail -n 1)
sudo tune2fs -l "$dev" | grep -i 'block count'

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

این مقدار را با sudo tune2fs -m <percent> "$dev" تغییر دهید. تغییرات بلافاصله اعمال می‌شوند و نیازی به remount کردن نیست. کاهش فضای رزرو شده در یک فایل‌سیستم داده‌ای مجزا منطقی است. اما در فایل‌سیستم root، مقداری فضا باقی بگذارید تا root همچنان امکان نوشتن داشته باشد، زیرا تعمیر فایل‌سیستمی که هیچ فضای آزادی ندارد بسیار دشوارتر است. tune2fs روی ext2، ext3 و ext4 کار می‌کند. XFS تنظیم مشابهی ندارد.

مواردی که du شما را به اشتباه می‌اندازد

چهار عادت du باعث می‌شود مجموع‌هایی که می‌بینید نادرست به نظر برسند.

  • لینک‌های سخت (Hard links): ابزار du هر inode را تنها یک‌بار می‌شمارد، حتی اگر چندین نام به آن اشاره کنند؛ بنابراین در درختی که پر از لینک‌های سخت است، مجموع گزارش‌شده کمتر از مجموع واقعی فایل‌ها خواهد بود.
  • فایل‌های پراکنده (Sparse files): ابزار du بلوک‌های واقعاً تخصیص‌یافته را گزارش می‌دهد، در حالی که ls -l اندازه ظاهری فایل را نشان می‌دهد. برای مشاهده عدد دیگر، از پرچم --apparent-size استفاده کنید.
  • مجوزها (Permissions): اگر دستور را با یک کاربر معمولی اجرا کنید، du فایل‌هایی را که اجازه خواندنشان را ندارد نادیده می‌گیرد و مقدار کمتری را گزارش می‌دهد. خطاهایی که این ابزار چاپ می‌کند همان مواردی هستند که کاربران معمولاً به /dev/null هدایت (redirect) می‌کنند و دیگر آن‌ها را نمی‌خوانند.
  • مرزهای سیستم‌فایل (Filesystem boundaries): بدون استفاده از -x، ابزار du / تمام سیستم‌فایل‌های mount شده در زیرمجموعه / را می‌شمارد؛ بنابراین مجموع آن می‌تواند از مقداری که df / گزارش می‌دهد بیشتر باشد.

ابزار df نیز یک عادت دارد که دانستن آن مفید است. این ابزار هر سیستم‌فایل را به‌طور جداگانه گزارش می‌دهد؛ بنابراین آن را دقیقاً روی مسیری اجرا کنید که عملیات نوشتن در آن با خطا مواجه شده است. یک /boot جداگانه با انباشته شدن بسته‌های هسته (kernel packages) طبق زمان‌بندی خود پر می‌شود و حذف هسته‌های قدیمی در اوبونتو کاری متفاوت از آزادسازی فضا در / است.

ترتیب عملیاتی برای یک حادثه واقعی

  1. دستورات df -h <path> و df -i <path> را روی فایل‌سیستمی که عملیات نوشتن در آن ناموفق بوده اجرا کنید، نه اینکه از روی عادت روی / اجرا کنید.
  2. دستور sudo du -xh -d 1 <mountpoint> 2>/dev/null | sort -h را اجرا کنید و سپس به درون بزرگترین دایرکتوری بروید.
  3. اگر du نمی‌تواند فضای گزارش‌شده توسط df را توجیه کند، /proc را برای یافتن فایل‌های حذف‌شده‌ای که هنوز باز هستند جستجو کنید.
  4. اگر خروجی هر دو ابزار با هم مطابقت داشت، فایل‌سیستم را در جای دیگری bind mount کنید و به دنبال فایل‌هایی بگردید که زیر یک mount point پنهان شده‌اند.
  5. اگر محدودیت به دلیل تعداد inodeها است، به جای بایت‌ها، تعداد فایل‌ها را بشمارید.

در هر مرحله، دستوری وجود دارد که خروجی آن قابل خواندن است. تفاوت بین رفع اصولی مشکل و حدس زدن در همین است.

FAQ

چرا df دیسک را پر نشان می‌دهد در حالی که du فضای بسیار کمتری را گزارش می‌کند؟

دلیل معمول این است که فایلی حذف شده اما یک پردازش همچنان آن را باز نگه داشته است. حذف فایل، ورودی آن را از دایرکتوری پاک می‌کند، بنابراین du نامی برای پیمایش ندارد و شمارش آن را متوقف می‌کند. inode و بلوک‌های مربوط به آن تا زمانی که آخرین توصیف‌گر (descriptor) بسته نشود، در حالت تخصیص‌یافته باقی می‌مانند و df بلوک‌های تخصیص‌یافته را می‌شمارد. در /proc/<pid>/fd به دنبال لینک‌های نمادینی بگردید که هدف آن‌ها به عنوان حذف‌شده علامت‌گذاری شده است؛ به این ترتیب هم فایل و هم پردازشی که آن را نگه داشته است، پیدا می‌شوند. پیش از اعتماد به این مقایسه، اطمینان حاصل کنید که du را با دسترسی root و با پرچم -x اجرا کرده‌اید، زیرا کاربر معمولی دایرکتوری‌هایی را که اجازه خواندن آن‌ها را ندارد، بدون هیچ هشداری نادیده می‌گیرد.

چگونه بدون استفاده از lsof، فایلی حذف‌شده که همچنان باز است را پیدا کنم؟

از سوابق توصیف‌گرهای باز که در هسته (kernel) موجود است استفاده کنید. sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null تمام توصیف‌گرهایی که به فایلی بدون نام اشاره می‌کنند را فهرست می‌کند و شناسه پردازش (PID) در مسیری که چاپ می‌شود، قرار دارد. دستور sudo stat -Lc %s روی یکی از آن مسیرهای توصیف‌گر، اندازه آن را گزارش می‌دهد، بنابراین می‌توانید آن‌ها را مرتب کرده و مورد مهم را انتخاب کنید. این کار به هیچ بسته‌ای نیاز ندارد، که اهمیت آن زمانی مشخص می‌شود که نصب یک بسته روی فایل‌سیستمی که فضای خالی ندارد، ممکن است با شکست مواجه شود.

آیا می‌توانم بدون کشتن (kill) پردازش، فضا را آزاد کنم؟

گاهی اوقات. sudo truncate -s 0 /proc/<pid>/fd/<n> از طریق توصیف‌گر به همان inode دسترسی پیدا می‌کند و بلوک‌های آن را آزاد می‌کند در حالی که پردازش همچنان در حال اجراست. این کار زمانی تمیزترین روش است که پردازش فایل را در حالت append باز کرده باشد، زیرا عملیات نوشتن همیشه به انتهای فعلی فایل می‌رود. اگر این‌طور نباشد، آفست نوشتن در همان‌جایی که بود باقی می‌ماند و نوشتن بعدی، فایل را با یک حفره در ابتدا بازسازی می‌کند؛ بنابراین اندازه گزارش‌شده دوباره افزایش می‌یابد در حالی که بلوک‌های زیر حفره آزاد می‌مانند. راه‌اندازی مجدد واحد (unit) یا ارسال سیگنالی به آن برای باز کردن مجدد لاگ‌ها (طبق مستندات همان سرویس)، راهکاری است که هیچ فایل پراکنده‌ای (sparse file) باقی نمی‌گذارد.

df فضای خالی نشان می‌دهد اما نوشتن همچنان با خطا مواجه می‌شود. علت دیگر چه می‌تواند باشد؟

تعداد inodeها را با df -i در همان مسیر بررسی کنید، زیرا فایل‌سیستمی که بلوک خالی دارد اما inode خالی ندارد، فایل‌های جدید را نمی‌پذیرد. بررسی کنید که آیا عملیات نوشتن توسط کاربری غیر از root روی یک فایل‌سیستم ext4 انجام می‌شود که فقط بلوک‌های رزرو شده در آن باقی مانده است؛ این موضوع را sudo tune2fs -l روی دستگاه نشان می‌دهد. اطمینان حاصل کنید که در حال خواندن همان فایل‌سیستمی هستید که عملیات نوشتن واقعاً آن را هدف قرار داده است، زیرا یک /boot یا /var جداگانه، مستقل از / پر می‌شود.

چرا du مجموع بزرگ‌تری نسبت به df گزارش می‌کند؟

du بدون -x وارد هر فایل‌سیستمی می‌شود که در مسیر داده‌شده mount شده است، بنابراین مجموع چندین فایل‌سیستم را محاسبه می‌کند در حالی که df فقط یک فایل‌سیستم را توصیف می‌کند. Bind mountها این وضعیت را بدتر می‌کنند، زیرا فایل‌های یکسان در هر مسیری که ظاهر می‌شوند، دوباره شمرده می‌شوند. از -x استفاده کنید تا du را در یک فایل‌سیستم واحد نگه دارید و به df همان مسیر را بدهید تا هر دو دستور، یک چیز واحد را توصیف کنند.