بازیابی حافظه: حذف و تعویض

در بخش‌های قبلی، دیدیم که چگونه هسته، حافظه را در سطح سیستم مدیریت می‌کند. در این فصل، عمیق‌تر به گروه‌های کنترل حافظه (memcg) می‌پردازیم، مکانیزمی که اندروید برای تقسیم‌بندی و کنترل استفاده از حافظه برای برنامه‌های جداگانه استفاده می‌کند.

درک memcg بسیار مهم است زیرا سطحی است که سیستم می‌تواند برنامه‌های منفرد را برای بازیابی یا تعیین محدودیت‌های حافظه برای آنها هدف قرار دهد و امکان مدیریت پیشگیرانه ردپای حافظه برنامه را فراهم کند.

گروه‌های کنترل حافظه (memcg)

گروه‌های کنترل (cgroups) یک ویژگی هسته لینوکس هستند که امکان سازماندهی فرآیندها را در گروه‌های سلسله مراتبی و توزیع منابع سیستم (مانند CPU، حافظه و I/O) بین آنها فراهم می‌کنند. memcg کنترل‌کننده cgroup مخصوص حافظه است.

مفاهیم کلیدی memcg

  • شارژ Memcg : وقتی یک فرآیند در یک memcg یک صفحه از حافظه (چه ناشناس و چه فایل-پشتیبانی‌شده) را اختصاص می‌دهد، آن صفحه به memcg "شارژ" می‌شود. شارژ کل یک memcg، مجموع تمام صفحاتی است که توسط تمام فرآیندهای درون آن استفاده می‌شود.
  • حسابداری سلسله مراتبی : میزان استفاده از حافظه در بالای درخت محاسبه می‌شود. هزینه یک گروه فرزند نیز در میزان استفاده گروه والد آن لحاظ می‌شود.
  • محدودیت‌های حافظه : هر memcg می‌تواند محدودیت‌هایی (مانند memory.max یا memory.high ) داشته باشد که در صورت تجاوز از آنها، مستقل از حافظه سراسری سیستم، باعث بازیابی یا حتی حذف OOM می‌شود.
  • بازپس‌گیری Per-memcg : وقتی یک memcg از حد مجاز خود فراتر می‌رود، یا وقتی سیستم به حافظه نیاز دارد، هسته می‌تواند یک memcg خاص را برای بازپس‌گیری هدف قرار دهد. این به معنای حذف صفحات فایل آن یا تعویض صفحات ناشناس آن به ZRAM است.

سلسله مراتب memcg اندروید

اندروید از یک سلسله مراتب خاص برای مدیریت فرآیندهای برنامه استفاده می‌کند. این ساختار به سیستم اجازه می‌دهد تا سیاست‌های مختلفی را برای انواع مختلف برنامه‌ها (مثلاً پیش‌زمینه در مقابل پس‌زمینه) اعمال کند.

سلسله مراتب memcg اندروید

  • /sys/fs/cgroup/apps/ : ریشه همه برنامه‌های اندروید.
  • uid_<UID>/ : یک دایرکتوری برای شناسه کاربری هر برنامه. همه فرآیندهای متعلق به یک بسته برنامه، این گروه را به اشتراک می‌گذارند.
  • pid_<PID>/ : یک دایرکتوری برای هر فرآیند جداگانه و هر فرزندی که از آن منشعب شده است. این امکان کنترل و حسابداری دقیق برای برنامه‌هایی با چندین فرآیند را فراهم می‌کند.

تمرین عملی: بررسی memcg

در این تمرین، دایرکتوری memcg را برای MemoryLab پیدا خواهید کرد و میزان شارژ حافظه آن را به صورت بلادرنگ مشاهده خواهید کرد.

۱. MemoryLab را اجرا کنید

مطمئن شوید که MemoryLab روی دستگاه شما اجرا می‌شود.

۲. حافظه‌ی Memcg مربوط به MemoryLab را پیدا کنید

ابتدا، PID برنامه‌ی در حال اجرا MemoryLab را دریافت کنید:

adb shell pidof com.android.memorylab
# Example output: 11672

حالا، دایرکتوری cgroup آن را پیدا کنید. می‌توانید آن را در /proc/<PID>/cgroup پیدا کنید:

adb shell cat /proc/11672/cgroup
# Example output: 0::/apps/uid_10274/pid_11672

در cgroup نسخه ۲، مسیر بعد از 0:: نشان دهنده سلسله مراتب memcg نسبت به /sys/fs/cgroup است. بنابراین مسیر کامل /sys/fs/cgroup/apps/uid_10274/pid_11672/ است.

۳. آمار memcg را بخوانید

به آن دایرکتوری بروید و فایل‌های کلیدی را بررسی کنید:

# Current memory usage (in bytes)
adb shell cat /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.current
# Example output:
# 1591324672

memory.current مقدار کل حافظه (بر حسب بایت) که در حال حاضر به این cgroup اختصاص داده شده است را نشان می‌دهد.

# Detailed statistics
adb shell cat /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.stat | head -n 10
# Example output:
# anon 1585098752
# file 741376
# kernel 5484544
# kernel_stack 393216
# pagetables 4583424
# sec_pagetables 0
# percpu 216
# sock 0
# vmalloc 4096
# shmem 20480

memory.stat جزئیات را ارائه می‌دهد: * anon : مقدار حافظه ناشناس (heaps، Stacks). * file : مقدار حافظه پشتیبان فایل (page cache). * swap : مقدار حافظه‌ای که به ZRAM منتقل شده است.

۴. تغییرات را مشاهده کنید

  1. MemoryLab را باز کنید.
  2. به مقدار memory.current توجه کنید.
  3. چندین بار روی «اختصاص حافظه جاوا (10 مگابایت)» ضربه بزنید.
  4. دوباره memory.current بخوانید. باید ببینید که با هر بار لمس، تقریباً 10 مگابایت افزایش می‌یابد.
  5. روی «اختصاص بیت‌مپ‌ها» ضربه بزنید. برای مشاهده افزایش anon memory.stat را بررسی کنید.

بازیابی پیشگیرانه با memory.reclaim

یکی از ویژگی‌های قدرتمند memcg (نسخه ۲) فایل memory.reclaim است. نوشتن مقداری در این فایل به هسته دستور می‌دهد که بلافاصله تلاش کند تا آن مقدار حافظه را از این memcg و هر memcg تحت آن بازپس گیرد.

نحوه استفاده اندروید از memory.reclaim

ابزار CachedAppOptimizer اندروید از این ویژگی برای بازیابی حداکثری حافظه از برنامه‌ها پس از فریز شدن آنها استفاده می‌کند. قابلیت Android App Freezer تضمین می‌کند که برنامه‌های ذخیره شده در حافظه پنهان، در حالی که اجرا نمی‌شوند، تا حد امکان رم کمتری مصرف کنند. هنگامی که یک برنامه به پس‌زمینه منتقل می‌شود و فریز می‌شود، سیستم میزان استفاده فعلی برنامه از حافظه را در فایل memory.reclaim خود می‌نویسد. این کار هسته را مجبور می‌کند تا تمام صفحات فایل ممکن را حذف کرده و تمام صفحات ناشناس را به ZRAM منتقل کند و ردپای موجود برنامه را به حداقل برساند.

شما می‌توانید با خواندن memory.current و نوشتن مجدد آن در memory.reclaim ، به صورت دستی به همان "حداکثر بازیابی" دست یابید:

# Force reclaim of everything
adb shell "cat /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.current > /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.reclaim"

تمرین: بازیابی اجباری و ردیابی

اکنون هسته را مجبور می‌کنیم که حافظه را از MemoryLab بازپس بگیرد و فعالیت را در یک ردیابی Perfetto ثبت کند.

  1. آماده‌سازی : مطمئن شوید که MemoryLab مقداری تخصیص (جاوا و Bitmaps) دارد.
  2. شروع ردیابی : از این پیکربندی ردیابی درون‌خطی برای ثبت رویدادهای زمان‌بندی، بازیابی و ذخیره‌سازی صفحه استفاده کنید.

    # Use a config that captures scheduling, reclaim and page cache events
    adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/reclaim.perfetto-trace <<EOF
    buffers: {
        size_kb: 131072
        fill_policy: RING_BUFFER
    }
    data_sources: {
        config {
            name: "linux.ftrace"
            ftrace_config {
                ftrace_events: "sched/sched_switch"
                ftrace_events: "sched/sched_wakeup"
                ftrace_events: "kmem/rss_stat"
                ftrace_events: "mm_filemap_add_to_page_cache"
                ftrace_events: "mm_filemap_delete_from_page_cache"
                ftrace_events: "vmscan/mm_vmscan_direct_reclaim_begin"
                ftrace_events: "vmscan/mm_vmscan_direct_reclaim_end"
                ftrace_events: "vmscan/mm_vmscan_memcg_reclaim_begin"
                ftrace_events: "vmscan/mm_vmscan_memcg_reclaim_end"
                symbolize_ksyms: true
            }
        }
    }
    data_sources: {
        config {
            name: "linux.process_stats"
            process_stats_config {
                scan_all_processes_on_start: true
            }
        }
    }
    EOF
    
  3. فشار محرک و بازیابی :

    • در MemoryLab، روی گزینه Allocate Native Memory (1GB) ضربه بزنید. این کار باعث ایجاد فشار در کل سیستم می‌شود.
    • در یک ترمینال جداگانه، ۲۰۰ مگابایت را بازیابی کنید:

      adb shell "echo 200M > /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.reclaim"
      
  4. بازگشت به خطا : به MemoryLab برگردید. روی Thrash Pagecache (تست خطا) ضربه بزنید.

  5. توقف ردیابی : در ترمینال ردیابی، کلیدهای Ctrl+C را فشار دهید.

تجزیه و تحلیل بازپس گیری در Perfetto

وقتی ردیابی را باز می‌کنید، می‌توانید عملیات بازپس‌گیری را هم در سطح سیستم و هم در هر برنامه مشاهده کنید:

پرفتو که بازیابی مستقیم و ممکگ را نشان می‌دهد

در مسیر ردیابی به دنبال موارد زیر باشید:

  • kswapd : در زیر thread های Kernel به دنبال kswapd0 بگردید (در تصویر بالا به صورت دستی در بالا پین شده است). خواهید دید که در حالی که سیستم برای یافتن صفحات خالی تلاش می‌کند، بیدار شده و اجرا می‌شود (برش‌های سبز).
  • بازیابی مستقیم : به رشته‌های پردازش com.android.memorylab نگاه کنید. برش‌های بنفش رویداد ftrace (مانند mm_vmscan_direct_reclaim_begin ) را خواهید دید که مستقیماً زیر مسیر زمان‌بندی رشته ظاهر می‌شوند. این نشان می‌دهد که خود رشته برنامه در انتظار آزاد شدن صفحات توسط هسته متوقف شده است.
  • شمارنده‌های RSS و Swap :
    • mem.rss.anon : با لمس دکمه‌های تخصیص، افزایش می‌یابد.
    • mem.swap : با فشرده‌سازی صفحات ناشناس توسط kswapd و رشته‌های خود برنامه (در صورت درخواست مستقیم) در ZRAM، به طور پیوسته افزایش می‌یابد.
  • بازیابی memcg : اگر لحظه‌ای که بازیابی دستی را فعال کرده‌اید را بزرگنمایی کنید، افت شدیدی در rss.anon و rss.file مشاهده خواهید کرد که با رویدادهای mm_vmscan_memcg_reclaim همراه است.

← در سطح سیستم | ↑ بالا | تعامل kswapd و lmkd →