ईबीपीएफ के साथ कर्नेल का विस्तार

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

ईबीपीएफ आंतरिक और आर्किटेक्चर के बारे में अधिक जानकारी ब्रेंडन ग्रेग के ईबीपीएफ पेज पर पाई जा सकती है।

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

एंड्रॉइड बीपीएफ लोडर

एंड्रॉइड बूट के दौरान, /system/etc/bpf/ पर स्थित सभी eBPF प्रोग्राम लोड किए जाते हैं। ये प्रोग्राम C प्रोग्राम से Android बिल्ड सिस्टम द्वारा निर्मित बाइनरी ऑब्जेक्ट हैं और Android स्रोत ट्री में Android.bp फ़ाइलों के साथ हैं। बिल्ड सिस्टम जेनरेट किए गए ऑब्जेक्ट को /system/etc/bpf पर संग्रहीत करता है, और वे ऑब्जेक्ट सिस्टम छवि का हिस्सा बन जाते हैं।

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 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_helpers.h हेडर द्वारा प्रदान की गई है।
  • PROGTYPE/PROGNAME प्रोग्राम के प्रकार और प्रोग्राम नाम का प्रतिनिधित्व करता है। प्रोग्राम का प्रकार निम्न तालिका में सूचीबद्ध किसी भी प्रकार का हो सकता है। जब किसी प्रकार का प्रोग्राम सूचीबद्ध नहीं होता है, तो प्रोग्राम के लिए कोई सख्त नामकरण परंपरा नहीं होती है; नाम को केवल उस प्रक्रिया के लिए जाना जाना चाहिए जो प्रोग्राम को संलग्न करती है।

  • PROGFUNC एक फ़ंक्शन है, जो संकलित होने पर, परिणामी फ़ाइल के एक अनुभाग में रखा जाता है।

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

अतिरिक्त प्रकार लोडर स्रोत कोड में पाए जा सकते हैं।

उदाहरण के लिए, निम्नलिखित myschedtp.c प्रोग्राम किसी विशेष CPU पर चलने वाले नवीनतम कार्य 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 फ़ाइल का प्रारूप

एंड्रॉइड बिल्ड सिस्टम के लिए 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 है। बूट होने पर, एंड्रॉइड सिस्टम स्वचालित रूप से bpf_test.o प्रोग्राम को कर्नेल में लोड करता है।

फ़ाइलें sysfs में उपलब्ध हैं

बूट के दौरान, एंड्रॉइड सिस्टम स्वचालित रूप से /system/etc/bpf/ से सभी eBPF ऑब्जेक्ट्स को लोड करता है, प्रोग्राम के लिए आवश्यक मानचित्र बनाता है, और लोड किए गए प्रोग्राम को उसके मानचित्रों के साथ BPF फ़ाइल सिस्टम में पिन करता है। फिर इन फ़ाइलों का उपयोग ईबीपीएफ प्रोग्राम के साथ आगे की बातचीत या मानचित्र पढ़ने के लिए किया जा सकता है। यह अनुभाग इन फ़ाइलों और sysfs में उनके स्थानों के नामकरण के लिए उपयोग की जाने वाली परंपराओं का वर्णन करता है।

निम्नलिखित फ़ाइलें बनाई और पिन की गई हैं:

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

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

  • बनाए गए किसी भी मानचित्र के लिए, यह मानते हुए कि MAPNAME का नाम है और FILENAME eBPF C फ़ाइल का नाम है, एंड्रॉइड लोडर प्रत्येक मानचित्र को बनाता है और /sys/fs/bpf/map_FILENAME_MAPNAME पर पिन करता है।

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

  • एंड्रॉइड बीपीएफ लाइब्रेरी में bpf_obj_get() पिन की गई /sys/fs/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));

मानचित्रों से पढ़ना

बीपीएफ मानचित्र मनमाने ढंग से जटिल कुंजी और मूल्य संरचनाओं या प्रकारों का समर्थन करते हैं। Android BPF लाइब्रेरी में एक android::BpfMap क्लास शामिल है जो प्रश्न में मानचित्र के लिए कुंजी और मान के प्रकार के आधार पर BpfMap इंस्टेंट करने के लिए C++ टेम्पलेट्स का उपयोग करता है। पिछला कोड नमूना पूर्णांक के रूप में कुंजी और मान के साथ BpfMap उपयोग दर्शाता है। पूर्णांक मनमानी संरचनाएं भी हो सकते हैं।

इस प्रकार टेम्प्लेटाइज़्ड BpfMap वर्ग विशेष मानचित्र के लिए उपयुक्त कस्टम BpfMap ऑब्जेक्ट को परिभाषित करना आसान बनाता है। इसके बाद मानचित्र को कस्टम-जनरेटेड फ़ंक्शंस का उपयोग करके एक्सेस किया जा सकता है, जो प्रकार के प्रति जागरूक होते हैं, जिसके परिणामस्वरूप क्लीनर कोड प्राप्त होता है।

BpfMap के बारे में अधिक जानकारी के लिए, Android स्रोतों को देखें।

डिबगिंग मुद्दे

बूट समय के दौरान, बीपीएफ लोडिंग से संबंधित कई संदेश लॉग किए जाते हैं। यदि किसी कारण से लोडिंग प्रक्रिया विफल हो जाती है, तो लॉगकैट में एक विस्तृत लॉग संदेश प्रदान किया जाता है। लॉगकैट लॉग को "बीपीएफ" द्वारा फ़िल्टर करने से लोड समय के दौरान सभी संदेश और कोई भी विस्तृत त्रुटि प्रिंट हो जाती है, जैसे कि ईबीपीएफ सत्यापनकर्ता त्रुटियां।

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

AOSP में निम्नलिखित प्रोग्राम eBPF का उपयोग करने के अतिरिक्त उदाहरण प्रदान करते हैं:

  • netd ईबीपीएफ सी प्रोग्राम का उपयोग एंड्रॉइड में नेटवर्किंग डेमॉन (नेटडी) द्वारा सॉकेट फ़िल्टरिंग और सांख्यिकी एकत्रण जैसे विभिन्न उद्देश्यों के लिए किया जाता है। यह देखने के लिए कि इस प्रोग्राम का उपयोग कैसे किया जाता है, eBPF ट्रैफ़िक मॉनिटर स्रोतों की जाँच करें।

  • time_in_state eBPF C प्रोग्राम एक एंड्रॉइड ऐप द्वारा विभिन्न सीपीयू आवृत्तियों पर बिताए गए समय की गणना करता है, जिसका उपयोग पावर की गणना करने के लिए किया जाता है।

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