eBPF की मदद से कर्नेल को बड़ा करना

एक्सटेंडेड बर्कले पैकेट फ़िल्टर (ईबीपीएफ़), कर्नल में मौजूद एक वर्चुअल मशीन है. यह कर्नल की सुविधाओं को बढ़ाने के लिए, उपयोगकर्ता के दिए गए ईबीपीएफ़ प्रोग्राम चलाती है. इन प्रोग्राम को कर्नल में मौजूद जांच या इवेंट से जोड़ा जा सकता है. इनका इस्तेमाल, कर्नल के काम के आंकड़े इकट्ठा करने, निगरानी करने, और डीबग करने के लिए किया जा सकता है. bpf(2) syscall का इस्तेमाल करके, प्रोग्राम को कर्नल में लोड किया जाता है. इसे उपयोगकर्ता, eBPF मशीन निर्देशों के बाइनरी ब्लॉब के तौर पर उपलब्ध कराता है. Android बिल्ड सिस्टम, इस दस्तावेज़ में बताए गए बिल्ड फ़ाइल के आसान सिंटैक्स का इस्तेमाल करके, C प्रोग्राम को eBPF में कंपाइल करने की सुविधा देता है.

eBPF के इंटरनल और आर्किटेक्चर के बारे में ज़्यादा जानकारी, ब्रेंडन ग्रेग के eBPF पेज पर देखी जा सकती है.

Android में एक eBPF लोडर और लाइब्रेरी शामिल होती है. यह बूट होने के समय eBPF प्रोग्राम लोड करती है.

Android BPF लोडर

Android बूट के दौरान, /system/etc/bpf/ पर मौजूद सभी eBPF प्रोग्राम लोड किए जाते हैं. ये प्रोग्राम, Android बिल्ड सिस्टम से बनाए गए बाइनरी ऑब्जेक्ट होते हैं. इन्हें C प्रोग्राम से बनाया जाता है. साथ ही, ये Android सोर्स ट्री में Android.bp फ़ाइलों के साथ होते हैं. बिल्ड सिस्टम, जनरेट किए गए ऑब्जेक्ट को /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 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_helpers.h हेडर से मिलती है.
  • PROGTYPE/PROGNAME से प्रोग्राम का टाइप और प्रोग्राम का नाम पता चलता है. प्रोग्राम का टाइप, यहां दी गई टेबल में मौजूद किसी भी टाइप का हो सकता है. जब किसी प्रोग्राम का टाइप नहीं दिया जाता है, तो प्रोग्राम के नाम के लिए कोई तय नियम नहीं होता. प्रोग्राम का नाम सिर्फ़ उस प्रोसेस को पता होना चाहिए जो प्रोग्राम को अटैच करती है.

  • PROGFUNC एक ऐसा फ़ंक्शन है जिसे कंपाइल करने पर, नतीजे के तौर पर मिली फ़ाइल के एक सेक्शन में रखा जाता है.

kprobe यह kprobe इन्फ़्रास्ट्रक्चर का इस्तेमाल करके, कर्नल के निर्देश पर PROGFUNC होता है. PROGNAME, कर्नल फ़ंक्शन का नाम होना चाहिए, जिसे kprobe किया जा रहा है. kprobes के बारे में ज़्यादा जानने के लिए, kprobe कर्नेल दस्तावेज़ देखें.
ट्रेसपॉइंट यह हुक, ट्रेसपॉइंट PROGFUNC पर अटैच होता है. PROGNAME का फ़ॉर्मैट SUBSYSTEM/EVENT होना चाहिए. उदाहरण के लिए, शेड्यूलर कॉन्टेक्स्ट स्विच इवेंट में फ़ंक्शन अटैच करने के लिए, ट्रेसपॉइंट सेक्शन SEC("tracepoint/sched/sched_switch") होगा. इसमें sched, ट्रेस सबसिस्टम का नाम है और sched_switch, ट्रेस इवेंट का नाम है. ट्रेसपॉइंट के बारे में ज़्यादा जानकारी के लिए, ट्रेस इवेंट कर्नल का दस्तावेज़ देखें.
skfilter यह प्रोग्राम, नेटवर्किंग सॉकेट फ़िल्टर के तौर पर काम करता है.
schedcls यह प्रोग्राम, नेटवर्क ट्रैफ़िक को क्लासिफ़ायर के तौर पर काम करता है.
cgroupskb, cgroupsock यह प्रोग्राम तब चलता है, जब किसी CGroup में मौजूद प्रोसेस, AF_INET या AF_INET6 सॉकेट बनाती हैं.

अन्य टाइप, लोडर के सोर्स कोड में देखे जा सकते हैं.

उदाहरण के लिए, यहां दिया गया myschedtp.c प्रोग्राम, किसी सीपीयू पर चलने वाले टास्क के सबसे नए पीआईडी की जानकारी जोड़ता है. यह प्रोग्राम, मैप बनाकर और 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 फ़ाइल में एक एंट्री बनानी होगी. उदाहरण के लिए, bpf_test.c नाम का eBPF 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 सिस्टम /system/etc/bpf/ से सभी eBPF ऑब्जेक्ट अपने-आप लोड करता है. साथ ही, प्रोग्राम के लिए ज़रूरी मैप बनाता है. इसके बाद, लोड किए गए प्रोग्राम को उसके मैप के साथ BPF फ़ाइल सिस्टम में पिन करता है. इसके बाद, इन फ़ाइलों का इस्तेमाल eBPF प्रोग्राम के साथ इंटरैक्ट करने या मैप पढ़ने के लिए किया जा सकता है. इस सेक्शन में, इन फ़ाइलों के नाम रखने के लिए इस्तेमाल किए गए नियमों और sysfs में उनकी जगहों के बारे में बताया गया है.

ये फ़ाइलें बनाई जाती हैं और पिन की जाती हैं:

  • लोड किए गए किसी भी प्रोग्राम के लिए, मान लें कि प्रोग्राम का नाम PROGNAME है और eBPF C फ़ाइल का नाम FILENAME है. ऐसे में, Android लोडर हर प्रोग्राम को /sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME पर बनाता है और पिन करता है.

    उदाहरण के लिए, myschedtp.c में दिए गए sched_switch ट्रेसपॉइंट के पिछले उदाहरण के लिए, एक प्रोग्राम फ़ाइल बनाई जाती है और उसे /sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch में पिन किया जाता है.

  • मान लें कि बनाए गए किसी मैप का नाम MAPNAME है और eBPF C फ़ाइल का नाम FILENAME है. ऐसे में, Android लोडर हर मैप को /sys/fs/bpf/map_FILENAME_MAPNAME में बनाता है और पिन करता है.

    उदाहरण के लिए, myschedtp.c में sched_switch के पिछले ट्रेसपॉइंट उदाहरण के लिए, एक मैप फ़ाइल बनाई जाती है और उसे /sys/fs/bpf/map_myschedtp_cpu_pid_map पर पिन किया जाता है.

  • Android BPF लाइब्रेरी में मौजूद bpf_obj_get(), पिन की गई /sys/fs/bpf फ़ाइल से फ़ाइल डिस्क्रिप्टर दिखाता है. इस फ़ाइल डिस्क्रिप्टर का इस्तेमाल, आगे की कार्रवाइयों के लिए किया जा सकता है. जैसे, मैप पढ़ना या किसी प्रोग्राम को ट्रेसपॉइंट से अटैच करना.

Android BPF लाइब्रेरी

Android BPF लाइब्रेरी का नाम libbpf_android.so है. यह सिस्टम इमेज का हिस्सा है. यह लाइब्रेरी, उपयोगकर्ता को eBPF की बुनियादी सुविधाएं उपलब्ध कराती है. इनकी ज़रूरत मैप बनाने और उन्हें पढ़ने, जांच करने वाले टूल, ट्रेसपॉइंट, और परफ़ बफ़र बनाने के लिए होती है.

प्रोग्राम को ट्रेसपॉइंट से अटैच करना

ट्रेसपॉइंट प्रोग्राम, बूट होने पर अपने-आप लोड हो जाते हैं. लोड होने के बाद, ट्रेसपॉइंट प्रोग्राम को चालू करने के लिए यह तरीका अपनाएं:

  1. पिन की गई फ़ाइल की जगह से प्रोग्राम fd पाने के लिए, bpf_obj_get() को कॉल करें. ज़्यादा जानकारी के लिए, sysfs में उपलब्ध फ़ाइलें देखें.
  2. BPF लाइब्रेरी में bpf_attach_tracepoint() को कॉल करें. इसमें प्रोग्राम fd और ट्रेसपॉइंट का नाम पास करें.

यहां दिए गए कोड के सैंपल में, यह दिखाया गया है कि पिछली myschedtp.c सोर्स फ़ाइल में तय किए गए sched_switch ट्रेसपॉइंट को कैसे अटैच किया जाता है. इसमें गड़बड़ी की जांच करने का तरीका नहीं दिखाया गया है:

  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++ टेंप्लेट का इस्तेमाल करके android::BpfMap को इंस्टैंटिएट करती है. ऐसा, सवाल में दिए गए मैप के लिए कुंजी और वैल्यू टाइप के आधार पर किया जाता है.BpfMap पिछले कोड सैंपल में, पूर्णांक के तौर पर कुंजी और वैल्यू के साथ BpfMap का इस्तेमाल करने का तरीका दिखाया गया है. पूर्णांकों को किसी भी स्ट्रक्चर में रखा जा सकता है.

इसलिए, टेंप्लेट वाली BpfMap क्लास की मदद से, मैप के हिसाब से कस्टम BpfMap ऑब्जेक्ट तय किया जा सकता है. इसके बाद, मैप को कस्टम-जनरेट किए गए फ़ंक्शन का इस्तेमाल करके ऐक्सेस किया जा सकता है. ये फ़ंक्शन टाइप के बारे में जानते हैं. इससे कोड ज़्यादा साफ़ होता है.

BpfMap के बारे में ज़्यादा जानकारी के लिए, Android के सोर्स देखें.

गड़बड़ियाँ ठीक करने के लिए

बूट टाइम के दौरान, BPF लोड होने से जुड़े कई मैसेज लॉग किए जाते हैं. अगर किसी वजह से डेटा लोड नहीं हो पाता है, तो logcat में लॉग मैसेज की पूरी जानकारी दी जाती है. bpf के हिसाब से logcat लॉग फ़िल्टर करने पर, लोड होने के समय के सभी मैसेज और गड़बड़ियां दिखती हैं. जैसे, eBPF वेरिफ़ायर से जुड़ी गड़बड़ियां.

Android में eBPF के उदाहरण

AOSP में मौजूद इन प्रोग्राम में, eBPF का इस्तेमाल करने के अन्य उदाहरण दिए गए हैं:

  • Android में नेटवर्किंग डेमॉन (netd), netd eBPF प्रोग्राम का इस्तेमाल कई कामों के लिए करता है. जैसे, सॉकेट फ़िल्टर करना और आंकड़े इकट्ठा करना. इस प्रोग्राम का इस्तेमाल कैसे किया जाता है, यह देखने के लिए eBPF ट्रैफ़िक मॉनिटर के सोर्स देखें.

  • time_in_state eBPF C program से यह पता चलता है कि Android ऐप्लिकेशन, अलग-अलग सीपीयू फ़्रीक्वेंसी पर कितना समय बिताता है. इसका इस्तेमाल पावर का हिसाब लगाने के लिए किया जाता है.

  • Android 12 में, gpu_mem eBPF C प्रोग्राम हर प्रोसेस और पूरे सिस्टम के लिए, जीपीयू मेमोरी के कुल इस्तेमाल को ट्रैक करता है. इस प्रोग्राम का इस्तेमाल, GPU मेमोरी की प्रोफ़ाइलिंग के लिए किया जाता है.