در بخشهای قبلی دیدیم که هسته چگونه حافظه را در سراسر سیستم مدیریت میکند. در این فصل، عمیقتر به گروههای کنترل حافظه (memcg) میپردازیم، سازوکاری که Android برای پارتیشنبندی و کنترل استفاده از حافظه برای برنامههای مجزا استفاده میکند.
درک memcg بسیار مهم است زیرا در این سطح است که سیستم میتواند برنامههای جداگانه را برای بازیابی هدف قرار دهد یا محدودیتهای حافظه را برای آنها تنظیم کند، که این امر امکان مدیریت پیشگیرانه ردپای حافظه برنامه را فراهم میکند.
گروههای کنترل حافظه (memcg)
«گروههای کنترل» (cgroups) یکی از ویژگیهای هسته Linux است که امکان سازماندهی فرایندها در گروههای سلسلهمراتبی و توزیع منابع سیستم (مانند CPU، حافظه، و I/O) را در میان آنها فراهم میکند. memcg کنترلکننده cgroup مخصوص حافظه است.
مفاهیم کلیدی memcg
- Memcg Charge: وقتی فرایندی در memcg صفحه حافظهای را اختصاص میدهد (چه ناشناس یا با پشتیبانی فایل)، آن صفحه به memcg «شارژ» میشود. The total charge of a memcg is the sum of all pages used by all processes within it.
- حسابداری سلسلهمراتبی: استفاده از حافظه در بالای درخت حساب میشود. A charge in a child cgroup also counts towards the usage of its parent.
- محدودیتهای حافظه: هر memcg میتواند محدودیتهایی (مانند
memory.maxیاmemory.high) داشته باشد که درصورت فراتر رفتن از آنها، مستقل از حافظه سیستم جهانی، باعث فعال شدن بازیابی یا حتی OOM killer میشود. - Per-memcg Reclaim: وقتی memcg از حد مجاز خود فراتر میرود، یا وقتی سیستم به حافظه نیاز دارد، هسته میتواند memcg خاصی را برای بازیابی هدف قرار دهد. این یعنی صفحههای فایل آن را تخلیه میکند یا صفحههای ناشناس آن را به ZRAM منتقل میکند.
سلسلهمراتب memcg در Android
Android از سلسلهمراتب خاصی برای مدیریت فرایندهای برنامه استفاده میکند. این ساختار به سیستم امکان میدهد خطمشیهای متفاوتی را برای انواع مختلف برنامهها (مثلاً پیشزمینه درمقابل پسزمینه) اعمال کند.

/sys/fs/cgroup/apps/: ریشه همه برنامههای Android.uid_<UID>/: دایرکتوری برای «شناسه کاربر» هر برنامه. همه فرایندهای متعلق به یک بسته برنامه در این گروه مشترک هستند.pid_<PID>/: دایرکتوری برای هر فرایند جداگانه و هر فرزند منشعبشده از آن. این کار امکان کنترل دقیق و حسابداری برای برنامههای دارای چند فرایند را فراهم میکند.
تمرین عملی: کاوش memcg
در این تمرین، دایرکتوری memcg را برای MemoryLab پیدا خواهید کرد و شارژ حافظه آن را بهطور همزمان مشاهده خواهید کرد.
۱. راهاندازی MemoryLab
مطمئن شوید MemoryLab در دستگاهتان درحال اجرا باشد.
۲. یافتن memcg مربوط به MemoryLab
ابتدا، شناسه پردازش برنامه 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 v2، مسیر بعداز 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: مقدار حافظه ناشناس
(تودهها، پشتهها). * file: مقدار حافظه پشتیبانگیریشده با فایل (حافظه نهان صفحه). *
swap: مقدار حافظه مبادلهشده به ZRAM.
۴. مشاهده تغییرات
- MemoryLab را باز کنید.
- مقدار
memory.currentرا یادداشت کنید. - چندینبار روی اختصاص دادن حافظه Java (۱۰ مگابایت) تکضرب بزنید.
memory.currentرا دوباره بخوانید. باید ببینید که با هر ضربه، تقریباً ۱۰ مگابایت افزایش مییابد.- روی اختصاص دادن نگاشتهای بیتی تکضرب بزنید.
memory.statرا بررسی کنید تا افزایشanonرا ببینید.
بازپسگیری پیشکنشی با memory.reclaim
یکی از ویژگیهای قدرتمند memcg (v2) فایل memory.reclaim است. نوشتن مقدار
در این فایل به هسته دستور میدهد که فوراً تلاش کند آن مقدار
حافظه را از این memcg و هر memcg زیر آن پس بگیرد.
نحوه استفاده Android از memory.reclaim
CachedAppOptimizer در Android از این ویژگی استفاده میکند تا حداکثر حافظه را از برنامهها پساز مسدود شدن آنها بازیابی کند. App Freezer Android تضمین میکند که
برنامههای ذخیرهشده در حافظه نهان تا حد امکان کمترین میزان RAM را در زمانی که اجرا نمیشوند مصرف کنند. وقتی
برنامهای به پسزمینه منتقل میشود و منجمد میشود، سیستم میزان استفاده فعلی برنامه از حافظه را
در فایل 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 چند تخصیص (Java و 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، روی اختصاص دادن حافظه بومی (۱ گیگابایت) تکضرب بزنید. این کار باعث ایجاد فشار در سراسر سیستم میشود.
در پایانه جداگانهای، ۲۰۰ مگابایت را بازیابی کنید:
adb shell "echo 200M > /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.reclaim"
Fault Back In: به MemoryLab برگردید. روی Thrash Pagecache (Refault test) (Thrash Pagecache (آزمایش Refault)) تکضرب بزنید.
توقف ردیابی: کلیدهای Ctrl+C را در پایانه ردیابی فشار دهید.
درحال تجزیهوتحلیل بازپسگیری در Perfetto
وقتی ردیابی را باز میکنید، میتوانید هم بازیابی سراسری سیستم و هم بازیابی برای هر برنامه را در عمل ببینید:

در ردیابی بهدنبال موارد زیر بگردید:
- kswapd:
kswapd0را در بخش رشتههای هسته جستجو کنید (در نماگرفت بالا، بهصورت دستی در بالا سنجاق شده است). خواهید دید که بیدار میشود و اجرا میشود (بخشهای سبز) چون سیستم برای پیدا کردن صفحههای آزاد تلاش میکند. - Direct Reclaim: Look at the
com.android.memorylabprocess threads. برشهای بنفش رویداد ftrace (مانندmm_vmscan_direct_reclaim_begin) را خواهید دید که مستقیماً زیر قطعه زمانبندی رشته ظاهر میشوند. این نشان میدهد که رشته برنامه خودش متوقف شده است و منتظر است تا هسته صفحهها را آزاد کند. - شمارندههای RSS و Swap:
-
mem.rss.anon: با تکضرب زدن روی دکمههای تخصیص افزایش مییابد. mem.swap: بهطور پیوسته افزایش مییابد زیراkswapdو رشتههای خود برنامه (مشمول بازپسگیری مستقیم) آن صفحههای ناشناس را در ZRAM فشرده میکنند.
-
- memcg Reclaim: اگر روی لحظهای که بازیابی دستی را راهاندازی کردید بزرگنمایی کنید،
افت شدیدی را در هر دو
rss.anonوrss.fileخواهید دید که با رویدادهایmm_vmscan_memcg_reclaimهمراه است.
← در سراسر سیستم | ↑ بالا | تعامل kswapd و lmkd →