مقدمة موجزة عن BPF و eBPF

مرحبا يا هابر! نحيطكم علما بأننا نستعد لإصدار كتاب " قابلية لينكس للمراقبة مع BPF ".


مع استمرار تطور الجهاز الظاهري BPF وتطبيقه بشكل عملي ، قمنا بترجمة مقال لك يصف ميزاته الرئيسية وحالته الحالية.

في السنوات الأخيرة ، بدأت أدوات وتقنيات البرمجة تكتسب شعبية ، مصممة لتعويض قيود نواة لينكس في الحالات التي تتطلب معالجة حزمة عالية الأداء. إحدى الطرق الأكثر شيوعًا من هذا النوع تسمى تجاوز النواة (تجاوز النواة) وتتيح تمرير نوى طبقة الشبكة لأداء جميع معالجة الحزم من مساحة المستخدم. يتضمن تجاوز النواة أيضًا إدارة بطاقة الشبكة من مساحة المستخدم . بمعنى آخر ، عند العمل باستخدام بطاقة شبكة ، نعتمد على برنامج تشغيل مساحة المستخدم .

من خلال نقل التحكم الكامل في بطاقة الشبكة إلى البرنامج من مساحة المستخدم ، فإننا نخفض التكاليف المرتبطة بالنواة (تبديل السياق ، ومعالجة مستوى الشبكة ، والمقاطعات ، وما إلى ذلك) ، وهو أمر مهم للغاية عند العمل بسرعات تصل إلى 10 جيجابت / ثانية أو أعلى. نواة الالتفافية بالإضافة إلى مجموعة من المزايا الأخرى ( تجهيز الدفعات ) وضبط دقة الأداء ( المحاسبة NUMA ، العزلة وحدة المعالجة المركزية ، وما إلى ذلك) تتوافق مع أساسيات معالجة شبكة عالية الأداء في الفضاء المستخدم. ربما يكون أحد الأمثلة النموذجية على هذا النهج الجديد لمعالجة الحزمة هو DPDK ( مجموعة تطوير طائرة البيانات من إنتل)) ، على الرغم من وجود أدوات وتقنيات أخرى معروفة ، بما في ذلك VPP من Cisco (معالجة حزم المتجهات) و Netmap وبالطبع Snabb .

ينطوي تنظيم تفاعلات الشبكة في مساحة المستخدم على عدد من العيوب:

  • نواة نظام التشغيل هي مستوى التجريد لموارد الأجهزة. نظرًا لأن برامج مساحة المستخدم يجب أن تدير مواردها مباشرة ، يجب عليها أيضًا إدارة أجهزتها الخاصة. غالبًا ما يعني هذا أنك بحاجة إلى برمجة برامج التشغيل الخاصة بك.
  • , , . , , , .
  • , .

في الجوهر ، عند تنظيم تفاعلات الشبكة في مساحة المستخدم ، يتم تحقيق زيادة الإنتاجية عن طريق نقل معالجة الحزم من النواة إلى مساحة المستخدم. يقوم XDP بالعكس تمامًا: ينقل برامج الشبكة من مساحة المستخدم (المرشحات والمحولات والتوجيه وما إلى ذلك) إلى منطقة النواة. يسمح لنا XDP بأداء وظيفة شبكة بمجرد وصول الحزمة إلى واجهة الشبكة وقبل أن تبدأ في الانتقال إلى النظام الفرعي للشبكة للنواة. ونتيجة لذلك ، تزداد سرعة معالجة الحزم بشكل ملحوظ. ومع ذلك ، كيف تسمح النواة للمستخدم بتنفيذ برامجهم في مساحة النواة؟ قبل الإجابة على هذا السؤال ، دعنا ننظر إلى ما هو BPF.

BPF و eBPF

على الرغم من الاسم غير الواضح BPF (Packet Filtering، Berkeley) ، فهو في الواقع نموذج آلة افتراضية. تم تصميم هذا الجهاز الافتراضي في الأصل للتعامل مع تصفية الحزم ، ومن هنا جاء الاسم.

واحدة من أفضل الأدوات المعروفة باستخدام BPF هي tcpdump. عند التقاط الحزم باستخدام ، tcpdumpيمكن للمستخدم تحديد تعبير لتصفية الحزم. سيتم التقاط الحزم المطابقة لهذا التعبير فقط. على سبيل المثال ، tcp dst port 80ينطبق التعبير " " على كافة حزم TCP التي تصل إلى المنفذ 80. يمكن للمترجم تقصير هذا التعبير عن طريق تحويله إلى رمز BPF بايت. إليك ما يقوم به البرنامج أعلاه بشكل أساسي:

$ sudo tcpdump -d "tcp dst port 80"
(000) ldh [12]
(001) jeq #0x86dd jt 2 jf 6
(002) ldb [20]
(003) jeq #0x6 jt 4 jf 15
(004) ldh [56]
(005) jeq #0x50 jt 14 jf 15
(006) jeq #0x800 jt 7 jf 15
(007) ldb [23]
(008) jeq #0x6 jt 9 jf 15
(009) ldh [20]
(010) jset #0x1fff jt 15 jf 11
(011) ldxb 4*([14]&0xf)
(012) ldh [x + 16]
(013) jeq #0x50 jt 14 jf 15
(014) ret #262144
(015) ret #0




  • التعليمات (بالألف): تنزيل حزمة عند الإزاحة 12 ككلمة 16 بت في البطارية. يقابل الإزاحة 12 حزمة إيثربي.
  • (001): 0x86dd, , ethertype- IPv6. true, (002), – (006).
  • (006): 0x800 (ethertype- IPv4). true, (007), – (015).

وهكذا ، حتى إرجاع برنامج تصفية الحزم نتيجة. هذا هو عادة بوليان. إرجاع قيمة غير صفرية (تعليمات (014)) يعني أن الحزمة قد اقتربت ، وإرجاع صفر (تعليمات (015)) يعني أن الحزمة لم تصل.

تم اقتراح الجهاز الظاهري BPF ورمزه الثانوي بواسطة ستيف ماكان وفان جاكوبسون في أواخر عام 1992 عندما تم تقديم مقالهما BSD Packet Filter: بنية جديدة لالتقاط الحزم على مستوى المستخدم ، لأول مرة في مؤتمر Usenix في شتاء عام 1993.

لأن BPF هي آلة افتراضية ، فهي تحدد البيئة التي تعمل فيها البرامج. بالإضافة إلى الرمز الثانوي ، فإنه يحدد أيضًا نموذج ذاكرة الدُفعة (يتم تطبيق تعليمات التمهيد ضمنيًا على الحزمة) ، والسجلات (A و X ؛ تسجيلات البطارية والفهرس) ، وتخزين ذاكرة التخزين ، وعداد البرنامج الضمني. ومن المثير للاهتمام أن نموذج الرمز البريدي BPF تم تصميمه على غرار Motorola 6502 ISA. كما ذكر ستيف ماكان في حديثه العام في Sharkfest '11 ، فقد كان على دراية ببناء 6502 منذ المدرسة الثانوية عندما برمج في Apple II ، وأثرت هذه المعرفة على عمله في تصميم BPF bytecode.

يتم تنفيذ دعم BPF في نواة Linux في الإصدار v2.5 والإصدارات الأحدث ، والتي تتم إضافتها بشكل أساسي من خلال جهود Jay Schullist. ظل رمز BPF دون تغيير حتى عام 2011 ، عندما قام Eric Dumazett بإعادة ترجمة مترجم BPF للعمل في وضع JIT (المصدر: JIT لمرشحات الحزمة ). بعد ذلك ، يمكن للنواة ، بدلاً من تفسير رمز بايت BPF ، تحويل برامج BPF مباشرة إلى البنية المستهدفة: x86 ، ARM ، MIPS ، إلخ.

في وقت لاحق ، في عام 2014 ، اقترح أليكسي ستاروفيتوف آلية JIT جديدة لـ BPF. في الواقع ، أصبحت JIT الجديدة هي البنية الجديدة القائمة على BPF وتسمى eBPF. أعتقد أنه لبعض الوقت يتعايش كلا الجهازين الافتراضيين ، ولكن يتم حاليًا تصفية الحزم بناءً على eBPF. في الواقع ، في العديد من الأمثلة على التوثيق الحديث ، يُفهم أن BPF يعني eBPF ، ويُعرف BPF الكلاسيكي الآن باسم cBPF.

يوسع eBPF الجهاز الظاهري BPF الكلاسيكي بعدة طرق:

  • بناء على معماريات حديثة 64 بت. يستخدم eBPF تسجيلات 64 بت ويزيد عدد السجلات المتاحة من 2 (البطارية و X) إلى 10. يوفر eBPF أيضًا رموز تشغيل إضافية (BPF_MOV ، BPF_JNE ، BPF_CALL ...).
  • . BPF . , , . , eBPF . , eBPF tracepoint kprobe. eBPF, . eBPF : kernel/bpf.
  • . – «-», . eBPF .
  • . , , . . , eBPF .
  • . eBPF 4096 . eBPF eBPF- ( 32 ).

eBPF: مثال

هناك عدة أمثلة لـ eBPF في مصادر نواة Linux. وهي متوفرة في عينات / bpf /. لتجميع هذه الأمثلة ، أدخل ببساطة:

$ sudo make samples/bpf/

لن أكتب مثالًا جديدًا لـ eBPF بنفسي ، ولكنني سأستخدم إحدى العينات المتاحة في العينات / bpf /. سوف ألقي نظرة على بعض أقسام الكود وأشرح كيف يعمل. كمثال ، اخترت برنامجًا tracex4.

بشكل عام ، يتكون كل من الأمثلة في العينات / bpf / من ملفين. في هذه الحالة:

  • tracex4_kern.c، يحتوي على شفرة المصدر التي يجب تنفيذها في kernel كـ eBPF bytecode.
  • tracex4_user.c، يحتوي على برنامج من مساحة المستخدم.

في هذه الحالة ، نحتاج إلى تجميع tracex4_kern.ceBPF إلى رمز ثانوي. لا يوجد حاليًا gccجزء خادم لـ eBPF. لحسن الحظ ، clangيمكن أن يصدر eBPF بايت كود. Makefileيستخدم clangلترجمة tracex4_kern.cملف كائن.

ذكرت أعلاه أن إحدى أهم ميزات eBPFs هي البطاقات. يحدد tracex4_kern خريطة واحدة:

struct pair {
    u64 val;
    u64 ip;
};  

struct bpf_map_def SEC("maps") my_map = {
    .type = BPF_MAP_TYPE_HASH,
    .key_size = sizeof(long),
    .value_size = sizeof(struct pair),
    .max_entries = 1000000,
};

BPF_MAP_TYPE_HASH- أحد أنواع البطاقات العديدة التي يقدمها eBPF. في هذه الحالة ، إنها مجرد تجزئة. ربما لاحظت أيضًا إعلانًا SEC("maps"). SEC هو ماكرو يُستخدم لإنشاء مقطع جديد من ملف ثنائي. في الواقع ، tracex4_kernيحدد المثال قسمين آخرين:

SEC("kprobe/kmem_cache_free")
int bpf_prog1(struct pt_regs *ctx)
{   
    long ptr = PT_REGS_PARM2(ctx);

    bpf_map_delete_elem(&my_map, &ptr); 
    return 0;
}
    
SEC("kretprobe/kmem_cache_alloc_node") 
int bpf_prog2(struct pt_regs *ctx)
{
    long ptr = PT_REGS_RC(ctx);
    long ip = 0;

    //  ip-   kmem_cache_alloc_node() 
    BPF_KRETPROBE_READ_RET_IP(ip, ctx);

    struct pair v = {
        .val = bpf_ktime_get_ns(),
        .ip = ip,
    };
    
    bpf_map_update_elem(&my_map, &ptr, &v, BPF_ANY);
    return 0;
}   

تتيح لك هاتان الوظيفتان حذف إدخال من البطاقة ( kprobe/kmem_cache_free) وإضافة سجل جديد ( kretprobe/kmem_cache_alloc_node) إلى البطاقة . تتوافق جميع أسماء الوظائف المكتوبة بحروف كبيرة مع وحدات الماكرو المحددة في bpf_helpers.h.

إذا قمت بإخراج ملف تفريغ لأقسام ملف الكائن ، يجب أن أرى أن هذه الأقسام الجديدة محددة بالفعل: لا يزال هناك ، البرنامج الرئيسي. في الأساس ، يستمع هذا البرنامج إلى الأحداث . عند حدوث مثل هذا الحدث ، يتم تنفيذ رمز eBPF المقابل. يخزن الرمز سمة IP للكائن في الخريطة ، ثم يتم عرض هذا الكائن بشكل دوري في البرنامج الرئيسي. مثال: كيف يرتبط برنامج مساحة المستخدم وبرنامج eBPF؟ عند التهيئة ، يتم تحميل ملف كائن باستخدام دالة .

$ objdump -h tracex4_kern.o

tracex4_kern.o: file format elf64-little

Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000000 0000000000000000 0000000000000000 00000040 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 kprobe/kmem_cache_free 00000048 0000000000000000 0000000000000000 00000040 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
2 kretprobe/kmem_cache_alloc_node 000000c0 0000000000000000 0000000000000000 00000088 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
3 maps 0000001c 0000000000000000 0000000000000000 00000148 2**2
CONTENTS, ALLOC, LOAD, DATA
4 license 00000004 0000000000000000 0000000000000000 00000164 2**0
CONTENTS, ALLOC, LOAD, DATA
5 version 00000004 0000000000000000 0000000000000000 00000168 2**2
CONTENTS, ALLOC, LOAD, DATA
6 .eh_frame 00000050 0000000000000000 0000000000000000 00000170 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA


tracex4_user.ckmem_cache_alloc_node

$ sudo ./tracex4
obj 0xffff8d6430f60a00 is 2sec old was allocated at ip ffffffff9891ad90
obj 0xffff8d6062ca5e00 is 23sec old was allocated at ip ffffffff98090e8f
obj 0xffff8d5f80161780 is 6sec old was allocated at ip ffffffff98090e8f


tracex4_user.ctracex4_kern.oload_bpf_file

int main(int ac, char **argv)
{
    struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY};
    char filename[256];
    int i;

    snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);

    if (setrlimit(RLIMIT_MEMLOCK, &r)) {
        perror("setrlimit(RLIMIT_MEMLOCK, RLIM_INFINITY)");
        return 1;
    }

    if (load_bpf_file(filename)) {
        printf("%s", bpf_log_buf);
        return 1;
    }

    for (i = 0; ; i++) {
        print_old_objects(map_fd[1]);
        sleep(1);
    }

    return 0;
}

عند التنفيذ ، load_bpf_fileتتم إضافة المجسات المحددة في ملف eBPF إلى /sys/kernel/debug/tracing/kprobe_events. نحن الآن نستمع إلى هذه الأحداث ، ويمكن لبرنامجنا أن يفعل شيئًا عند حدوثها. يتم تنظيم جميع البرامج الأخرى في العينة / bpf / بطريقة مماثلة. لديهم دائمًا ملفين:

$ sudo cat /sys/kernel/debug/tracing/kprobe_events
p:kprobes/kmem_cache_free kmem_cache_free
r:kprobes/kmem_cache_alloc_node kmem_cache_alloc_node




  • XXX_kern.c: برنامج eBPF.
  • XXX_user.c: البرنامج الرئيسي.

يحدد برنامج eBPF البطاقات والوظائف المرتبطة بالقسم. عندما تقوم النواة بإلقاء حدث من نوع معين (على سبيل المثال ، tracepoint) ، يتم تنفيذ الدوال المقيدة. توفر الخرائط تبادل البيانات بين برنامج kernel وبرنامج مساحة المستخدم.

الخلاصة

توضح هذه المقالة BPF و eBPF. وأنا أعلم أن هناك اليوم الكثير من المعلومات والموارد حول eBPF، حيث أوصى بعض المواد أكثر لمزيد من الدراسة I.

يوصي القراءة:


All Articles