تمديد النواة باستخدام eBPF

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

يمكن العثور على مزيد من المعلومات حول الأجزاء الداخلية لـ eBPF وبنيتها على صفحة eBPF الخاصة بـ Brendan Gregg .

يتضمن Android أداة تحميل eBPF ومكتبة تقوم بتحميل برامج eBPF في وقت التمهيد.

محمل BPF للاندرويد

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

تنسيق برنامج Android eBPF C

يجب أن يكون لبرنامج 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 will also define 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 بتعليمات kernel باستخدام البنية الأساسية لـ kprobe. يجب أن يكون PROGNAME هو اسم وظيفة kernel التي يتم kprobed فيها. راجع وثائق kprobe kernel لمزيد من المعلومات حول kprobes.
نقطة التتبع يربط PROGFUNC على نقطة التتبع. يجب أن يكون PROGNAME بالتنسيق SUBSYSTEM/EVENT . على سبيل المثال، قسم نقاط التتبع لإرفاق الوظائف بأحداث تبديل سياق المجدول سيكون SEC("tracepoint/sched/sched_switch") ، حيث sched هو اسم نظام التتبع الفرعي، و sched_switch هو اسم حدث التتبع. تحقق من وثائق kernel لأحداث التتبع لمزيد من المعلومات حول نقاط التتبع.
skfilter يعمل البرنامج كمرشح لمقبس الشبكة.
schedcls وظائف البرنامج كمصنف حركة مرور الشبكة.
كجروبسكب، كجروبسوك يتم تشغيل البرنامج عندما تقوم العمليات في CGroup بإنشاء مقبس AF_INET أو AF_INET6.

يمكن العثور على أنواع إضافية في الكود المصدري لبرنامج التحميل .

على سبيل المثال، يضيف برنامج myschedtp.c التالي معلومات حول آخر مهمة PID تم تشغيلها على وحدة المعالجة المركزية (CPU) معينة. يحقق هذا البرنامج هدفه عن طريق إنشاء خريطة وتحديد وظيفة 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 للتحقق مما إذا كان البرنامج متوافقًا مع ترخيص kernel عندما يستخدم البرنامج وظائف مساعد BPF التي توفرها kernel. حدد اسم ترخيص برنامجك في شكل سلسلة، مثل 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() في مكتبة Android BPF بإرجاع واصف ملف من الملف /sys/fs/bpf المثبت. يمكن استخدام واصف الملف هذا لإجراء المزيد من العمليات، مثل قراءة الخرائط أو إرفاق برنامج بنقطة تتبع.

مكتبة أندرويد BPF

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

ربط البرامج بنقاط التتبع

يتم تحميل برامج Tracepoint تلقائيًا عند التمهيد. بعد التحميل يجب تفعيل برنامج نقطة التتبع باتباع الخطوات التالية:

  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 هياكل أو أنواع المفاتيح والقيمة المعقدة التعسفية. تشتمل مكتبة Android BPF على فئة 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 إجمالي استخدام ذاكرة GPU لكل عملية وللنظام بأكمله. يستخدم هذا البرنامج لتوصيف ذاكرة GPU.