eBPF-এর মাধ্যমে কার্নেল এক্সটেন্ড করা

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

eBPF ইন্টার্নাল ও আর্কিটেকচার সম্পর্কে আরও তথ্য Brendan Gregg-এর 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 লোডারকে কী ধরনের ম্যাপ তৈরি করতে হবে এবং কী কী প্যারামিটার ব্যবহার করতে হবে সেই সম্পর্কে জানায়। এই স্ট্রাক্ট সংজ্ঞা অন্তর্ভুক্ত 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 প্রোগ্রামটি নেটওয়ার্কিং সকেট ফিল্টার হিসেবে কাজ করে।
schedcls প্রোগ্রামটি নেটওয়ার্ক ট্রাফিক ক্লাসিফায়ার হিসেবে কাজ করে।
cgroupskb, cgroupsock CGroup-এর প্রসেস যখনই AF_INET বা AF_INET6 সকেট তৈরি করে, তখনই প্রোগ্রাম রান করে।

Loader সোর্স কোডে অতিরিক্ত ধরন খুঁজে পাওয়া যেতে পারে।

যেমন, নিম্নলিখিত 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 ফাইলের ফর্ম্যাট

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

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

BpfMap সম্পর্কে আরও তথ্যের জন্য, Android সোর্স দেখুন।

সমস্যা সমাধান করা

বুট করার সময়, BPF লোডিং সম্পর্কিত একাধিক মেসেজ লগ করা হয়। কোনও কারণে লোডিং প্রক্রিয়া সম্পূর্ণ না হলে, logcat-এ একটি বিস্তারিত লগ মেসেজ প্রদান করা হয়। bpf-এর মাধ্যমে logcat লগ ফিল্টার করলে সব মেসেজ এবং লোড টাইমে হওয়া যেকোনও বিস্তারিত সমস্যা প্রিন্ট করে, যেমন eBPF যাচাইকারী সংক্রান্ত সমস্যা।

Android-এ eBPF-এর উদাহরণ

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

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

  • time_in_state eBPF C প্রোগ্রাম গণনা করে যে Android অ্যাপ কতক্ষণ ধরে আলাদা আলাদা CPU ফ্রিকোয়েন্সিতে কাজ করে, যা পাওয়ার গণনা করতে ব্যবহার করা হয়।

  • Android 12-এ, gpu_mem eBPF C প্রোগ্রাম প্রতিটি প্রসেস ও সম্পূর্ণ সিস্টেমের জন্য মোট GPU মেমরি ব্যবহার ট্র্যাক করে। এই প্রোগ্রামটি GPU মেমরি প্রোফাইলিংয়ের জন্য ব্যবহার করা হয়।

statsd-এর জন্য লো-ওভারহেড কার্নেল মেমোরি ম্যানেজমেন্ট এবং পাওয়ার লেটেন্সি হিস্টোগ্রাম সংগ্রহ করতে eBPF ব্যবহার করার বিষয়ে আরও তথ্যের জন্য, eBPF-এর মাধ্যমে কার্নেল টেলিমেট্রি সংগ্রহ করুন দেখুন।