توسيع النواة باستخدام 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، ويصبح هذان الكائنان جزءًا من صورة النظام.

تنسيق برنامج eBPF C لنظام التشغيل 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 هو اسم حدث التتبُّع. يمكنك الاطّلاع على وثائق نواة أحداث التتبُّعللحصول على مزيد من المعلومات حول نقاط التتبُّع.
skfilter يعمل البرنامج كفلتر مقبس شبكات.
جداول زمنية يعمل البرنامج كمُصنِّف لحركة بيانات الشبكة.
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 برنامج eBPF .c، عليك إنشاء إدخال في ملف 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 إجمالي استخدام ذاكرة وحدة معالجة الرسومات في كل عملية وعلى النظام بأكمله. يُستخدَم هذا البرنامج لإنشاء ملف تعريف لذاكرة وحدة معالجة الرسومات.