توسيع النطاق باستخدام eBPF

أداة Extended Berkeley Packet Filter ‏ (eBPF) هي جهاز افتراضي داخل النواة ينفذ برامج eBPF التي يقدّمها المستخدم لتوسيع وظائف النواة. يمكن ربط هذه البرامج بعمليات الفحص أو الأحداث في النواة واستخدامها لجمع إحصاءات مفيدة عن النواة ومراقبتها وتصحيح أخطاءها. يتم تحميل برنامج في kernel باستخدام طلب نظام التشغيل bpf(2)، ويقدّمه المستخدم كمجموعة ثنائية من تعليمات eBPF للآلة. يتيح نظام إنشاء Android compiling compiling C programs to eBPF باستخدام بنية ملف الإنشاء البسيطة الموضّحة في هذا المستند.

يمكنك الاطّلاع على مزيد من المعلومات حول البنية الداخلية لبروتوكول eBPF وتصميمه على صفحة Brendan Gregg حول eBPF.

يتضمّن نظام Android أداة تحميل ومكتبة eBPF لتحميل برامج eBPF عند بدء التشغيل.

أداة تحميل BPF في Android

أثناء تشغيل Android، يتم تحميل كل برامج eBPF المتوفّرة في /system/etc/bpf/. هذه البرامج هي عناصر ثنائية أنشأها نظام إنشاء Android من برامج C، وتصاحبها ملفات Android.bp في شجرة ملف مصدر Android. يخزِّن نظام الإنشاء العناصر التي تم إنشاؤها في /system/etc/bpf، ويصبح هذان الكائنان جزءًا من صورة النظام.

تنسيق برنامج C لبروتوكول eBPF على Android

يجب أن يكون لبرنامج eBPF C التنسيق التالي:

#include <bpf_helpers.h>

/* Define one or more maps in the maps section, for example
 * define a map of type array int -> uint32_t, with 10 entries
 */
DEFINE_BPF_MAP(name_of_my_map, ARRAY, int, uint32_t, 10);

/* this also defines type-safe accessors:
 *   value * bpf_name_of_my_map_lookup_elem(&key);
 *   int bpf_name_of_my_map_update_elem(&key, &value, flags);
 *   int bpf_name_of_my_map_delete_elem(&key);
 * as such it is heavily suggested to use lowercase *_map names.
 * Also note that due to compiler deficiencies you cannot use a type
 * of 'struct foo' but must instead use just 'foo'.  As such structs
 * must not be defined as 'struct foo {}' and must instead be
 * 'typedef struct {} foo'.
 */

DEFINE_BPF_PROG("PROGTYPE/PROGNAME", AID_*, AID_*, PROGFUNC)(..args..) {
   <body-of-code
    ... read or write to MY_MAPNAME
    ... do other things
   >
}

LICENSE("GPL"); // or other license

المكان:

  • name_of_my_map هو اسم متغيّر الخريطة. يُعلم هذا الاسم أداة تحميل BPF بنوع الخريطة المطلوب إنشاؤها وبمَن يلي من المَعلمات. يتم توفير تعريف البنية هذا من خلال عنوان bpf_helpers.h المضمّن.
  • يمثّل الرمز PROGTYPE/PROGNAME نوع البرنامج واسم البرنامج. يمكن أن يكون نوع البرنامج أيًا مما هو مذكور في الجدول التالي. عندما لا يكون نوع البرنامج مُدرَجًا، لا تكون هناك قاعدة صارمة لاختيار اسم للبرنامج، بل يجب أن يكون الاسم معروفًا للعملية التي تُرفِق البرنامج.

  • PROGFUNC هي دالة يتم وضعها في قسم من الملف الناتج عند تجميعها.

kprobe تلتصق PROGFUNC بتعليمات النواة باستخدام البنية التحتية kprobe. يجب أن يكون PROGNAME هو اسم دالة kernel التي يتم فحصها. يُرجى الرجوع إلى مستندات kernel kprobe للحصول على مزيد من المعلومات حول kprobes.
نقطة تتبُّع ربط PROGFUNC بنقطة تتبُّع يجب أن يكون PROGNAME بالتنسيق SUBSYSTEM/EVENT. على سبيل المثال، قسم نقطة التتبّع لإرفاق الدوالّ بأحداث تبديل سياق المخطِّط سيكون هو SEC("tracepoint/sched/sched_switch")، حيث يكون sched هو اسم النظام الفرعي للتتبّع، وsched_switch هو اسم حدث التتبّع. اطّلِع على مستندات ملف kernel لأحداث التتبُّع للحصول على مزيد من المعلومات عن نقاط التتبُّع.
skfilter يعمل البرنامج كفلتر لمنافذ الشبكات.
schedcls يعمل البرنامج كمصنّف لحركة المرور في الشبكة.
cgroupskb وcgroupsock يتم تشغيل البرنامج عندما تنشئ العمليات في مجموعة CGroup مقبس AF_INET أو AF_INET6.

يمكن العثور على أنواع إضافية في رمز مصدر Loader.

على سبيل المثال، يضيف برنامج myschedtp.c التالي معلومات عن رقم تعريف ‎PID لآخر مهمة تم تشغيلها على وحدة معالجة مركزية معيّنة. يحقّق هذا البرنامج هدفه من خلال إنشاء خريطة وتحديد دالة tp_sched_switch يمكن إرفاقها بحدث التتبّع sched:sched_switch. لمزيد من المعلومات، اطّلِع على ربط البرامج بنقاط التتبّع.

#include <linux/bpf.h>
#include <stdbool.h>
#include <stdint.h>
#include <bpf_helpers.h>

DEFINE_BPF_MAP(cpu_pid_map, ARRAY, int, uint32_t, 1024);

struct switch_args {
    unsigned long long ignore;
    char prev_comm[16];
    int prev_pid;
    int prev_prio;
    long long prev_state;
    char next_comm[16];
    int next_pid;
    int next_prio;
};

DEFINE_BPF_PROG("tracepoint/sched/sched_switch", AID_ROOT, AID_SYSTEM, tp_sched_switch)
(struct switch_args *args) {
    int key;
    uint32_t val;

    key = bpf_get_smp_processor_id();
    val = args->next_pid;

    bpf_cpu_pid_map_update_elem(&key, &val, BPF_ANY);
    return 1; // return 1 to avoid blocking simpleperf from receiving events
}

LICENSE("GPL");

تُستخدَم وحدة الماكرو LICENSE للتحقّق مما إذا كان البرنامج متوافقًا مع الترخيص للنواة عندما يستخدِم البرنامج وظائف المساعدة BPF التي تقدّمها النواة. حدِّد اسم ترخيص برنامجك في شكل سلسلة، مثل LICENSE("GPL") أو LICENSE("Apache 2.0").

تنسيق ملف Android.bp

لكي ينشئ نظام إنشاء Android برنامج .c باستخدام eBPF، عليك إنشاء إدخال في ملف Android.bp للمشروع. على سبيل المثال، ل إنشاء برنامج eBPF C باسم bpf_test.c، عليك إجراء القيد التالي في ملف Android.bp الخاص بمشروعك:

bpf {
    name: "bpf_test.o",
    srcs: ["bpf_test.c"],
    cflags: [
        "-Wall",
        "-Werror",
    ],
}

يُجمِّع هذا الإدخال برنامج C، ما يؤدي إلى إنشاء الكائن /system/etc/bpf/bpf_test.o. عند بدء التشغيل، يحمِّل نظام Android تلقائيًا برنامج bpf_test.o في النواة.

الملفات المتوفّرة في sysfs

أثناء عملية التشغيل، يحمِّل نظام Android تلقائيًا جميع عناصر eBPF من /system/etc/bpf/، وينشئ الخرائط التي يحتاجها البرنامج، ويثبت البرنامج المحمَّل مع خرائطه في نظام ملفات BPF. ويمكن بعد ذلك استخدام هذه الملفات لمزيد من التفاعل مع برنامج eBPF أو قراءة الخرائط. يصف هذا القسم الاصطلاحات المستخدَمة لتسمية هذه الملفات ومواقعها في sysfs.

يتم إنشاء الملفات التالية وتثبيتها:

  • بالنسبة إلى أي برامج يتم تحميلها، بافتراض أنّ PROGNAME هو اسم البرنامج و FILENAME هو اسم ملف eBPF C، ينشئ أداة تحميل Android كل برنامج وي يثبته في /sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME.

    على سبيل المثال، في مثال نقطة التتبّع sched_switch السابق في myschedtp.c، يتم إنشاء ملف برنامج وتثبيته في /sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch.

  • بالنسبة إلى أيّ خرائط تم إنشاؤها، بافتراض أنّ MAPNAME هو اسم الخريطة و FILENAME هو اسم ملف eBPF C، ينشئ أداة تحميل Android كل خريطة وي يربطها بـ /sys/fs/bpf/map_FILENAME_MAPNAME.

    على سبيل المثال، في مثال نقطة التتبّع sched_switch السابق في myschedtp.c، يتم إنشاء ملف خريطة وتثبيته في /sys/fs/bpf/map_myschedtp_cpu_pid_map.

  • تعرض دالة bpf_obj_get() في مكتبة BPF لنظام التشغيل Android وصفًا للملف من ملف /sys/fs/bpf المُثبَّت. يمكن استخدام وصف الملف هذا لإجراء المزيد من العمليات، مثل قراءة الخرائط أو إرفاق برنامج بنقطة تتبُّع.

مكتبة BPF لنظام التشغيل Android

اسم مكتبة BPF في Android هو libbpf_android.so وهي جزء من صورة النظام. توفّر هذه المكتبة للمستخدم إمكانات eBPF من المستوى الأدنى اللازمة لإنشاء الخرائط وقراءتها، وإنشاء نقاط الاستكشاف ونقاط التتبّع ووحدات تخزين الأداء.

إرفاق البرامج بنقاط التتبّع

يتم تحميل برامج نقاط التتبُّع تلقائيًا عند بدء التشغيل. بعد التحميل، يجب تفعيل برنامج نقاط التتبّع باتّباع الخطوات التالية:

  1. يُرجى الاتصال برقم bpf_obj_get() للحصول على البرنامج fd من مكان الملف المثبَّت. لمزيد من المعلومات، يُرجى الرجوع إلى الملفات المتوفّرة في sysfs.
  2. استخدِم bpf_attach_tracepoint() في مكتبة BPF مع تمرير البرنامج fd واسم نقطة التتبُّع.

يوضّح نموذج الرمز البرمجي التالي كيفية إرفاق نقطة تتبُّع sched_switch المحدّدة في ملف المصدر myschedtp.c السابق (لا يتم عرض التحقّق من الأخطاء):

  char *tp_prog_path = "/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch";
  char *tp_map_path = "/sys/fs/bpf/map_myschedtp_cpu_pid";

  // Attach tracepoint and wait for 4 seconds
  int mProgFd = bpf_obj_get(tp_prog_path);
  int mMapFd = bpf_obj_get(tp_map_path);
  int ret = bpf_attach_tracepoint(mProgFd, "sched", "sched_switch");
  sleep(4);

  // Read the map to find the last PID that ran on CPU 0
  android::bpf::BpfMap<int, int> myMap(mMapFd);
  printf("last PID running on CPU %d is %d\n", 0, myMap.readValue(0));

القراءة من الخرائط

تتيح خرائط BPF أنواعًا أو هياكل معقدة عشوائية للمفتاح والقيمة. تتضمّن مكتبة BPF لنظام التشغيل Android فئة android::BpfMap تستخدِم نماذج C++ لإنشاء مثيل BpfMap استنادًا إلى نوع المفتاح والقيمة للقائمة المعنيّة. يوضّح نموذج الرمز البرمجي السابق استخدام BpfMap مع المفتاح والقيمة كعدد صحيحَين. يمكن أن تكون الأعداد الصحيحة أيضًا هياكل عشوائية.

وبالتالي، تتيح لك فئة BpfMap المستندة إلى نموذج تحديد عنصر BpfMap مخصّص مناسب للخريطة المحدّدة. ويمكن بعد ذلك الوصول إلى الخريطة باستخدام الدوال المُنشأة مخصّصة والتي تكون مدركة للنوع، ما يؤدي إلى إنشاء رمز برمجي أكثر وضوحًا.

لمزيد من المعلومات عن BpfMap، يُرجى الرجوع إلى مصادر Android.

تصحيح الأخطاء

أثناء عملية التمهيد، يتم تسجيل عدة رسائل مرتبطة بتحميل BPF. إذا تعذّر اكتمال عملية التحميل لأي سبب، يتم عرض رسالة سجلّ تفصيلية في logcat. يؤدي فلترة سجلّات logcat حسب bpf إلى طباعة كل الرسائل و أي أخطاء تفصيلية أثناء وقت التحميل، مثل أخطاء مدقّق eBPF.

أمثلة على تنسيق eBPF في Android

تقدّم البرامج التالية في AOSP أمثلة إضافية لاستخدام eBPF:

  • يستخدم برنامج netd eBPF C الخادم الدائم للشبكة (netd) في Android لأغراض مختلفة، مثل فلترة مآخذ التوصيل وجمع الإحصاءات. للاطّلاع على كيفية استخدام هذا البرنامج، تحقّق من مصادر أداة مراقبة الزيارات باستخدام برمجة النطاقات الحدودية للبرنامج (eBPF).

  • يحتسِب time_in_state برنامج eBPF C الوقت الذي يقضيه تطبيق Android عند ترددات مختلفة لوحدة المعالجة المركزية، والتي تُستخدَم لاحتساب الطاقة.

  • في Android 12، يتتبّع gpu_mem برنامج eBPF C إجمالي استخدام ذاكرة وحدة معالجة الرسومات لكل عملية وللنظام بأكمله. يُستخدَم هذا البرنامج لإنشاء ملف تعريف لذاكرة وحدة معالجة الرسومات.