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

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

eBPF के अंदरूनी हिस्सों और आर्किटेक्चर के बारे में ज़्यादा जानकारी Brendan पर मिल सकती है ग्रेग का eBPF पेज.

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

Android बीपीएफ़ लोडर

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 लोडर को यह बताता है कि किस तरह का मैप बनाना है और किस तरह का पैरामीटर का इस्तेमाल करें. निर्देश की यह परिभाषा, शामिल किए गए bpf_helpers.h में दी गई है हेडर.
  • PROGTYPE/PROGNAME से पता चलता है कि प्रोग्राम किस तरह का है और प्रोग्राम का नाम. प्रोग्राम का टाइप, नीचे दी गई टेबल में बताए गए किसी भी टाइप का हो सकता है. अगर किसी तरह का प्रोग्राम सूची में नहीं है, तो प्रोग्राम के नाम के लिए कोई ज़रूरी नियम नहीं है. प्रोग्राम को जोड़ने वाली प्रोसेस को नाम पता होना चाहिए.

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

केप्रोब PROGFUNC को, kprobe इन्फ़्रास्ट्रक्चर का इस्तेमाल करके, kernel निर्देश पर हुक किया जाता है. PROGNAME, कर्नेल का नाम होना चाहिए फ़ंक्शन को Kprobed किया जा रहा है. kprobes के बारे में ज़्यादा जानकारी के लिए, kprobe kernel दस्तावेज़ देखें.
tracepoint ट्रेसपॉइंट पर PROGFUNC को हुक कर देता है. PROGNAME होना चाहिए SUBSYSTEM/EVENT फ़ॉर्मैट में. उदाहरण के लिए, शेड्यूलर कॉन्टेक्स्ट स्विच इवेंट में फ़ंक्शन अटैच करने के लिए, ट्रैकपॉइंट सेक्शन SEC("tracepoint/sched/sched_switch") होगा. इसमें sched, ट्रैक सबसिस्टम का नाम है और sched_switch, ट्रैक इवेंट का नाम है. ट्रेसपॉइंट के बारे में ज़्यादा जानकारी के लिए, ट्रेस इवेंट के लिए उपलब्ध कराए गए कोर के दस्तावेज़ देखें.
skफ़िल्टर प्रोग्राम, नेटवर्किंग सॉकेट फ़िल्टर के तौर पर काम करता है.
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");

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

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

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

    उदाहरण के लिए, myschedtp.c में दिए गए पिछले sched_switch ट्रैसपॉइंट के उदाहरण के लिए, एक प्रोग्राम फ़ाइल बनाई जाती है और उसे /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.

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

Android BPF लाइब्रेरी

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

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

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

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

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

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

डीबग करने से जुड़ी समस्याएं

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

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

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

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

  • time_in_state eBPF C प्रोग्राम यह पता लगाता है कि कोई Android ऐप्लिकेशन, सीपीयू फ़्रीक्वेंसी का इस्तेमाल, पावर कैलकुलेट करने के लिए किया जाता है.

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