eBPF: إمكانيات التأمل الحديثة في Linux ، أو أن Kernel لم يعد صندوقًا أسود





كل شخص لديه كتاب مفضل عن السحر. شخص ما هو تولكين ، شخص ما هو براتشيت ، شخص مثلي هو ماكس فراي. سأخبركم اليوم عن سحر تكنولوجيا المعلومات المفضل لدي - حول BPF والبنية التحتية الحديثة من حوله.



BPF في ذروته الآن. تتطور التكنولوجيا على قدم وساق ، وتتغلغل في أكثر الأماكن غير المتوقعة وتصبح أكثر وأكثر في متناول المستخدم العادي. في كل مؤتمر شعبي تقريبًا اليوم ، يمكنك سماع تقرير حول هذا الموضوع ، ولا يُعد GopherCon Russia استثناءً: أقدم لك نسخة نصية من تقريري .



لن تكون هناك اكتشافات فريدة في هذه المقالة. سأحاول فقط أن أوضح لك ما هو BPF ، وما الذي يمكنه فعله ، وكيف يمكنه مساعدتك شخصيًا. سنلقي نظرة أيضًا على الميزات المتعلقة بـ Go.



بعد قراءة مقالتي ، أود حقًا أن تضيء عيناك بنفس الطريقة التي تضيء بها عيون الطفل الذي قرأ كتاب هاري بوتر لأول مرة ، حتى تعود إلى المنزل أو للعمل وتجرب "لعبة" جديدة في العمل.



ما هو eBPF؟



إذن ، ما نوع السحر الذي سيخبرك به رجل ملتح يبلغ من العمر 34 عامًا وعيناه مشتعلة؟



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







ربما هم على حق: بدون ألف تعليق ، لن نكتشف ذلك. لكن ما سأتفق معه بالتأكيد هو أن مجموعة البرامج الحديثة أصبحت أكثر تعقيدًا من أي وقت مضى.



BIOS ، EFI ، نظام التشغيل ، برامج التشغيل ، الوحدات النمطية ، المكتبات ، الشبكات ، قواعد البيانات ، ذاكرات التخزين المؤقت ، المنسقون مثل K8s ، الحاويات مثل Docker ، أخيرًا ، برنامجنا مع أوقات التشغيل ومجمعات القمامة. يمكن للمحترف الحقيقي الإجابة على سؤال عما يحدث بعد كتابة ya.ru في متصفحك لعدة أيام.



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



غالبًا ما أسأل الناس في المقابلات عن كيفية تصحيح الأخطاء إذا تم إيقاظهم في الرابعة صباحًا.



نهج واحد هو تحليل السجلات . لكن المشكلة هي أن فقط تلك التي وضعها المطور في نظامه هي المتاحة. إنها ليست مرنة.



الطريقة الثانية الشائعة هي دراسة المقاييس . تمت كتابة أنظمة المقاييس الثلاثة الأكثر شيوعًا في Go. المقاييس مفيدة جدًا ، لكنها لا تساعدك دائمًا على فهم الأسباب من خلال السماح لك برؤية الأعراض.



الطريقة الثالثة التي تكتسب شعبية هي ما يسمى بإمكانية الملاحظة: القدرة على طرح أسئلة معقدة بشكل تعسفي حول سلوك النظام والحصول على إجابات لها. نظرًا لأن السؤال قد يكون معقدًا للغاية ، فقد تتطلب الإجابة مجموعة متنوعة من المعلومات ، وحتى يتم طرح السؤال ، لا نعرف ماذا. هذا يعني المرونة أمر حيوي للملاحظة.



إعطاء القدرة على تغيير مستوى التسجيل على الطاير؟ تواصل مع مصحح أخطاء لبرنامج قيد التشغيل وافعل شيئًا هناك دون مقاطعة عمله؟ فهم الطلبات التي تأتي إلى النظام ، وتصور مصادر الطلبات البطيئة ، ومعرفة الذاكرة التي يتم إنفاقها من خلال pprof ، والحصول على رسم بياني للتغيير مع مرور الوقت؟ قياس زمن الوصول لوظيفة واحدة واعتماد الكمون على الوسائط؟ كل هذه الأساليب سوف أشير إلى إمكانية الملاحظة. هذه مجموعة من المرافق ، والأساليب ، والمعرفة ، والخبرة ، والتي ستمنحك معًا الفرصة للقيام ، إن لم يكن كل شيء ، فالكثير من "الربح" في نظام العمل. سكين تكنولوجيا المعلومات السويسرية الحديثة.







ولكن كيف يمكن القيام بذلك؟ كان هناك العديد من الأدوات في السوق: بسيطة ، معقدة ، خطيرة ، بطيئة. لكن موضوع مقال اليوم هو BPF.



نواة Linux تعتمد على الأحداث. يمكن تمثيل كل ما يحدث في النواة وفي النظام ككل تقريبًا كمجموعة من الأحداث. الانقطاع هو حدث ، واستلام حزمة عبر الشبكة هو حدث ، ونقل المعالج إلى عملية أخرى هو حدث ، وإطلاق وظيفة هو حدث.

لذلك ، BPF هو نظام فرعي من Linux kernel يجعل من الممكن كتابة البرامج الصغيرة التي سيتم إطلاقها بواسطة kernel استجابة للأحداث. يمكن لهذه البرامج إلقاء الضوء على ما يحدث في نظامك والتحكم فيه.



كانت مقدمة طويلة جدا. دعنا نقترب من الواقع.



شهد عام 1994 الإصدار الأول من BPF ، والذي ربما يكون قد صادفه بعضكم عند كتابة قواعد بسيطة لأداة tcpdump لعرض حزم الشبكة أو شمها. يمكن لـ tcpdump تعيين "عوامل تصفية" ليس كل شيء ، ولكن فقط الحزم التي تهتم بها. على سبيل المثال ، "بروتوكول TCP فقط ومنفذ 80 فقط". لكل حزمة مرور ، تم تشغيل وظيفة لتقرير ما إذا كنت تريد حفظ هذه الحزمة المعينة أم لا. يمكن أن يكون هناك الكثير من الحزم ، مما يعني أن وظيفتنا يجب أن تكون سريعة جدًا. لقد تم تحويل مرشحات tcpdump الخاصة بنا للتو إلى وظائف BPF ، ويظهر مثال على ذلك في الصورة أدناه.





يتم تقديم مرشح بسيط لـ tcpdump كبرنامج BPF



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







في عام 2014 ، قام Alexey Starovoitov بتوسيع وظائف BPF. قام بزيادة عدد السجلات والحجم المسموح به للبرنامج ، وأضاف تجميع JIT وعمل أداة تحقق للتحقق من سلامة البرامج. ولكن الشيء الأكثر إثارة للإعجاب هو أن برامج BPF الجديدة يمكن إطلاقها ليس فقط أثناء معالجة الحزم ، ولكن أيضًا استجابة للعديد من أحداث kernel ، وتمرير المعلومات ذهابًا وإيابًا بين kernel ومساحة المستخدم.



فتحت هذه التغييرات الطريق لحالات استخدام جديدة لـ BPF. بعض الأشياء التي تم إجراؤها سابقًا عن طريق كتابة وحدات نواة معقدة وخطيرة أصبح الآن من السهل نسبيًا القيام بها من خلال BPF. لماذا هذا رائع؟ نعم ، لأن أي خطأ أثناء كتابة الوحدة غالبًا ما يؤدي إلى الذعر. ليس ذعر Go-shnoy رقيق ، ولكن للذعر kernel ، وبعد ذلك - إعادة التشغيل فقط.



يتمتع مستخدم Linux العادي الآن بقدرة فائقة على النظر تحت غطاء المحرك ، ولم يكن متاحًا في السابق إلا لمطوري النواة المتشددين أو أي شخص آخر. هذا الخيار مشابه للقدرة على كتابة برنامج بدون جهد لنظام iOS أو Android: على الهواتف القديمة كان الأمر مستحيلاً أو أصعب بكثير.



الإصدار الجديد من BPF من Alexey يسمى eBPF (من الكلمة الموسعة - الموسعة). ولكن الآن قد حلت محل جميع الإصدارات القديمة من BPF وأصبحت شائعة جدًا لدرجة أن الجميع يطلق عليها ببساطة BPF من أجل البساطة.



أين يتم استخدام BPF؟



إذن ما هي هذه الأحداث ، أو المحفزات ، التي يمكن ربط برامج BPF بها ، وكيف بدأ الناس في تسخير هذه القوة المكتشفة حديثًا؟



يوجد حاليًا مجموعتان كبيرتان من المشغلات.



تُستخدم المجموعة الأولى لمعالجة حزم الشبكة وإدارة حركة مرور الشبكة. هذه هي XDP وأحداث التحكم في حركة المرور وغير ذلك.



هذه الأحداث ضرورية من أجل:



  • , . Cloudflare Facebook BPF- DDoS-. ( BPF- ), . .

  • , , — , , . . Facebook, , , .

  • بناء موازين ذكية. المثال الأبرز هو مشروع Cilium ، والذي يستخدم غالبًا في مجموعة K8s كشبكة متداخلة . يدير Cilium حركة المرور: يوازنها ويعيد توجيهها ويحللها. ويتم كل هذا بمساعدة برامج BPF الصغيرة التي أطلقتها النواة استجابة لحدث أو آخر متعلق بحزم الشبكة أو مآخذ التوصيل.



كانت هذه هي المجموعة الأولى من المشغلات المرتبطة بالمسائل المتصلة بالشبكة مع القدرة على التأثير على السلوك. المجموعة الثانية تتعلق بملاحظة أكثر عمومية ؛ غالبًا ما لا تمتلك البرامج من هذه المجموعة القدرة على التأثير في شيء ما ، ولكن يمكنها فقط "الملاحظة". هي تهمني أكثر



تحتوي هذه المجموعة على مشغلات مثل:



  • perf events — , Linux- perf: , , minor/major- . . , , , - . , , , , .

  • tracepoints — ( ) , (, ). , — , , , , . - , tracepoints :
    • ;

    • , ;

    • API, , , , , API.



      , , , , , pprof .


  • USDT — , tracepoints, user space-. . : MySQL, , PHP, Python. enable-dtrace . , Go . -, , DTrace . , , Solaris: , , GC -, .



حسنًا ، يبدأ مستوى آخر من السحر:



  • تمنحنا مشغلات ftrace القدرة على تشغيل برنامج BPF في بداية أي وظيفة kernel تقريبًا. ديناميكية بالكامل. هذا يعني أن النواة ستستدعي وظيفة BPF قبل تنفيذ أي وظيفة kernel تختارها. أو كل وظائف kernel - أيا كان. يمكنك إرفاق جميع وظائف kernel والحصول على تصور جيد لجميع المكالمات في الإخراج.

  • تعطي kprobes / uprobes نفس الشيء تقريبًا مثل ftrace ، فقط لدينا القدرة على الانجذاب إلى أي مكان عند تنفيذ وظيفة ، سواء في النواة أو في مساحة المستخدم. في منتصف الوظيفة ، هل يوجد نوع ما إذا كان على متغير وتحتاج إلى رسم رسم بياني لقيم هذا المتغير؟ لا مشكلة.

  • kretprobes/uretprobes — , user space. , , . , , PID fork.



إن الشيء الأكثر روعة في كل هذا ، أكرر ، هو أنه ، عند استدعائه لأي من هذه المشغلات ، يمكن لبرنامج BPF الخاص بنا إلقاء نظرة جيدة حوله: اقرأ حجج الدالة ، ووقتها ، وقراءة المتغيرات ، والمتغيرات العامة ، واتخاذ تتبع مكدس ، وحفظ ذلك ثم لاحقًا ، قم بنقل البيانات إلى مساحة المستخدم للمعالجة ، أو الحصول على البيانات من مساحة المستخدم للتصفية أو بعض أوامر التحكم. جمال!



لا أعرف عنك ، لكن بالنسبة لي البنية التحتية الجديدة هي مثل لعبة كنت أنتظرها بفارغ الصبر لفترة طويلة.



API ، أو كيفية استخدامه



حسنًا ، ماركو ، لقد أقنعتنا بالتطلع نحو BPF. لكن كيف نتعامل معها؟



دعنا نلقي نظرة على ما يتكون منه برنامج BPF وكيفية التفاعل معه.







أولاً ، لدينا برنامج BPF الذي ، إذا تم التحقق منه ، سيتم تحميله في kernel. هناك سيتم تجميع JIT في كود الآلة وتشغيله في وضع kernel عندما يتم توصيل المشغل بالنيران.



برنامج BPF لديه القدرة على التفاعل مع الجزء الثاني - برنامج مساحة المستخدم. هناك طريقتان للقيام بذلك. يمكننا الكتابة إلى مخزن مؤقت دائري ، ويمكن لجزء مساحة المستخدم القراءة منه. يمكننا أيضًا الكتابة والقراءة في مخزن قيمة المفتاح ، والذي يُسمى خريطة BPF ، ويمكن لجزء مساحة المستخدم ، على التوالي ، فعل الشيء نفسه ، وبالتالي ، يمكنهم نقل بعض المعلومات لبعضهم البعض.



طريق مستقيم



أسهل طريقة للعمل مع BPF ، والتي لا يجب أن تبدأ بها بأي حال من الأحوال ، هي كتابة برامج BPF مشابهة للغة C وتجميع هذا الرمز باستخدام مترجم Clang في كود الجهاز الظاهري. ثم نقوم بتحميل هذا الرمز باستخدام استدعاء نظام BPF مباشرة ونتفاعل مع برنامج BPF الخاص بنا أيضًا باستخدام استدعاء نظام BPF.







التبسيط الأول المتاح هو استخدام مكتبة libbpf ، التي تأتي مع مصادر kernel وتسمح لك بعدم العمل مباشرة مع استدعاء نظام BPF. في الواقع ، يوفر أغلفة ملائمة لتحميل الكود ، والعمل مع ما يسمى بالخرائط لنقل البيانات من النواة إلى مساحة المستخدم والعكس.



نسخة مخفية الوجهة



من الواضح أن هذا الاستخدام بعيد كل البعد عن أن يكون صديقًا للإنسان. لحسن الحظ ، ظهر مشروع BCC تحت العلامة التجارية iovizor ، مما يبسط حياتنا إلى حد كبير.







في الواقع ، تقوم بإعداد بيئة التجميع بأكملها وتمنحنا الفرصة لكتابة برامج BPF واحدة ، حيث سيتم تجميع الجزء C وتحميله في النواة تلقائيًا ، ويمكن عمل جزء مساحة المستخدم في لغة بايثون بسيطة ومفهومة.



bpftrace



لكن BCC تبدو معقدة لكثير من الأشياء. لسبب ما ، لا يحب الناس كتابة أجزاء في C.



قدم نفس الأشخاص من iovizor أداة bpftrace ، والتي تسمح لك بكتابة نصوص برمجية BPF بلغة برمجة بسيطة على غرار AWK (أو بشكل عام أحادي الخط).







أعد خبير الأداء والملاحظة الشهير Brendan Gregg التصور التالي للطرق المتاحة للعمل مع BPF:







عموديًا ، لدينا بساطة الأداة وقوتها الأفقية. يمكن ملاحظة أن BCC هي أداة قوية للغاية ، ولكنها ليست بسيطة للغاية. bpftrace أبسط بكثير ، لكنه أقل قوة.



أمثلة على استخدام BPF



لكن دعونا نلقي نظرة على القدرات السحرية التي أصبحت متاحة لنا ، مع أمثلة محددة.



يحتوي كل من BCC و bpftrace على مجلد Tools ، والذي يحتوي على عدد كبير من البرامج النصية الجاهزة والمثيرة للاهتمام والمفيدة. إنها أيضًا Stack Overflow المحلية التي يمكنك من خلالها نسخ أجزاء من التعليمات البرمجية لنصوصك.



على سبيل المثال ، إليك نص برمجي يُظهر وقت الاستجابة لاستعلامات DNS:



 ╭─marko@marko-home ~ 
╰─$ sudo gethostlatency-bpfcc
TIME      PID    COMM                  LATms HOST
16:27:32  21417  DNS Res~ver #93        3.97 live.github.com
16:27:33  22055  cupsd                  7.28 NPI86DDEE.local
16:27:33  15580  DNS Res~ver #87        0.40 github.githubassets.com
16:27:33  15777  DNS Res~ver #89        0.54 github.githubassets.com
16:27:33  21417  DNS Res~ver #93        0.35 live.github.com
16:27:42  15580  DNS Res~ver #87        5.61 ac.duckduckgo.com
16:27:42  15777  DNS Res~ver #89        3.81 www.facebook.com
16:27:42  15777  DNS Res~ver #89        3.76 tech.badoo.com :-)
16:27:43  21417  DNS Res~ver #93        3.89 static.xx.fbcdn.net
16:27:43  15580  DNS Res~ver #87        3.76 scontent-frt3-2.xx.fbcdn.net
16:27:43  15777  DNS Res~ver #89        3.50 scontent-frx5-1.xx.fbcdn.net
16:27:43  21417  DNS Res~ver #93        4.98 scontent-frt3-1.xx.fbcdn.net
16:27:44  15580  DNS Res~ver #87        5.53 edge-chat.facebook.com
16:27:44  15777  DNS Res~ver #89        0.24 edge-chat.facebook.com
16:27:44  22099  cupsd                  7.28 NPI86DDEE.local
16:27:45  15580  DNS Res~ver #87        3.85 safebrowsing.googleapis.com
^C%


تعرض الأداة وقت تنفيذ استعلامات DNS في الوقت الفعلي ، بحيث يمكنك على سبيل المثال اكتشاف بعض القيم المتطرفة غير المتوقعة.



وهذا نص "يتجسس" على ما يكتبه الآخرون على أجهزتهم الطرفية:



 ╭─marko@marko-home ~ 
╰─$ sudo bashreadline-bpfcc         
TIME      PID    COMMAND
16:51:42  24309  uname -a
16:52:03  24309  rm -rf src/badoo


يمكن استخدام هذا النوع من البرامج النصية للقبض على "الجار السيئ" أو تدقيق أمان خوادم الشركة.



برنامج نصي لعرض مكالمات التدفق للغات عالية المستوى:



 ╭─marko@marko-home ~/tmp 
╰─$ sudo /usr/sbin/lib/uflow -l python 20590
Tracing method calls in python process 20590... Ctrl-C to quit.
CPU PID    TID    TIME(us) METHOD
5   20590  20590  0.173    -> helloworld.py.hello                  
5   20590  20590  0.173      -> helloworld.py.world                
5   20590  20590  0.173      <- helloworld.py.world                
5   20590  20590  0.173    <- helloworld.py.hello                  
5   20590  20590  1.174    -> helloworld.py.hello                  
5   20590  20590  1.174      -> helloworld.py.world                
5   20590  20590  1.174      <- helloworld.py.world                
5   20590  20590  1.174    <- helloworld.py.hello                  
5   20590  20590  2.175    -> helloworld.py.hello                  
5   20590  20590  2.176      -> helloworld.py.world                
5   20590  20590  2.176      <- helloworld.py.world                
5   20590  20590  2.176    <- helloworld.py.hello                  
6   20590  20590  3.176    -> helloworld.py.hello                  
6   20590  20590  3.176      -> helloworld.py.world                
6   20590  20590  3.176      <- helloworld.py.world                
6   20590  20590  3.176    <- helloworld.py.hello                  
6   20590  20590  4.177    -> helloworld.py.hello                  
6   20590  20590  4.177      -> helloworld.py.world                
6   20590  20590  4.177      <- helloworld.py.world                
6   20590  20590  4.177    <- helloworld.py.hello                  
^C%


يوضح هذا المثال مكدس الاستدعاءات لبرنامج بايثون.



قام نفس Brendan Gregg بعمل صورة قام فيها بجمع كل النصوص الموجودة مع الأسهم التي تشير إلى تلك الأنظمة الفرعية التي تسمح كل أداة "بمراقبتها". كما ترى ، لدينا بالفعل عدد كبير من المرافق الجاهزة المتاحة - في أي مناسبة تقريبًا.





لا تحاول أن ترى شيئًا هنا. الصورة تستخدم كمرجع



ماذا عنا مع Go؟ 



الآن دعنا نتحدث عن Go. لدينا سؤالان رئيسيان:



  • هل يمكنك كتابة برامج BPF في Go؟

  • هل من الممكن تحليل البرامج المكتوبة في Go؟



دعنا نذهب بالترتيب.



حتى الآن ، فإن المترجم الوحيد الذي يمكنه التحويل إلى تنسيق يفهمه محرك BPF هو Clang. مترجم آخر شهير ، GCC ، ليس لديه خلفية BPF حتى الآن. ولغة البرمجة الوحيدة التي يمكن أن تترجم إلى BPF هي نسخة محدودة للغاية من C.



ومع ذلك ، فإن برنامج BPF له جزء ثان ، وهو في مساحة المستخدم. ويمكن كتابتها في Go.



كما ذكرت أعلاه ، تسمح لك BCC بكتابة هذا الجزء في Python ، وهي اللغة الأساسية للأداة. في الوقت نفسه ، في المستودع الرئيسي ، يدعم BCC أيضًا Lua و C ++ ، وفي مستودع الطرف الثالث ، يدعم Go أيضًا .







مثل هذا البرنامج يشبه تمامًا برنامج Python. في البداية يوجد سطر فيه برنامج BPF في لغة C ، ثم نخبر مكان إرفاق هذا البرنامج ، ونتفاعل معه بطريقة ما ، على سبيل المثال ، نحصل على بيانات من خريطة EPF.



في الواقع ، هذا كل شيء. يمكنك مشاهدة المثال بمزيد من التفاصيل على جيثب .

ربما يكون العيب الرئيسي هو استخدام مكتبة C libbcc أو libbpf للعمل ، وبناء برنامج Go بمثل هذه المكتبة لا يبدو على الإطلاق وكأنه نزهة لطيفة في الحديقة.



بالإضافة إلى iovisor / gobpf ، وجدت ثلاثة مشاريع حالية أخرى تسمح لك بكتابة جزء userland في Go.





لا يتطلب إصدار Dropbox أي مكتبات C ، ولكن عليك إنشاء جزء kernel من برنامج BPF بنفسك باستخدام Clang ثم تحميله في kernel باستخدام برنامج Go.



يحتوي إصدار Cilium على نفس ميزات إصدار Dropbox. لكن من الجدير بالذكر ، فقط لأنه تم تنفيذه بواسطة رجال من مشروع Cilium ، مما يعني أنه محكوم عليه بالنجاح.



أحضرت المشروع الثالث لاكتمال الصورة. مثل الاثنين السابقين ، لا يحتوي على تبعيات C خارجية ، ويتطلب التجميع اليدوي لبرنامج BPF C ، لكن لا يبدو أنه يحمل الكثير من الأمل.



في الواقع ، هناك سؤال آخر: لماذا تكتب برامج BPF في Go على الإطلاق؟ بعد كل شيء ، إذا نظرت إلى BCC أو bpftrace ، فإن برامج BPF تأخذ عمومًا أقل من 500 سطر من التعليمات البرمجية. أليس من الأسهل كتابة نص بلغة bpftrace أو الكشف عن القليل من بايثون؟ أرى سببين هنا. 



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



الخيار الثاني: أنت لا تكتب نصًا بسيطًا ، بل نظامًا واسع النطاق يستخدم أيضًا BPF داخليًا. لدي مثال على مثل هذا النظام في Go :







يبدو مشروع Scope وكأنه ثنائي واحد ، عند إطلاقه في البنية التحتية لـ K8s أو سحابة أخرى ، يقوم بتحليل كل ما يحدث حوله ، ويظهر ما هي الحاويات والخدمات وكيف تتفاعل ، وما إلى ذلك ، ويتم الكثير من هذا باستخدام BPF. مشروع مثير للاهتمام.



تحليل برامج Go



إذا كنت تتذكر ، كان لدينا سؤال آخر: هل يمكننا تحليل البرامج المكتوبة في Go باستخدام BPF؟ الفكرة الأولى - بالطبع! ما الفرق في أي لغة يُكتب البرنامج؟ بعد كل شيء ، هذا مجرد رمز مجمع ، مثل جميع البرامج الأخرى ، يحسب شيئًا ما على المعالج ، ويأكل الذاكرة كما لو لم يكن في نفسه ، ويتفاعل مع الأجهزة من خلال النواة ، ومع النواة من خلال مكالمات النظام. من حيث المبدأ ، هذا صحيح ، لكن هناك ميزات لمستويات مختلفة من التعقيد.



مجادلات عابرة



إحدى الميزات هي أن Go لا يستخدم ABI كما تفعل معظم اللغات الأخرى. لقد حدث أن قرر الآباء المؤسسون أخذ ABI لنظام الخطة 9 ، والذي كانوا يعرفون جيدًا.



ABI مثل API ، اتفاقية التشغيل البيني ، فقط على مستوى وحدات البت والبايت ورمز الجهاز.



عنصر ABI الرئيسي الذي يثير اهتمامنا هو كيفية تمرير وسيطاته إلى الوظيفة وكيف يتم تمرير الاستجابة مرة أخرى من الوظيفة. بينما يستخدم معيار x86-64 ABI سجلات المعالج لتمرير الحجج والاستجابات ، يستخدم Plan 9 ABI مكدسًا لهذا الغرض.



لم يخطط روب بايك وفريقه لوضع معيار آخر: كان لديهم بالفعل مترجم C جاهز تقريبًا لنظام Plan 9 ، بسيط مثل اثنين أو اثنين ، والذي تم تحويله بسرعة إلى مترجم لـ Go. النهج الهندسي في العمل.



لكن هذه ، في الواقع ، ليست مشكلة حرجة للغاية. أولاً ، قد نرى قريبًا في Go تمرير الحجج عبر السجلات ، وثانيًا ، الحصول على الحجج من المكدس من BPF ليس بالأمر الصعب: تمت إضافة الاسم المستعار sargX إلى bpftrace ، وسيظهر نفس الشيء في BCC ، على الأرجح في المستقبل القريب ...



تحديث : من لحظة إعداد التقرير ، ظهر حتى اقتراح رسمي مفصل للانتقال إلى استخدام السجلات في ABI.



معرف الخيط الفريد



الميزة الثانية تتعلق بميزة Go المفضلة ، goroutines. تتمثل إحدى طرق قياس زمن انتقال الوظيفة في توفير الوقت المستغرق لاستدعاء الوظيفة ، ووقت الخروج من الوظيفة ، وحساب الفرق ؛ وحفظ وقت البدء بمفتاح يحتوي على اسم الوظيفة و TID (رقم الموضوع). هناك حاجة إلى رقم الخيط ، حيث يمكن استدعاء نفس الوظيفة في وقت واحد بواسطة برامج مختلفة أو خيوط مختلفة من نفس البرنامج.



لكن في Go ، يتنقل goroutines بين خيوط النظام: الآن يتم تنفيذ goroutine على مؤشر ترابط واحد ، وبعد ذلك بقليل على آخر. وفي حالة Go ، لن نضع TID في المفتاح ، ولكن GID ، أي معرف goroutine ، لكن لا يمكننا الحصول عليه. من الناحية الفنية ، هذا المعرف موجود. يمكنك حتى إخراجها باستخدام الاختراقات القذرة ، نظرًا لأنها موجودة في مكان ما على المكدس ، لكن القيام بذلك محظور تمامًا بموجب توصيات مجموعة تطوير Go الرئيسية. لقد شعروا أننا لن نحتاج أبدًا إلى مثل هذه المعلومات. وكذلك التخزين المحلي Goroutine ، لكني استطرادا.



توسيع المكدس



المشكلة الثالثة هي الأخطر. خطيرة جدًا لدرجة أنه حتى لو حللنا المشكلة الثانية بطريقة ما ، فلن تساعدنا بأي طريقة في قياس زمن انتقال وظائف Go.



ربما يفهم معظم القراء جيدًا ما هو المكدس. نفس المكدس حيث ، على عكس الكومة أو الكومة ، يمكنك تخصيص ذاكرة للمتغيرات وعدم التفكير في تحريرها.



إذا تحدثنا عن C ، فسيكون للمكدس حجم ثابت. إذا تجاوزنا هذا الحجم الثابت ، فسيحدث تجاوز المكدس الشهير .



في Go ، المكدس ديناميكي. في الإصدارات القديمة ، كانت عبارة عن أجزاء متسلسلة من الذاكرة. إنه الآن قطعة ذات حجم ديناميكي مستمر. هذا يعني أنه إذا لم تكن القطعة المحددة كافية لنا ، فسنقوم بتوسيع القطعة الحالية. وإذا لم نتمكن من التوسع ، فإننا نختار واحدة أخرى أكبر وننقل جميع البيانات من المكان القديم إلى المكان الجديد. إنها قصة رائعة تلامس الضمانات الأمنية ، cgo ، جامع القمامة ، لكن هذا موضوع لمقال آخر.



من المهم أن تعرف أنه لكي يقوم Go بتحريك المكدس ، فإنه يحتاج إلى السير في مكدس استدعاء البرنامج ، وجميع المؤشرات الموجودة على المكدس.



هذا هو المكان الذي تكمن فيه المشكلة الرئيسية: مجس الحالب ، الذي يستخدم لإرفاق وظيفة BPF ، يقوم ديناميكيًا بتغيير المكدس في نهاية تنفيذ الوظيفة لتضمين استدعاء لمعالجهم ، ما يسمى الترامبولين. وينتهي مثل هذا التغيير غير المتوقع في حزمة Go في معظم الحالات بتعطل البرنامج. وجه الفتاة!



ومع ذلك ، هذه القصة ليست فريدة من نوعها. تعطل أداة إزالة الالتفاف "المكدس" C ++ أيضًا مرة واحدة في وقت معالجة الاستثناء.



لا يوجد حل لهذه المشكلة. كالعادة في مثل هذه الحالات ، يتبادل الطرفان الحجج المعقولة تمامًا حول ذنب كل منهما.



ولكن إذا كنت حقًا بحاجة إلى وضع مجس البول ، فيمكن التحايل على المشكلة. كيف؟ لا تضع مجس البول. يمكننا وضع رداء في جميع الأماكن التي نخرج فيها من الوظيفة. قد يكون هناك مكان واحد ، أو ربما 50.



وهنا يأتي تفرد Go في أيدينا.



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



تكمن الميزة في أن الإصدار 1.14 من برنامج التحويل البرمجي Go غير قادر بعد على القيام بتحسين استدعاء الذيل. هذا يعني أن خدعة التعلق بجميع عمليات الخروج الصريحة من الوظيفة تعمل ، وإن كانت مملة للغاية.



أمثلة على



لا تعتقد أن BPF غير مجدي لـ Go. هذا أبعد ما يكون عن الحالة: يمكننا القيام بكل شيء آخر لا يمس الفروق الدقيقة المذكورة أعلاه. ونحن سوف. 

دعنا نلقي نظرة على بعض الأمثلة.



لنأخذ برنامجًا بسيطًا للتحضير. في الأساس هو خادم ويب يستمع إلى المنفذ 8080 ولديه معالج طلب HTTP. سيحصل المعالج على معلمة الاسم ، ومعلمة Go من عنوان URL ويقوم بنوع من التحقق من "الموقع" ، ثم يرسل جميع المتغيرات الثلاثة (الاسم والسنة والتحقق من الحالة) إلى وظيفة PreparAnswer () ، والتي ستجهز استجابة كسلسلة.







التحقق من صحة الموقع هو طلب HTTP يتحقق مما إذا كان موقع المؤتمر يعمل باستخدام أنبوب و goroutine. ووظيفة تحضير الاستجابة تحولها كلها إلى سلسلة نصية يمكن قراءتها.



سنطلق برنامجنا بطلب تجعيد بسيط:







كمثال أول ، سنستخدم bpftrace لطباعة جميع استدعاءات الوظائف لبرنامجنا. نعلق هنا على جميع الوظائف التي تقع تحت الرئيسي. في Go ، تحتوي جميع وظائفك على رمز يشبه اسم الحزمة-dot-function. مجموعتنا رئيسية ، وسيكون وقت تشغيل الوظيفة هو وقت التشغيل.







عندما أقوم بالتجعيد ، يتم تشغيل المعالج ووظيفة التحقق من الموقع والوظيفة الفرعية goroutine ، ثم وظيفة إعداد الاستجابة. صف دراسي!



بعد ذلك ، لا أريد فقط عرض الوظائف التي يتم تنفيذها ، ولكن أيضًا عرض حججها. لنأخذ وظيفة PreparAnswer (). لديها ثلاث حجج. دعنا نحاول طباعة اثنين int.

نحن نأخذ bpftrace ، الآن ليس سطرًا واحدًا ، ولكن نصًا. نعلق على وظيفتنا ونستخدم الأسماء المستعارة لحجج المكدس التي ذكرتها.



في الناتج ، نرى ما مررنا به في عام 2020 ، وحصلنا على الحالة 200 ، واجتازنا عام 2021 مرة واحدة.لكن







للدالة ثلاث حجج. الأول هو سلسلة. ماذا عنه؟



دعنا فقط نطبع كل وسيطات المكدس من 0 إلى 4. وماذا نرى؟ بعض الشخصيات الكبيرة ، وبعضها الأصغر والأرقام القديمة 2021 و 200. ما هي هذه الأرقام الغريبة في البداية؟







هذا هو المكان الذي يكون من المفيد معرفة جهاز Go. إذا كانت السلسلة في لغة C عبارة عن مصفوفة من الأحرف صفرية منتهية ، فإن السلسلة في Go هي في الواقع بنية تتكون من مؤشر إلى مجموعة من الأحرف (بالمناسبة ليست منتهية بصفر) وطول.







لكن مترجم Go ، عند تمرير سلسلة نصية كوسيطة ، يوسع هذه البنية ويمررها كوسيطتين. واتضح أن الرقم الغريب الأول هو مجرد مؤشر للمصفوفة ، والثاني هو الطول.



والحقيقة: الطول المتوقع للسلسلة هو 22.



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







حسنًا ، دعنا نلقي نظرة على وقت التشغيل. على سبيل المثال ، أردت أن أعرف ما الذي يطلقه برنامجنا goroutines. أعلم أن goroutines يتم تشغيلها بواسطة وظائف newproc () و newproc1 (). دعونا نتواصل معهم. الوسيطة الأولى لوظيفة newproc1 () هي مؤشر إلى بنية funcval ، التي تحتوي على حقل واحد فقط - مؤشر دالة:







في هذه الحالة ، سنستخدم الفرصة لتحديد الهياكل مباشرة في البرنامج النصي. إنه أسهل قليلاً من اللعب بمجموعات الأوفست. هنا قمنا بإخراج جميع goroutines التي تم إطلاقها عند استدعاء المعالج الخاص بنا. وإذا حصلنا بعد ذلك على أسماء الرموز لتعويضاتنا ، فسنرى من بينها وظيفة checkSite الخاصة بنا. الصيحة!







هذه الأمثلة هي قطرة في محيط قدرات BPF و BCC و bpftrace. من خلال المعرفة الصحيحة بالأمور الداخلية والخبرة ، يمكنك الحصول على أي معلومات تقريبًا من برنامج قيد التشغيل دون إيقافه أو تغييره.



خاتمة



هذا كل ما أردت أن أخبرك عنه. آمل أن أكون قادرًا على إلهامك.



BPF هو أحد أكثر الاتجاهات الواعدة في لينكس. وأنا متأكد من أننا سنرى في السنوات القادمة أشياء أكثر إثارة للاهتمام ليس فقط في التكنولوجيا نفسها ، ولكن أيضًا في الأدوات وتوزيعها.



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



بالنسبة إلى Go ، كنا ، كالعادة ، مميزين جدًا. لدينا دائمًا بعض الفروق الدقيقة: إما أن يكون المترجم مختلفًا ، ثم ABI ، فنحن بحاجة إلى نوع من GOPATH ، اسم لا يمكن أن يكون Google. لكننا أصبحنا قوة لا يستهان بها ، وأعتقد أن الحياة ستتحسن فقط.



All Articles