ميزانيات ذاكرة النظام

في الإصدار 17 من نظام التشغيل Android والإصدارات الأحدث، تتحكّم المنصة في ذاكرة عمليات النظام باستخدام آليتَين تكميليتَين من الإصدار الثاني من مجموعة التحكّم (cgroup) في نظام التشغيل Linux، وتعملان جنبًا إلى جنب مع محدِّد الذاكرة العام للتطبيقات:

  • الحدود الدنيا لحماية برامج daemon للنظام (SystemMemoryBudgetManager مع memory.min): تحمي حجوزات memory.min ذاتية الضبط في الإصدار الثاني من cgroup (تم ضبطها على %90 من مجموعة العمل المقيمة المستقرة لكل برنامج daemon وتم تحديد سقفها على مستوى العالم بنسبة% 10 من إجمالي ذاكرة الوصول العشوائي الفعلية) برامج daemon التفاعلية المهمة وعمليات واجهة الأوامر (system_server وsurfaceflinger وcomposer HAL للرسومات وaudioserver وcameraserver وnetd وSystemUI ومشغّل الشاشة الرئيسية) تحت ضغط الذاكرة، لتحل محل تثبيت ملفات PinnerService mlock الثابتة.
  • الحدود القصوى لذاكرة تطبيقات النظام (<memory-budget> مع memory.high): يتم تحديد الحدود المرنة memory.high لمجموعة التحكم cgroup v2 التي تمت معايرتها حسب حجم العمل (عادةً من 30 ميغابايت إلى 190 ميغابايت) في ملفات البيان لتطبيقات النظام الثابتة والتي تعمل في الخلفية (Bluetooth وNfc وCalendarProvider وTraceur وMediaProvider و:PhotoPicker وNetworkStack وAppSearch وTeleService) لاستعادة ذاكرة التخزين المؤقت لصفحات الملفات غير النشطة والكومة المجهولة المصدر بعد الاندفاع بشكل استباقي قبل حدوث ضغط على الذاكرة العامة.

يقارن الجدول التالي بين الآليتَين:

البُعد الحدود الدنيا لحماية برامج النظام الخفية الحدود القصوى لذاكرة تطبيقات النظام
خدمة المنصة SystemMemoryBudgetManager MemoryLimiter
التحكّم في Cgroup v2 الحد الأدنى الصارم للحماية: memory.min الحد الأقصى المرن لاسترداد الموارد: memory.high
العمليات المستهدَفة البرامج الخفية التفاعلية وواجهة مستخدم واجهة الأوامر التي تتأثر بوقت الاستجابة (system_server وsurfaceflinger وcomposer HAL وaudioserver وcameraserver وnetd وSystemUI وتطبيق Home) تطبيقات النظام الثابتة والتي لا تتضمّن واجهة مستخدم، وأدوات توفير المحتوى، والخدمات التي تُشغَّل في الخلفية (مثل Bluetooth وNfc وMediaProvider وNetworkStack وAppSearch وTeleService)
نموذج تحديد الحجم ضبط ذاتي في وقت التشغيل (‎90% من مجموعة العمل المستقرة، بحد أقصى ‎10% من إجمالي ذاكرة الوصول العشوائي على مستوى العالم) بدون جداول ضبط لكل جهاز سمات XML لبيان <memory-budget> التعريفي التي تمت معايرتها حسب فئة ذاكرة الوصول العشوائي (RAM) وحالة العملية وعدد وحدات بكسل الشاشة
الميزة الأساسية تمنع هذه الميزة حدوث أي خلل في واجهة المستخدم التفاعلية وتوقُّف التنشيط في حال الضغط على الذاكرة أثناء استبدال قوائم ملفات PinnerService mlock الثابتة تزيل هذه الميزة باستمرار ذاكرة التخزين المؤقت للملفات غير النشطة التي يتم استخدامها لمرة واحدة والذاكرة الديناميكية بعد زيادة الاستخدام، وذلك لتوفير المزيد من ذاكرة RAM الفعلية للتطبيقات التي تعمل في المقدّمة والتطبيقات المخزَّنة مؤقتًا

لمزيد من المعلومات حول بنية <memory-budget> وسمات XML وواجهات برمجة التطبيقات لوقت التشغيل واحتساب ذاكرة نظام التشغيل، يُرجى الاطّلاع على ضبط حدود استخدام الذاكرة في التطبيقات وفهم ذاكرة Android وتحديد مشاكلها وحلّها.

حماية برامج الخلفية التفاعلية المهمة باستخدام SystemMemoryBudgetManager

يحتفظ SystemMemoryBudgetManager بالعمليات التي تقع مباشرةً في مسار المستخدم التفاعلي في ذاكرة الوصول العشوائي (RAM) الفعلية أثناء ضغط الذاكرة. بدلاً من تثبيت قائمة ثابتة من ملفات APK وODEX وVDEX في الذاكرة باستخدام mlock (كما يفعل PinnerService لـ config_defaultPinnerServiceFiles و config_pinnerHomePinBytes)، يخصّص SystemMemoryBudgetManager الحد الأدنى لـ memory.min في الإصدار الثاني من مجموعة التحكم (cgroup) الديناميكية للعمليات التالية:

  • شريحة النظام الأساسية (/sys/fs/cgroup/system): system_server وsurfaceflinger و"طبقة تجريد الأجهزة" (HAL) لرسومات composer وaudioserver وcameraserver وnetd، بالإضافة إلى أي نظام أصلي خاص بعامل الشكل أو برامج الخلفية الخاصة بالمورّدين والمُدرَجة في config_systemMemoryBudgetAdditionalSystemProcesses (على سبيل المثال، android.hardware.perceptionhost وauracamerahal على أجهزة XR).
  • شريحة التطبيق التفاعلية (/sys/fs/cgroup/apps): العملية الرئيسية لحزمة SystemUI التي تم ضبطها (config_systemUi) والعملية الرئيسية لدور الشاشة الرئيسية للمستخدم الحالي.

إنّ حماية حامل دور "الصفحة الرئيسية" إلى جانب "config_systemUi" يضمن التغطية على جميع أشكال الأجهزة. على أشكال الأجهزة التي يكون فيها config_systemUi حزمة توافق خفيفة (مثل Wear OS، حيث يتم تشغيل خلفية شاشة الساعة والإشعارات والمربّعات داخل تطبيق Home)، لا يزال الغلاف الموجّه للمستخدمين يحصل على حماية كاملة لمجموعة العمل. إذا تم حلّ نية Home إلى حزمة config_systemUi، تتم حماية العملية مرة واحدة. تتلقّى العملية الرئيسية فقط لكل حزمة (processName == packageName) حدًا أدنى للحماية، وبالتالي لا تستهلك العمليات الفرعية المساعدة (مثل :screenshot) ميزانية الحماية.

سياسة الضبط الذاتي وتفويض cgroup على ثلاثة مستويات

بما أنّ مجموعة العمل لبرنامج daemon التفاعلي تختلف بشكل كبير حسب أشكال الأجهزة وسعات ذاكرة الوصول العشوائي (على سبيل المثال، يستخدم SystemUI حوالي 45 ميغابايت على ساعة ذكية، و60 ميغابايت على هاتف بسعة 6 غيغابايت، و180 ميغابايت على هاتف بسعة 16 غيغابايت)، فإنّ جداول البحث الثابتة لكل جهاز تكون غير مستقرة. بدلاً من ذلك، يستمدّ SystemMemoryBudgetManager الحدود الدنيا مباشرةً من البصمة المقيمة لكل عملية باستخدام ثلاثة ثوابت:

مَعلمة السياسة القيمة التلقائية السلوك
الحد الأدنى لكل عملية ‫90% من ملفات memory.current التي تمت تسويتها باستثناء ذاكرة التخزين المؤقت للملفات غير المرتبطة تحمي هذه الميزة الرموز البرمجية المجهولة المصدر في الذاكرة الديناميكية والذاكرة المؤقتة والذاكرة المخصّصة، مع ترك آخر% 10 من الرموز البرمجية الأقل استخدامًا قابلة للاسترداد، ما يتيح إزالة الصفحات غير المستخدَمة فعليًا. يتم طرح ذاكرة التخزين المؤقت للملفات غير المرتبطة (max(0, file - shmem - file_mapped) من memory.stat) قبل تطبيق نسبة% 90، وبالتالي لا تؤدي ذاكرة التخزين المؤقت لفحص الحزمة عند التشغيل أو قراءة مواد العرض لمرة واحدة إلى زيادة الحد الأدنى.
الحدّ الأقصى العام لذاكرة الوصول العشوائي (RAM) ‫% 10 من إجمالي ذاكرة الوصول العشوائي (config_systemMemoryBudgetLowKb) تتطابق مع حاجز الوقاية config_pinnerMaxPinnedMemoryPercentage في PinnerService. إذا تجاوز مجموع الحد الأدنى للسعر بنسبة% 90 نسبة% 10 من MemTotal، يتم خفض كل حد أدنى للسعر بشكل نسبي بالعامل نفسه.
وتيرة إعادة التحميل بعد ثانية واحدة و4 ثوانٍ و16 ثانية و64 ثانية من البدء، ثم كل 30 دقيقة تتقارب سلسلة هندسية من عمليات الاستقرار على مجموعة العمل لكل برنامج خفي بعد بدء التشغيل بدون ترميز تأخيرات الإحماء لكل جهاز، وتتتبّع عملية إعادة التحميل في الحالة الثابتة لمدة 30 دقيقة النمو التدريجي لمجموعة العمل وانكماشها على مدار عمر الجهاز.

في وحدة التحكّم في الذاكرة cgroup v2 في Linux (mm/memcontrol.c:effective_protection)، يكون الحد الأقصى للحماية الفعّالة memory.min لمجموعة cgroup فرعية محدودًا بالقيمة memory.min التي تم ضبطها على مجموعات cgroup الرئيسية والسابقة. يؤدي ضبط السياسة memory.min على دليل pid_<pid> فرعي فقط بينما تكون السياسة uid_<uid>/memory.min مضبوطة على 0 إلى عدم توفير أي حماية فعّالة. في المقابل، يؤدي ضبط قيمة ثابتة لـ memory.min على الشريحة /sys/fs/cgroup/system العليا فقط إلى توزيع الحماية غير المستخدَمة على جميع معرّفات UID الفرعية والعمليات بشكل متناسب (memory_recursiveprot)، ما يسمح للعمليات التشخيصية أو الثانوية التي تشارك uid_1000 (android.uid.system) بسحب الحماية من surfaceflinger وaudioserver.

لتوفير عزل دقيق لكل برنامج خفي، يكتب SystemMemoryBudgetManager الحماية على جميع المستويات الثلاثة للتسلسل الهرمي لـ cgroup v2 في كل عملية إعادة تحميل، كما هو موضّح في الشكل 1:

  1. عقدة عملية طرفية (pid_<pid>/memory.min): يتم ضبطها على الحد الأدنى لمجموعة العمل (المُعدَّلة) بنسبة% 90 للعملية.
  2. عقدة UID (uid_<uid>/memory.min): يتم ضبطها على المجموع الدقيق للحدود الدنيا pid_* المُدارة، وبالتالي فإنّ العمليات غير المُدارة التي تشارك معرّف UID هذا (مثل برامج AID_SYSTEM الخفية الأخرى أو حزم sharedUserId="android.uid.system") memory.min = 0 ولا يمكنها المطالبة بالحماية.
  3. الشريحة ذات المستوى الأعلى (/sys/fs/cgroup/system/memory.min و/sys/fs/cgroup/apps/memory.min): يتم ضبطها على المجموع الدقيق للشرائح الفرعية uid_*. تتم إزالة أي قيم memory.low قديمة في العُقد المُدارة إلى 0، وتتم إعادة ضبط أي أدلة uid_* تمت إدارتها في عملية إعادة تحميل سابقة (مثل مشغّل الصفحة الرئيسية السابق بعد تبديل المستخدم للمشغّلات) إلى 0.

التسلسل الهرمي للحماية memory.min في cgroup v2 المكوّن من ثلاثة مستويات في ‎ /sys/fs/cgroup/system و‎ /sys/fs/cgroup/apps، والذي يعرض الحد الأدنى للعمليات الفرعية المجمّعة في عُقد UID والشرائح، بينما تظل العمليات الشقيقة غير المحمية عند الصفر

الشكل 1: تفويض الحد الأدنى للحماية على ثلاثة مستويات عبر شرائح cgroup

سبب استخدام memory.min بدلاً من memory.low في نواة MGLRU

في إطار Multi-Generational LRU (MGLRU، وهو محرّك استعادة الصفحات التلقائي في Android 6.6 والنواة الأحدث)، يتصرف memory.low وmemory.min بشكل مختلف في mm/vmscan.c:shrink_one:

  • ‫memory.low (تأجيل لمرة واحدة): يتم نقل مجموعة التحكم التي يقل استخدامها عن memory.low إلى نهاية قائمة "الأقل استخدامًا مؤخرًا" (LRU) في memcg (MEMCG_LRU_TAIL) ويتم تخطّيها لمرة واحدة في عملية استرداد الذاكرة. في المرة التالية التي يواجه فيها النظام ضغطًا مستمرًا على الذاكرة، تستعيد النواة مجموعة التحكم بالكامل وتسجّل حدث MEMCG_LOW. في اختبارات الضغط على الأجهزة الفعلية مع زيادة استهلاك الذاكرة المجهولة المصدر إلى% 85 من ذاكرة الوصول العشوائي الفعلية، سمح memory.low لـ SystemUI على ساعة ذكية بالانخفاض من حوالي 45 ميغابايت إلى حوالي 5 إلى 10 ميغابايت وaudioserver على جهاز قابل للطي بسعة 16 غيغابايت بالانخفاض من 15 ميغابايت إلى 2 ميغابايت مقابل حد أدنى يبلغ حوالي 14 ميغابايت.
  • memory.min (الحد الأدنى الصارم للحماية): يتم تخطّي مجموعة التحكّم التي يقل استخدامها عن memory.min من خلال عملية الاسترداد الشاملة. كما هو موضّح في الشكل 2، في ظل عبء العمل نفسه الذي يبلغ% 85 من ضغط ذاكرة الوصول العشوائي، يتخلص memory.min من أبرد% 10 فقط من كل برنامج خفي ويحتفظ بـ SystemUI وsurfaceflinger وaudioserver عند الحد الأدنى البالغ% 90 بدون أي أخطاء مجهولة المصدر وبدون زيادة في إنهاء عمليات lmkd.

يوفّر memory.min حجزًا ماديًا قويًا يطابق حماية تثبيت PinnerService mlock الذي يحلّ محله، مع ميزة أنّ LRU للنواة (بدلاً من قائمة ملفات ثابتة) يختار صفحات الرموز البرمجية النشطة والكومة والرسومات التي تظل مقيمة.

مقارنة بين الذاكرة المقيمة لبرنامج daemon مع زيادة ضغط ذاكرة النظام من% 40 إلى% 95 من ذاكرة الوصول العشوائي الفعلية، ما يُظهر ثبات memory.min عند الحد الأدنى البالغ% 90 بينما تنهار memory.low بعد تمرير تأجيل واحد لـ MGLRU

الشكل 2: الاحتفاظ بمجموعة العمليات الخفية في ظل تزايد الضغط على الذاكرة

الإحماء المسبق لعملية التشغيل واستعادة ذاكرة التخزين المؤقت للملفات غير المعيّنة بعد التشغيل

يتطلب استبدال mlock الدائم لـ PinnerService في config_defaultPinnerServiceFiles تنسيق سلوكين في وقت التشغيل:

  • التشغيل المُسبَق لعملية التشغيل في الخلفية لمرة واحدة (PinnerService): يتم تخطّي PinnerService mlock بدون تشغيل ذاكرة التخزين المؤقت للصفحة، ما يؤدي إلى نقل حوالي 95 ميغابايت من SystemUI APK وODEX وVDEX إلى مسار التشغيل المتزامن عند بدء تشغيل SystemUI (OnBootPhase_550)، ما يضيف من 300 إلى 450 ملي ثانية إلى وقت اكتمال التشغيل. لا يكفي استدعاء madvise(MADV_WILLNEED) لأنّ force_page_cache_ra في النواة يثبّت استدعاء MADV_WILLNEED واحدًا في نافذة قراءة مسبقة واحدة (عادةً من 128 كيلوبايت إلى 2 ميغابايت). بدلاً من ذلك، عندما يكون SystemMemoryBudgetManager.shouldSkipDefaultSystemPinning نشطًا، يربط PinnerService كل ملف في config_defaultPinnerServiceFiles بسلسلة مهام في الخلفية أثناء onStart، ويملأ الملف بشكل متزامن في ذاكرة التخزين المؤقت للصفحة باستخدام Os.mlock، ويستدعي على الفور Os.munlock وmunmap وclose. يحافظ ذلك على وقت التشغيل السريع مع ترك صفر بايت مقفل بشكل دائم حتى يمكن إزالة صفحات بدء التشغيل على البارد لاحقًا.
  • استبعاد ذاكرة التخزين المؤقت لفحص حِزم التشغيل في system_server واستعادتها: أثناء التشغيل، يفحص PackageManagerService ملفات APK المثبَّتة، ما يترك من 1.5 إلى 2.5 غيغابايت من ذاكرة التخزين المؤقت لصفحات الملفات غير المعيّنة التي يتم تحميلها إلى مجموعة التحكّم (cgroup) في system_server. إذا تم تضمينها في عملية حساب memory.min لـ system_server، فإنّ ذاكرة التخزين المؤقت للتشغيل العادي تستنفد الحد الأقصى العام لذاكرة الوصول العشوائي (RAM) الذي يبلغ% 10 وتخفّض بشكل متناسب الحدود الدنيا للحماية في surfaceflinger وSystemUI وaudioserver. لتجنُّب مشكلة نقص الحد الأقصى، يقرأ SystemMemoryBudgetManager memory.stat لـ system_server، ويحسب ذاكرة التخزين المؤقت للملفات غير المرتبطة على أنّها max(0, file - shmem - file_mapped)، ويطرح ذاكرة التخزين المؤقت غير المرتبطة من memory.current قبل حساب الحد الأدنى البالغ% 90، كما هو موضّح في الشكل 3. بالإضافة إلى ذلك، بعد 10 ثوانٍ من PHASE_BOOT_COMPLETED، ينفّذ المدير عملية كتابة لمرة واحدة لـ <unmapped_file> swappiness=0 في عقدة memory.reclaim من system_server على Linux 6.2 والنواة الأحدث لإزالة ذاكرة التخزين المؤقت لفحص الحزمة غير النشطة بشكل استباقي بدون تبديل الذاكرة الديناميكية المجهولة أو إزالة صفحات DEX والرموز التي تم ربطها. في النواة القديمة (مثل android14-6.1) التي ترفض swappiness=0 مع EINVAL، يتخطّى المدير عملية كتابة memory.reclaim بدلاً من الرجوع إلى عملية كتابة غير مؤهَّلة لاستعادة المساحة، والتي من شأنها تبديل كومة الذاكرة المؤقتة المجهولة أثناء التشغيل.

مخطط زمني لذاكرة system_server بعد التشغيل يعرض عمليات إعادة تحميل التسوية الهندسية عند 1 و4 و16 و64 ثانية، واستبعاد ذاكرة التخزين المؤقت لفحص حزم التشغيل غير المرتبطة من الحد الأدنى لذاكرة ‎90% memory.min، واسترداد الذاكرة لمرة واحدة باستخدام swappiness=0

الشكل 3: تقارب الحد الأدنى للحماية واستعادة ذاكرة التخزين المؤقت بعد التشغيل

متطلبات النواة وتراكبات إعدادات الجهاز

يعتمد SystemMemoryBudgetManager على mem_cgroup_calculate_protection في نواة Linux لنشر memory.min عبر مجموعات التحكم المُدمجة. تتضمّن نواة Android الشائعة android15-6.6 وandroid16-6.12 دعمًا كاملاً للحماية المتداخلة من خلال MGLRU. تتطلّب الأجهزة التي تعمل بنظام التشغيل android14-6.1 مع تفعيل ميزة MGLRU ثلاثة منافذ خلفية في mm/vmscan.c وmm/memcontrol.c، بحيث تنشر مجموعات التحكم (cgroups) الفارغة في الشرائح المتوسطة ومعرّفات UID الحماية إلى مجموعات التحكم (cgroups) في عمليات الأوراق:

  • ‫30d77b7eef01 ‎(mm/mglru: fix ineffective protection calculation)
  • ‫8aa420617918 ‎(mm/mglru: respect min_ttl_ms with memcgs)
  • ‫adb8213014b2 ‎(mm: memcg: fix stale protection of reclaim target memcg)

يمكن للمصنّعين الأصليين للأجهزة تخصيص ميزة "SystemMemoryBudgetManager" أو إيقافها باستخدام تراكبات موارد إطار العمل التالية:

  • ‫config_systemMemoryBudgetLowKb (عدد صحيح، كيلوبايت): يحدّد 0 (تلقائي) مجموع جميع الحدود الدنيا لـ memory.min عند% 10 من إجمالي ذاكرة الوصول العشوائي (RAM) الفعلية. تؤدي القيمة الموجبة إلى ضبط حد أقصى صريح بالكيلوبايت، بينما تؤدي القيمة السالبة إلى إيقاف SystemMemoryBudgetManager على الجهاز (مع الاحتفاظ بتثبيت mlock الثابت القديم في PinnerService).
  • ‫config_systemMemoryBudgetHighKb (عدد صحيح، كيلوبايت): 0 (تلقائي) يترك /sys/fs/cgroup/system/memory.high عند max. تحدّد القيمة الموجبة الحد الأقصى للشريحة العليا memory.high (يجب أن تكون أكبر من أو تساوي الحد الأدنى للسعر).
  • ‫config_systemMemoryBudgetAdditionalSystemProcesses (مصفوفة السلاسل النصية): تعرض أسماء العمليات الخفية الإضافية للنظام الأصلي أو المورّد التي تتأثر بوقت الاستجابة في /sys/fs/cgroup/system (تتم مطابقتها مع /proc/<pid>/comm أو الاسم الأساسي لـ argv[0] في /proc/<pid>/cmdline لمعرّفات المستخدمين للنظام والمورّد ضمن 10000) لحمايتها باستخدام الحد الأدنى من memory.min بنسبة% 90 إلى جانب العمليات الخفية الست التلقائية للنظام. استخدِم هذا التراكب بدلاً من ضبط memory.min في init.rc لأنّ SystemMemoryBudgetManager يعيد احتساب uid_<uid>/memory.min كمجموع عمليات الأوراق المُدارة ويمحو عُقد uid_* غير المُدارة عند كل إعادة تحميل. على سبيل المثال، يمكن لجهاز XR حماية برامج تتبُّع المواقع المكانية وبرامج HAL للكاميرا باستخدام التراكب التالي:

    <string-array translatable="false" name="config_systemMemoryBudgetAdditionalSystemProcesses">
        <item>android.hardware.perceptionhost</item>
        <item>auracamerahal</item>
    </string-array>
    
  • ‫config_systemUi (سلسلة): اسم حزمة تطبيق SystemUI للنظام الأساسي، يتم حله باستخدام MATCH_SYSTEM_ONLY.

لفحص عُقد SystemMemoryBudgetManager floors وcgroup النشطة على جهاز، شغِّل:

# Inspect policy state, resolved PIDs, and applied floors in dumpsys meminfo
adb shell dumpsys meminfo | sed -n '/System Memory Budgets/,$p'
# Inspect raw cgroup v2 memory.min nodes across the system and apps slices
adb shell head -n 1 \
  /sys/fs/cgroup/system/memory.min \
  /sys/fs/cgroup/system/uid_*/memory.min \
  /sys/fs/cgroup/system/uid_*/pid_*/memory.min \
  /sys/fs/cgroup/apps/memory.min \
  /sys/fs/cgroup/apps/uid_*/memory.min \
  /sys/fs/cgroup/apps/uid_*/pid_*/memory.min

ربط تطبيقات النظام الثابتة والتي تعمل في الخلفية بميزانيات الذاكرة

على الرغم من أنّ SystemMemoryBudgetManager يحمي برامج الخلفية التفاعلية من استعادة الموارد باستخدام الحد الأدنى من memory.min، فإنّ العديد من تطبيقات النظام الأخرى وخدمات النظام الأساسي المميّزة تعمل في الخلفية لفترات طويلة كعمليات PERSISTENT أو خدمات تعمل في المقدّمة أو أدوات توفير المحتوى التي يتم الاستعلام عنها بشكل متكرر. بدون حدود قصوى صريحة للذاكرة، لا يتم تقييد هذه العمليات إلى أن تصل إلى حدود محدِّد الذاكرة التي تبلغ عدة غيغابايت (مثل 4 غيغابايت إلى 10 غيغابايت للتطبيقات المرئية و2 غيغابايت إلى 5 غيغابايت للتطبيقات التي تعمل في الخلفية) أو إلى أن يؤدي الضغط العام على الذاكرة إلى تشغيل نشاط kswapd و"أداة إنهاء العمليات عند انخفاض الذاكرة" (lmkd) على مستوى النظام.

إذا لم يتم تقييد عمليات النظام في الخلفية، فإنّها غالبًا ما تجمّع كميات كبيرة من الذاكرة غير المستخدَمة التي لا تشكّل جزءًا من مجموعة العمل النشطة:

  • ذاكرة التخزين المؤقت لصفحات الملفات غير المحدودة: الخدمات التي تفهرس الوسائط أو تبحث في قواعد البيانات أو تزامِن جداول التقويم ومقدّمي الخدمات (مثل AppSearch وMediaProvider وCalendarProvider) ترسم خرائط أو تبث من خلال ملفات كبيرة على القرص. بدون حدّ أقصى لمجموعة التحكّم memory.high، تظلّ الصفحات النظيفة المستندة إلى الملفات من عمليات القراءة لمرة واحدة تُحتسب ضمن مجموعة التحكّم للعملية إلى أجل غير مسمى، ما يستهلك مئات الميغابايت من ذاكرة الوصول العشوائي (RAM) الفعلية.
  • الاحتفاظ بالكومة المجهولة المصدر بعد الارتفاع المفاجئ: الخدمات التي تشهد ارتفاعات مفاجئة عابرة في النشاط، مثل Bluetooth الارتفاعات المفاجئة في الصوت واكتشاف BLE، أو NetworkStack التحقّق من صحة بروتوكول أمان طبقة النقل (TLS) للمدخل المشروط الوصول إليه، أو انتقال مساعدي واجهة المستخدم إلى الخلفية، يمكنها الاحتفاظ بصفحات الكومة المجهولة المصدر غير النشطة لفترة طويلة بعد اكتمال الارتفاع المفاجئ.

Applying calibrated <memory-budget> declarations (memory.high soft limits, typically 30 MB to 190 MB) to system apps provides two primary benefits:

  • استعادة الصفحات بشكل استباقي ومحلي: عندما تتجاوز عملية النظام ميزانية memory.high التي تم ضبطها، توجّه نواة Linux استعادة الصفحات تحديدًا إلى مجموعة التحكم هذه، وتطرد الصفحات الباردة المدعومة بالملفات أولاً وتضغط الصفحات المجهولة غير النشطة إلى ZRAM بدون انتظار ضغط ذاكرة الجهاز العامة.
  • توفير المزيد من ذاكرة الوصول العشوائي (RAM) للتطبيقات التي تعمل في المقدّمة ومجموعة التطبيقات المخزَّنة مؤقتًا: يؤدي تقييد ذاكرة التخزين المؤقت للملفات غير النشطة في خدمات النظام التي تعمل دائمًا إلى استعادة عشرات إلى مئات الميغابايت من ذاكرة الوصول العشوائي (RAM) الفعلية على الجهاز، ما يقلّل من تأخُّر واجهة المستخدم، ويخفّف الضغط على تشغيل الكاميرا، ويحافظ على نشاط المزيد من تطبيقات المستخدم في الحالة المخزَّنة مؤقتًا.

ضبط ميزانيات الذاكرة لتطبيقات النظام

تستخدم تطبيقات النظام بنية بيان <memory-budget> نفسها، وتتالي حالة العملية، وتغيير حجم كثافة العرض، وقاعدة حلّ عبارة المطابقة الأخيرة مثل التطبيقات العامة. للاطّلاع على القائمة الكاملة للسمات، يُرجى مراجعة مرجع سمات XML.

يمكن للمصنّعين الأصليين للأجهزة الإعلان عن عناصر <memory-budget> مباشرةً داخل ملف AndroidManifest.xml الخاص بتطبيق النظام أو دمجها من ملف XML منفصل باستخدام السمة additional_manifests في Android.bp. وفقًا للعرف، يخزّن مشروع Android المفتوح المصدر (AOSP) ميزانيات تطبيقات النظام المرجعية في ملفات *-budget.xml مستقلة (إما في frameworks/base/core/memory_budgets/ لحِزم المنصة الأساسية أو إلى جانب الوحدات الفردية مثل packages/modules/NetworkStack/memory_budgets/ وpackages/modules/AppSearch/apk/ وpackages/services/Telephony/) ويرفقها بـ additional_manifests:

android_app {
    name: "TeleService",
    manifest: "AndroidManifest.xml",
    additional_manifests: ["Telephony-budget.xml"],
    // ...
}

تحديد النطاق على مستوى الحزمة (UID) مقابل مستوى العملية (PID)

يحدّد ما إذا كنت تعلن عن ميزانية أساسية على مستوى الحزمة (<application>) أو ميزانية متعددة العمليات (<processes><process>) ما إذا كانت النواة تفرض memory.high على مستوى مجموعة التحكم في معرّف المستخدم (/sys/fs/cgroup/uid_<uid>/memory.high) أو على مستوى مجموعة التحكم في معرّف العملية (/sys/fs/cgroup/uid_<uid>/pid_<pid>/memory.high)، كما هو موضّح في الشكل 4:

  • الحدّ الأقصى على مستوى الحزمة (<application>): يُستخدَم عندما يشغّل ملف APK عملية واحدة ضمن معرّف UID مخصّص له (على سبيل المثال، Bluetooth أو Nfc أو NetworkStack أو CalendarProvider أو Traceur أو AppSearch).
  • الحدّ الأقصى المسموح به على مستوى العملية (<processes><process>): يُستخدَم عندما تشارك حزمة APK android:sharedUserId (مثل android.uid.phone مع معرّف UID 1001 أو android.uid.system مع معرّف UID 1000) مع حِزم النظام الأخرى حتى لا يتم تقييد العمليات الأخرى التي تشارك معرّف UID هذا معًا، أو عندما تشغّل حزمة APK واحدة عمليات متعدّدة بأدوار ذاكرة مختلفة (على سبيل المثال، برنامج الخلفية الخفي MediaProvider والعملية الفرعية لواجهة المستخدم :PhotoPicker).

مقارنة بين التسلسل الهرمي لـ Cgroup v2 الذي يفرض حدًا أقصى لاستخدام الذاكرة على مستوى معرّف المستخدم (UID) لمعرّفات المستخدمين المخصّصة، والتسلسل الهرمي الذي يفرض حدًا أقصى لاستخدام الذاكرة على مستوى معرّف العملية (PID) لمعرّفات المستخدمين المشتركة في النظام وحِزم APK المتعدّدة العمليات

الشكل 4. الحدود القصوى للذاكرة على مستوى UID مقابل مستوى PID

يوضّح المثال التالي MediaProvider-budget.xml، الذي يحدّد نطاق الميزانيات المستندة إلى ذاكرة الوصول العشوائي (RAM) لعملية MediaProvider التلقائية (<process>) وميزانية مستندة إلى حجم العرض لعملية واجهة المستخدم :PhotoPicker. تتطابق كل سمة android:maxDeviceRamMb مع الأجهزة التي يكون إجمالي ذاكرة الوصول العشوائي (RAM) الفعلية (MemTotal) فيها أقل من أو يساوي الحدّ الأدنى المحدّد بالميغابايت (أو تنطبق على أي سعة لذاكرة الوصول العشوائي عند حذفها). يتم ترتيب البنود من التلقائي غير المقيّد إلى أصغر حدّ maxDeviceRamMb بحيث تختار قاعدة حلّ البند الأخير المطابق أضيق فئة تنطبق:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.android.providers.media.module">
    <uses-sdk android:targetSdkVersion="10000" />
    <application>
        <processes>
            <process>
                <memory-budget
                    android:maxMb="85" />
                <memory-budget
                    android:maxMb="75"
                    android:maxDeviceRamMb="12288" />
                <memory-budget
                    android:maxMb="60"
                    android:maxDeviceRamMb="8192" />
            </process>
            <process android:process=":PhotoPicker">
                <memory-budget
                    android:maxMb="85"
                    android:additionalBytesPerDisplayPixel="25" />
            </process>
        </processes>
    </application>
</manifest>

كيفية معايرة الميزانيات المرجعية لنظام Android المفتوح المصدر (AOSP) على أجهزة Google Pixel

تمت معايرة ميزانيات ذاكرة تطبيقات النظام المرجعية في مشروع Android المفتوح المصدر (AOSP) على مستوى أجهزة Google Pixel التي تتراوح سعة ذاكرة الوصول العشوائي (RAM) الفعلية فيها بين 6 غيغابايت و16 غيغابايت (بما في ذلك الهواتف والأجهزة القابلة للطي والأجهزة اللوحية بسعة 6 غيغابايت و8 غيغابايت و12 غيغابايت و16 غيغابايت) ودقة العرض التي تتراوح بين 1.2 ميغابكسل تقريبًا (720×1600) و4.5 ميغابكسل تقريبًا (2076×2152).

جمعت المعايرة بين مصدرَي بيانات متكاملَين: بيانات القياس عن بُعد على مستوى الأسطول في حقل cgroup v2، وعمليات فحص memory.high للأجهزة الفعلية الخاضعة للتحكّم في رحلات المستخدم الأساسية (CUJ).

تحليل بيانات القياس عن بُعد في cgroup v2 على مستوى الأسطول

تم جمع لقطات دورية من الإصدار الثاني من cgroup من جميع الأجهزة وتقسيمها حسب فئة ذاكرة الوصول العشوائي (RAM) الفعلية (أقل من أو تساوي 8 غيغابايت، و12 غيغابايت، وأكبر من أو تساوي 16 غيغابايت) ومجموعة حالة العملية (foreground وperceptible وbackground وcached). تم الاستناد إلى ستة مبادئ في التحليل الإحصائي:

  • تقسيم memory.current إلى مجموعة عمل مقابل ذاكرة تخزين مؤقت للملفات قابلة للاسترداد: بما أنّ memory.high يتحكّم في شحن مجموعة التحكم (cgroup) المقيمة (memory.current)، تم تقسيم كل لقطة باستخدام memory.stat إلى كومة مجهولة (anon)، وصفحات مجهولة تم تبديلها (swap)، وذاكرة تخزين مؤقت للصفحات المستندة إلى الملفات (file)، كما هو موضّح في الشكل 5. يحدّد إجمالي الطلب المجهول غير القابل للضغط (anon + swap في P99) الحد الأدنى لمجموعة العمل للميزانية حتى لا تتعرّض العملية لخطر استنفاد ZRAM، بينما تشير الفجوة الكبيرة بين memory.current P99 وanon + swap P99 إلى ذاكرة التخزين المؤقت لصفحة الملف التي يمكن لـ memory.high استردادها، بشرط أن تكون ذاكرة التخزين المؤقت غير معيّنة وغير نشطة.
  • مراعاة الذاكرة التي لا يمكن لـ "memory.high" استعادتها: ليست كل الملفات والذاكرة المشتركة عبارة عن ذاكرة تخزين مؤقت غير نشطة. الصفحات المقفلة (unevictable في memory.stat، مثل الذاكرة المشتركة المقفلة باستخدام mlock أو ذاكرة الوصول العشوائي (RAM) المقفلة للضيف في جهاز افتراضي يتم تحميل عمليات المضيف الخاصة به على مجموعة التحكم (cgroup) الخاصة بالتطبيق) تضع حدًا أدنى صارمًا ضمن الميزانية، وغالبًا ما تكون الصفحات المعيّنة (file_mapped) جزءًا من مجموعة العمل النشطة. يُرجى التحقّق من unevictable وshmem وfile_mapped قبل التعامل مع ذاكرة الملفات على أنّها قابلة للاسترداد. على سبيل المثال، يتم قفل حوالي 100 ميغابايت من ذاكرة AppSearch ولا يمكن إخلاؤها، لذا تغطي ميزانيتها هذا الحد الأدنى بالإضافة إلى مجموعة العمل المضيفة النشطة، مع توفير مساحة لنمو المنطقة المقفلة.
  • فلترة التخفيف في حالة عدم النشاط: تقضي أدوات توفير المحتوى (CalendarProvider) وأدوات المطوّرين عند الطلب (Traceur) من% 70 إلى% 99.9 من لقطات المجال في حالة عدم النشاط عند حجم يتراوح بين 1 ميغابايت و3 ميغابايت تقريبًا. يؤدي احتساب النسب المئوية الأولية في جميع اللقطات إلى تقليل تقديرات P95 وP99 بشكل كبير. بدلاً من ذلك، تم احتساب النسب المئوية وفواصل الثقة على اللقطات النشطة غير المخففة (مع استبعاد لقطات CACHED واللقطات المرجعية غير النشطة).
  • استخدام فواصل الثقة المجمّعة حسب الجهاز: نظرًا لأنّ الجهاز الواحد يساهم في العديد من اللقطات الدورية بمرور الوقت، فإنّ التعامل مع اللقطات كعيّنات مستقلة يقلل من عدم اليقين في الذيل. تم تقييم جميع تقديرات مجموعة العمل P95 وP99 باستخدام فواصل ثقة متحفظة بنسبة %95 مجمّعة حسب الجهاز (باستخدام عدد الأجهزة المميّزة N_dev كحجم العينة الفعلي).
  • مراعاة الارتفاعات المفاجئة المؤقتة في استخدام الذاكرة من خلال memory.peak: قد لا ترصد عملية أخذ العينات الدورية الارتفاعات المفاجئة القصيرة الأجل في استخدام الذاكرة (مثل NetworkStack عملية التحقّق من بروتوكول أمان طبقة النقل (TLS) في المدخل المشروط الوصول إليه وعمليات فحص نظام أسماء النطاقات المتزامنة، أو Bluetooth عملية إعداد ملف توزيع الصوت المتقدّم (A2DP) وتكنولوجيا BLE). بالنسبة إلى الخدمات التي تتطلّب موارد كثيرة، تم تحديد حجم الميزانيات لتغطية الشريحة المئوية من 90 إلى 95 من الحد الأقصى لاستخدام memory.peak في النواة طوال فترة الاستخدام على مستوى فئات ذاكرة الوصول العشوائي.
  • تأكَّد من أنّ فئة ذاكرة الوصول العشوائي (RAM) تعكس ذاكرة الوصول العشوائي، وليس جيل الجهاز: تميل الأجهزة الأحدث إلى أن تحتوي على المزيد من ذاكرة الوصول العشوائي والمزيد من الميزات، لذا فإنّ مجموعة العمل التي تزداد مع فئة ذاكرة الوصول العشوائي قد تزداد في الواقع مع الميزات. قبل إضافة فئة maxDeviceRamMb، يمكنك تقسيم البيانات حسب طراز الجهاز. على سبيل المثال، يصل تطبيق "com.android.phone" إلى قيمة P99 نفسها في "anon + swap" (من 85 إلى 90 ميغابايت تقريبًا) على الهواتف التي تتضمّن ذاكرة وصول عشوائي بسعة 12 غيغابايت و16 غيغابايت من الجيل نفسه، بينما يستخدم هاتف قديم قابل للطي يتضمّن ذاكرة وصول عشوائي بسعة 12 غيغابايت 40 ميغابايت. وبالتالي، تستخدم ميزانيتها قيمة واحدة بدون فئة ذاكرة وصول عشوائي (RAM)، حتى لا يتم تخصيص ميزانية أقل من اللازم لجهاز مزوّد بذاكرة وصول عشوائي أقل ويوفّر ميزات الاتصال الهاتفي الأحدث.

تحليل ذاكرة cgroup بمرور الوقت إلى مجموعة عمل مجهولة غير قابلة للضغط، ونافذة ذاكرة تخزين مؤقت للملفات النشطة، وذاكرة تخزين مؤقت للملفات غير النشطة التي يتم استخدامها لمرة واحدة والتي تتم استعادتها بعد تجاوز memory.high

الشكل 5: مجموعة العمل مقابل ذاكرة التخزين المؤقت للملفات القابلة للاسترداد

رحلة المستخدم الأساسية (CUJ) على الجهاز الفعلي وعمليات فحص تغيير حجم العرض

للتحقّق من صحة الميزانيات المستمَدة من بيانات القياس عن بُعد ومعايرة الميزانيات المتناسبة مع الشاشة (مثل :PhotoPicker) والميزانيات المخصّصة للخلفية حسب الحالة (مثل Traceur)، تم تنفيذ عمليات فحص memory.high مباشرة على أجهزة فعلية تتراوح سعة ذاكرة الوصول العشوائي (RAM) فيها بين 6 غيغابايت و16 غيغابايت:

  • العثور على نقطة الانحناء التي لا تتضمّن أخطاء في الذاكرة: أثناء تشغيل رحلات المستخدمين المهمة الشاملة (مثل التمرير السريع لشبكة الصور وتصفُّح الألبومات والتمرير السريع لمعاينة الصور والفيديوهات بدقة عالية في وضع ملء الشاشة وتسجيل عمليات التتبُّع وحفظها)، تم خفض memory.high أثناء قياس عدّادات memory.stat لمجموعة التحكم (workingset_refault_anon وworkingset_refault_file وpgscan وpgsteal) وmemory.events (high). كما هو موضّح في الشكل 6، تقع الميزانية المثالية فوق نقطة الانحناء التي لا تتضمّن أخطاء في الذاكرة مباشرةً. نقطة الانحناء لعدم حدوث أخطاء في الذاكرة هي الحدّ الذي تستعيد عنده النواة ذاكرة التخزين المؤقت للملفات غير النشطة التي يتم استخدامها لمرة واحدة (pgsteal > 0) بدون بدء عملية تبديل نشطة لمجموعة العمل (workingset_refault == 0).
  • التحقّق من صحة تغيير حجم بكسل الشاشة على مستوى درجات الدقة: أكّد الاختبار :PhotoPicker على مستوى أربع درجات دقة شاشة فعلية ومحاكاة من ~1.2 ميغا بكسل (720×1600) إلى ~4.5 ميغا بكسل (2076×2152 شاشة داخلية غير مطوية) أنّ مجموعة العمل النشطة لصور Glide وViewPager2 النقطية تتغيّر بشكل خطي مع عدد بكسل الشاشة. يختلف أيضًا الحدّ الأساسي لعدم العرض حسب الجهاز: عند عدد وحدات البكسل نفسه تقريبًا، كان الحدّ الأدنى لعدم إعادة التحميل حوالي 140 ميغابايت على الشاشة الخارجية لجهاز قابل للطي بسعة 16 غيغابايت مقابل 110 إلى 125 ميغابايت على هاتف بسعة 6 غيغابايت. يظل الحد الأدنى البالغ 85 ميغابايت بالإضافة إلى 25 بايت لكل بكسل في الشاشة (android:maxMb="85" وandroid:additionalBytesPerDisplayPixel="25") أعلى من نقطة الانحناء التي تم قياسها على كل شاشة تم اختبارها (حوالي 110 ميغابايت على شاشات HD+‎ وحوالي 145 ميغابايت على شاشات FHD+‎ وحوالي 190 ميغابايت على الشاشات غير المطوية).
  • التحقّق من عملية "تجميع الموارد غير الصالحة" في وقت تشغيل Android في حالات الخلفية: في الإصدار الثاني من cgroup، تظل صفحات صور إطار العمل وboot.art التي تتم مشاركتها من خلال Zygote مشحونة إلى cgroup في Zygote ويتم التعامل معها كمساحة محصّنة أثناء عملية "تجميع الموارد غير الصالحة" المتزامنة في وقت تشغيل Android. أكّد اختبار الضغط أنّه عندما ينتقل تطبيق واجهة مستخدم النظام إلى background، فإنّ تضييق memory.high (على سبيل المثال، إلى 30 ميغابايت على Traceur) يحدّ بشكل واضح من الذاكرة الديناميكية الخاصة وذاكرة التخزين المؤقت للصفحة بدون التسبّب في حدوث تكرار في تجميع المهملات أو نشاط المبادلة.

رسم بياني يوضّح استرداد ذاكرة التخزين المؤقت غير النشطة (pgsteal) والاحتدام لمجموعة العمل (workingset_refault) مع انخفاض memory.high تدريجيًا أثناء رحلة المستخدم الأساسية (CUJ)، مع إبراز نقطة الانحناء التي تشير إلى عدم حدوث أخطاء في المرجع والميزانية المستهدفة

الشكل 6: العثور على نقطة الانحناء التي لا تتضمّن أي أخطاء في الذاكرة أثناء رحلة المستخدم الأساسية

ملخّص ميزانيات تطبيقات النظام المرجعية في مشروع Android المفتوح المصدر (AOSP)

يلخّص الجدول التالي الميزانيات المرجعية التي تمت معايرتها في مشروع Android المفتوح المصدر (AOSP) على مستوى فئات ذاكرة الوصول العشوائي وحالات العمليات:

حزمة أو عملية تابعة للنظام النطاق ذاكرة وصول عشوائي (RAM) بسعة 8 غيغابايت أو أقل ذاكرة وصول عشوائي (RAM) بسعة 12 غيغابايت ذاكرة وصول عشوائي (RAM) بسعة 16 غيغابايت أو أكثر برنامج التشغيل الأساسي للمعايرة
com.android.bluetooth رقم التعريف الفريد (<application>) ‫80 ميغابايت ‫95 ميغابايت ‫110 ميغابايت يغطي هذا الإذن عمليات الاندفاع في memory.peak خلال عمر P90 (من 75 إلى 110 ميغابايت تقريبًا) و >2x من memory.current النشط في P99.
com.android.nfc UID (<application>) ‫50 ميغابايت ‫60 ميغابايت ‫60 ميغابايت تغطي هذه الميزة وظيفة محاكاة البطاقة المُضيفة النشطة P99 (ما يصل إلى 50 ميغابايت تقريبًا) وP90 إلى P95 memory.peak أثناء تقليص ذاكرة التخزين المؤقت للملفات غير النشطة.
com.android.providers.calendar UID (<application>) ‫70 ميغابايت تلقائيًا (في المقدّمة)
‫50 ميغابايت في perceptible وbackground
مجموعة العمل النشطة لا تتغيّر في جميع فئات ذاكرة الوصول العشوائي (RAM). تحتفظ ميزانية الخلفية بمساحة تخزين مجهولة الهوية للتنبيهات التذكيرية أثناء تقليص ذاكرة التخزين المؤقت لصفحة SQLite أثناء المزامنة.
com.android.traceur رقم التعريف الفريد (<application>) 85 ميغابايت تلقائيًا (في المقدّمة ويمكن ملاحظته)
30 ميغابايت background
يحافظ على مساحة تخزين كاملة تبلغ 85 ميغابايت أثناء تسجيل التتبُّع النشط وتصديره (TraceService)، مع تحديد الحد الأقصى لحالة عدم النشاط والبث عند 30 ميغابايت.
com.android.providers.media.module (الرئيسي) رقم تعريف المنتج (<process>) ‫60 ميغابايت ‫75 ميغابايت ‫85 ميغابايت توفير مساحة تخزين تتراوح بين 10 و30 ميغابايت تقريبًا على P99 anon + swap أثناء استرداد ذاكرة التخزين المؤقت لملفات SQLite والصور المصغّرة غير المستخدَمة
com.android.providers.media.module:PhotoPicker رقم تعريف المنتج (<process>) ‫85 ميغابايت + 25 بايت لكل بكسل
(‫~110 ميغابايت على شاشة HD+‎، و‫~145 ميغابايت على شاشة FHD+‎، و‫~190 ميغابايت على شاشة قابلة للطي بدقة ‎~4.5 ميغابكسل)
يتم تغيير الحجم بشكل متناسب مع عدد وحدات البكسل في الشاشة (additionalBytesPerDisplayPixel="25") ليتطابق مع نقطة الانحناء في مجموعة العمل الخاصة بالصورة النقطية في Glide.
com.android.networkstack رقم التعريف الفريد (<application>) ‫65 ميغابايت ‫70 ميغابايت ‫85 ميغابايت ذاكرة مجهولة الهوية في الغالب، بحجم P95 مدى الحياة memory.peak لتغطية بروتوكول أمان طبقة النقل (TLS) ونظام أسماء النطاقات (DNS) وبروتوكول الإعداد الديناميكي للمضيف (DHCP) في المدخل المشروط الوصول إليه.
com.android.appsearch.apk رقم التعريف الفريد (<application>) ‫130 ميغابايت يغطي هذا الخيار حوالي 100 ميغابايت من الذاكرة المقفلة وغير القابلة للإخلاء، بالإضافة إلى مجموعة عمل مضيف نشطة تبلغ حوالي 5 ميغابايت، مع توفير مساحة إضافية لنمو المنطقة المقفلة. تم قياس صفر أخطاء في إعادة التحميل عند ≥125 ميغابايت أثناء استرداد ذاكرة التخزين المؤقت للصفحات غير النشطة (ما يصل إلى 330 ميغابايت تقريبًا في المئوية 99).
‫com.android.phone ‎(TeleService) رقم تعريف المنتج (<process>) ‫120 ميغابايت يتم تحديد نطاق كل معرّف PID على معرّف UID المشترك 1001. تتتبّع مجموعة العمل جيل الجهاز بدلاً من ذاكرة الوصول العشوائي (RAM)، لذا تزيد الميزانية الواحدة بمقدار 30 ميغابايت تقريبًا عن أعلى قيمة anon + swap في الشريحة المئوية 99 لكل طراز.

كيف يمكن للمصنّعين الأصليين للأجهزة إعادة معايرة ميزانيات تطبيقات النظام لأجهزتهم؟

يمكن للمصنّعين الأصليين للأجهزة الذين يخصّصون تطبيقات النظام (على سبيل المثال، إضافة ملفات تعريف بلوتوث خاصة بالمورّدين أو إضافات هاتفية مخصّصة أو مفهرسات وسائط خاصة بالمصنّعين الأصليين للأجهزة) أو يشحنون خدمات نظام إضافية مثبّتة مسبقًا إعادة معايرة الميزانيات الحالية أو إنشاء ملفات *-budget.xml جديدة لمجموعة أجهزتهم.

جمع الملفات الشخصية النشطة لذاكرة cgroup v2

بالنسبة إلى كل عملية في النظام المستهدَف، اقرأ عُقد الذاكرة cgroup v2 ضمن /sys/fs/cgroup/uid_<uid>/pid_<pid>/ في أعباء العمل التمثيلية ومستويات ذاكرة الوصول العشوائي:

  • memory.current: الذاكرة المقيمة الحالية التي تم تحميلها إلى مجموعة التحكم (باستثناء الصفحات التي تمت مشاركتها مع Zygote)
  • ‫memory.peak: الحد الأقصى لعدد عمليات memory.current منذ بدء العملية
  • ‫memory.swap.current: الاستخدام الحالي لملف المبادلة المضغوط في ZRAM
  • memory.stat: تفاصيل anon وfile وpgscan وpgsteal وworkingset_refault_anon وworkingset_refault_file
  • memory.events: العدد التراكمي لانتهاكات high (memory.high أحداث التقييد والاسترداد)

عند تجميع بيانات القياس عن بُعد من أجهزة الاختبار الداخلي أو الأجهزة الميدانية، افصل حالات العمليات النشطة (TOP وFOREGROUND_SERVICE وعمليات الربط النشطة وعمليات المزامنة النشطة) عن حالات CACHED غير النشطة أو حالات PERSISTENT غير النشطة حتى لا تؤدي اللقطات غير النشطة إلى انحراف حسابات مجموعة العمل P95 وP99 إلى الأسفل.

فصل الذاكرة الديناميكية غير القابلة للضغط عن ذاكرة التخزين المؤقت للملفات القابلة للاسترداد

قارِن الشريحة المئوية التاسعة والتسعين من anon + swap بالشريحة المئوية التاسعة والتسعين من memory.current:

  • الخدمات التي تهيمن عليها ذاكرة التخزين المؤقت للملفات (file &gg; anon، مثل موفّري قواعد البيانات وفهارس البحث وبرامج تسجيل الدخول): اضبط android:maxMb لتوفير مساحة مريحة (عادةً من 1.5 إلى 4 أضعاف) على P99 anon + swap بالإضافة إلى نافذة عمل لصفحات الملفات النشطة. يتيح هذا الإعداد للنواة إخلاء صفحات الملفات غير المعيّنة الباردة باستمرار بدون لمس الذاكرة الديناميكية المجهولة. إذا كان حجم unevictable أو file_mapped كبيرًا (كما هو الحال مع الذاكرة المقفلة أو قواعد البيانات المعيّنة في الذاكرة)، يتم احتساب هذه المساحة كمجموعة عمل أيضًا.
  • الخدمات التي يغلب عليها استخدام الذاكرة المؤقتة المجهولة أو الخدمات المتقطعة (يشكّل anon معظم memory.current، مثل حِزم الشبكات أو الاتصال): يجب ضبط حجم android:maxMb لتغطية P90 إلى P95 من memory.peak حتى لا تؤدي الاندفاعات العابرة إلى تشغيل عملية تبديل ZRAM المتزامنة على سلاسل المحادثات الحساسة لزمن الاستجابة.
  • عمليات واجهة المستخدم التي تعتمد بشكل كبير على العرض (مثل أدوات الاختيار أو المعارض أو أدوات مساعدة واجهة المستخدم في النظام): يمكنك قياس المساحة الأساسية غير المخصّصة لواجهة المستخدم والمساحة المتزايدة للصور النقطية لكل ميغابكسل على الشاشات ذات الدقة الأدنى والأعلى، والتعبير عن الميزانية باستخدام android:maxMb بالإضافة إلى android:additionalBytesPerDisplayPixel.

تنظيف memory.high على الأجهزة الفعلية

قبل تخصيص ميزانية جديدة للإصدار، استخدِم am memory-limiter manual (أو اكتب مباشرةً في عقدة مجموعة التحكّم memory.high للعملية في إصدار userdebug) لاختبار الحدود المرشحة أثناء إجراء اختبارات CUJ الآلية:

# Override memory.high for a specific PID to 60 MB during a CUJ test
adb shell am memory-limiter manual <PID> 60MB

مراقبة memory.stat وmemory.events قبل رحلة المستخدم الأساسية (CUJ) وبعدها:

  • الميزانية منخفضة جدًا: يرتفع معدّل استخدام workingset_refault_anon أو workingset_refault_file بشكل حاد أثناء رحلة المستخدم الأساسية، أو memory.swap.currentيزداد معدّل الاستخدام باستمرار أثناء العمل النشط في المقدّمة، أو يتراجع وقت استجابة Binder أو إطار رحلة المستخدم الأساسية. زيادة الميزانية بعد نقطة الانحناء التلقائية
  • الميزانية مرتفعة جدًا: يظل memory.events:high 0 حتى بعد أعباء العمل الثقيلة المتعلقة بالوصول المباشر إلى الملفات أو الفهرسة، وينمو file في memory.stat بشكل غير محدود مع الصفحات الباردة. يجب تقليل الميزانية المخصّصة لمجموعة العمل النشطة.
  • الميزانية مضبوطة بشكل جيد: تظل قيمة memory.current في الحالة الثابتة أقل من memory.high أثناء التشغيل العادي. أثناء عمليات نقل الملفات الكبيرة أو عمليات التخزين المؤقت، يستعيد pgsteal صفحات الملفات غير النشطة بينما يظل workingset_refault_anon قريبًا من الصفر ولا يتغيّر وقت استجابة CUJ.