BPF ለትናንሾቹ፣ ክፍል አንድ፡ የተራዘመ BPF

መጀመሪያ ላይ ቴክኖሎጂ ነበረ እና BPF ተብሎ ይጠራ ነበር. ተመለከትናት ቀዳሚየዚህ ተከታታይ የብሉይ ኪዳን አንቀጽ። እ.ኤ.አ. በ 2013 በአሌሴ ስታሮቮይቶቭ እና በዳንኤል ቦርክማን ጥረት የተሻሻለው የእሱ ስሪት ፣ ለዘመናዊ ባለ 64-ቢት ማሽኖች ተዘጋጅቶ በሊኑክስ ከርነል ውስጥ ተካቷል። ይህ አዲስ ቴክኖሎጂ ለአጭር ጊዜ Internal BPF ተባለ፣ በመቀጠልም Extended BPF ተባለ፣ እና አሁን፣ ከብዙ አመታት በኋላ፣ ሁሉም ሰው በቀላሉ BPF ይለዋል።

በግምት፣ BPF በዘፈቀደ በተጠቃሚ የቀረበ ኮድ በሊኑክስ ከርነል ቦታ ላይ እንዲያሄዱ ይፈቅድልዎታል፣ እና አዲሱ አርክቴክቸር በጣም የተሳካ ሆኖ ስለተገኘ ሁሉንም አፕሊኬሽኖቹን ለመግለጽ ደርዘን ተጨማሪ ጽሑፎች እንፈልጋለን። (ከዚህ በታች ባለው የአፈጻጸም ኮድ ላይ እንደምታዩት ገንቢዎቹ ጥሩ ያላደረጉት ብቸኛው ነገር ጥሩ አርማ መፍጠር ነበር።)

ይህ ጽሑፍ የBPF ቨርቹዋል ማሽንን አወቃቀር፣ ከ BPF ጋር ለመስራት የከርነል መገናኛዎች፣ የልማት መሳሪያዎች፣ እንዲሁም ስለ ነባር ችሎታዎች አጭር፣ በጣም አጭር አጠቃላይ እይታን ያብራራል። የ BPF ተግባራዊ አተገባበርን በጥልቀት ለማጥናት ወደፊት የሚያስፈልገንን ሁሉ።
BPF ለትናንሾቹ፣ ክፍል አንድ፡ የተራዘመ BPF

የጽሁፉ ማጠቃለያ

የ BPF ሥነ ሕንፃ መግቢያ። በመጀመሪያ፣ ስለ BPF አርክቴክቸር በወፍ በረር እንመለከተዋለን እና ዋና ዋና ክፍሎችን እንዘረዝራለን።

የBPF ቨርቹዋል ማሽን መመዝገቢያ እና ትዕዛዝ ስርዓት። ስለ አጠቃላይ የስነ-ህንፃው ሀሳብ ካለን ፣ የ BPF ምናባዊ ማሽንን አወቃቀር እንገልፃለን።

የ BPF እቃዎች የሕይወት ዑደት, bpffs የፋይል ስርዓት. በዚህ ክፍል ውስጥ የ BPF ዕቃዎችን የሕይወት ዑደት - ፕሮግራሞችን እና ካርታዎችን በዝርዝር እንመለከታለን.

የbpf ስርዓት ጥሪን በመጠቀም ነገሮችን ማስተዳደር። ስለ ስርዓቱ የተወሰነ ግንዛቤ በመያዝ በመጨረሻ ልዩ የስርዓት ጥሪን በመጠቀም እቃዎችን ከተጠቃሚ ቦታ እንዴት መፍጠር እና ማቀናበር እንደሚቻል እንመለከታለን - bpf(2).

Пишем программы BPF с помощью libbpf. እርግጥ ነው, የስርዓት ጥሪን በመጠቀም ፕሮግራሞችን መጻፍ ይችላሉ. ግን ከባድ ነው። ለበለጠ ተጨባጭ ሁኔታ፣ የኑክሌር ፕሮግራም አውጪዎች ቤተ መፃህፍት ገነቡ libbpf. በሚቀጥሉት ምሳሌዎች የምንጠቀመው መሰረታዊ የ BPF መተግበሪያ አጽም እንፈጥራለን።

የከርነል አጋዥዎች. እዚህ የ BPF ፕሮግራሞች የከርነል አጋዥ ተግባራትን እንዴት ማግኘት እንደሚችሉ እንማራለን - ይህ መሳሪያ ከካርታዎች ጋር ፣ ከጥንታዊው ጋር ሲነፃፀር የአዲሱን BPF ችሎታዎች በመሠረቱ ያሰፋል።

ከ BPF ፕሮግራሞች የካርታዎች መዳረሻ። በዚህ ነጥብ ካርታዎችን የሚጠቀሙ ፕሮግራሞችን እንዴት በትክክል መፍጠር እንደምንችል ለመረዳት በቂ እውቀት እናገኛለን። እና ወደ ታላቁ እና ኃያል አረጋጋጭ በፍጥነት እንመልከታቸው።

የልማት መሳሪያዎች. ለሙከራዎች የሚያስፈልጉትን መገልገያዎች እና ከርነል እንዴት እንደሚሰበሰቡ የእገዛ ክፍል።

መደምደሚያ. በአንቀጹ መጨረሻ ላይ ይህን ያህል የሚያነቡ አነቃቂ ቃላት እና በሚቀጥሉት መጣጥፎች ውስጥ ስለሚሆነው ነገር አጭር መግለጫ ያገኛሉ። እንዲሁም ለቀጣይ የመጠበቅ ፍላጎት እና ችሎታ ለሌላቸው እራስን ለማጥናት በርካታ አገናኞችን እንዘረዝራለን።

የ BPF አርክቴክቸር መግቢያ

የBPF አርክቴክቸርን ማጤን ከመጀመራችን በፊት፣ ለመጨረሻ ጊዜ (ኦህ) እንጠቅሳለን። ክላሲክ BPF, ለ RISC ማሽኖች መምጣት ምላሽ ሆኖ የተገነባ እና ውጤታማ የፓኬት ማጣሪያ ችግርን ፈታ. አርክቴክቸር በጣም ስኬታማ ከመሆኑ የተነሳ በዘጠናዎቹ ዓመታት ውስጥ በበርክሌይ ዩኒክስ ውስጥ በመወለዱ ለአብዛኞቹ ነባር ስርዓተ ክወናዎች ተላልፏል ፣ ከእብድ ሃያዎቹ ተርፏል እና አሁንም አዳዲስ መተግበሪያዎችን እያገኘ ነው።

አዲሱ BPF የተገነባው ለ64 ቢት ማሽኖች፣ ለደመና አገልግሎቶች እና ኤስዲኤንን ለመፍጠር ለሚፈልጉ መሳሪያዎች ፍላጎት ምላሽ ለመስጠት ነው።Sኦቨርትዌር -dተጣራ nሥራ መሥራት)። በከርነል ኔትወርክ መሐንዲሶች የተገነባው ለጥንታዊው BPF ምትክ ፣ አዲሱ BPF በትክክል ከስድስት ወራት በኋላ የሊኑክስ ስርዓቶችን የመከታተል ከባድ ሥራ ውስጥ አፕሊኬሽኖችን አገኘ ፣ እና አሁን ፣ ከታየ ከስድስት ዓመታት በኋላ ፣ የሚቀጥለው ጽሑፍ እንፈልጋለን ። የተለያዩ የፕሮግራሞችን ዓይነቶች ይዘርዝሩ።

አስቂኝ ስዕሎች

በዋናው ላይ፣ BPF ደህንነትን ሳይጎዳ በከርነል ቦታ ላይ “የዘፈቀደ” ኮድ እንዲያሄዱ የሚያስችልዎ ማጠሪያ ምናባዊ ማሽን ነው። BPF ፕሮግራሞች በተጠቃሚ ቦታ ውስጥ ተፈጥረዋል፣ ወደ ከርነል ተጭነዋል እና ከአንዳንድ የክስተት ምንጭ ጋር የተገናኙ ናቸው። አንድ ክስተት ለምሳሌ ፓኬትን ወደ አውታረ መረብ በይነገጽ ማድረስ፣ የከርነል ተግባር መጀመር፣ ወዘተ ሊሆን ይችላል። በጥቅል ሁኔታ የ BPF ፕሮግራም የጥቅሉን ውሂብ እና ሜታዳታ (ለማንበብ እና ምናልባትም ለመጻፍ እንደ ፕሮግራሙ ዓይነት) የከርነል ተግባርን በሚሠራበት ጊዜ ክርክሮች ተግባሩ፣ የከርነል ማህደረ ትውስታ ጠቋሚዎችን ጨምሮ፣ ወዘተ.

ይህን ሂደት በጥልቀት እንመልከተው። ለመጀመር፣ ከጥንታዊው BPF ስለ መጀመሪያው ልዩነት እንነጋገር፣ በተሰብሳቢ ውስጥ የተፃፉ ፕሮግራሞች። በአዲሱ እትም ኘሮግራሞች በከፍተኛ ደረጃ ቋንቋዎች እንዲፃፉ አርክቴክቸር ተዘርግቷል፣ በዋነኛነት፣ በ C. ለዚህ፣ ለኤልቪኤም የጀርባ ገፅ ተዘጋጅቷል፣ ይህም ለ BPF አርክቴክቸር ባይትኮድ እንዲያመነጩ የሚያስችል ነው።

BPF ለትናንሾቹ፣ ክፍል አንድ፡ የተራዘመ BPF

የBPF አርክቴክቸር የተነደፈው በከፊል በዘመናዊ ማሽኖች ላይ በብቃት እንዲሠራ ነው። ይህንን በተግባር ለመስራት፣ BPF ባይት ኮድ፣ አንዴ ወደ ከርነል ከተጫነ፣ JIT compiler የሚባል አካል በመጠቀም ወደ ቤተኛ ኮድ ተተርጉሟል።Just In Tኢሜ)። በመቀጠል ፣ ካስታወሱ ፣ በሚታወቀው BPF ፕሮግራሙ ወደ ከርነል ተጭኖ ከዝግጅቱ ምንጭ ጋር በአቶሚክ ተያይዟል - በአንድ የስርዓት ጥሪ አውድ ውስጥ። በአዲሱ አርክቴክቸር, ይህ በሁለት ደረጃዎች ይከናወናል - በመጀመሪያ, ኮዱ የስርዓት ጥሪን በመጠቀም ወደ ከርነል ይጫናል. bpf(2)እና ከዚያ በኋላ, እንደ መርሃግብሩ አይነት በሚለያዩ ሌሎች ዘዴዎች, ፕሮግራሙ ከዝግጅቱ ምንጭ ጋር ይያያዛል.

እዚህ አንባቢው አንድ ጥያቄ ሊኖረው ይችላል: ይቻል ነበር? የእንደዚህ አይነት ኮድ አፈፃፀም ደህንነት እንዴት ይረጋገጣል? የማስፈጸሚያ ደህንነት ዋስትና ተሰጥቶናል አረጋጋጭ በሚባሉ የ BPF ፕሮግራሞችን የመጫን ደረጃ (በእንግሊዘኛ ይህ ደረጃ አረጋጋጭ ይባላል እና የእንግሊዝኛውን ቃል መጠቀሜን እቀጥላለሁ)

BPF ለትናንሾቹ፣ ክፍል አንድ፡ የተራዘመ BPF

አረጋጋጭ አንድ ፕሮግራም መደበኛውን የከርነል አሠራር እንደማይረብሽ የሚያረጋግጥ የማይንቀሳቀስ ተንታኝ ነው። ይህ በነገራችን ላይ መርሃግብሩ በስርዓቱ አሠራር ውስጥ ጣልቃ መግባት አይችልም ማለት አይደለም - የ BPF ፕሮግራሞች እንደ ዓይነቱ ላይ በመመስረት የከርነል ማህደረ ትውስታ ክፍሎችን ማንበብ እና መፃፍ ፣ የተግባር እሴቶችን መመለስ ፣ ማሳጠር ፣ ማያያዝ ፣ እንደገና መፃፍ ይችላሉ ። እና የአውታረ መረብ እሽጎችን እንኳን ያስተላልፉ። አረጋጋጭ የBPF ፕሮግራምን ማስኬድ ከርነል እንደማይበላሽ እና በህጉ መሰረት የፅሁፍ መዳረሻ ያለው ፕሮግራም ለምሳሌ የወጪ ፓኬት መረጃ ከፓኬቱ ውጭ የከርነል ማህደረ ትውስታን መፃፍ እንደማይችል ዋስትና ይሰጣል። ከሌሎች የ BPF አካላት ጋር ካወቅን በኋላ አረጋጋጭን በተዛማጅ ክፍል ውስጥ በጥቂቱ በዝርዝር እንመለከታለን።

ታዲያ እስካሁን ምን ተማርን? ተጠቃሚው በ C ውስጥ አንድ ፕሮግራም ይጽፋል, የስርዓት ጥሪን በመጠቀም ወደ ከርነል ይጭናል bpf(2)፣ በአረጋጋጭ የተረጋገጠ እና ወደ ቤተኛ ባይት ኮድ የተተረጎመ። ከዚያ ተመሳሳይ ወይም ሌላ ተጠቃሚ ፕሮግራሙን ከዝግጅቱ ምንጭ ጋር ያገናኘዋል እና መፈጸም ይጀምራል. ቡት እና ግንኙነትን መለየት ለብዙ ምክንያቶች አስፈላጊ ነው. በመጀመሪያ አረጋጋጭን ማስኬድ በአንጻራዊነት ውድ ነው እና ተመሳሳይ ፕሮግራም ብዙ ጊዜ በማውረድ የኮምፒተር ጊዜን እናጠፋለን። በሁለተኛ ደረጃ, አንድ ፕሮግራም በትክክል እንዴት እንደሚገናኝ በአይነቱ ላይ የተመሰረተ ነው, እና ከአንድ አመት በፊት የተሰራ አንድ "ሁለንተናዊ" በይነገጽ ለአዳዲስ ፕሮግራሞች ተስማሚ ላይሆን ይችላል. (አሁን አርክቴክቸር የበለጠ ብስለት እየሆነ ቢመጣም ይህንን በይነገጽ በደረጃ አንድ ለማድረግ ሀሳብ አለ። libbpf.)

በትኩረት የሚከታተለው አንባቢ በሥዕሎቹ ላይ ገና እንዳልጨረስን ሊያስተውል ይችላል። በእርግጥ፣ ከላይ ያሉት ሁሉም BPF ከጥንታዊ BPF ጋር ሲወዳደር ምስሉን በመሠረታዊነት የሚቀይርበትን ምክንያት አያብራራም። የተግባራዊነትን ወሰን በእጅጉ የሚያሰፉ ሁለት ፈጠራዎች የጋራ ማህደረ ትውስታ እና የከርነል አጋዥ ተግባራትን የመጠቀም ችሎታ ናቸው። በ BPF ውስጥ የጋራ ማህደረ ትውስታ ካርታዎች የሚባሉትን በመጠቀም ተተግብሯል - የጋራ የውሂብ አወቃቀሮችን ከተወሰነ ኤፒአይ ጋር። ምናልባት ይህን ስም ያገኙት የመጀመሪያው የካርታ አይነት የሃሽ ጠረጴዛ ስለነበር ነው። ከዚያም ድርድሮች ታዩ፣ የሀገር ውስጥ (በሲፒዩ) የሃሽ ጠረጴዛዎች እና የአካባቢ ድርድሮች፣ የፍለጋ ዛፎች፣ የ BPF ፕሮግራሞች ጠቋሚዎችን የያዙ ካርታዎች እና ሌሎችም። አሁን ለእኛ የሚያስደንቀን የBPF ፕሮግራሞች በጥሪዎች መካከል ያለውን ሁኔታ የመቀጠል እና ለሌሎች ፕሮግራሞች እና ከተጠቃሚ ቦታ ጋር የመጋራት ችሎታ መቻላቸው ነው።

ካርታዎች የስርዓት ጥሪን በመጠቀም ከተጠቃሚ ሂደቶች ይደርሳሉ bpf(2), እና የረዳት ተግባራትን በመጠቀም በከርነል ውስጥ ከሚሰሩ የ BPF ፕሮግራሞች. ከዚህም በላይ ረዳቶች ከካርታዎች ጋር ለመስራት ብቻ ሳይሆን ሌሎች የከርነል ችሎታዎችን ለማግኘትም አሉ. ለምሳሌ፣ BPF ፕሮግራሞች እሽጎችን ወደ ሌሎች በይነገጾች ለማስተላለፍ፣ የፐርፍ ክስተቶችን ለማመንጨት፣ የከርነል አወቃቀሮችን ለመድረስ እና የመሳሰሉትን ለማድረግ የረዳት ተግባራትን መጠቀም ይችላሉ።

BPF ለትናንሾቹ፣ ክፍል አንድ፡ የተራዘመ BPF

ለማጠቃለል፣ BPF የዘፈቀደ፣ ማለትም፣ አረጋጋጭ የተፈተነ፣ የተጠቃሚ ኮድ ወደ የከርነል ቦታ የመጫን ችሎታ ይሰጣል። ይህ ኮድ በጥሪዎች መካከል ሁኔታን መቆጠብ እና ከተጠቃሚ ቦታ ጋር ውሂብ መለዋወጥ እና እንዲሁም በዚህ አይነት ፕሮግራም የተፈቀዱ የከርነል ንዑስ ስርዓቶችን ማግኘት ይችላል።

ይህ ቀድሞውኑ በከርነል ሞጁሎች ከሚሰጡት ችሎታዎች ጋር ተመሳሳይ ነው ፣ ከዚህ ጋር ሲነፃፀር BPF አንዳንድ ጥቅሞች አሉት (በእርግጥ ፣ ተመሳሳይ መተግበሪያዎችን ብቻ ማወዳደር ይችላሉ ፣ ለምሳሌ ፣ የስርዓት ፍለጋ - የዘፈቀደ ሹፌር ከ BPF ጋር መፃፍ አይችሉም)። ዝቅተኛ የመግቢያ ገደብ (BPF ን የሚጠቀሙ አንዳንድ መገልገያዎች ተጠቃሚው የከርነል ፕሮግራሚንግ ክህሎት ወይም በአጠቃላይ የፕሮግራም አወጣጥ ችሎታን አይጠይቁም) ፣ የሩታይም ደህንነት (በሚጽፉበት ጊዜ ስርዓቱን ላልጣሱ ሰዎች በአስተያየቶች ውስጥ እጃችሁን አንሱ) ልብ ይበሉ። ወይም ሙከራ ሞጁሎች), atomity - ሞጁሎች ዳግም ሲጫን ጊዜ ማጣት አለ, እና BPF subsystem ምንም ክስተቶች ያመለጡ መሆኑን ያረጋግጣል (ፍትሃዊ መሆን, ይህ BPF ፕሮግራሞች ሁሉም ዓይነቶች የሚሆን እውነት አይደለም).

እንዲህ ያሉ ችሎታዎች መገኘት BPF ዩኒቨርሳል መሣሪያ ከርነል ለማስፋፋት ያደርገዋል, ይህም በተግባር የተረጋገጠ ነው: ፕሮግራሞች ተጨማሪ እና ተጨማሪ አዲስ አይነቶች BPF ታክሏል, ተጨማሪ እና ተጨማሪ ትላልቅ ኩባንያዎች BPF ፍልሚያ አገልጋዮች 24 × 7 ላይ, ተጨማሪ እና ተጨማሪ ይጠቀማሉ. ጀማሪዎች ሥራቸውን የሚገነቡት በ BPF ላይ በተመሰረቱ መፍትሄዎች ላይ ነው። BPF በሁሉም ቦታ ጥቅም ላይ ይውላል: ከ DDoS ጥቃቶች ለመጠበቅ, SDN መፍጠር (ለምሳሌ, ለ kubernetes አውታረ መረቦችን መተግበር), እንደ ዋናው የስርዓት መፈለጊያ መሳሪያ እና ስታቲስቲክስ ሰብሳቢ, በጠለፋ ማወቂያ ስርዓቶች እና ማጠሪያ ስርዓቶች, ወዘተ.

የጽሁፉን አጠቃላይ እይታ እዚህ ላይ እንጨርስ እና ቨርቹዋል ማሽንን እና የቢፒኤፍ ስነ-ምህዳርን በበለጠ ዝርዝር እንመልከት።

Digression: መገልገያዎች

ምሳሌዎችን በሚቀጥሉት ክፍሎች ለማስኬድ እንዲችሉ ፣ቢያንስ ብዙ መገልገያዎችን ያስፈልግዎታል llvm/clang በ bpf ድጋፍ እና bpftool... በክፍል ውስጥ የልማት መሳሪያዎች መገልገያዎችን, እንዲሁም ከርነልዎን ለመሰብሰብ መመሪያዎችን ማንበብ ይችላሉ. የአቀራረባችንን ስምምነት እንዳይረብሽ ይህ ክፍል ከዚህ በታች ተቀምጧል።

BPF ምናባዊ ማሽን መመዝገቢያ እና መመሪያ ስርዓት

የBPF አርክቴክቸር እና የትእዛዝ ስርዓት ፕሮግራሞች በC ቋንቋ እንደሚፃፉ እና ወደ ከርነል ከተጫነ በኋላ ወደ ቤተኛ ኮድ መተርጎሙን ከግምት ውስጥ በማስገባት ተዘጋጅተዋል። ስለዚህ, የመመዝገቢያዎች ቁጥር እና የትዕዛዝ ስብስቦች በዘመናዊ ማሽኖች አቅም በሂሳብ አቆጣጠር ወደ መገናኛው በአይን ተመርጠዋል. በተጨማሪም በፕሮግራሞች ላይ የተለያዩ እገዳዎች ተጥለዋል ለምሳሌ እስከ ቅርብ ጊዜ ድረስ loops እና subroutines መጻፍ አልተቻለም እና የመመሪያዎቹ ብዛት በ 4096 ብቻ ተወስኗል (አሁን ልዩ ልዩ ፕሮግራሞች እስከ አንድ ሚሊዮን መመሪያዎችን ሊጫኑ ይችላሉ).

BPF አስራ አንድ ለተጠቃሚ ተደራሽ የሆኑ 64-ቢት መዝገቦች አሉት r0-r10 እና የፕሮግራም ቆጣሪ. ይመዝገቡ r10 ፍሬም ጠቋሚ ይዟል እና ተነባቢ-ብቻ ነው። ፕሮግራሞች በአሂድ ጊዜ 512-ባይት ቁልል እና ያልተገደበ የተጋራ ማህደረ ትውስታ በካርታ መልክ ማግኘት ይችላሉ።

BPF ፕሮግራሞች የተወሰኑ የፕሮግራም አይነት የከርነል አጋዥዎችን እና በቅርቡ ደግሞ መደበኛ ተግባራትን እንዲያሄዱ ተፈቅዶላቸዋል። እያንዳንዱ ተግባር ተብሎ የሚጠራው እስከ አምስት ክርክሮች ሊወስድ ይችላል, በመመዝገቢያ ውስጥ ያልፋል r1-r5, እና የመመለሻ ዋጋው ወደ ተላልፏል r0. ከተግባሩ ከተመለሰ በኋላ የመመዝገቢያዎቹ ይዘቶች ዋስትና ተሰጥቶታል r6-r9 አይለወጥም።

ቀልጣፋ ፕሮግራም ትርጉም ለማግኘት, ይመዘግባል r0-r11 ለሁሉም የሚደገፉ አርክቴክቸሮች አሁን ያለውን የአርክቴክቸር ገፅታዎች ከግምት ውስጥ በማስገባት ለእውነተኛ መዝገቦች በተለየ ካርታ ተዘጋጅተዋል። ለምሳሌ ለ x86_64 ይመዘግባል r1-r5, የተግባር መለኪያዎችን ለማለፍ ጥቅም ላይ ይውላል, በ ላይ ይታያሉ rdi, rsi, rdx, rcx, r8መለኪያዎችን ወደ ተግባራት ለማለፍ የሚያገለግሉ x86_64. ለምሳሌ በግራ በኩል ያለው ኮድ በቀኝ በኩል ያለውን ኮድ እንደዚህ ይተረጎማል።

1:  (b7) r1 = 1                    mov    $0x1,%rdi
2:  (b7) r2 = 2                    mov    $0x2,%rsi
3:  (b7) r3 = 3                    mov    $0x3,%rdx
4:  (b7) r4 = 4                    mov    $0x4,%rcx
5:  (b7) r5 = 5                    mov    $0x5,%r8
6:  (85) call pc+1                 callq  0x0000000000001ee8

ይመዝገቡ r0 እንዲሁም የፕሮግራም አፈፃፀም ውጤቱን ለመመለስ እና በመመዝገቢያ ውስጥ r1 ፕሮግራሙ ወደ አውድ ጠቋሚው ተላልፏል - እንደ መርሃግብሩ አይነት, ይህ ለምሳሌ መዋቅር ሊሆን ይችላል struct xdp_md (ለ XDP) ወይም መዋቅር struct __sk_buff (ለተለያዩ የኔትወርክ ፕሮግራሞች) ወይም መዋቅር struct pt_regs (ለተለያዩ የክትትል ፕሮግራሞች) ወዘተ.

ስለዚህ፣ የተመዝጋቢዎች ስብስብ፣ የከርነል አጋዥዎች፣ ቁልል፣ የአውድ ጠቋሚ እና የጋራ ማህደረ ትውስታ በካርታዎች መልክ ነበረን። ይህ ሁሉ በጉዞው ላይ የግድ አስፈላጊ ነው ማለት አይደለም, ነገር ግን ...

መግለጫውን እንቀጥል እና ከእነዚህ ነገሮች ጋር ለመስራት ስለ ትዕዛዝ ስርዓት እንነጋገር. ሁሉም (ሁሉም ማለት ይቻላል) የ BPF መመሪያዎች ቋሚ ባለ 64-ቢት መጠን አላቸው። በ64-ቢት ቢግ ኢንዲያን ማሽን ላይ አንዱን መመሪያ ከተመለከቱ ያያሉ።

BPF ለትናንሾቹ፣ ክፍል አንድ፡ የተራዘመ BPF

ይህ ነው Code - ይህ የመመሪያው ኢንኮዲንግ ነው ፣ Dst/Src በቅደም ተከተል የተቀባዩ እና ምንጭ ኢንኮዲንግ ናቸው ፣ Off - 16-ቢት የተፈረመ ገብ፣ እና Imm በአንዳንድ መመሪያዎች ውስጥ ጥቅም ላይ የዋለ ባለ 32-ቢት የተፈረመ ኢንቲጀር ነው (ከሲቢፒኤፍ ቋሚ ኬ ጋር ተመሳሳይ)። ኢንኮዲንግ Code ከሁለት ዓይነቶች አንዱ አለው:

BPF ለትናንሾቹ፣ ክፍል አንድ፡ የተራዘመ BPF

የትምህርት ክፍሎች 0 ፣ 1 ፣ 2 ፣ 3 ከማህደረ ትውስታ ጋር ለመስራት ትዕዛዞችን ይገልፃሉ። እነሱ ተብለው ይጠራሉ, BPF_LD, BPF_LDX, BPF_ST, BPF_STX, በቅደም ተከተል. ክፍል 4፣ 7BPF_ALU, BPF_ALU64) የ ALU መመሪያዎች ስብስብ ይመሰርታል። ክፍል 5፣ 6BPF_JMP, BPF_JMP32) የመዝለል መመሪያዎችን ይዟል።

የ BPF መመሪያ ስርዓትን ለማጥናት የሚቀጥለው እቅድ እንደሚከተለው ነው-ሁሉንም መመሪያዎች እና መመዘኛዎቻቸውን በጥንቃቄ ከመዘርዘር ይልቅ በዚህ ክፍል ውስጥ ሁለት ምሳሌዎችን እንመለከታለን እና ከነሱ መመሪያው እንዴት እንደሚሰራ እና እንዴት እንደሚሰራ ግልጽ ይሆናል. ለ BPF ማንኛውንም ሁለትዮሽ ፋይል በእጅ ይንጠቁ። በጽሁፉ ውስጥ ያለውን ይዘት ለማዋሃድ፣ ስለ አረጋጋጭ፣ ጂአይቲ ማቀናበሪያ፣ የጥንታዊ BPF ትርጉም እና እንዲሁም ካርታዎችን ስናጠና፣ የመደወያ ተግባራትን እና የመሳሰሉትን በተናጥል መመሪያዎችን እንገናኛለን።

ስለ ግለሰባዊ መመሪያዎች ስንነጋገር, ዋናዎቹን ፋይሎች እንጠቅሳለን bpf.h и bpf_common.hየ BPF መመሪያዎችን የቁጥር ኮዶች የሚገልፅ። አርክቴክቸርን በራስዎ ሲያጠኑ እና/ወይም ሁለትዮሾችን ሲተነተኑ፣ ውስብስብነት ባለው ቅደም ተከተል የተደረደሩ የትርጉም ጽሑፎችን በሚከተሉት ምንጮች ማግኘት ይችላሉ። ኦፊሴላዊ ያልሆነ eBPF ዝርዝር, BPF እና XDP ማመሳከሪያ መመሪያ, መመሪያ ስብስብ, Documentation/networking/filter.txt እና በእርግጥ, በሊኑክስ ምንጭ ኮድ - አረጋጋጭ, JIT, BPF አስተርጓሚ.

ምሳሌ፡- BPF በጭንቅላትህ ውስጥ መገንጠል

አንድ ፕሮግራም ያጠናቀርንበትን አንድ ምሳሌ እንመልከት readelf-example.c እና የተገኘውን ሁለትዮሽ ይመልከቱ. ዋናውን ይዘት እንገልፃለን። readelf-example.c ከሁለትዮሽ ኮዶች አመክንዮውን ከመለስን በኋላ፡-

$ clang -target bpf -c readelf-example.c -o readelf-example.o -O2
$ llvm-readelf -x .text readelf-example.o
Hex dump of section '.text':
0x00000000 b7000000 01000000 15010100 00000000 ................
0x00000010 b7000000 02000000 95000000 00000000 ................

በውጤቱ ውስጥ የመጀመሪያው አምድ readelf መግቢያ ነው እና ፕሮግራማችን ስለዚህ አራት ትዕዛዞችን ያቀፈ ነው-

Code Dst Src Off  Imm
b7   0   0   0000 01000000
15   0   1   0100 00000000
b7   0   0   0000 02000000
95   0   0   0000 00000000

የትእዛዝ ኮዶች እኩል ናቸው። b7, 15, b7 и 95. ቢያንስ ጉልህ የሆኑት ሶስት ቢት የማስተማሪያ ክፍል መሆናቸውን አስታውስ። በእኛ ሁኔታ ፣ ከሁሉም መመሪያዎች ውስጥ አራተኛው ቢት ባዶ ነው ፣ ስለሆነም የመማሪያ ክፍሎች 7 ፣ 5 ፣ 7 ፣ 5 ናቸው ። ክፍል 7 ነው BPF_ALU64, እና 5 ነው BPF_JMP. ለሁለቱም ክፍሎች የመመሪያው ቅርፀት አንድ ነው (ከላይ ያለውን ይመልከቱ) እና ፕሮግራማችንን እንደዚህ እንደገና መፃፍ እንችላለን (በተመሳሳይ ጊዜ የቀሩትን አምዶች በሰው መልክ እንጽፋለን)

Op S  Class   Dst Src Off  Imm
b  0  ALU64   0   0   0    1
1  0  JMP     0   1   1    0
b  0  ALU64   0   0   0    2
9  0  JMP     0   0   0    0

ክዋኔ b ደረጃ ALU64 ነው BPF_MOV. ለመድረሻ መመዝገቢያ ዋጋ ይመድባል. ቢት ከተዘጋጀ s (ምንጭ), ከዚያም እሴቱ ከምንጩ መመዝገቢያ ይወሰዳል, እና እንደእኛ ሁኔታ, ካልተዋቀረ, እሴቱ ከእርሻው ይወሰዳል. Imm. ስለዚህ በመጀመሪያው እና በሶስተኛው መመሪያ ውስጥ ቀዶ ጥገናውን እናከናውናለን r0 = Imm. በተጨማሪም የ JMP ክፍል 1 አሠራር ነው BPF_JEQ (እኩል ከሆነ ይዝለሉ)። በእኛ ሁኔታ, ከትንሽ ጀምሮ S ዜሮ ነው፣ የምንጭ መመዝገቢያውን ዋጋ ከመስክ ጋር ያወዳድራል። Imm. እሴቶቹ ከተገጣጠሙ ፣ ከዚያ ሽግግሩ ወደ ይከሰታል PC + Offየት PC, እንደተለመደው, የሚቀጥለውን መመሪያ አድራሻ ይዟል. በመጨረሻም፣ JMP ክፍል 9 ኦፕሬሽን ነው። BPF_EXIT. ይህ መመሪያ ፕሮግራሙን ያቋርጣል, ወደ ከርነል ይመለሳል r0. ወደ ጠረጴዛችን አዲስ አምድ እንጨምር፡-

Op    S  Class   Dst Src Off  Imm    Disassm
MOV   0  ALU64   0   0   0    1      r0 = 1
JEQ   0  JMP     0   1   1    0      if (r1 == 0) goto pc+1
MOV   0  ALU64   0   0   0    2      r0 = 2
EXIT  0  JMP     0   0   0    0      exit

ይህንን ይበልጥ ምቹ በሆነ ቅጽ እንደገና መፃፍ እንችላለን-

     r0 = 1
     if (r1 == 0) goto END
     r0 = 2
END:
     exit

በመዝገቡ ውስጥ ያለውን ካስታወስን r1 ፕሮግራሙ ከከርነል እና በመዝገቡ ውስጥ ወደ አውድ ጠቋሚው ተላልፏል r0 እሴቱ ወደ ከርነል ይመለሳል፣ ከዚያም የዐውደ-ጽሑፉ ጠቋሚው ዜሮ ከሆነ፣ 1 ን እንመለሳለን እና ካልሆነ - 2. ምንጩን በማየት ትክክል መሆናችንን እንፈትሽ።

$ cat readelf-example.c
int foo(void *ctx)
{
        return ctx ? 2 : 1;
}

አዎ፣ ትርጉም የሌለው ፕሮግራም ነው፣ ግን ወደ አራት ቀላል መመሪያዎች ብቻ ይተረጎማል።

ልዩ ምሳሌ: 16-ባይት መመሪያ

አንዳንድ መመሪያዎች ከ64 ቢት በላይ እንደሚወስዱ ቀደም ብለን ጠቅሰናል። ይህ ለምሳሌ, መመሪያዎችን ይመለከታል lddw (ኮድ = 0x18 = BPF_LD | BPF_DW | BPF_IMM) - ከመስኮቹ ውስጥ ድርብ ቃል ወደ መዝገቡ ውስጥ ይጫኑ Imm... እውነታው ግን ያ ነው Imm መጠኑ 32 ነው፣ እና ድርብ ቃል 64 ቢት ነው፣ ስለዚህ ባለ 64-ቢት ወዲያዉኑ ዋጋ በአንድ ባለ 64-ቢት መመሪያ ወደ መዝገብ ቤት መጫን አይሰራም። ይህንን ለማድረግ, በመስክ ውስጥ የ 64-ቢት እሴት ሁለተኛ ክፍልን ለማስቀመጥ ሁለት ተያያዥ መመሪያዎች ጥቅም ላይ ይውላሉ Imm. ምሳሌ

$ cat x64.c
long foo(void *ctx)
{
        return 0x11223344aabbccdd;
}
$ clang -target bpf -c x64.c -o x64.o -O2
$ llvm-readelf -x .text x64.o
Hex dump of section '.text':
0x00000000 18000000 ddccbbaa 00000000 44332211 ............D3".
0x00000010 95000000 00000000                   ........

በሁለትዮሽ ፕሮግራም ውስጥ ሁለት መመሪያዎች ብቻ አሉ።

Binary                                 Disassm
18000000 ddccbbaa 00000000 44332211    r0 = Imm[0]|Imm[1]
95000000 00000000                      exit

መመሪያ ይዘን እንደገና እንገናኛለን። lddw, ስለ ማዛወር እና ከካርታዎች ጋር ስንሰራ.

ምሳሌ፡ መደበኛ መሳሪያዎችን በመጠቀም BPFን መበተን

ስለዚህ፣ BPF ሁለትዮሽ ኮዶችን ማንበብ ተምረናል እና አስፈላጊ ከሆነ ማንኛውንም መመሪያ ለመተንተን ዝግጁ ነን። ሆኖም ፣ በመደበኛ መሳሪያዎችን በመጠቀም ፕሮግራሞችን ለመበተን በተግባር የበለጠ ምቹ እና ፈጣን ነው ማለት ተገቢ ነው ፣ ለምሳሌ-

$ llvm-objdump -d x64.o

Disassembly of section .text:

0000000000000000 <foo>:
 0: 18 00 00 00 dd cc bb aa 00 00 00 00 44 33 22 11 r0 = 1234605617868164317 ll
 2: 95 00 00 00 00 00 00 00 exit

የ BPF ነገሮች የሕይወት ዑደት፣ bpffs የፋይል ስርዓት

(በዚህ ንዑስ ክፍል የተገለጹትን አንዳንድ ዝርዝሮች በመጀመሪያ ተማርኩ። መጾም አሌክሲ ስታሮቮይቶቭ በ BPF ብሎግ.)

BPF ነገሮች - ፕሮግራሞች እና ካርታዎች - ትዕዛዞችን በመጠቀም ከተጠቃሚ ቦታ የተፈጠሩ ናቸው BPF_PROG_LOAD и BPF_MAP_CREATE የስርዓት ጥሪ bpf(2)ይህ እንዴት እንደሚከሰት በሚቀጥለው ክፍል እንነጋገራለን ። ይህ የከርነል መረጃ አወቃቀሮችን እና ለእያንዳንዳቸው ይፈጥራል refcount (የማጣቀሻ ቆጠራ) ወደ አንድ ተቀናብሯል እና ነገሩን የሚያመለክት የፋይል ገላጭ ለተጠቃሚው ይመለሳል። መያዣው ከተዘጋ በኋላ refcount እቃው በአንድ ይቀንሳል, እና ወደ ዜሮ ሲደርስ እቃው ይደመሰሳል.

ፕሮግራሙ ካርታዎችን ከተጠቀመ, ከዚያ refcount እነዚህ ካርታዎች ፕሮግራሙን ከጫኑ በኋላ በአንድ ይጨምራሉ, ማለትም. የፋይል ገላጭዎቻቸው ከተጠቃሚው ሂደት እና አሁንም ሊዘጉ ይችላሉ refcount ዜሮ አይሆንም

BPF ለትናንሾቹ፣ ክፍል አንድ፡ የተራዘመ BPF

አንድን ፕሮግራም በተሳካ ሁኔታ ከጫንን በኋላ ብዙውን ጊዜ ወደ አንድ ዓይነት የዝግጅት ጀነሬተር እናያይዛለን። ለምሳሌ፣ የሚመጡ እሽጎችን ለማስኬድ ወይም ከአንዳንዶቹ ጋር ለማገናኘት በኔትወርክ በይነገጽ ላይ ልናስቀምጠው እንችላለን tracepoint በዋና ውስጥ. በዚህ ጊዜ የማጣቀሻ ቆጣሪው እንዲሁ በአንድ ይጨምራል እና የፋይል ገላጭውን በጫኝ ፕሮግራም ውስጥ መዝጋት እንችላለን.

አሁን ቡት ጫኚውን ብንዘጋው ምን ይሆናል? እንደ የዝግጅት ጀነሬተር (መንጠቆ) አይነት ይወሰናል. ሁሉም የኔትወርክ መንጠቆዎች መጫኛው ከተጠናቀቀ በኋላ ይኖራሉ, እነዚህ ዓለም አቀፍ መንጠቆዎች የሚባሉት ናቸው. እና ለምሳሌ ፣ የመከታተያ ፕሮግራሞች የፈጠሩት ሂደት ካለቀ በኋላ ይለቀቃሉ (እና ስለዚህ “አካባቢያዊ ፣ ከ “አካባቢያዊ ወደ ሂደቱ” ይባላሉ)። በቴክኒካዊ ሁኔታ, የአካባቢ መንጠቆዎች ሁልጊዜ በተጠቃሚ ቦታ ውስጥ ተጓዳኝ የፋይል ገላጭ አላቸው እና ስለዚህ ሂደቱ ሲዘጋ ይዘጋሉ, ነገር ግን ዓለም አቀፋዊ መንጠቆዎች አያደርጉም. በሚከተለው ስእል ውስጥ, ቀይ መስቀሎች በመጠቀም, እኔ ጫኚ ፕሮግራም መቋረጥ የአካባቢ እና ዓለም አቀፍ መንጠቆ ውስጥ ነገሮች ሕይወት ላይ ተጽዕኖ እንዴት ለማሳየት እሞክራለሁ.

BPF ለትናንሾቹ፣ ክፍል አንድ፡ የተራዘመ BPF

በአካባቢያዊ እና በአለምአቀፍ መንጠቆዎች መካከል ልዩነት ለምን አለ? አንዳንድ የአውታረ መረብ ፕሮግራሞችን ማስኬድ ያለ የተጠቃሚ ቦታ ትርጉም ይሰጣል ፣ ለምሳሌ ፣ የ DDoS ጥበቃን ያስቡ - ቡት ጫኚው ደንቦቹን ይጽፋል እና የ BPF ፕሮግራሙን ከአውታረ መረብ በይነገጽ ጋር ያገናኛል ፣ ከዚያ በኋላ ቡት ጫኚው ሄዶ እራሱን ሊያጠፋ ይችላል። በሌላ በኩል በአስር ደቂቃዎች ውስጥ በጉልበቶችዎ ላይ የፃፉትን የማረም የመከታተያ ፕሮግራም ያስቡ - ሲጠናቀቅ በስርዓቱ ውስጥ ምንም ቆሻሻ እንዳይኖር ይፈልጋሉ እና የአካባቢ መንጠቆዎች ያንን ያረጋግጣሉ።

በሌላ በኩል፣ በከርነል ውስጥ ካለው የመከታተያ ነጥብ ጋር መገናኘት እና ለብዙ አመታት ስታቲስቲክስን መሰብሰብ እንደምትፈልግ አስብ። በዚህ አጋጣሚ የተጠቃሚውን ክፍል ማጠናቀቅ እና ከጊዜ ወደ ጊዜ ወደ ስታቲስቲክስ መመለስ ይፈልጋሉ. የ bpf ፋይል ስርዓት ይህንን እድል ይሰጣል. BPF ነገሮችን የሚያጣቅሱ ፋይሎች እንዲፈጠሩ እና በዚህም እንዲጨምሩ የሚያስችል በማህደረ ትውስታ ውስጥ ብቻ የሚገኝ የውሸት ፋይል ስርዓት ነው። refcount እቃዎች. ከዚህ በኋላ ጫኚው መውጣት ይችላል, እና የፈጠራቸው ነገሮች በህይወት ይቆያሉ.

BPF ለትናንሾቹ፣ ክፍል አንድ፡ የተራዘመ BPF

BPF ነገሮችን የሚያጣቅሱ ፋይሎችን በbpffs መፍጠር "ፒንኒንግ" (እንደሚከተለው ሐረግ፡ "ሂደቱ የ BPF ፕሮግራም ወይም ካርታ ሊሰካ ይችላል") ይባላል። ለ BPF ዕቃዎች የፋይል ዕቃዎችን መፍጠር የአካባቢ ዕቃዎችን ዕድሜ ለማራዘም ብቻ ሳይሆን ለዓለም አቀፍ ዕቃዎች አጠቃቀምም ትርጉም ይሰጣል - ከዓለም አቀፉ DDoS ጥበቃ ፕሮግራም ጋር ወደ ምሳሌነት ስንመለስ ፣ መጥተን ስታቲስቲክስን ለመመልከት እንፈልጋለን። ከጊዜ ወደ ጊዜ.

የBPF ፋይል ስርዓት ብዙውን ጊዜ የሚጫነው በ ውስጥ ነው። /sys/fs/bpfነገር ግን በአገር ውስጥም ሊሰቀል ይችላል፣ ለምሳሌ እንደዚህ፡-

$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpoint

የፋይል ስርዓት ስሞች የተፈጠሩት ትዕዛዙን በመጠቀም ነው። BPF_OBJ_PIN የ BPF ስርዓት ጥሪ. ለማብራራት አንድ ፕሮግራም ወስደን እናጠናቅረዋለን፣ እንጭነው እና እንሰካው። bpffs. ፕሮግራማችን ምንም ጠቃሚ ነገር አይሰራም፣ ምሳሌውን እንደገና ማባዛት እንድትችሉ ኮዱን ብቻ እያቀረብን ነው።

$ cat test.c
__attribute__((section("xdp"), used))
int test(void *ctx)
{
        return 0;
}

char _license[] __attribute__((section("license"), used)) = "GPL";

ይህንን ፕሮግራም እናጠናቅቅ እና የፋይል ስርዓቱን አካባቢያዊ ቅጂ እንፍጠር bpffs:

$ clang -target bpf -c test.c -o test.o
$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpoint

አሁን መገልገያውን ተጠቅመን ፕሮግራማችንን እናውርደው bpftool እና ተጓዳኝ የስርዓት ጥሪዎችን ይመልከቱ bpf(2) (ከዝርዝር ውፅዓት አንዳንድ ተዛማጅነት የሌላቸው መስመሮች ተወግደዋል)

$ sudo strace -e bpf bpftool prog load ./test.o bpf-mountpoint/test
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, prog_name="test", ...}, 120) = 3
bpf(BPF_OBJ_PIN, {pathname="bpf-mountpoint/test", bpf_fd=3}, 120) = 0

እዚህ በመጠቀም ፕሮግራሙን ጫንን BPF_PROG_LOAD፣ የፋይል ገላጭ ከከርነል ተቀብሏል። 3 እና ትዕዛዙን በመጠቀም BPF_OBJ_PIN ይህን ፋይል ገላጭ እንደ ፋይል ሰክቷል። "bpf-mountpoint/test". ከዚህ በኋላ የቡት ጫኚው ፕሮግራም bpftool ሥራውን አጠናቅቋል ፣ ግን ፕሮግራማችን በከርነል ውስጥ እንዳለ ቆይቷል ፣ ምንም እንኳን ከማንኛውም የአውታረ መረብ በይነገጽ ጋር ባያያዝነውም-

$ sudo bpftool prog | tail -3
783: xdp  name test  tag 5c8ba0cf164cb46c  gpl
        loaded_at 2020-05-05T13:27:08+0000  uid 0
        xlated 24B  jited 41B  memlock 4096B

የፋይሉን ነገር በመደበኛነት መሰረዝ እንችላለን unlink(2) እና ከዚያ በኋላ ተጓዳኝ ፕሮግራሙ ይሰረዛል:

$ sudo rm ./bpf-mountpoint/test
$ sudo bpftool prog show id 783
Error: get by id (783): No such file or directory

ዕቃዎችን መሰረዝ

ነገሮችን ስለማጥፋት ከተነጋገርን በኋላ ፕሮግራሙን ከመንጠቆው (የክስተት ጀነሬተር) ካቋረጥን በኋላ አንድም አዲስ ክስተት እንደማይጀምር ግልጽ ማድረግ አስፈላጊ ነው, ሆኖም ግን, ሁሉም የፕሮግራሙ ወቅታዊ ሁኔታዎች በተለመደው ቅደም ተከተል ይጠናቀቃሉ. .

አንዳንድ የ BPF ፕሮግራሞች ፕሮግራሙን በበረራ ላይ ለመተካት ያስችሉዎታል, ማለትም. ቅደም ተከተል atomity ያቅርቡ replace = detach old program, attach new program. በዚህ ሁኔታ ፣ ሁሉም የድሮው የፕሮግራሙ ስሪት ንቁ ሁኔታዎች ስራቸውን ያጠናቅቃሉ ፣ እና አዲስ የክስተት ተቆጣጣሪዎች ከአዲሱ ፕሮግራም ይፈጠራሉ ፣ እና እዚህ “አቶሚቲ” ማለት አንድም ክስተት አያመልጥም።

ፕሮግራሞችን ከክስተት ምንጮች ጋር በማያያዝ ላይ

በዚህ ጽሑፍ ውስጥ ፕሮግራሞችን ከዝግጅቱ ምንጮች ጋር ማገናኘት በተናጥል አንገልጽም ፣ ምክንያቱም ይህንን በአንድ የተወሰነ የፕሮግራም አውድ ውስጥ ማጥናት ምክንያታዊ ነው። ሴ.ሜ. ምሳሌ ከዚህ በታች እንደ XDP ያሉ ፕሮግራሞች እንዴት እንደሚገናኙ እናሳያለን.

የbpf ስርዓት ጥሪን በመጠቀም ነገሮችን ማቀናበር

BPF ፕሮግራሞች

ሁሉም BPF ነገሮች ከተጠቃሚ ቦታ የተፈጠሩ እና የሚተዳደሩት የስርዓት ጥሪን በመጠቀም ነው። bpf, የሚከተለውን ምሳሌ ያለው:

#include <linux/bpf.h>

int bpf(int cmd, union bpf_attr *attr, unsigned int size);

ቡድኑ እነሆ cmd ከአይነት እሴቶች አንዱ ነው። enum bpf_cmd, attr - ለአንድ የተወሰነ ፕሮግራም መለኪያዎች ጠቋሚ እና size - በጠቋሚው መሰረት የእቃው መጠን, ማለትም. ብዙውን ጊዜ ይህ sizeof(*attr). በከርነል 5.8 የስርዓት ጥሪ bpf 34 የተለያዩ ትዕዛዞችን ይደግፋል, እና ትርጉም union bpf_attr 200 መስመሮችን ይይዛል. ግን በብዙ መጣጥፎች ሂደት ውስጥ እራሳችንን በትእዛዞች እና መለኪያዎች ስለምናውቅ በዚህ ልንፈራ አይገባም።

ከቡድኑ እንጀምር BPF_PROG_LOAD, BPF ፕሮግራሞችን የሚፈጥር - የ BPF መመሪያዎችን ስብስብ ወስዶ ወደ ከርነል ይጭናል. በሚጫኑበት ጊዜ አረጋጋጩ ተጀምሯል, እና ከዚያ የጂአይቲ ማጠናከሪያ እና, ከተሳካ አፈፃፀም በኋላ, የፕሮግራሙ ፋይል ገላጭ ወደ ተጠቃሚው ይመለሳል. ቀጥሎ ምን እንደሚገጥመው ባለፈው ክፍል አይተናል ስለ BPF እቃዎች የሕይወት ዑደት.

አሁን ቀላል BPF ፕሮግራምን የሚጭን ብጁ ፕሮግራም እንጽፋለን, ነገር ግን በመጀመሪያ ምን አይነት ፕሮግራም መጫን እንደምንፈልግ መወሰን አለብን - መምረጥ አለብን. ይተይቡ እና በዚህ አይነት ማዕቀፍ ውስጥ የማረጋገጫ ፈተናውን የሚያልፍ ፕሮግራም ይፃፉ. ይሁን እንጂ ሂደቱን እንዳያወሳስበው, ዝግጁ የሆነ መፍትሄ እዚህ አለ: እንደ አንድ ፕሮግራም እንወስዳለን BPF_PROG_TYPE_XDP, ይህም ዋጋውን ይመልሳል XDP_PASS (ሁሉንም ፓኬጆች ይዝለሉ)። በ BPF ሰብሳቢው ውስጥ በጣም ቀላል ይመስላል።

r0 = 2
exit

ከወሰንን በኋላ እንሰቅላለን፣ እንዴት እንደምናደርገው ልንነግርዎ እንችላለን፡-

#define _GNU_SOURCE
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/bpf.h>

static inline __u64 ptr_to_u64(const void *ptr)
{
        return (__u64) (unsigned long) ptr;
}

int main(void)
{
    struct bpf_insn insns[] = {
        {
            .code = BPF_ALU64 | BPF_MOV | BPF_K,
            .dst_reg = BPF_REG_0,
            .imm = XDP_PASS
        },
        {
            .code = BPF_JMP | BPF_EXIT
        },
    };

    union bpf_attr attr = {
        .prog_type = BPF_PROG_TYPE_XDP,
        .insns     = ptr_to_u64(insns),
        .insn_cnt  = sizeof(insns)/sizeof(insns[0]),
        .license   = ptr_to_u64("GPL"),
    };

    strncpy(attr.prog_name, "woo", sizeof(attr.prog_name));
    syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));

    for ( ;; )
        pause();
}

በፕሮግራሙ ውስጥ ያሉ አስደሳች ክስተቶች የሚጀምሩት በድርድር ፍቺ ነው። insns - የእኛ BPF ፕሮግራም በማሽን ኮድ. በዚህ ሁኔታ, እያንዳንዱ የ BPF ፕሮግራም መመሪያ ወደ መዋቅሩ ተሞልቷል bpf_insn. የመጀመሪያው አካል insns መመሪያዎችን ያከብራል r0 = 2, ቀጣዩ, ሁለተኛው - exit.

ማፈግፈግ ከርነል የማሽን ኮዶችን ለመፃፍ እና የከርነል ራስጌ ፋይልን ለመጠቀም የበለጠ ምቹ ማክሮዎችን ይገልጻል tools/include/linux/filter.h ብለን መፃፍ እንችላለን

struct bpf_insn insns[] = {
    BPF_MOV64_IMM(BPF_REG_0, XDP_PASS),
    BPF_EXIT_INSN()
};

ነገር ግን የ BPF ፕሮግራሞችን በአፍ መፍቻ ኮድ መፃፍ በከርነል ውስጥ ፈተናዎችን ለመፃፍ እና ስለ BPF መጣጥፎች ብቻ አስፈላጊ ስለሆነ የእነዚህ ማክሮዎች አለመኖር የገንቢውን ሕይወት አያወሳስበውም።

የ BPF ፕሮግራምን ከገለፅን በኋላ ወደ ከርነል ለመጫን እንቀጥላለን. የእኛ ዝቅተኛው የመለኪያዎች ስብስብ attr የፕሮግራሙ አይነት፣ የመመሪያው ስብስብ እና ቁጥር፣ አስፈላጊ ፍቃድ እና ስም ያካትታል "woo", ከማውረድ በኋላ ፕሮግራማችንን በሲስተሙ ላይ ለማግኘት የምንጠቀመው. ፕሮግራሙ, በገባው ቃል መሰረት, የስርዓት ጥሪን በመጠቀም ወደ ስርዓቱ ውስጥ ይጫናል bpf.

በፕሮግራሙ መጨረሻ ላይ ክፍያውን በሚመስለው ማለቂያ በሌለው ዑደት ውስጥ እንጨርሳለን. እሱ ከሌለ ፕሮግራሙ ወደ እኛ የተመለሰው የፋይል ገላጭ ሲዘጋ በከርነል ይገደላል bpf, እና በስርዓቱ ውስጥ አናየውም.

ደህና, እኛ ለሙከራ ዝግጁ ነን. እንሰበስባለን እና ፕሮግራሙን በስር እናስኬድ straceሁሉም ነገር በትክክል እየሰራ መሆኑን ለማረጋገጥ:

$ clang -g -O2 simple-prog.c -o simple-prog

$ sudo strace ./simple-prog
execve("./simple-prog", ["./simple-prog"], 0x7ffc7b553480 /* 13 vars */) = 0
...
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, insn_cnt=2, insns=0x7ffe03c4ed50, license="GPL", log_level=0, log_size=0, log_buf=NULL, kern_version=KERNEL_V
ERSION(0, 0, 0), prog_flags=0, prog_name="woo", prog_ifindex=0, expected_attach_type=BPF_CGROUP_INET_INGRESS}, 72) = 3
pause(

ሁሉ ነገር ጥሩ ነው, bpf(2) እጀታውን 3 ተመለሰልን እና ወደ ማለቂያ ወደሌለው ዑደት ገባን። pause(). ፕሮግራማችንን በስርዓቱ ውስጥ ለማግኘት እንሞክር። ይህንን ለማድረግ ወደ ሌላ ተርሚናል እንሄዳለን እና መገልገያውን እንጠቀማለን bpftool:

# bpftool prog | grep -A3 woo
390: xdp  name woo  tag 3b185187f1855c4c  gpl
        loaded_at 2020-08-31T24:66:44+0000  uid 0
        xlated 16B  jited 40B  memlock 4096B
        pids simple-prog(10381)

በስርዓቱ ላይ የተጫነ ፕሮግራም እንዳለ እናያለን woo የአለምአቀፍ መታወቂያው 390 እና በአሁኑ ጊዜ በሂደት ላይ ነው። simple-prog ወደ ፕሮግራሙ የሚያመለክት ክፍት ፋይል ገላጭ አለ (እና ከሆነ simple-prog ስራውን ያጠናቅቃል, ከዚያ woo ይጠፋል)። እንደተጠበቀው, ፕሮግራሙ woo 16 ባይት ይወስዳል - ሁለት መመሪያዎች - በ BPF አርክቴክቸር ውስጥ የሁለትዮሽ ኮዶች ፣ ግን በአፍ መፍቻው ቅርፅ (x86_64) ቀድሞውኑ 40 ባይት ነው። ፕሮግራማችንን በዋናው መልክ እንመልከተው፡-

# bpftool prog dump xlated id 390
   0: (b7) r0 = 2
   1: (95) exit

ምንም አያስደንቅም. አሁን በጂአይቲ ኮምፕሌተር የተፈጠረውን ኮድ እንመልከት፡-

# bpftool prog dump jited id 390
bpf_prog_3b185187f1855c4c_woo:
   0:   nopl   0x0(%rax,%rax,1)
   5:   push   %rbp
   6:   mov    %rsp,%rbp
   9:   sub    $0x0,%rsp
  10:   push   %rbx
  11:   push   %r13
  13:   push   %r14
  15:   push   %r15
  17:   pushq  $0x0
  19:   mov    $0x2,%eax
  1e:   pop    %rbx
  1f:   pop    %r15
  21:   pop    %r14
  23:   pop    %r13
  25:   pop    %rbx
  26:   leaveq
  27:   retq

በጣም ውጤታማ አይደለም ለ exit(2)ነገር ግን በፍትሃዊነት፣ ፕሮግራማችን በጣም ቀላል ነው፣ እና ቀላል ላልሆኑ ፕሮግራሞች በጂአይቲ ኮምፕሌተር የተጨመረው ፕሮሎግ እና አፈ ታሪክ በእርግጥ ያስፈልጋል።

ካርታዎች

BPF ፕሮግራሞች ለሌሎች BPF ፕሮግራሞች እና በተጠቃሚ ቦታ ላሉ ፕሮግራሞች ተደራሽ የሆኑ የተዋቀሩ የማህደረ ትውስታ ቦታዎችን መጠቀም ይችላሉ። እነዚህ ነገሮች ካርታ ይባላሉ እና በዚህ ክፍል የስርዓት ጥሪን በመጠቀም እንዴት እንደሚጠቀሙባቸው እናሳያለን። bpf.

ወዲያውኑ የካርታዎች ችሎታዎች የጋራ ማህደረ ትውስታን ለመድረስ ብቻ የተገደቡ አይደሉም እንበል። ልዩ ዓላማ ያላቸው ካርታዎች ለምሳሌ የ BPF ፕሮግራሞች ጠቋሚዎች ወይም የአውታረ መረብ መገናኛዎች ጠቋሚዎች, ከፐርፍ ዝግጅቶች ጋር ለመስራት ካርታዎች, ወዘተ. አንባቢን እንዳናደናግር ስለእነሱ እዚህ አንነጋገርም። ከዚህ ውጪ፣ ይህ ለምሳሌዎቻችን አስፈላጊ ስላልሆነ የማመሳሰል ጉዳዮችን ችላ እንላለን። የሚገኙ የካርታ ዓይነቶች ሙሉ ዝርዝር በ ውስጥ ይገኛሉ <linux/bpf.h>በዚህ ክፍል ውስጥ እንደ ምሳሌ እንወስዳለን በታሪክ የመጀመሪያው ዓይነት, የሃሽ ጠረጴዛ BPF_MAP_TYPE_HASH.

የሃሽ ጠረጴዛ ከፈጠሩ፣ C++ ይበሉ፣ ይላሉ unordered_map<int,long> woo, በሩሲያኛ ትርጉሙ "ጠረጴዛ እፈልጋለሁ woo ያልተገደበ መጠን ፣ ቁልፎቹ ዓይነት ናቸው። int, እና እሴቶቹ ዓይነት ናቸው long" የቢፒኤፍ ሃሽ ሠንጠረዥ ለመፍጠር የሠንጠረዡን ከፍተኛ መጠን ከመግለጽ በስተቀር ብዙ ተመሳሳይ ነገር ማድረግ አለብን, እና የቁልፍ እና የእሴቶችን አይነት ከመጥቀስ ይልቅ መጠኖቻቸውን በባይት መግለጽ አለብን. . ካርታዎችን ለመፍጠር ትዕዛዙን ይጠቀሙ BPF_MAP_CREATE የስርዓት ጥሪ bpf. ካርታ የሚፈጥር ብዙ ወይም ያነሰ አነስተኛ ፕሮግራም እንይ። ከቀዳሚው የ BPF ፕሮግራሞችን ከጫነ በኋላ ይህ ለእርስዎ ቀላል ሊመስልዎት ይገባል፡-

$ cat simple-map.c
#define _GNU_SOURCE
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/bpf.h>

int main(void)
{
    union bpf_attr attr = {
        .map_type = BPF_MAP_TYPE_HASH,
        .key_size = sizeof(int),
        .value_size = sizeof(int),
        .max_entries = 4,
    };
    strncpy(attr.map_name, "woo", sizeof(attr.map_name));
    syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));

    for ( ;; )
        pause();
}

እዚህ የመለኪያዎችን ስብስብ እንገልፃለን attr, እኛ የምንለው ውስጥ "እኔ ቁልፎች እና መጠን እሴቶች ጋር hash ጠረጴዛ ያስፈልጋቸዋል sizeof(int)ቢበዛ አራት ንጥረ ነገሮችን ማስቀመጥ የምችልበት። የ BPF ካርታዎችን በሚፈጥሩበት ጊዜ, ሌሎች መለኪያዎችን መግለጽ ይችላሉ, ለምሳሌ, ከፕሮግራሙ ጋር ባለው ምሳሌ ላይ በተመሳሳይ መልኩ, የእቃውን ስም እንደሚከተለው ገልጸናል. "woo".

ፕሮግራሙን እናጠናቅቅ እና እንሂድ፡-

$ clang -g -O2 simple-map.c -o simple-map
$ sudo strace ./simple-map
execve("./simple-map", ["./simple-map"], 0x7ffd40a27070 /* 14 vars */) = 0
...
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_HASH, key_size=4, value_size=4, max_entries=4, map_name="woo", ...}, 72) = 3
pause(

የስርዓት ጥሪው እነሆ bpf(2) ገላጭ ካርታ ቁጥሩን መለሰልን 3 እና ከዚያ ፕሮግራሙ, እንደተጠበቀው, በስርዓት ጥሪ ውስጥ ተጨማሪ መመሪያዎችን ይጠብቃል pause(2).

አሁን ፕሮግራማችንን ወደ ዳራ እንልካ ወይም ሌላ ተርሚናል ከፍተን መገልገያውን ተጠቅመን እቃችንን እንይ bpftool (የእኛን ካርታ ከሌሎች በስሙ መለየት እንችላለን)

$ sudo bpftool map
...
114: hash  name woo  flags 0x0
        key 4B  value 4B  max_entries 4  memlock 4096B
...

ቁጥር 114 የዕቃችን ዓለም አቀፍ መታወቂያ ነው። በስርአቱ ላይ ያለ ማንኛውም ፕሮግራም ትዕዛዙን ተጠቅሞ ያለውን ካርታ ለመክፈት ይህንን መታወቂያ መጠቀም ይችላል። BPF_MAP_GET_FD_BY_ID የስርዓት ጥሪ bpf.

አሁን በሃሽ ጠረጴዛችን መጫወት እንችላለን። ይዘቱን እንመልከት፡-

$ sudo bpftool map dump id 114
Found 0 elements

ባዶ በውስጡ ዋጋ እናስቀምጠው hash[1] = 1:

$ sudo bpftool map update id 114 key 1 0 0 0 value 1 0 0 0

ጠረጴዛውን እንደገና እንመልከተው፡-

$ sudo bpftool map dump id 114
key: 01 00 00 00  value: 01 00 00 00
Found 1 element

ሆራይ! አንድ አካል ለመጨመር ችለናል። ይህንን ለማድረግ በባይት ደረጃ መስራት እንዳለብን ልብ ይበሉ, ጀምሮ bptftool በሃሽ ሠንጠረዥ ውስጥ ያሉት ዋጋዎች ምን ዓይነት እንደሆኑ አያውቅም። (ይህ እውቀት BTFን በመጠቀም ወደ እሷ ሊተላለፍ ይችላል ፣ ግን የበለጠ አሁን።)

bpftool በትክክል እንዴት ያነባል እና አባሎችን ይጨምራል? በኮፈኑ ስር እንመልከት፡-

$ sudo strace -e bpf bpftool map dump id 114
bpf(BPF_MAP_GET_FD_BY_ID, {map_id=114, next_id=0, open_flags=0}, 120) = 3
bpf(BPF_MAP_GET_NEXT_KEY, {map_fd=3, key=NULL, next_key=0x55856ab65280}, 120) = 0
bpf(BPF_MAP_LOOKUP_ELEM, {map_fd=3, key=0x55856ab65280, value=0x55856ab652a0}, 120) = 0
key: 01 00 00 00  value: 01 00 00 00
bpf(BPF_MAP_GET_NEXT_KEY, {map_fd=3, key=0x55856ab65280, next_key=0x55856ab65280}, 120) = -1 ENOENT

በመጀመሪያ ትዕዛዙን በመጠቀም ካርታውን በአለምአቀፍ መታወቂያው ከፍተናል BPF_MAP_GET_FD_BY_ID и bpf(2) ገላጭ 3ን ወደ እኛ ተመለሰ።በተጨማሪ ትዕዛዙን በመጠቀም BPF_MAP_GET_NEXT_KEY በማለፍ የመጀመሪያውን ቁልፍ በሰንጠረዡ ውስጥ አግኝተናል NULL "የቀድሞ" ቁልፍን እንደ ጠቋሚ. ቁልፉ ካለን ማድረግ እንችላለን BPF_MAP_LOOKUP_ELEMእሴትን ወደ ጠቋሚ የሚመልስ value. ቀጣዩ እርምጃ ጠቋሚን ወደ የአሁኑ ቁልፍ በማለፍ ቀጣዩን አካል ለማግኘት እንሞክራለን ፣ ግን የእኛ ጠረጴዛ አንድ አካል እና ትዕዛዙን ብቻ ይይዛል ። BPF_MAP_GET_NEXT_KEY ይመለሳል ENOENT.

እሺ፣ እሴቱን በቁልፍ 1 እንለውጠው፣ የኛ የንግድ ሎጂክ መመዝገብ ያስፈልገዋል እንበል hash[1] = 2:

$ sudo strace -e bpf bpftool map update id 114 key 1 0 0 0 value 2 0 0 0
bpf(BPF_MAP_GET_FD_BY_ID, {map_id=114, next_id=0, open_flags=0}, 120) = 3
bpf(BPF_MAP_UPDATE_ELEM, {map_fd=3, key=0x55dcd72be260, value=0x55dcd72be280, flags=BPF_ANY}, 120) = 0

እንደተጠበቀው, በጣም ቀላል ነው: ትዕዛዙ BPF_MAP_GET_FD_BY_ID ካርታችንን በመታወቂያ እና በትእዛዝ ይከፍታል። BPF_MAP_UPDATE_ELEM ኤለመንቱን ይተካል።

ስለዚህ, ከአንድ ፕሮግራም የሃሽ ሰንጠረዥ ከፈጠርን በኋላ, ይዘቱን ከሌላው ማንበብ እና መጻፍ እንችላለን. ይህንን ከትእዛዝ መስመሩ ማድረግ ከቻልን በሲስተሙ ላይ ያለ ማንኛውም ፕሮግራም ሊያደርገው እንደሚችል ልብ ይበሉ። ከላይ ከተገለጹት ትዕዛዞች በተጨማሪ ከተጠቃሚ ቦታ ካርታዎች ጋር ለመስራት, የሚከተሉት:

  • BPF_MAP_LOOKUP_ELEMዋጋን በቁልፍ ያግኙ
  • BPF_MAP_UPDATE_ELEM: ማዘመን/እሴት ፍጠር
  • BPF_MAP_DELETE_ELEM: ቁልፍ አስወግድ
  • BPF_MAP_GET_NEXT_KEYየሚቀጥለውን (ወይም የመጀመሪያ) ቁልፍን ያግኙ
  • BPF_MAP_GET_NEXT_ID: ሁሉንም ነባር ካርታዎች ውስጥ እንዲያልፉ ይፈቅድልዎታል, እንደዛ ነው የሚሰራው bpftool map
  • BPF_MAP_GET_FD_BY_IDአሁን ያለውን ካርታ በአለምአቀፍ መታወቂያው ይክፈቱ
  • BPF_MAP_LOOKUP_AND_DELETE_ELEM: የነገሩን ዋጋ በአቶሚክ ያዘምኑ እና አሮጌውን ይመልሱ
  • BPF_MAP_FREEZEካርታውን ከተጠቃሚ ቦታ የማይለዋወጥ ያድርጉት (ይህ ክወና ሊቀለበስ አይችልም)
  • BPF_MAP_LOOKUP_BATCH, BPF_MAP_LOOKUP_AND_DELETE_BATCH, BPF_MAP_UPDATE_BATCH, BPF_MAP_DELETE_BATCHየጅምላ ስራዎች. ለምሳሌ, BPF_MAP_LOOKUP_AND_DELETE_BATCH ከካርታው ላይ ሁሉንም እሴቶች ለማንበብ እና ለማስጀመር ይህ ብቸኛው አስተማማኝ መንገድ ነው።

እነዚህ ሁሉ ትዕዛዞች ለሁሉም የካርታ ዓይነቶች አይሰሩም, ነገር ግን በአጠቃላይ ከሌሎች የካርታ ዓይነቶች ከተጠቃሚ ቦታ ጋር አብሮ መስራት ከሃሽ ጠረጴዛዎች ጋር ተመሳሳይ ይመስላል.

ለትዕዛዝ ያህል፣ የሃሽ ሠንጠረዥ ሙከራችንን እንጨርስ። እስከ አራት ቁልፎችን የሚይዝ ጠረጴዛ እንደፈጠርን አስታውስ? ጥቂት ተጨማሪ አካላትን እንጨምር፡-

$ sudo bpftool map update id 114 key 2 0 0 0 value 1 0 0 0
$ sudo bpftool map update id 114 key 3 0 0 0 value 1 0 0 0
$ sudo bpftool map update id 114 key 4 0 0 0 value 1 0 0 0

እስካሁን ጥሩ፡-

$ sudo bpftool map dump id 114
key: 01 00 00 00  value: 01 00 00 00
key: 02 00 00 00  value: 01 00 00 00
key: 04 00 00 00  value: 01 00 00 00
key: 03 00 00 00  value: 01 00 00 00
Found 4 elements

አንድ ተጨማሪ ለመጨመር እንሞክር፡-

$ sudo bpftool map update id 114 key 5 0 0 0 value 1 0 0 0
Error: update failed: Argument list too long

እንደተጠበቀው አልተሳካልንም። ስህተቱን በበለጠ ዝርዝር እንመልከት፡-

$ sudo strace -e bpf bpftool map update id 114 key 5 0 0 0 value 1 0 0 0
bpf(BPF_MAP_GET_FD_BY_ID, {map_id=114, next_id=0, open_flags=0}, 120) = 3
bpf(BPF_OBJ_GET_INFO_BY_FD, {info={bpf_fd=3, info_len=80, info=0x7ffe6c626da0}}, 120) = 0
bpf(BPF_MAP_UPDATE_ELEM, {map_fd=3, key=0x56049ded5260, value=0x56049ded5280, flags=BPF_ANY}, 120) = -1 E2BIG (Argument list too long)
Error: update failed: Argument list too long
+++ exited with 255 +++

ሁሉም ነገር ጥሩ ነው: እንደተጠበቀው, ቡድኑ BPF_MAP_UPDATE_ELEM አዲስ ፣ አምስተኛ ፣ ቁልፍ ፣ ግን ብልሽቶችን ለመፍጠር ይሞክራል። E2BIG.

ስለዚህ፣ የ BPF ፕሮግራሞችን መፍጠር እና መጫን፣ እንዲሁም ከተጠቃሚ ቦታ ካርታዎችን መፍጠር እና ማስተዳደር እንችላለን። አሁን ከ BPF ፕሮግራሞች ካርታዎችን እንዴት መጠቀም እንደምንችል መመልከቱ ምክንያታዊ ነው. ስለዚህ ጉዳይ ለማንበብ በሚከብዱ ፕሮግራሞች ቋንቋ በማሽን ማክሮ ኮድ ልንነጋገር እንችላለን ፣ ግን በእውነቱ የ BPF ፕሮግራሞች በትክክል እንዴት እንደሚፃፉ እና እንደሚጠበቁ ለማሳየት ጊዜው ደርሷል - በመጠቀም። libbpf.

(በዝቅተኛ ደረጃ ምሳሌ እጥረት ላልረኩ አንባቢዎች፡- በመጠቀም የተፈጠሩ ካርታዎችን እና አጋዥ ተግባራትን የሚጠቀሙ ፕሮግራሞችን በዝርዝር እንመረምራለን። libbpf እና በመመሪያው ደረጃ ምን እንደሚከሰት ይንገሩ. ላልረኩ አንባቢዎች እጅግ በጣም, ጨምረናል ምሳሌ በአንቀጹ ውስጥ በተገቢው ቦታ.)

libbpf በመጠቀም የ BPF ፕሮግራሞችን መፃፍ

የማሽን ኮዶችን በመጠቀም የ BPF ፕሮግራሞችን መፃፍ አስደሳች ሊሆን የሚችለው ለመጀመሪያ ጊዜ ብቻ ነው፣ እና ከዚያ ጥጋብ ይጀምራል። በዚህ ጊዜ ትኩረትዎን ወደ ላይ ማዞር ያስፈልግዎታል llvmለ BPF አርክቴክቸር ኮድ የማመንጨት ጀርባ ያለው እና ቤተመጻሕፍት ያለው libbpf, ይህም የ BPF አፕሊኬሽኖችን የተጠቃሚ ጎን ለመጻፍ እና በመጠቀም የተፈጠሩትን የ BPF ፕሮግራሞች ኮድ ለመጫን ያስችልዎታል llvm/clang.

በእውነቱ በዚህ እና በሚቀጥሉት መጣጥፎች ውስጥ እንደምናየው. libbpf ያለ እሱ ብዙ ስራዎችን ይሰራል (ወይም ተመሳሳይ መሳሪያዎች - iproute2, libbcc, libbpf-goወዘተ) መኖር አይቻልም። የፕሮጀክቱ ገዳይ ባህሪያት አንዱ libbpf BPF CO-RE ነው (አንድ ጊዜ ያጠናቅራል ፣ በሁሉም ቦታ ያሂዱ) - ከአንድ ከርነል ወደ ሌላ ተንቀሳቃሽ የ BPF ፕሮግራሞችን ለመፃፍ የሚያስችልዎ ፣ በተለያዩ ኤፒአይዎች ላይ የመሄድ ችሎታ ያለው (ለምሳሌ ፣ የከርነል መዋቅር ከስሪት ሲቀየር) ወደ ስሪት)። ከ CO-RE ጋር ለመስራት፣ የእርስዎ ከርነል ከ BTF ድጋፍ ጋር ማጠናቀር አለበት (ይህን እንዴት ማድረግ እንደሚቻል በክፍል ውስጥ እንገልፃለን) የልማት መሳሪያዎች. ከርነልዎ BTF የተሰራ መሆኑን ወይም በጣም ቀላል አለመሆኑን ማረጋገጥ ይችላሉ - በሚከተለው ፋይል መኖር።

$ ls -lh /sys/kernel/btf/vmlinux
-r--r--r-- 1 root root 2.6M Jul 29 15:30 /sys/kernel/btf/vmlinux

ይህ ፋይል በከርነል ውስጥ ጥቅም ላይ ስለሚውሉ ሁሉም የውሂብ አይነቶች መረጃን ያከማቻል እና በሁሉም ምሳሌዎቻችን ውስጥ ጥቅም ላይ ይውላል libbpf. በሚቀጥለው ርዕስ ውስጥ ስለ CO-RE በዝርዝር እንነጋገራለን, ነገር ግን በዚህ ውስጥ - እራስዎን በከርነል ብቻ ይገንቡ CONFIG_DEBUG_INFO_BTF.

ቤተ መጻሕፍት libbpf በቀጥታ በማውጫው ውስጥ ይኖራል tools/lib/bpf ከርነል እና እድገቱ የሚከናወነው በደብዳቤ ዝርዝሩ በኩል ነው [email protected]. ነገር ግን ከከርነል ውጭ ለሚኖሩ አፕሊኬሽኖች ፍላጎቶች የተለየ ማከማቻ ተይዟል። https://github.com/libbpf/libbpf በውስጡም የከርነል ቤተ-መጽሐፍት ለንባብ ተደራሽነት ብዙ ወይም ያነሰ የሚንፀባረቅበት።

በዚህ ክፍል ውስጥ ጥቅም ላይ የሚውል ፕሮጀክት እንዴት መፍጠር እንደሚችሉ እንመለከታለን libbpf፣ በርካታ (ብዙ ወይም ያነሰ ትርጉም የለሽ) የሙከራ ፕሮግራሞችን እንፃፍ እና ሁሉም እንዴት እንደሚሰራ በዝርዝር እንመርምር። ይህ በሚቀጥሉት ክፍሎች የ BPF ፕሮግራሞች ከካርታዎች ፣ ከርነል አጋዥዎች ፣ ቢቲኤፍ ፣ ወዘተ ጋር እንዴት እንደሚገናኙ በትክክል እንድናብራራ ያስችለናል ።

በተለምዶ ፕሮጀክቶችን በመጠቀም libbpf የ GitHub ማከማቻን እንደ git ንዑስ ሞዱል አክል፣ እኛም ተመሳሳይ ነገር እናደርጋለን፡

$ mkdir /tmp/libbpf-example
$ cd /tmp/libbpf-example/
$ git init-db
Initialized empty Git repository in /tmp/libbpf-example/.git/
$ git submodule add https://github.com/libbpf/libbpf.git
Cloning into '/tmp/libbpf-example/libbpf'...
remote: Enumerating objects: 200, done.
remote: Counting objects: 100% (200/200), done.
remote: Compressing objects: 100% (103/103), done.
remote: Total 3354 (delta 101), reused 118 (delta 79), pack-reused 3154
Receiving objects: 100% (3354/3354), 2.05 MiB | 10.22 MiB/s, done.
Resolving deltas: 100% (2176/2176), done.

ወደ ~ ​​መሄድ libbpf በጣም ቀላል

$ cd libbpf/src
$ mkdir build
$ OBJDIR=build DESTDIR=root make -s install
$ find root
root
root/usr
root/usr/include
root/usr/include/bpf
root/usr/include/bpf/bpf_tracing.h
root/usr/include/bpf/xsk.h
root/usr/include/bpf/libbpf_common.h
root/usr/include/bpf/bpf_endian.h
root/usr/include/bpf/bpf_helpers.h
root/usr/include/bpf/btf.h
root/usr/include/bpf/bpf_helper_defs.h
root/usr/include/bpf/bpf.h
root/usr/include/bpf/libbpf_util.h
root/usr/include/bpf/libbpf.h
root/usr/include/bpf/bpf_core_read.h
root/usr/lib64
root/usr/lib64/libbpf.so.0.1.0
root/usr/lib64/libbpf.so.0
root/usr/lib64/libbpf.a
root/usr/lib64/libbpf.so
root/usr/lib64/pkgconfig
root/usr/lib64/pkgconfig/libbpf.pc

በዚህ ክፍል የሚቀጥለው እቅዳችን እንደሚከተለው ነው፡ እንደ BPF ፕሮግራም እንጽፋለን። BPF_PROG_TYPE_XDP, በቀድሞው ምሳሌ ላይ ካለው ጋር ተመሳሳይ ነው, ነገር ግን በ C ውስጥ, በመጠቀም እንሰበስባለን clang, እና ወደ ከርነል የሚጭን አጋዥ ፕሮግራም ይፃፉ. በሚቀጥሉት ክፍሎች የሁለቱም የ BPF ፕሮግራም እና የረዳት ፕሮግራሙን አቅም እናሰፋለን።

ምሳሌ፡ libbpfን በመጠቀም የተሟላ መተግበሪያ መፍጠር

ለመጀመር, ፋይሉን እንጠቀማለን /sys/kernel/btf/vmlinuxከላይ የተጠቀሰው እና አቻውን በርዕስ ፋይል መልክ የፈጠረው፡-

$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

ይህ ፋይል በእኛ ከርነል ውስጥ የሚገኙትን ሁሉንም የውሂብ አወቃቀሮች ያከማቻል ፣ ለምሳሌ ፣ የ IPv4 ራስጌ በከርነል ውስጥ የሚገለፀው በዚህ መንገድ ነው ።

$ grep -A 12 'struct iphdr {' vmlinux.h
struct iphdr {
    __u8 ihl: 4;
    __u8 version: 4;
    __u8 tos;
    __be16 tot_len;
    __be16 id;
    __be16 frag_off;
    __u8 ttl;
    __u8 protocol;
    __sum16 check;
    __be32 saddr;
    __be32 daddr;
};

አሁን የ BPF ፕሮግራማችንን በ C ውስጥ እንጽፋለን-

$ cat xdp-simple.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

SEC("xdp/simple")
int simple(void *ctx)
{
        return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

ፕሮግራማችን በጣም ቀላል ቢሆንም አሁንም ለብዙ ዝርዝሮች ትኩረት መስጠት አለብን. በመጀመሪያ፣ የምናካትተው የመጀመሪያው የራስጌ ፋይል ነው። vmlinux.hአሁን በመጠቀም የፈጠርነው bpftool btf dump - አሁን የከርነል አወቃቀሮች ምን እንደሚመስሉ ለማወቅ የከርነል-ራስጌዎች ጥቅል መጫን አያስፈልገንም. የሚከተለው የራስጌ ፋይል ከቤተ-መጽሐፍት ወደ እኛ ይመጣል libbpf. አሁን ማክሮውን ለመወሰን ብቻ ያስፈልገናል SEC, ቁምፊውን ወደ ELF ነገር ፋይል ወደ ትክክለኛው ክፍል ይልካል. ፕሮግራማችን በክፍል ውስጥ ተካቷል xdp/simple, ከመጥፋቱ በፊት የፕሮግራሙን አይነት BPF እንገልፃለን - ይህ በ ውስጥ ጥቅም ላይ የዋለው ኮንቬንሽን ነው libbpf፣ በክፍል ስም ላይ በመመስረት በጅምር ላይ ትክክለኛውን ዓይነት ይተካል። bpf(2). የ BPF ፕሮግራም ራሱ ነው። C - በጣም ቀላል እና አንድ መስመር ያካትታል return XDP_PASS. በመጨረሻም የተለየ ክፍል "license" የፍቃዱ ስም ይዟል።

ፕሮግራማችንን llvm/clang፣ ስሪት>= 10.0.0፣ ወይም የተሻለ ነገርን በመጠቀም ማጠናቀር እንችላለን (ክፍልን ይመልከቱ) የልማት መሳሪያዎች):

$ clang --version
clang version 11.0.0 (https://github.com/llvm/llvm-project.git afc287e0abec710398465ee1f86237513f2b5091)
...

$ clang -O2 -g -c -target bpf -I libbpf/src/root/usr/include xdp-simple.bpf.c -o xdp-simple.bpf.o

ከሚያስደስቱ ባህሪያት መካከል: የታለመውን አርክቴክቸር እንጠቁማለን -target bpf እና ወደ ራስጌዎች የሚወስደው መንገድ libbpf, በቅርቡ የጫንነው. እንዲሁም ስለ አይርሱ -O2ያለዚህ አማራጭ ለወደፊቱ አስገራሚዎች ሊሆኑ ይችላሉ ። የእኛን ኮድ እንይ, እኛ የምንፈልገውን ፕሮግራም መፃፍ ችለናል?

$ llvm-objdump --section=xdp/simple --no-show-raw-insn -D xdp-simple.bpf.o

xdp-simple.bpf.o:       file format elf64-bpf

Disassembly of section xdp/simple:

0000000000000000 <simple>:
       0:       r0 = 2
       1:       exit

አዎ፣ ሰርቷል! አሁን, ከፕሮግራሙ ጋር ሁለትዮሽ ፋይል አለን, እና ወደ ከርነል የሚጭን መተግበሪያ መፍጠር እንፈልጋለን. ለዚህ ዓላማ ቤተ-መጽሐፍት libbpf ሁለት አማራጮችን ይሰጠናል - ዝቅተኛ ደረጃ ኤፒአይ ወይም ከፍተኛ ደረጃ ኤፒአይ ይጠቀሙ። ለቀጣይ ጥናታቸው በትንሹ ጥረት ቢፒኤፍ ፕሮግራሞችን እንዴት መጻፍ፣ መጫን እና ማገናኘት ስለምንፈልግ በሁለተኛው መንገድ እንሄዳለን።

በመጀመሪያ, ተመሳሳይ መገልገያ በመጠቀም የፕሮግራማችንን "አጽም" ከሱ ሁለትዮሽ ማመንጨት አለብን bpftool - የ BPF ዓለም የስዊስ ቢላዋ (በጥሬው ሊወሰድ ይችላል ፣ ከ BPF ፈጣሪዎች እና ጠባቂዎች አንዱ የሆነው ዳንኤል ቦርክማን ስዊዘርላንድ ነው)

$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h

በፋይል ውስጥ xdp-simple.skel.h የፕሮግራማችንን ሁለትዮሽ ኮድ እና የማስተዳደር ተግባራትን ይይዛል - እቃችንን መጫን ፣ ማያያዝ ፣ መሰረዝ። በቀላል ጉዳያችን ይህ ከመጠን በላይ መጨናነቅን ይመስላል ነገር ግን የነገር ፋይሉ ብዙ BPF ፕሮግራሞችን እና ካርታዎችን በሚይዝበት ሁኔታ ላይም ይሠራል እና ይህንን ግዙፍ ኢኤልኤፍ ለመጫን አፅሙን ማመንጨት እና አንድ ወይም ሁለት ተግባራትን ከብጁ መተግበሪያ መደወል አለብን። እየጻፉ ነው አሁን እንቀጥል።

በትክክል አነጋገር፣ የእኛ ሎደር ፕሮግራማችን ቀላል ነው፡-

#include <err.h>
#include <unistd.h>
#include "xdp-simple.skel.h"

int main(int argc, char **argv)
{
    struct xdp_simple_bpf *obj;

    obj = xdp_simple_bpf__open_and_load();
    if (!obj)
        err(1, "failed to open and/or load BPF objectn");

    pause();

    xdp_simple_bpf__destroy(obj);
}

ይህ ነው struct xdp_simple_bpf በፋይሉ ውስጥ ይገለጻል xdp-simple.skel.h እና የእኛን የነገር ፋይል ይገልፃል፡-

struct xdp_simple_bpf {
    struct bpf_object_skeleton *skeleton;
    struct bpf_object *obj;
    struct {
        struct bpf_program *simple;
    } progs;
    struct {
        struct bpf_link *simple;
    } links;
};

የዝቅተኛ ደረጃ ኤፒአይ ምልክቶችን እዚህ ማየት እንችላለን፡ መዋቅሩ struct bpf_program *simple и struct bpf_link *simple. የመጀመሪያው መዋቅር በተለይ በክፍል ውስጥ የተጻፈውን ፕሮግራማችንን ይገልፃል xdp/simple, እና ሁለተኛው ፕሮግራሙ ከዝግጅቱ ምንጭ ጋር እንዴት እንደሚገናኝ ይገልጻል.

ሥራ xdp_simple_bpf__open_and_load, የ ELF ነገርን ይከፍታል, ይተነትናል, ሁሉንም አወቃቀሮች እና ንኡስ አወቃቀሮችን ይፈጥራል (ከፕሮግራሙ በተጨማሪ ELF በተጨማሪ ሌሎች ክፍሎችን ይይዛል - ዳታ, ተነባቢ ውሂብ, ማረም መረጃ, ፍቃድ, ወዘተ.) እና ከዚያም ስርዓቱን በመጠቀም ወደ ከርነል ይጭናል. ይደውሉ bpfፕሮግራሙን በማዘጋጀት እና በማስኬድ ማረጋገጥ የምንችለው፡-

$ clang -O2 -I ./libbpf/src/root/usr/include/ xdp-simple.c -o xdp-simple ./libbpf/src/root/usr/lib64/libbpf.a -lelf -lz

$ sudo strace -e bpf ./xdp-simple
...
bpf(BPF_BTF_LOAD, 0x7ffdb8fd9670, 120)  = 3
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, insn_cnt=2, insns=0xdfd580, license="GPL", log_level=0, log_size=0, log_buf=NULL, kern_version=KERNEL_VERSION(5, 8, 0), prog_flags=0, prog_name="simple", prog_ifindex=0, expected_attach_type=0x25 /* BPF_??? */, ...}, 120) = 4

አሁን በመጠቀም ፕሮግራማችንን እንይ bpftool. መታወቂያዋን እናግኝ፡-

# bpftool p | grep -A4 simple
463: xdp  name simple  tag 3b185187f1855c4c  gpl
        loaded_at 2020-08-01T01:59:49+0000  uid 0
        xlated 16B  jited 40B  memlock 4096B
        btf_id 185
        pids xdp-simple(16498)

እና መጣል (የትእዛዝ አጭር ቅጽ እንጠቀማለን bpftool prog dump xlated):

# bpftool p d x id 463
int simple(void *ctx):
; return XDP_PASS;
   0: (b7) r0 = 2
   1: (95) exit

አዲስ ነገር! ፕሮግራሙ የC ምንጭ ፋይላችንን ቁርጥራጮች አሳትሟል።ይህ የተደረገው በቤተ-መጽሐፍት ነው። libbpf, በሁለትዮሽ ውስጥ የማረም ክፍሉን ያገኘው, ወደ BTF ነገር ያጠናቀረው, በመጠቀም ወደ ከርነል ጭኖታል. BPF_BTF_LOAD, እና ከዚያ ፕሮግራሙን በትእዛዙ ሲጫኑ የተገኘውን የፋይል ገላጭ ገልፀዋል BPG_PROG_LOAD.

የከርነል አጋዥዎች

BPF ፕሮግራሞች "ውጫዊ" ተግባራትን - የከርነል አጋዥዎችን ማሄድ ይችላሉ. እነዚህ አጋዥ ተግባራት BPF ፕሮግራሞች የከርነል አወቃቀሮችን እንዲደርሱ፣ ካርታዎችን እንዲያስተዳድሩ እና እንዲሁም ከ“እውነተኛው ዓለም” ጋር እንዲገናኙ ያስችላቸዋል - የፐርፍ ዝግጅቶችን መፍጠር፣ ሃርድዌርን መቆጣጠር (ለምሳሌ ፓኬቶችን አቅጣጫ ማዞር) ወዘተ።

ምሳሌ፡ bpf_get_smp_processor_id

“በምሳሌ መማር” በሚለው ዘይቤ ማዕቀፍ ውስጥ፣ ከረዳት ተግባራት ውስጥ አንዱን እንመልከት፣ bpf_get_smp_processor_id(), የተወሰነ በፋይል ውስጥ kernel/bpf/helpers.c. የጠራው BPF ፕሮግራም የሚሰራበትን ፕሮሰሰር ቁጥር ይመልሳል። ነገር ግን አፈፃፀሙ አንድ መስመር እንደሚይዝ ያህል ለትርጉም ስራው ፍላጎት የለንም።

BPF_CALL_0(bpf_get_smp_processor_id)
{
    return smp_processor_id();
}

የBPF አጋዥ ተግባር ትርጓሜዎች ከሊኑክስ ስርዓት የጥሪ ፍቺዎች ጋር ተመሳሳይ ናቸው። እዚህ, ለምሳሌ, ምንም ክርክር የሌለበት ተግባር ይገለጻል. (በማለት ሶስት ነጋሪ እሴቶችን የሚወስድ ተግባር ማክሮን በመጠቀም ይገለጻል። BPF_CALL_3. ከፍተኛው የክርክር ብዛት አምስት ነው።) ሆኖም ይህ የትርጉሙ የመጀመሪያ ክፍል ብቻ ነው። ሁለተኛው ክፍል የዓይነት አወቃቀሩን መወሰን ነው struct bpf_func_protoአረጋጋጭ የሚረዳውን የረዳት ተግባር መግለጫ የያዘ፡-

const struct bpf_func_proto bpf_get_smp_processor_id_proto = {
    .func     = bpf_get_smp_processor_id,
    .gpl_only = false,
    .ret_type = RET_INTEGER,
};

የረዳት ተግባራትን መመዝገብ

የአንድ የተወሰነ ዓይነት BPF ፕሮግራሞች ይህንን ተግባር ለመጠቀም መመዝገብ አለባቸው ፣ ለምሳሌ ለአይነቱ BPF_PROG_TYPE_XDP አንድ ተግባር በከርነል ውስጥ ይገለጻል xdp_func_protoXDP ይህንን ተግባር ይደግፈዋል ወይም አይደግፍም የሚለውን ከረዳት ተግባር መታወቂያ የሚወስነው። የእኛ ተግባር ነው። ድጋፎች:

static const struct bpf_func_proto *
xdp_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
{
    switch (func_id) {
    ...
    case BPF_FUNC_get_smp_processor_id:
        return &bpf_get_smp_processor_id_proto;
    ...
    }
}

አዲስ የ BPF ፕሮግራም ዓይነቶች በፋይሉ ውስጥ "የተገለጹ" ናቸው include/linux/bpf_types.h ማክሮ በመጠቀም BPF_PROG_TYPE. በጥቅሶች የተገለፀው አመክንዮአዊ ፍቺ ስለሆነ እና በ C ቋንቋ አነጋገር የአንድ ሙሉ የኮንክሪት መዋቅር ፍቺ በሌሎች ቦታዎች ይከሰታል። በተለይም በፋይሉ ውስጥ kernel/bpf/verifier.c ሁሉም ትርጓሜዎች ከፋይል bpf_types.h የተዋቀሩ ድርድር ለመፍጠር ያገለግላሉ bpf_verifier_ops[]:

static const struct bpf_verifier_ops *const bpf_verifier_ops[] = {
#define BPF_PROG_TYPE(_id, _name, prog_ctx_type, kern_ctx_type) 
    [_id] = & _name ## _verifier_ops,
#include <linux/bpf_types.h>
#undef BPF_PROG_TYPE
};

ያም ማለት ለእያንዳንዱ የቢፒኤፍ ፕሮግራም አይነት የመረጃ አወቃቀሩ ጠቋሚ ይገለጻል። struct bpf_verifier_ops, እሱም ከዋጋው ጋር የተጀመረ _name ## _verifier_ops፣ ማለትም ፣ xdp_verifier_opsxdp... መዋቅር xdp_verifier_ops ተወስኗል በፋይል ውስጥ net/core/filter.c እንደሚከተለው ይሆናል;

const struct bpf_verifier_ops xdp_verifier_ops = {
    .get_func_proto     = xdp_func_proto,
    .is_valid_access    = xdp_is_valid_access,
    .convert_ctx_access = xdp_convert_ctx_access,
    .gen_prologue       = bpf_noop_prologue,
};

እዚህ የታወቁ ተግባራችንን እናያለን xdp_func_protoፈታኝ በሆነ ጊዜ ሁሉ አረጋጋጩን ያስኬዳል አንድ ዓይነት በ BPF ፕሮግራም ውስጥ ተግባራት ፣ ይመልከቱ verifier.c.

ግምታዊ BPF ፕሮግራም ተግባሩን እንዴት እንደሚጠቀም እንመልከት bpf_get_smp_processor_id. ይህንን ለማድረግ ፕሮግራሙን ከቀደመው ክፍላችን እንደሚከተለው እንጽፋለን-

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

SEC("xdp/simple")
int simple(void *ctx)
{
    if (bpf_get_smp_processor_id() != 0)
        return XDP_DROP;
    return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

ምልክት bpf_get_smp_processor_id ተወስኗል в <bpf/bpf_helper_defs.h> ቤተመፃህፍት libbpf እንዴት

static u32 (*bpf_get_smp_processor_id)(void) = (void *) 8;

ያውና, bpf_get_smp_processor_id እሴቱ 8 የሆነ የተግባር ጠቋሚ ሲሆን 8 እሴቱ ነው። BPF_FUNC_get_smp_processor_id ተይብ enum bpf_fun_id, በፋይሉ ውስጥ ለእኛ የተገለፀው vmlinux.h (ፋይል bpf_helper_defs.h በከርነል ውስጥ የሚመነጨው በስክሪፕት ነው, ስለዚህ "አስማት" ቁጥሮች ደህና ናቸው). ይህ ተግባር ምንም ነጋሪ እሴቶችን አይወስድም እና የአይነት እሴትን ይመልሳል __u32. በፕሮግራማችን ውስጥ ስናካሂድ, clang መመሪያ ያመነጫል BPF_CALL "ትክክለኛው ዓይነት" ፕሮግራሙን እናጠናቅቅ እና ክፍሉን እንይ xdp/simple:

$ clang -O2 -g -c -target bpf -I libbpf/src/root/usr/include xdp-simple.bpf.c -o xdp-simple.bpf.o
$ llvm-objdump -D --section=xdp/simple xdp-simple.bpf.o

xdp-simple.bpf.o:       file format elf64-bpf

Disassembly of section xdp/simple:

0000000000000000 <simple>:
       0:       85 00 00 00 08 00 00 00 call 8
       1:       bf 01 00 00 00 00 00 00 r1 = r0
       2:       67 01 00 00 20 00 00 00 r1 <<= 32
       3:       77 01 00 00 20 00 00 00 r1 >>= 32
       4:       b7 00 00 00 02 00 00 00 r0 = 2
       5:       15 01 01 00 00 00 00 00 if r1 == 0 goto +1 <LBB0_2>
       6:       b7 00 00 00 01 00 00 00 r0 = 1

0000000000000038 <LBB0_2>:
       7:       95 00 00 00 00 00 00 00 exit

በመጀመሪያው መስመር መመሪያዎችን እናያለን call, መለኪያ IMM ከ 8 ጋር እኩል የሆነ, እና SRC_REG - ዜሮ. አረጋጋጭ በተጠቀመው የ ABI ስምምነት መሰረት፣ ይህ የረዳት ተግባር ቁጥር ስምንት ጥሪ ነው። አንዴ ከተጀመረ, አመክንዮው ቀላል ነው. ከመመዝገቢያ ዋጋ ይመለሱ r0 ተገልብጧል r1 እና በመስመሮች 2,3 ላይ ወደ መተየብ ይቀየራል u32 - የላይኛው 32 ቢት ተጠርጓል. በመስመሮች 4,5,6,7፣2፣XNUMX፣XNUMX XNUMX እንመለሳለን (XDP_PASS) ወይም 1 (XDP_DROP) ከመስመር 0 የረዳው ተግባር ዜሮ ወይም ዜሮ ያልሆነ ዋጋ እንደመለሰው ይወሰናል።

እራሳችንን እንፈትሽ፡ ፕሮግራሙን ጫን እና ውጤቱን ተመልከት bpftool prog dump xlated:

$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h
$ clang -O2 -g -I ./libbpf/src/root/usr/include/ -o xdp-simple xdp-simple.c ./libbpf/src/root/usr/lib64/libbpf.a -lelf -lz
$ sudo ./xdp-simple &
[2] 10914

$ sudo bpftool p | grep simple
523: xdp  name simple  tag 44c38a10c657e1b0  gpl
        pids xdp-simple(10915)

$ sudo bpftool p d x id 523
int simple(void *ctx):
; if (bpf_get_smp_processor_id() != 0)
   0: (85) call bpf_get_smp_processor_id#114128
   1: (bf) r1 = r0
   2: (67) r1 <<= 32
   3: (77) r1 >>= 32
   4: (b7) r0 = 2
; }
   5: (15) if r1 == 0x0 goto pc+1
   6: (b7) r0 = 1
   7: (95) exit

እሺ፣ አረጋጋጭ ትክክለኛውን የከርነል ረዳት አግኝቷል።

ምሳሌ፡ ክርክሮችን ማለፍ እና በመጨረሻም ፕሮግራሙን ማስኬድ!

ሁሉም የሩጫ ደረጃ አጋዥ ተግባራት ፕሮቶታይፕ አላቸው።

u64 fn(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)

የረዳት ተግባራት መለኪያዎች በመመዝገቢያ ውስጥ ተላልፈዋል r1-r5, እና ዋጋው በመመዝገቢያው ውስጥ ይመለሳል r0. ከአምስት ክርክሮች በላይ የሚወስዱ ተግባራት የሉም, እና ለእነሱ ድጋፍ ለወደፊቱ መጨመር አይጠበቅም.

አዲሱን የከርነል አጋዥ እና BPF እንዴት መለኪያዎችን እንደሚያልፍ እንይ። እንደገና እንፃፍ xdp-simple.bpf.c እንደሚከተለው (የተቀሩት መስመሮች አልተቀየሩም):

SEC("xdp/simple")
int simple(void *ctx)
{
    bpf_printk("running on CPU%un", bpf_get_smp_processor_id());
    return XDP_PASS;
}

ፕሮግራማችን የሚሰራበትን ሲፒዩ ቁጥር ያትማል። እናጠናቅቅና ኮዱን እንይ፡

$ llvm-objdump -D --section=xdp/simple --no-show-raw-insn xdp-simple.bpf.o

0000000000000000 <simple>:
       0:       r1 = 10
       1:       *(u16 *)(r10 - 8) = r1
       2:       r1 = 8441246879787806319 ll
       4:       *(u64 *)(r10 - 16) = r1
       5:       r1 = 2334956330918245746 ll
       7:       *(u64 *)(r10 - 24) = r1
       8:       call 8
       9:       r1 = r10
      10:       r1 += -24
      11:       r2 = 18
      12:       r3 = r0
      13:       call 6
      14:       r0 = 2
      15:       exit

በመስመሮች 0-7 ውስጥ ገመዱን እንጽፋለን running on CPU%un, እና ከዚያም በመስመር 8 ላይ የተለመደውን እንሰራለን bpf_get_smp_processor_id. በመስመሮች 9-12 ላይ የረዳት ክርክሮችን እናዘጋጃለን bpf_printk - ይመዘግባል r1, r2, r3. ለምን ሦስቱ አሉ እና ሁለት አይደሉም? ምክንያቱም bpf_printkይህ ማክሮ መጠቅለያ ነው። በእውነተኛው ረዳት ዙሪያ bpf_trace_printk, ይህም የቅርጸት ሕብረቁምፊውን መጠን ማለፍ ያስፈልገዋል.

አሁን ሁለት መስመሮችን እንጨምርበት xdp-simple.cፕሮግራማችን ከበይነገጽ ጋር እንዲገናኝ lo እና በእውነት ተጀምሯል!

$ cat xdp-simple.c
#include <linux/if_link.h>
#include <err.h>
#include <unistd.h>
#include "xdp-simple.skel.h"

int main(int argc, char **argv)
{
    __u32 flags = XDP_FLAGS_SKB_MODE;
    struct xdp_simple_bpf *obj;

    obj = xdp_simple_bpf__open_and_load();
    if (!obj)
        err(1, "failed to open and/or load BPF objectn");

    bpf_set_link_xdp_fd(1, -1, flags);
    bpf_set_link_xdp_fd(1, bpf_program__fd(obj->progs.simple), flags);

cleanup:
    xdp_simple_bpf__destroy(obj);
}

እዚህ ተግባሩን እንጠቀማለን bpf_set_link_xdp_fdየ XDP-አይነት BPF ፕሮግራሞችን ከአውታረ መረብ መገናኛዎች ጋር የሚያገናኝ። የበይነገጽ ቁጥሩን ሃርድ ኮድ አድርገናል። lo, ሁልጊዜም 1. አሮጌውን ፕሮግራም ከተያያዘ መጀመሪያ ለመለያየት ተግባሩን ሁለት ጊዜ እናካሂዳለን. አሁን ተግዳሮት አያስፈልገንም። pause ወይም ማለቂያ የሌለው ዑደት፡ የእኛ ሎደር ፕሮግራማችን ይወጣል፣ ነገር ግን የBPF ፕሮግራም ከክስተቱ ምንጭ ጋር የተገናኘ ስለሆነ አይገደልም። ከተሳካ ማውረድ እና ግንኙነት በኋላ ለእያንዳንዱ የአውታረ መረብ ፓኬት ለደረሰው ፕሮግራም ይጀምራል lo.

ፕሮግራሙን አውርደን በይነገጹን እንይ lo:

$ sudo ./xdp-simple
$ sudo bpftool p | grep simple
669: xdp  name simple  tag 4fca62e77ccb43d6  gpl
$ ip l show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    prog/xdp id 669

ያወረድነው ፕሮግራም መታወቂያ 669 አለው እና በይነገጹ ላይ ተመሳሳይ መታወቂያ እናያለን። lo. ሁለት ጥቅሎችን እንልካለን። 127.0.0.1 (ጥያቄ + መልስ)

$ ping -c1 localhost

እና አሁን የማረም ምናባዊ ፋይልን ይዘቶች እንይ /sys/kernel/debug/tracing/trace_pipe, የትኛው ውስጥ bpf_printk መልእክቱን ይጽፋል፡-

# cat /sys/kernel/debug/tracing/trace_pipe
ping-13937 [000] d.s1 442015.377014: bpf_trace_printk: running on CPU0
ping-13937 [000] d.s1 442015.377027: bpf_trace_printk: running on CPU0

ሁለት ፓኬጆች ታይተዋል። lo እና በ CPU0 ላይ ተሰራ - የመጀመሪያው ሙሉ ትርጉም የለሽ BPF ፕሮግራማችን ሰርቷል!

መሆኑን መገንዘብ ተገቢ ነው bpf_printk ወደ ማረም ፋይል የሚጽፈው በከንቱ አይደለም-ይህ በምርት ውስጥ ለመጠቀም በጣም የተሳካው ረዳት አይደለም ፣ ግን ግባችን ቀለል ያለ ነገር ለማሳየት ነበር።

ካርታዎችን ከ BPF ፕሮግራሞች መድረስ

ምሳሌ፡ ከ BPF ፕሮግራም ካርታ መጠቀም

ቀደም ባሉት ክፍሎች ካርታዎችን ከተጠቃሚ ቦታ እንዴት መፍጠር እና መጠቀም እንዳለብን ተምረናል, እና አሁን የከርነል ክፍሉን እንይ. እንደተለመደው በምሳሌ እንጀምር። ፕሮግራማችንን እንደገና እንፃፍ xdp-simple.bpf.c እንደሚከተለው ይሆናል;

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __uint(max_entries, 8);
    __type(key, u32);
    __type(value, u64);
} woo SEC(".maps");

SEC("xdp/simple")
int simple(void *ctx)
{
    u32 key = bpf_get_smp_processor_id();
    u32 *val;

    val = bpf_map_lookup_elem(&woo, &key);
    if (!val)
        return XDP_ABORTED;

    *val += 1;

    return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

በፕሮግራሙ መጀመሪያ ላይ የካርታ ፍቺ ጨምረናል wooይህ የ 8-ኤለመንት ድርድር ነው እሴቶችን የሚያከማች u64 (በ C ውስጥ እንዲህ ዓይነቱን ድርድር እንገልፃለን u64 woo[8]). በአንድ ፕሮግራም ውስጥ "xdp/simple" የአሁኑን ፕሮሰሰር ቁጥር በተለዋዋጭ ውስጥ እናገኛለን key እና ከዚያ የረዳት ተግባሩን በመጠቀም bpf_map_lookup_element በድርድር ውስጥ ለሚገኘው ተዛማጅ ግቤት ጠቋሚ እናገኛለን ፣ ይህም በአንድ እንጨምራለን ። ወደ ራሽያኛ ተተርጉሟል፡ ሲፒዩ የገቢ ፓኬቶችን ያከናወነባቸውን ስታቲስቲክስ እናሰላለን። ፕሮግራሙን ለማስኬድ እንሞክር-

$ clang -O2 -g -c -target bpf -I libbpf/src/root/usr/include xdp-simple.bpf.c -o xdp-simple.bpf.o
$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h
$ clang -O2 -g -I ./libbpf/src/root/usr/include/ -o xdp-simple xdp-simple.c ./libbpf/src/root/usr/lib64/libbpf.a -lelf -lz
$ sudo ./xdp-simple

እሷ እንደተያያዘች እንፈትሽ lo እና አንዳንድ እሽጎች ይላኩ

$ ip l show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    prog/xdp id 108

$ for s in `seq 234`; do sudo ping -f -c 100 127.0.0.1 >/dev/null 2>&1; done

አሁን የአደራደሩን ይዘት እንመልከት፡-

$ sudo bpftool map dump name woo
[
    { "key": 0, "value": 0 },
    { "key": 1, "value": 400 },
    { "key": 2, "value": 0 },
    { "key": 3, "value": 0 },
    { "key": 4, "value": 0 },
    { "key": 5, "value": 0 },
    { "key": 6, "value": 0 },
    { "key": 7, "value": 46400 }
]

ሁሉም ማለት ይቻላል ሂደቶች በ CPU7 ላይ ተካሂደዋል። ይህ ለእኛ አስፈላጊ አይደለም, ዋናው ነገር ፕሮግራሙ ይሰራል እና ካርታዎችን ከ BPF ፕሮግራሞች እንዴት ማግኘት እንደሚቻል እንረዳለን - በመጠቀም. хелперов bpf_mp_*.

ሚስጥራዊ መረጃ ጠቋሚ

ስለዚህ፣ እንደ ጥሪዎችን በመጠቀም ካርታውን ከ BPF ፕሮግራም ማግኘት እንችላለን

val = bpf_map_lookup_elem(&woo, &key);

የረዳት ተግባሩ በሚመስልበት ቦታ

void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)

ግን ጠቋሚን እናልፋለን &woo ወደማይታወቅ መዋቅር struct { ... }...

የፕሮግራሙን ሰብሳቢ ከተመለከትን, እሴቱን እናያለን &woo በትክክል አልተገለጸም (መስመር 4)

llvm-objdump -D --section xdp/simple xdp-simple.bpf.o

xdp-simple.bpf.o:       file format elf64-bpf

Disassembly of section xdp/simple:

0000000000000000 <simple>:
       0:       85 00 00 00 08 00 00 00 call 8
       1:       63 0a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r0
       2:       bf a2 00 00 00 00 00 00 r2 = r10
       3:       07 02 00 00 fc ff ff ff r2 += -4
       4:       18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll
       6:       85 00 00 00 01 00 00 00 call 1
...

እና በመዛወሮች ውስጥ ይገኛል፡-

$ llvm-readelf -r xdp-simple.bpf.o | head -4

Relocation section '.relxdp/simple' at offset 0xe18 contains 1 entries:
    Offset             Info             Type               Symbol's Value  Symbol's Name
0000000000000020  0000002700000001 R_BPF_64_64            0000000000000000 woo

ነገር ግን አስቀድሞ የተጫነውን ፕሮግራም ከተመለከትን ለትክክለኛው ካርታ ጠቋሚን እናያለን (መስመር 4)

$ sudo bpftool prog dump x name simple
int simple(void *ctx):
   0: (85) call bpf_get_smp_processor_id#114128
   1: (63) *(u32 *)(r10 -4) = r0
   2: (bf) r2 = r10
   3: (07) r2 += -4
   4: (18) r1 = map[id:64]
...

ስለዚህ, የእኛን ሎደር ፕሮግራማችንን በሚጀምርበት ጊዜ, አገናኝ ወደ ብለን መደምደም እንችላለን &woo በቤተመጽሐፍት በሆነ ነገር ተተካ libbpf. በመጀመሪያ ውጤቱን እንመለከታለን strace:

$ sudo strace -e bpf ./xdp-simple
...
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_ARRAY, key_size=4, value_size=8, max_entries=8, map_name="woo", ...}, 120) = 4
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, prog_name="simple", ...}, 120) = 5

ያንን እናያለን libbpf ካርታ ፈጠረ woo እና ከዚያ ፕሮግራማችንን አውርደናል simple. ፕሮግራሙን እንዴት እንደጫንን በዝርዝር እንመልከት፡-

  • ይደውሉ xdp_simple_bpf__open_and_load ከፋይል xdp-simple.skel.h
  • የሚያስከትል xdp_simple_bpf__load ከፋይል xdp-simple.skel.h
  • የሚያስከትል bpf_object__load_skeleton ከፋይል libbpf/src/libbpf.c
  • የሚያስከትል bpf_object__load_xattrlibbpf/src/libbpf.c

የመጨረሻው ተግባር, ከሌሎች ነገሮች በተጨማሪ, ይጠራል bpf_object__create_mapsነባር ካርታዎችን የሚፈጥር ወይም የሚከፍት፣ ወደ ፋይል ገላጭነት የሚቀይራቸው። (እዚ ነው የምናየው BPF_MAP_CREATE በውጤቱ ውስጥ strace.) በመቀጠል ተግባሩ ይባላል bpf_object__relocate ያየነውን ስለምናስታውስ የምትፈልገው እሷ ነች woo በማዛወር ጠረጴዛው ውስጥ. እሱን ማሰስ፣ በመጨረሻ እራሳችንን በተግባሩ ውስጥ እናገኛለን bpf_program__relocate፣ የትኛው የካርታ ማዛወርን ይመለከታል:

case RELO_LD64:
    insn[0].src_reg = BPF_PSEUDO_MAP_FD;
    insn[0].imm = obj->maps[relo->map_idx].fd;
    break;

ስለዚህ መመሪያዎቻችንን እንወስዳለን

18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll

እና በውስጡ ያለውን የምንጭ መዝገብ ይተኩ BPF_PSEUDO_MAP_FD, እና የመጀመሪያው አይኤምኤም ወደ ካርታችን ፋይል ገላጭ እና, እኩል ከሆነ, ለምሳሌ, 0xdeadbeef, ከዚያ በውጤቱ መመሪያውን እንቀበላለን

18 11 00 00 ef eb ad de 00 00 00 00 00 00 00 00 r1 = 0 ll

የካርታ መረጃ ወደ አንድ የተወሰነ የተጫነ BPF ፕሮግራም የሚተላለፈው በዚህ መንገድ ነው። በዚህ አጋጣሚ ካርታው በመጠቀም ሊፈጠር ይችላል BPF_MAP_CREATE፣ እና በመጠቀም በመታወቂያ የተከፈተ BPF_MAP_GET_FD_BY_ID.

ጠቅላላ, ሲጠቀሙ libbpf ስልተ ቀመር እንደሚከተለው ነው

  • በማጠናቀር ጊዜ መዝገቦች ወደ ካርታዎች ማገናኛዎች በመዘዋወር ሠንጠረዥ ውስጥ ይፈጠራሉ።
  • libbpf የ ELF ነገር መጽሐፍ ይከፍታል ፣ ሁሉንም ያገለገሉ ካርታዎች ያገኛል እና ለእነሱ የፋይል ገላጭዎችን ይፈጥራል
  • የፋይል ገላጭዎች እንደ መመሪያው አካል በከርነል ውስጥ ተጭነዋል LD64

እርስዎ ሊገምቱት እንደሚችሉት፣ ብዙ የሚመጣ ነገር አለ እና ዋናውን መመልከት አለብን። እንደ እድል ሆኖ, ፍንጭ አለን - ትርጉሙን ጽፈናል BPF_PSEUDO_MAP_FD ወደ ምንጭ መዝገብ ውስጥ ገብተን ልንቀብረው እንችላለን, ይህም ወደ ቅዱሳን ሁሉ ቅዱስ ይመራናል - kernel/bpf/verifier.cልዩ ስም ያለው ተግባር የፋይል ገላጭን በአይነት መዋቅር አድራሻ ሲተካ struct bpf_map:

static int replace_map_fd_with_map_ptr(struct bpf_verifier_env *env) {
    ...

    f = fdget(insn[0].imm);
    map = __bpf_map_get(f);
    if (insn->src_reg == BPF_PSEUDO_MAP_FD) {
        addr = (unsigned long)map;
    }
    insn[0].imm = (u32)addr;
    insn[1].imm = addr >> 32;

(ሙሉ ኮድ ማግኘት ይቻላል ማያያዣ). ስለዚህ የእኛን አልጎሪዝም ማስፋፋት እንችላለን-

  • ፕሮግራሙን በሚጭኑበት ጊዜ አረጋጋጭ የካርታውን ትክክለኛ አጠቃቀም ይፈትሻል እና የተዛማጁን መዋቅር አድራሻ ይጽፋል struct bpf_map

በመጠቀም የ ELF ሁለትዮሽ ሲያወርዱ libbpf ብዙ ተጨማሪ ነገሮች አሉ ነገርግን በሌሎች ጽሑፎች ውስጥ እንነጋገራለን.

ፕሮግራሞችን እና ካርታዎችን ያለ libbpf በመጫን ላይ

ቃል በገባነው መሰረት፣ ያለ እገዛ ካርታ የሚጠቀም ፕሮግራም እንዴት መፍጠር እና መጫን እንደሚችሉ ለማወቅ ለሚፈልጉ አንባቢዎች ምሳሌ እዚህ አለ። libbpf. ጥገኞችን መገንባት በማይችሉበት አካባቢ ውስጥ ሲሰሩ ወይም እያንዳንዱን ቁጠባ ወይም ፕሮግራም ሲጽፉ ይህ ጠቃሚ ሊሆን ይችላል plyበበረራ ላይ BPF ሁለትዮሽ ኮድ የሚያመነጨው.

አመክንዮውን ለመከተል ቀላል ለማድረግ, ለእነዚህ አላማዎች ምሳሌያችንን እንደገና እንጽፋለን xdp-simple. በዚህ ምሳሌ ውስጥ የተብራራው የፕሮግራሙ ሙሉ እና ትንሽ የተስፋፋ ኮድ በዚህ ውስጥ ሊገኝ ይችላል ቁም ነገር.

የኛ ማመልከቻ አመክንዮ የሚከተለው ነው።

  • ዓይነት ካርታ ይፍጠሩ BPF_MAP_TYPE_ARRAY ትዕዛዙን በመጠቀም BPF_MAP_CREATE,
  • ይህንን ካርታ የሚጠቀም ፕሮግራም መፍጠር ፣
  • ፕሮግራሙን ወደ በይነገጽ ያገናኙ lo,

ወደ ሰው የሚተረጎመው

int main(void)
{
    int map_fd, prog_fd;

    map_fd = map_create();
    if (map_fd < 0)
        err(1, "bpf: BPF_MAP_CREATE");

    prog_fd = prog_load(map_fd);
    if (prog_fd < 0)
        err(1, "bpf: BPF_PROG_LOAD");

    xdp_attach(1, prog_fd);
}

ይህ ነው map_create ስለ ስርዓቱ ጥሪ በመጀመሪያው ምሳሌ ላይ እንዳደረግነው በተመሳሳይ መንገድ ካርታ ይፈጥራል bpf - “ከርነል፣ እባክዎን እንደ 8 ንጥረ ነገሮች ድርድር አዲስ ካርታ ስሩኝ። __u64 እና የፋይሉን ገላጭ መልሱልኝ"

static int map_create()
{
    union bpf_attr attr;

    memset(&attr, 0, sizeof(attr));
    attr.map_type = BPF_MAP_TYPE_ARRAY,
    attr.key_size = sizeof(__u32),
    attr.value_size = sizeof(__u64),
    attr.max_entries = 8,
    strncpy(attr.map_name, "woo", sizeof(attr.map_name));
    return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
}

ፕሮግራሙ እንዲሁ ለመጫን ቀላል ነው-

static int prog_load(int map_fd)
{
    union bpf_attr attr;
    struct bpf_insn insns[] = {
        ...
    };

    memset(&attr, 0, sizeof(attr));
    attr.prog_type = BPF_PROG_TYPE_XDP;
    attr.insns     = ptr_to_u64(insns);
    attr.insn_cnt  = sizeof(insns)/sizeof(insns[0]);
    attr.license   = ptr_to_u64("GPL");
    strncpy(attr.prog_name, "woo", sizeof(attr.prog_name));
    return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
}

አስቸጋሪው ክፍል prog_load የ BPF ፕሮግራማችን እንደ የመዋቅር ድርድር ነው። struct bpf_insn insns[]. ነገር ግን በC ያለን ፕሮግራም እየተጠቀምን ስለሆነ ትንሽ ማጭበርበር እንችላለን፡-

$ llvm-objdump -D --section xdp/simple xdp-simple.bpf.o

0000000000000000 <simple>:
       0:       85 00 00 00 08 00 00 00 call 8
       1:       63 0a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r0
       2:       bf a2 00 00 00 00 00 00 r2 = r10
       3:       07 02 00 00 fc ff ff ff r2 += -4
       4:       18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll
       6:       85 00 00 00 01 00 00 00 call 1
       7:       b7 01 00 00 00 00 00 00 r1 = 0
       8:       15 00 04 00 00 00 00 00 if r0 == 0 goto +4 <LBB0_2>
       9:       61 01 00 00 00 00 00 00 r1 = *(u32 *)(r0 + 0)
      10:       07 01 00 00 01 00 00 00 r1 += 1
      11:       63 10 00 00 00 00 00 00 *(u32 *)(r0 + 0) = r1
      12:       b7 01 00 00 02 00 00 00 r1 = 2

0000000000000068 <LBB0_2>:
      13:       bf 10 00 00 00 00 00 00 r0 = r1
      14:       95 00 00 00 00 00 00 00 exit

በአጠቃላይ እንደ መዋቅሮች መልክ 14 መመሪያዎችን መጻፍ ያስፈልገናል struct bpf_insn (ምክር፡- ከላይ ያለውን ቆሻሻ ይውሰዱ, የመመሪያውን ክፍል እንደገና ያንብቡ, ይክፈቱ linux/bpf.h и linux/bpf_common.h እና ለመወሰን ይሞክሩ struct bpf_insn insns[] በራሱ):

struct bpf_insn insns[] = {
    /* 85 00 00 00 08 00 00 00 call 8 */
    {
        .code = BPF_JMP | BPF_CALL,
        .imm = 8,
    },

    /* 63 0a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r0 */
    {
        .code = BPF_MEM | BPF_STX,
        .off = -4,
        .src_reg = BPF_REG_0,
        .dst_reg = BPF_REG_10,
    },

    /* bf a2 00 00 00 00 00 00 r2 = r10 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_X,
        .src_reg = BPF_REG_10,
        .dst_reg = BPF_REG_2,
    },

    /* 07 02 00 00 fc ff ff ff r2 += -4 */
    {
        .code = BPF_ALU64 | BPF_ADD | BPF_K,
        .dst_reg = BPF_REG_2,
        .imm = -4,
    },

    /* 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll */
    {
        .code = BPF_LD | BPF_DW | BPF_IMM,
        .src_reg = BPF_PSEUDO_MAP_FD,
        .dst_reg = BPF_REG_1,
        .imm = map_fd,
    },
    { }, /* placeholder */

    /* 85 00 00 00 01 00 00 00 call 1 */
    {
        .code = BPF_JMP | BPF_CALL,
        .imm = 1,
    },

    /* b7 01 00 00 00 00 00 00 r1 = 0 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_K,
        .dst_reg = BPF_REG_1,
        .imm = 0,
    },

    /* 15 00 04 00 00 00 00 00 if r0 == 0 goto +4 <LBB0_2> */
    {
        .code = BPF_JMP | BPF_JEQ | BPF_K,
        .off = 4,
        .src_reg = BPF_REG_0,
        .imm = 0,
    },

    /* 61 01 00 00 00 00 00 00 r1 = *(u32 *)(r0 + 0) */
    {
        .code = BPF_MEM | BPF_LDX,
        .off = 0,
        .src_reg = BPF_REG_0,
        .dst_reg = BPF_REG_1,
    },

    /* 07 01 00 00 01 00 00 00 r1 += 1 */
    {
        .code = BPF_ALU64 | BPF_ADD | BPF_K,
        .dst_reg = BPF_REG_1,
        .imm = 1,
    },

    /* 63 10 00 00 00 00 00 00 *(u32 *)(r0 + 0) = r1 */
    {
        .code = BPF_MEM | BPF_STX,
        .src_reg = BPF_REG_1,
        .dst_reg = BPF_REG_0,
    },

    /* b7 01 00 00 02 00 00 00 r1 = 2 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_K,
        .dst_reg = BPF_REG_1,
        .imm = 2,
    },

    /* <LBB0_2>: bf 10 00 00 00 00 00 00 r0 = r1 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_X,
        .src_reg = BPF_REG_1,
        .dst_reg = BPF_REG_0,
    },

    /* 95 00 00 00 00 00 00 00 exit */
    {
        .code = BPF_JMP | BPF_EXIT
    },
};

ይህንን እራሳቸው ላልፃፉ ሰዎች መልመጃ - ያግኙ map_fd.

በፕሮግራማችን ውስጥ አንድ ተጨማሪ ያልተገለጸ ክፍል ቀርቷል - xdp_attach. እንደ አለመታደል ሆኖ እንደ XDP ያሉ ፕሮግራሞች የስርዓት ጥሪን በመጠቀም መገናኘት አይችሉም bpf. BPF እና XDPን የፈጠሩት ሰዎች ከኦንላይን ሊኑክስ ማህበረሰብ ናቸው፣ ይህ ማለት ለእነሱ በጣም የተለመዱትን ተጠቅመዋል ማለት ነው (ነገር ግን ለ የተለመደ ሰዎች) ከከርነል ጋር ለመግባባት በይነገጽ: netlink ሶኬቶች, ተመልከት RFC3549. ለመተግበር ቀላሉ መንገድ xdp_attach ኮድ ከ እየቀዳ ነው። libbpf, ማለትም ከፋይሉ netlink.cያደረግነው ነው፣ ትንሽ እያሳጠርነው፡-

እንኳን ወደ የnetlink ሶኬቶች አለም በደህና መጡ

የnetlink ሶኬት አይነት ይክፈቱ NETLINK_ROUTE:

int netlink_open(__u32 *nl_pid)
{
    struct sockaddr_nl sa;
    socklen_t addrlen;
    int one = 1, ret;
    int sock;

    memset(&sa, 0, sizeof(sa));
    sa.nl_family = AF_NETLINK;

    sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (sock < 0)
        err(1, "socket");

    if (setsockopt(sock, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)) < 0)
        warnx("netlink error reporting not supported");

    if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0)
        err(1, "bind");

    addrlen = sizeof(sa);
    if (getsockname(sock, (struct sockaddr *)&sa, &addrlen) < 0)
        err(1, "getsockname");

    *nl_pid = sa.nl_pid;
    return sock;
}

ከዚህ ሶኬት ላይ እናነባለን-

static int bpf_netlink_recv(int sock, __u32 nl_pid, int seq)
{
    bool multipart = true;
    struct nlmsgerr *errm;
    struct nlmsghdr *nh;
    char buf[4096];
    int len, ret;

    while (multipart) {
        multipart = false;
        len = recv(sock, buf, sizeof(buf), 0);
        if (len < 0)
            err(1, "recv");

        if (len == 0)
            break;

        for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, len);
                nh = NLMSG_NEXT(nh, len)) {
            if (nh->nlmsg_pid != nl_pid)
                errx(1, "wrong pid");
            if (nh->nlmsg_seq != seq)
                errx(1, "INVSEQ");
            if (nh->nlmsg_flags & NLM_F_MULTI)
                multipart = true;
            switch (nh->nlmsg_type) {
                case NLMSG_ERROR:
                    errm = (struct nlmsgerr *)NLMSG_DATA(nh);
                    if (!errm->error)
                        continue;
                    ret = errm->error;
                    // libbpf_nla_dump_errormsg(nh); too many code to copy...
                    goto done;
                case NLMSG_DONE:
                    return 0;
                default:
                    break;
            }
        }
    }
    ret = 0;
done:
    return ret;
}

በመጨረሻም፣ ሶኬት የሚከፍት እና የፋይል ገላጭ የያዘ ልዩ መልእክት የሚልክለት ተግባራችን ይህ ነው።

static int xdp_attach(int ifindex, int prog_fd)
{
    int sock, seq = 0, ret;
    struct nlattr *nla, *nla_xdp;
    struct {
        struct nlmsghdr  nh;
        struct ifinfomsg ifinfo;
        char             attrbuf[64];
    } req;
    __u32 nl_pid = 0;

    sock = netlink_open(&nl_pid);
    if (sock < 0)
        return sock;

    memset(&req, 0, sizeof(req));
    req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
    req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
    req.nh.nlmsg_type = RTM_SETLINK;
    req.nh.nlmsg_pid = 0;
    req.nh.nlmsg_seq = ++seq;
    req.ifinfo.ifi_family = AF_UNSPEC;
    req.ifinfo.ifi_index = ifindex;

    /* started nested attribute for XDP */
    nla = (struct nlattr *)(((char *)&req)
            + NLMSG_ALIGN(req.nh.nlmsg_len));
    nla->nla_type = NLA_F_NESTED | IFLA_XDP;
    nla->nla_len = NLA_HDRLEN;

    /* add XDP fd */
    nla_xdp = (struct nlattr *)((char *)nla + nla->nla_len);
    nla_xdp->nla_type = IFLA_XDP_FD;
    nla_xdp->nla_len = NLA_HDRLEN + sizeof(int);
    memcpy((char *)nla_xdp + NLA_HDRLEN, &prog_fd, sizeof(prog_fd));
    nla->nla_len += nla_xdp->nla_len;

    /* if user passed in any flags, add those too */
    __u32 flags = XDP_FLAGS_SKB_MODE;
    nla_xdp = (struct nlattr *)((char *)nla + nla->nla_len);
    nla_xdp->nla_type = IFLA_XDP_FLAGS;
    nla_xdp->nla_len = NLA_HDRLEN + sizeof(flags);
    memcpy((char *)nla_xdp + NLA_HDRLEN, &flags, sizeof(flags));
    nla->nla_len += nla_xdp->nla_len;

    req.nh.nlmsg_len += NLA_ALIGN(nla->nla_len);

    if (send(sock, &req, req.nh.nlmsg_len, 0) < 0)
        err(1, "send");
    ret = bpf_netlink_recv(sock, nl_pid, seq);

cleanup:
    close(sock);
    return ret;
}

ስለዚህ ፣ ሁሉም ነገር ለሙከራ ዝግጁ ነው-

$ cc nolibbpf.c -o nolibbpf
$ sudo strace -e bpf ./nolibbpf
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_ARRAY, map_name="woo", ...}, 72) = 3
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, insn_cnt=15, prog_name="woo", ...}, 72) = 4
+++ exited with 0 +++

ፕሮግራማችን የተገናኘ መሆኑን እንይ lo:

$ ip l show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    prog/xdp id 160

ፒንግ እንልክና ካርታውን እንይ፡

$ for s in `seq 234`; do sudo ping -f -c 100 127.0.0.1 >/dev/null 2>&1; done
$ sudo bpftool m dump name woo
key: 00 00 00 00  value: 90 01 00 00 00 00 00 00
key: 01 00 00 00  value: 00 00 00 00 00 00 00 00
key: 02 00 00 00  value: 00 00 00 00 00 00 00 00
key: 03 00 00 00  value: 00 00 00 00 00 00 00 00
key: 04 00 00 00  value: 00 00 00 00 00 00 00 00
key: 05 00 00 00  value: 00 00 00 00 00 00 00 00
key: 06 00 00 00  value: 40 b5 00 00 00 00 00 00
key: 07 00 00 00  value: 00 00 00 00 00 00 00 00
Found 8 elements

ሁሬ ፣ ሁሉም ነገር ይሰራል። በነገራችን ላይ የእኛ ካርታ እንደገና በባይት መልክ መታየቱን ልብ ይበሉ። ይህ በተለየ እውነታ ምክንያት ነው libbpf ዓይነት መረጃ (BTF) አልጫንንም። ግን በሚቀጥለው ጊዜ ስለዚህ ጉዳይ የበለጠ እንነጋገራለን.

የልማት መሳሪያዎች

በዚህ ክፍል ዝቅተኛውን የBPF ገንቢ መሣሪያ ስብስብ እንመለከታለን።

በአጠቃላይ የ BPF ፕሮግራሞችን ለማዘጋጀት ምንም ልዩ ነገር አያስፈልግዎትም - BPF በማንኛውም ጥሩ የስርጭት ከርነል ይሰራል፣ እና ፕሮግራሞች የሚገነቡት በመጠቀም ነው። clang, ከጥቅሉ ሊቀርብ የሚችል. ሆኖም ግን, BPF በመገንባት ላይ ስለሆነ, ከርነል እና መሳሪያዎች በየጊዜው እየተለዋወጡ ነው, ከ 2019 ጀምሮ የቆዩ ዘዴዎችን በመጠቀም የ BPF ፕሮግራሞችን መጻፍ ካልፈለጉ, ከዚያም ማጠናቀር ይኖርብዎታል.

  • llvm/clang
  • pahole
  • አንኳርነቱ
  • bpftool

(ለማጣቀሻ፣ ይህ ክፍል እና በአንቀጹ ውስጥ ያሉት ሁሉም ምሳሌዎች በዴቢያን 10 ላይ ተካሂደዋል።)

lvm/clang

BPF ከኤልኤልቪኤም ጋር ወዳጃዊ ነው እና ምንም እንኳን በቅርብ ጊዜ የ BPF ፕሮግራሞች gccን በመጠቀም ሊጣመሩ ቢችሉም ሁሉም ወቅታዊ ልማት ለኤልኤልቪኤም ነው የሚከናወነው። ስለዚህ, በመጀመሪያ, የአሁኑን ስሪት እንገነባለን clang ከጂት፡

$ sudo apt install ninja-build
$ git clone --depth 1 https://github.com/llvm/llvm-project.git
$ mkdir -p llvm-project/llvm/build/install
$ cd llvm-project/llvm/build
$ cmake .. -G "Ninja" -DLLVM_TARGETS_TO_BUILD="BPF;X86" 
                      -DLLVM_ENABLE_PROJECTS="clang" 
                      -DBUILD_SHARED_LIBS=OFF 
                      -DCMAKE_BUILD_TYPE=Release 
                      -DLLVM_BUILD_RUNTIME=OFF
$ time ninja
... много времени спустя
$

አሁን ሁሉም ነገር በትክክል መገናኘቱን ማረጋገጥ እንችላለን-

$ ./bin/llc --version
LLVM (http://llvm.org/):
  LLVM version 11.0.0git
  Optimized build.
  Default target: x86_64-unknown-linux-gnu
  Host CPU: znver1

  Registered Targets:
    bpf    - BPF (host endian)
    bpfeb  - BPF (big endian)
    bpfel  - BPF (little endian)
    x86    - 32-bit X86: Pentium-Pro and above
    x86-64 - 64-bit X86: EM64T and AMD64

(የስብሰባ መመሪያዎች clang በእኔ የተወሰደ bpf_devel_QA.)

አሁን የገነባናቸው ፕሮግራሞችን አንጭናቸውም፣ ይልቁንስ እንጨምራቸው PATH, ለምሳሌ:

export PATH="`pwd`/bin:$PATH"

(ይህ ሊጨመር ይችላል .bashrc ወይም ወደተለየ ፋይል። በግሌ እንደዚህ አይነት ነገሮችን እጨምራለሁ ~/bin/activate-llvm.sh እና አስፈላጊ ሆኖ ሲገኝ አደርገዋለሁ . activate-llvm.sh.)

Pahole እና BTF

መገልገያ pahole በ BTF ቅርጸት የማረም መረጃን ለመፍጠር ከርነል በሚገነቡበት ጊዜ ጥቅም ላይ ይውላል። በዚህ ጽሑፍ ውስጥ ስለ BTF ቴክኖሎጂ ዝርዝሮች በዝርዝር አንናገርም, ምቹ እና ልንጠቀምበት የምንፈልገው እውነታ ካልሆነ በስተቀር. ስለዚህ ከርነልዎን ሊገነቡ ከሆነ መጀመሪያ ይገንቡ pahole (ያለ pahole ከምርጫው ጋር ኮርነሉን መገንባት አይችሉም CONFIG_DEBUG_INFO_BTF:

$ git clone https://git.kernel.org/pub/scm/devel/pahole/pahole.git
$ cd pahole/
$ sudo apt install cmake
$ mkdir build
$ cd build/
$ cmake -D__LIB=lib ..
$ make
$ sudo make install
$ which pahole
/usr/local/bin/pahole

ከ BPF ጋር ለመሞከር ከርነሎች

የBPF ዕድሎችን ስመረምር የራሴን ዋና ነገር መሰብሰብ እፈልጋለሁ። ይህ በአጠቃላይ አነጋገር አስፈላጊ አይደለም, ምክንያቱም የ BPF ፕሮግራሞችን በማከፋፈያው ከርነል ላይ ማሰባሰብ እና መጫን ይችላሉ, ነገር ግን የእራስዎ ከርነል መኖሩ የቅርብ ጊዜውን የ BPF ባህሪያትን ለመጠቀም ያስችላል, ይህም በወራት ውስጥ በተሻለ ሁኔታ በስርጭትዎ ውስጥ ይታያል. , ወይም እንደ አንዳንድ የማረሚያ መሳሪያዎች ለወደፊቱ በጭራሽ አይታሸጉም. እንዲሁም የራሱ ኮር ከኮዱ ጋር መሞከር አስፈላጊ ሆኖ እንዲሰማው ያደርጋል.

ከርነል ለመገንባት በመጀመሪያ ፣ ኮርነሉ ራሱ ፣ እና ሁለተኛ ፣ የከርነል ውቅር ፋይል ያስፈልግዎታል። ከ BPF ጋር ለመሞከር የተለመደውን መጠቀም እንችላለን ቫላ ከርነል ወይም ከልማት አስኳሎች አንዱ። ከታሪክ አኳያ የBPF ልማት የሚከናወነው በሊኑክስ አውታረመረብ ማህበረሰብ ውስጥ ነው እናም ሁሉም ለውጦች ይዋል ይደር እንጂ በሊኑክስ አውታረመረብ ጠባቂው ዴቪድ ሚለር በኩል ያልፋሉ። እንደ ተፈጥሮአቸው - አርትዖቶች ወይም አዲስ ባህሪያት - የአውታረ መረብ ለውጦች ከሁለት ኮሮች በአንዱ ውስጥ ይወድቃሉ - net ወይም net-next. የ BPF ለውጦች በመካከላቸው በተመሳሳይ መንገድ ይሰራጫሉ። bpf и bpf-next, ከዚያም እንደ ቅደም ተከተላቸው ወደ ኔት እና ኔት-ቀጣይ ይጣመራሉ. ለተጨማሪ ዝርዝሮች, ይመልከቱ bpf_devel_QA и netdev-FAQ. ስለዚህ እንደ ጣዕምዎ እና እየሞከሩበት ባለው የስርዓት ፍላጎቶች ላይ በመመስረት አስኳል ይምረጡ (*-next ከተዘረዘሩት ውስጥ አስኳሎች በጣም ያልተረጋጉ ናቸው)።

የከርነል ውቅር ፋይሎችን እንዴት ማስተዳደር እንደሚቻል ለመነጋገር ከዚህ ጽሑፍ ወሰን በላይ ነው - ይህንን እንዴት ማድረግ እንዳለብዎ አስቀድመው ያውቃሉ ፣ ወይም ለመማር ዝግጁ በራሱ። ነገር ግን፣ የሚከተሉት መመሪያዎች ለቢፒኤፍ የነቃ ስርዓት ለእርስዎ ለመስጠት ብዙ ወይም ያነሰ በቂ መሆን አለባቸው።

ከላይ ካሉት አስኳሎች አንዱን አውርድ፡-

$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git
$ cd bpf-next

አነስተኛ የሚሰራ የከርነል ውቅር ይገንቡ፡

$ cp /boot/config-`uname -r` .config
$ make localmodconfig

በፋይል ውስጥ የ BPF አማራጮችን አንቃ .config የእራስዎ ምርጫ (በጣም ሊሆን ይችላል CONFIG_BPF systemd ስለሚጠቀምበት አስቀድሞ ይነቃል። ለዚህ ጽሑፍ ጥቅም ላይ የዋለው የከርነል አማራጮች ዝርዝር ይኸውና፡-

CONFIG_CGROUP_BPF=y
CONFIG_BPF=y
CONFIG_BPF_LSM=y
CONFIG_BPF_SYSCALL=y
CONFIG_ARCH_WANT_DEFAULT_BPF_JIT=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_JIT_DEFAULT_ON=y
CONFIG_IPV6_SEG6_BPF=y
# CONFIG_NETFILTER_XT_MATCH_BPF is not set
# CONFIG_BPFILTER is not set
CONFIG_NET_CLS_BPF=y
CONFIG_NET_ACT_BPF=y
CONFIG_BPF_JIT=y
CONFIG_BPF_STREAM_PARSER=y
CONFIG_LWTUNNEL_BPF=y
CONFIG_HAVE_EBPF_JIT=y
CONFIG_BPF_EVENTS=y
CONFIG_BPF_KPROBE_OVERRIDE=y
CONFIG_DEBUG_INFO_BTF=y

ከዚያ በቀላሉ ሞጁሎችን እና ከርነሉን በቀላሉ ሰብስበን መጫን እንችላለን (በነገራችን ላይ አዲስ የተሰበሰበውን በመጠቀም ፍሬውን መሰብሰብ ይችላሉ) clangበማከል CC=clang):

$ make -s -j $(getconf _NPROCESSORS_ONLN)
$ sudo make modules_install
$ sudo make install

እና በአዲሱ ከርነል ዳግም አስነሳ (ለዚህ እጠቀማለሁ። kexec ከጥቅሉ kexec-tools):

v=5.8.0-rc6+ # если вы пересобираете текущее ядро, то можно делать v=`uname -r`
sudo kexec -l -t bzImage /boot/vmlinuz-$v --initrd=/boot/initrd.img-$v --reuse-cmdline &&
sudo kexec -e

bpftool

በአንቀጹ ውስጥ በብዛት ጥቅም ላይ የዋለው መገልገያ መገልገያው ይሆናል bpftool፣ እንደ ሊኑክስ ከርነል አካል ሆኖ የቀረበ። በ BPF ገንቢዎች ለ BPF ገንቢዎች የተጻፈ እና የሚቆይ እና ሁሉንም አይነት BPF ነገሮችን ለማስተዳደር - ፕሮግራሞችን መጫን ፣ ካርታዎችን መፍጠር እና ማረም ፣ የ BPF ሥነ-ምህዳርን ሕይወት ማሰስ ፣ ወዘተ. ለሰው ገፆች ምንጭ ኮድ መልክ ሰነዶች ሊገኙ ይችላሉ። በዋና ውስጥ ወይም አስቀድሞ የተጠናቀረ መረብ ላይ.

ይህ ጽሑፍ በተጻፈበት ጊዜ bpftool ለ RHEL፣ Fedora እና Ubuntu ብቻ ዝግጁ ሆኖ ይመጣል (ለምሳሌ ይመልከቱ፣ ይህ ክርያልጨረሰውን የማሸግ ታሪክ የሚናገረው bpftool በዴቢያን)። ግን ከርነልዎን አስቀድመው ከገነቡት ከዚያ ይገንቡ bpftool እንደ ኬክ ቀላል;

$ cd ${linux}/tools/bpf/bpftool
# ... пропишите пути к последнему clang, как рассказано выше
$ make -s

Auto-detecting system features:
...                        libbfd: [ on  ]
...        disassembler-four-args: [ on  ]
...                          zlib: [ on  ]
...                        libcap: [ on  ]
...               clang-bpf-co-re: [ on  ]

Auto-detecting system features:
...                        libelf: [ on  ]
...                          zlib: [ on  ]
...                           bpf: [ on  ]

$

( እዚህ ${linux} - ይህ የእርስዎ የከርነል ማውጫ ነው።) እነዚህን ትዕዛዞች ከፈጸሙ በኋላ bpftool በማውጫ ውስጥ ይሰበሰባል ${linux}/tools/bpf/bpftool እና በመንገዱ ላይ መጨመር ይቻላል (በመጀመሪያ ለተጠቃሚው root) ወይም ወደ ብቻ ይቅዱ /usr/local/sbin.

ሰብስብ bpftool የመጨረሻውን መጠቀም ጥሩ ነው clang, ከላይ እንደተገለፀው ተሰብስቦ እና በትክክል መገጣጠሙን ያረጋግጡ - ለምሳሌ ትዕዛዙን በመጠቀም

$ sudo bpftool feature probe kernel
Scanning system configuration...
bpf() syscall for unprivileged users is enabled
JIT compiler is enabled
JIT compiler hardening is disabled
JIT compiler kallsyms exports are enabled for root
...

የትኞቹ የ BPF ባህሪያት በእርስዎ ከርነል ውስጥ እንደነቁ ያሳያል።

በነገራችን ላይ የቀደመው ትዕዛዝ እንደ ሊሰራ ይችላል

# bpftool f p k

ይህ የሚደረገው ከጥቅሉ ውስጥ ከሚገኙ መገልገያዎች ጋር በማመሳሰል ነው iproute2ለምሳሌ ልንል የምንችልበት ቦታ ip a s eth0ip addr show dev eth0.

መደምደሚያ

BPF ውጤታማ በሆነ መንገድ ለመለካት እና በበረራ ላይ የኮርን ተግባራዊነት ለመለወጥ ቁንጫ ጫማ እንድታደርግ ይፈቅድልሃል። ስርዓቱ በጣም የተሳካ ሆኖ ተገኝቷል ፣ በ UNIX ምርጥ ወጎች ውስጥ - የከርነል ፕሮግራምን እንደገና እንዲያዘጋጁ የሚያስችልዎ ቀላል ዘዴ ብዙ ሰዎች እና ድርጅቶች እንዲሞክሩ አስችሏል። እና ምንም እንኳን ሙከራዎቹ እና የ BPF መሠረተ ልማት እድገታቸው ገና ብዙ ቢሆኑም ስርዓቱ አስተማማኝ እና ከሁሉም በላይ ውጤታማ የንግድ ሥራ አመክንዮ እንዲገነቡ የሚያስችልዎ የተረጋጋ ABI አለው።

በእኔ አስተያየት ቴክኖሎጂው በጣም ተወዳጅ እየሆነ መጥቷል ምክንያቱም በአንድ በኩል ጥቅም ላይ ሊውል እንደሚችል ልብ ማለት እፈልጋለሁ. ተጫወት (የማሽን አርክቴክቸር በአንድ ምሽት ብዙ ወይም ያነሰ ሊረዳ ይችላል) እና በሌላ በኩል ደግሞ ከመታየቱ በፊት ሊፈቱ ያልቻሉ ችግሮችን ለመፍታት (በሚያምር)። እነዚህ ሁለት አካላት አንድ ላይ ሰዎች እንዲሞክሩ እና እንዲያልሙ ያስገድዷቸዋል, ይህም ብዙ እና ብዙ አዳዲስ መፍትሄዎች እንዲፈጠሩ ያደርጋል.

ይህ ጽሑፍ ምንም እንኳን በተለይ አጭር ባይሆንም ለ BPF ዓለም መግቢያ ብቻ ነው እና "የላቁ" ባህሪያትን እና የሕንፃውን አስፈላጊ ክፍሎች አይገልጽም. ወደፊት የሚሄደው እቅድ ይህን ይመስላል፡ የሚቀጥለው መጣጥፍ የ BPF ፕሮግራም አይነቶች አጠቃላይ እይታ ይሆናል (በ 5.8 ከርነል ውስጥ የሚደገፉ 30 የፕሮግራም አይነቶች አሉ) በመጨረሻ የከርነል መፈለጊያ ፕሮግራሞችን በመጠቀም እውነተኛ የ BPF አፕሊኬሽኖችን እንዴት መፃፍ እንደምንችል እንመለከታለን። እንደ ምሳሌ፣ ከዚያም የBPF አውታረ መረብ እና የደህንነት አፕሊኬሽኖች ምሳሌዎችን በመከተል ስለ BPF architecture ለበለጠ ጥልቀት ኮርስ ጊዜው አሁን ነው።

በዚህ ተከታታይ ውስጥ ያለፉ መጣጥፎች

  1. BPF ለትናንሾቹ፣ ክፍል ዜሮ፡ ክላሲክ BPF

አገናኞች

  1. BPF እና XDP የማጣቀሻ መመሪያ - በ BPF ላይ ከሲሊየም ወይም በትክክል ከ BPF ፈጣሪዎች እና ጠባቂዎች አንዱ የሆነው ዳንኤል ቦርክማን የተገኘ ሰነድ። ይህ ከመጀመሪያዎቹ ከባድ መግለጫዎች አንዱ ነው, እሱም ከሌሎቹ የሚለየው ዳንኤል ስለ ጽፏል በትክክል ስለሚያውቅ እና እዚያ ምንም ስህተቶች የሉም. በተለይም ይህ ሰነድ ታዋቂውን መገልገያ በመጠቀም ከ XDP እና TC ዓይነቶች ከ BPF ፕሮግራሞች ጋር እንዴት እንደሚሰራ ይገልጻል ip ከጥቅሉ iproute2.

  2. Documentation/networking/filter.txt - ኦሪጅናል ፋይል ለጥንታዊ እና ከዚያም የተራዘመ BPF። ወደ መሰብሰቢያ ቋንቋ እና ቴክኒካል አርክቴክቸር ዝርዝሮች በጥልቀት ለመፈተሽ ከፈለጉ ጥሩ ንባብ።

  3. ስለ BPF ከፌስቡክ ብሎግ. በጣም አልፎ አልፎ ነው የሚዘመነው፣ ግን በትክክል፣ አሌክሲ ስታሮቮይቶቭ (የ eBPF ደራሲ) እና አንድሪ ናክሪይኮ - (ጠባቂ) እዚያ እንደሚጽፉ። libbpf).

  4. የ bpftool ምስጢሮች. ከ Quentin Monnet የ bpftool አጠቃቀም ምሳሌዎች እና ሚስጥሮች ያለው አዝናኝ የትዊተር ክር።

  5. ወደ BPF ይዝለሉ፡ የንባብ ቁሳቁስ ዝርዝር. ከኩዌንቲን ሞኔት ወደ BPF ሰነድ የሚወስዱ ግዙፍ (እና አሁንም የሚቆይ) አገናኞች ዝርዝር።

ምንጭ: hab.com

አስተያየት ያክሉ