eBPF দিয়ে কার্নেল প্রসারিত করুন

এক্সটেন্ডেড বার্কলে প্যাকেট ফিল্টার (eBPF) হলো একটি ইন-কার্নেল ভার্চুয়াল মেশিন যা কার্নেলের কার্যকারিতা বাড়ানোর জন্য ব্যবহারকারী-সরবরাহকৃত eBPF প্রোগ্রাম চালায়। এই প্রোগ্রামগুলিকে কার্নেলের প্রোব বা ইভেন্টের সাথে সংযুক্ত করা যায় এবং দরকারী কার্নেল পরিসংখ্যান সংগ্রহ, পর্যবেক্ষণ এবং ডিবাগ করার জন্য ব্যবহার করা যেতে পারে। একটি প্রোগ্রাম bpf(2) সিস্টেম কল ব্যবহার করে কার্নেলে লোড করা হয় এবং ব্যবহারকারী এটিকে eBPF মেশিন নির্দেশাবলীর একটি বাইনারি ব্লব হিসাবে সরবরাহ করে। অ্যান্ড্রয়েড বিল্ড সিস্টেমে এই নথিতে বর্ণিত সহজ বিল্ড ফাইল সিনট্যাক্স ব্যবহার করে C প্রোগ্রামগুলিকে eBPF-এ কম্পাইল করার জন্য সমর্থন রয়েছে।

eBPF-এর অভ্যন্তরীণ কার্যপ্রণালী ও গঠন সম্পর্কে আরও তথ্য ব্রেন্ডান গ্রেগের eBPF পেজে পাওয়া যাবে।

অ্যান্ড্রয়েডে একটি eBPF লোডার এবং লাইব্রেরি রয়েছে যা বুট করার সময় eBPF প্রোগ্রামগুলো লোড করে।

অ্যান্ড্রয়েড বিপিএফ লোডার

অ্যান্ড্রয়েড বুট করার সময়, /system/etc/bpf/ এ অবস্থিত সমস্ত eBPF প্রোগ্রাম লোড হয়। এই প্রোগ্রামগুলি হলো বাইনারি অবজেক্ট, যা অ্যান্ড্রয়েড বিল্ড সিস্টেম C প্রোগ্রাম থেকে তৈরি করে এবং অ্যান্ড্রয়েড সোর্স ট্রিতে এগুলি Android.bp ফাইলের সাথে থাকে। বিল্ড সিস্টেম তৈরি হওয়া অবজেক্টগুলিকে /system/etc/bpf এ সংরক্ষণ করে, এবং সেই অবজেক্টগুলি সিস্টেম ইমেজের অংশ হয়ে যায়।

একটি অ্যান্ড্রয়েড 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 হলো এমন একটি ফাংশন, যা কম্পাইল করার পর চূড়ান্ত ফাইলের একটি অংশে রাখা হয়।

কেপ্রোব kprobe পরিকাঠামো ব্যবহার করে PROGFUNC একটি কার্নেল নির্দেশনার সাথে সংযুক্ত করে। PROGNAME অবশ্যই সেই কার্নেল ফাংশনের নাম হতে হবে যাকে kprobe করা হচ্ছে। kprobe সম্পর্কে আরও তথ্যের জন্য kprobe কার্নেল ডকুমেন্টেশন দেখুন।
ট্রেসপয়েন্ট PROGFUNC একটি ট্রেসপয়েন্টের সাথে সংযুক্ত করে। PROGNAME অবশ্যই SUBSYSTEM/EVENT ফরম্যাটের হতে হবে। উদাহরণস্বরূপ, শিডিউলার কনটেক্সট সুইচ ইভেন্টের সাথে ফাংশন সংযুক্ত করার জন্য একটি ট্রেসপয়েন্ট সেকশন হবে SEC("tracepoint/sched/sched_switch") , যেখানে sched হলো ট্রেস সাবসিস্টেমের নাম এবং sched_switch হলো ট্রেস ইভেন্টের নাম। ট্রেসপয়েন্ট সম্পর্কে আরও তথ্যের জন্য ট্রেস ইভেন্টস কার্নেল ডকুমেন্টেশন দেখুন।
skfilter প্রোগ্রামটি একটি নেটওয়ার্কিং সকেট ফিল্টার হিসেবে কাজ করে।
সময়সূচী প্রোগ্রামটি নেটওয়ার্কিং ট্র্যাফিক ক্লাসিফায়ার হিসেবে কাজ করে।
cgroupskb, cgroupsock যখনই কোনো 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");

যখন কোনো প্রোগ্রাম কার্নেল দ্বারা প্রদত্ত BPF হেল্পার ফাংশন ব্যবহার করে, তখন প্রোগ্রামটি কার্নেলের লাইসেন্সের সাথে সামঞ্জস্যপূর্ণ কিনা তা যাচাই করার জন্য LICENSE ম্যাক্রোটি ব্যবহৃত হয়। আপনার প্রোগ্রামের লাইসেন্সের নামটি স্ট্রিং আকারে উল্লেখ করুন, যেমন 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 ফাইল সিস্টেমে পিন করে রাখে। এরপর এই ফাইলগুলো eBPF প্রোগ্রামের সাথে আরও ইন্টারঅ্যাকশনের জন্য বা ম্যাপ পড়ার জন্য ব্যবহার করা যেতে পারে। এই বিভাগে এই ফাইলগুলোর নামকরণের জন্য ব্যবহৃত প্রচলিত নিয়ম এবং 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 লাইব্রেরির bpf_obj_get() ফাংশনটি পিন করা /sys/fs/bpf ফাইল থেকে একটি ফাইল ডেসক্রিপ্টর রিটার্ন করে। এই ফাইল ডেসক্রিপ্টরটি পরবর্তী অপারেশনের জন্য ব্যবহার করা যেতে পারে, যেমন ম্যাপ পড়া বা কোনো ট্রেসপয়েন্টে একটি প্রোগ্রাম সংযুক্ত করা।

অ্যান্ড্রয়েড বিপিএফ লাইব্রেরি

অ্যান্ড্রয়েড 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 ম্যাপ যেকোনো জটিল কী এবং ভ্যালু স্ট্রাকচার বা টাইপ সমর্থন করে। অ্যান্ড্রয়েড BPF লাইব্রেরিতে android::BpfMap একটি ক্লাস রয়েছে, যা নির্দিষ্ট ম্যাপের কী এবং ভ্যালু টাইপের উপর ভিত্তি করে BpfMap ইনস্ট্যানশিয়েট করার জন্য C++ টেমপ্লেট ব্যবহার করে। পূর্ববর্তী কোড স্যাম্পলটিতে কী এবং ভ্যালু হিসেবে ইন্টিজার ব্যবহার করে একটি BpfMap দেখানো হয়েছে। এই ইন্টিজারগুলো যেকোনো স্ট্রাকচারও হতে পারে।

এইভাবে টেমপ্লেটাইজড BpfMap ক্লাসটি আপনাকে নির্দিষ্ট ম্যাপের জন্য উপযুক্ত একটি কাস্টম BpfMap অবজেক্ট সংজ্ঞায়িত করার সুযোগ দেয়। এরপর কাস্টম-জেনারেটেড ফাংশনগুলো ব্যবহার করে ম্যাপটি অ্যাক্সেস করা যায়, যেগুলো টাইপ-অ্যাওয়্যার হওয়ায় কোড আরও পরিচ্ছন্ন হয়।

BpfMap সম্পর্কে আরও তথ্যের জন্য অ্যান্ড্রয়েড সোর্সগুলো দেখুন।

ডিবাগ সমস্যা

বুট হওয়ার সময়, BPF লোডিং সম্পর্কিত বেশ কিছু বার্তা লগ করা হয়। কোনো কারণে লোডিং প্রক্রিয়া ব্যর্থ হলে, logcat-এ একটি বিস্তারিত লগ বার্তা প্রদান করা হয়। bpf দ্বারা logcat লগ ফিল্টার করলে লোড হওয়ার সময়কার সমস্ত বার্তা এবং যেকোনো বিস্তারিত ত্রুটি, যেমন eBPF ভেরিফায়ার ত্রুটি, প্রিন্ট হয়।

অ্যান্ড্রয়েডে eBPF এর উদাহরণ

AOSP-তে নিম্নলিখিত প্রোগ্রামগুলি eBPF ব্যবহারের আরও উদাহরণ প্রদান করে:

  • অ্যান্ড্রয়েডের নেটওয়ার্কিং ডেমন (netd) সকেট ফিল্টারিং এবং পরিসংখ্যান সংগ্রহের মতো বিভিন্ন উদ্দেশ্যে netd eBPF C প্রোগ্রামটি ব্যবহার করে। এই প্রোগ্রামটি কীভাবে ব্যবহৃত হয় তা দেখতে, eBPF ট্র্যাফিক মনিটরের সোর্সগুলো দেখুন।

  • time_in_state eBPF` C প্রোগ্রামটি একটি অ্যান্ড্রয়েড অ্যাপ বিভিন্ন সিপিইউ ফ্রিকোয়েন্সিতে কত সময় ব্যয় করে তা গণনা করে, যা শক্তি হিসাব করতে ব্যবহৃত হয়।

  • অ্যান্ড্রয়েড ১২-এ, gpu_mem eBPF C প্রোগ্রামটি প্রতিটি প্রসেস এবং সম্পূর্ণ সিস্টেমের মোট GPU মেমরি ব্যবহারের হিসাব রাখে। এই প্রোগ্রামটি GPU মেমরি প্রোফাইলিংয়ের জন্য ব্যবহৃত হয়।