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

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

ইবিপিএফ ইন্টারনাল এবং আর্কিটেকচার সম্পর্কে আরও তথ্য ব্রেন্ডন গ্রেগের ইবিপিএফ পৃষ্ঠায় পাওয়া যাবে।

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

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

অ্যান্ড্রয়েড বুট করার সময়, /system/etc/bpf/ এ অবস্থিত সমস্ত eBPF প্রোগ্রাম লোড হয়। এই প্রোগ্রামগুলি হল বাইনারি অবজেক্ট যা সি প্রোগ্রাম থেকে অ্যান্ড্রয়েড বিল্ড সিস্টেম দ্বারা তৈরি করা হয়েছে এবং অ্যান্ড্রয়েড সোর্স ট্রিতে 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 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 লোডারকে ম্যাপ তৈরির ধরন এবং কী কী প্যারামিটার দিয়ে জানাবে। এই struct সংজ্ঞা অন্তর্ভুক্ত bpf_helpers.h হেডার দ্বারা প্রদান করা হয়.
  • PROGTYPE/PROGNAME প্রোগ্রামের ধরন এবং প্রোগ্রামের নাম উপস্থাপন করে। প্রোগ্রামের ধরন নিম্নলিখিত টেবিলে তালিকাভুক্ত যেকোনও হতে পারে। যখন একটি ধরনের প্রোগ্রাম তালিকাভুক্ত করা হয় না, প্রোগ্রামের জন্য কোন কঠোর নামকরণের নিয়ম নেই; নামটি কেবল সেই প্রক্রিয়ার সাথে পরিচিত হওয়া দরকার যা প্রোগ্রামটিকে সংযুক্ত করে।

  • PROGFUNC হল একটি ফাংশন যা কম্পাইল করা হলে ফলাফল ফাইলের একটি বিভাগে রাখা হয়।

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 সকেট তৈরি করে তখনই প্রোগ্রাম চলে।

লোডার সোর্স কোডে অতিরিক্ত প্রকারগুলি পাওয়া যাবে।

উদাহরণস্বরূপ, নিম্নলিখিত 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 ফাইলের বিন্যাস

একটি eBPF .c প্রোগ্রাম তৈরি করার জন্য Android বিল্ড সিস্টেমের জন্য, আপনাকে প্রকল্পের 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 ফাইলের নাম, 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

    উদাহরণস্বরূপ, 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));

মানচিত্র থেকে পড়া

BPF মানচিত্র নির্বিচারে জটিল কী এবং মান কাঠামো বা প্রকারগুলিকে সমর্থন করে। অ্যান্ড্রয়েড বিপিএফ লাইব্রেরিতে একটি android::BpfMap ক্লাস রয়েছে যা প্রশ্নে থাকা মানচিত্রের কী এবং মানের ধরণের উপর ভিত্তি করে BpfMap ইনস্ট্যান্টিয়েট করতে C++ টেমপ্লেট ব্যবহার করে। পূর্ববর্তী কোড নমুনা পূর্ণসংখ্যা হিসাবে কী এবং মান সহ একটি BpfMap ব্যবহার করে প্রদর্শন করে। পূর্ণসংখ্যাগুলিও নির্বিচারে কাঠামো হতে পারে।

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

BpfMap সম্পর্কে আরও তথ্যের জন্য, Android উত্সগুলি পড়ুন৷

ডিবাগিং সমস্যা

বুট করার সময়, BPF লোডিং সম্পর্কিত বেশ কয়েকটি বার্তা লগ করা হয়। কোনো কারণে লোডিং প্রক্রিয়া ব্যর্থ হলে, লগক্যাটে একটি বিস্তারিত লগ বার্তা প্রদান করা হয়। লগক্যাট লগগুলিকে "bpf" দ্বারা ফিল্টার করা সমস্ত বার্তা এবং লোডের সময় যে কোনও বিস্তারিত ত্রুটি যেমন eBPF যাচাইকারী ত্রুটিগুলি প্রিন্ট করে৷

অ্যান্ড্রয়েডে ইবিপিএফ-এর উদাহরণ

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

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

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

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