در بخشهای قبلی، دیدیم که چگونه هسته، حافظه را در سطح سیستم مدیریت میکند. در این فصل، عمیقتر به گروههای کنترل حافظه (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 اندروید
اندروید از یک سلسله مراتب خاص برای مدیریت فرآیندهای برنامه استفاده میکند. این ساختار به سیستم اجازه میدهد تا سیاستهای مختلفی را برای انواع مختلف برنامهها (مثلاً پیشزمینه در مقابل پسزمینه) اعمال کند.

-
/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 منتقل شده است.
۴. تغییرات را مشاهده کنید
- MemoryLab را باز کنید.
- به مقدار
memory.currentتوجه کنید. - چندین بار روی «اختصاص حافظه جاوا (10 مگابایت)» ضربه بزنید.
- دوباره
memory.currentبخوانید. باید ببینید که با هر بار لمس، تقریباً 10 مگابایت افزایش مییابد. - روی «اختصاص بیتمپها» ضربه بزنید. برای مشاهده افزایش
anonmemory.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 ثبت کند.
- آمادهسازی : مطمئن شوید که MemoryLab مقداری تخصیص (جاوا و Bitmaps) دارد.
شروع ردیابی : از این پیکربندی ردیابی درونخطی برای ثبت رویدادهای زمانبندی، بازیابی و ذخیرهسازی صفحه استفاده کنید.
# 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فشار محرک و بازیابی :
- در MemoryLab، روی گزینه Allocate Native Memory (1GB) ضربه بزنید. این کار باعث ایجاد فشار در کل سیستم میشود.
در یک ترمینال جداگانه، ۲۰۰ مگابایت را بازیابی کنید:
adb shell "echo 200M > /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.reclaim"
بازگشت به خطا : به MemoryLab برگردید. روی Thrash Pagecache (تست خطا) ضربه بزنید.
توقف ردیابی : در ترمینال ردیابی، کلیدهای 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 →