කුඩා ළමුන් සඳහා BPF, පළමු කොටස: දීර්ඝ BPF

මුලදී තාක්ෂණයක් තිබුනා එය BPF ලෙස හැඳින්වේ. අපි ඇය දෙස බැලුවෙමු පෙර, මෙම ලිපි මාලාවේ පැරණි ගිවිසුමේ ලිපිය. 2013 දී, Alexei Starovoitov සහ Daniel Borkman ගේ උත්සාහයෙන්, නවීන 64-bit යන්ත්‍ර සඳහා ප්‍රශස්තිකරණය කරන ලද එහි වැඩිදියුණු කළ අනුවාදයක් සංවර්ධනය කර ලිනක්ස් කර්නලයට ඇතුළත් කරන ලදී. මෙම නව තාක්‍ෂණය කෙටියෙන් අභ්‍යන්තර BPF ලෙස හැඳින්වූ අතර පසුව එය Extended BPF ලෙස නම් කරන ලද අතර දැන්, වසර කිහිපයකට පසු, සෑම කෙනෙකුම එය සරලව BPF ලෙස හඳුන්වයි.

දළ වශයෙන්, BPF ඔබට ලිනක්ස් කර්නල් අවකාශයේ අත්තනෝමතික පරිශීලක-සපයන ලද කේතය ධාවනය කිරීමට ඉඩ සලසයි, සහ නව ගෘහ නිර්මාණ ශිල්පය කෙතරම් සාර්ථක වීද යත්, එහි සියලුම යෙදුම් විස්තර කිරීමට අපට තවත් ලිපි දුසිමක් අවශ්‍ය වනු ඇත. (පහත කාර්ය සාධන කේතයෙන් ඔබට පෙනෙන පරිදි සංවර්ධකයින් හොඳින් නොකළ එකම දෙය හොඳ ලාංඡනයක් නිර්මාණය කිරීමයි.)

මෙම ලිපිය BPF අතථ්‍ය යන්ත්‍රයේ ව්‍යුහය, BPF සමඟ වැඩ කිරීම සඳහා කර්නල් අතුරුමුහුණත්, සංවර්ධන මෙවලම් මෙන්ම පවතින හැකියාවන් පිළිබඳ කෙටි, ඉතා කෙටි දළ විශ්ලේෂණයක් විස්තර කරයි, i.e. 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-bit යන්ත්‍ර, වලාකුළු සේවා සහ SDN නිර්මාණය කිරීම සඳහා මෙවලම් සඳහා වැඩි අවශ්‍යතාවයට ප්‍රතිචාරයක් ලෙස ය.Sබොහෝ විට -dනිර්වචනය කර ඇත nවැඩ කිරීම). සම්භාව්‍ය BPF සඳහා වැඩි දියුණු කළ ආදේශකයක් ලෙස කර්නල් ජාල ඉංජිනේරුවන් විසින් වැඩි දියුණු කරන ලද, නව BPF වචනාර්ථයෙන් මාස හයකට පසුව Linux පද්ධති සොයා ගැනීමේ දුෂ්කර කාර්යයේ යෙදුම් සොයා ගත් අතර, දැන්, එහි පෙනුමෙන් වසර හයකට පසු, අපට සම්පූර්ණ ඊළඟ ලිපියක් අවශ්‍ය වනු ඇත. විවිධ වර්ගයේ වැඩසටහන් ලැයිස්තුගත කරන්න.

විහිලු පින්තූර

එහි හරය, BPF යනු සෑන්ඩ්බොක්ස් අතථ්‍ය යන්ත්‍රයක් වන අතර එය ආරක්ෂාවට බාධාවක් නොවන පරිදි කර්නල් අවකාශයේ “අත්තනෝමතික” කේතය ක්‍රියාත්මක කිරීමට ඔබට ඉඩ සලසයි. BPF වැඩසටහන් පරිශීලක අවකාශය තුළ නිර්මාණය කර, කර්නලය තුළට පටවා, යම් සිද්ධි මූලාශ්‍රයකට සම්බන්ධ කර ඇත. සිදුවීමක්, උදාහරණයක් ලෙස, ජාල අතුරුමුහුණතකට පැකට්ටුවක් ලබා දීම, සමහර කර්නල් ශ්‍රිතයක් දියත් කිරීම යනාදිය විය හැකිය. පැකේජයක් සම්බන්ධයෙන්, BPF වැඩසටහනට පැකේජයේ දත්ත සහ පාර-දත්ත වෙත ප්‍රවේශය ඇත (කියවීමට සහ, සමහර විට, ලිවීමට, වැඩසටහනේ වර්ගය අනුව); කර්නල් ශ්‍රිතයක් ක්‍රියාත්මක කිරීමේදී, තර්ක කර්නල් මතකයට පොයින්ටර් ඇතුළු ශ්‍රිතය යනාදිය.

මෙම ක්රියාවලිය දෙස සමීපව බලමු. ආරම්භ කිරීම සඳහා, සම්භාව්‍ය BPF වෙතින් පළමු වෙනස ගැන කතා කරමු, ඒ සඳහා වැඩසටහන් එකලස් කරන්නා තුළ ලියා ඇත. නව අනුවාදයේ, වාස්තු විද්‍යාව පුළුල් කරන ලද අතර එමඟින් වැඩසටහන් ඉහළ මට්ටමේ භාෂාවලින් ලිවිය හැකිය, මූලික වශයෙන්, ඇත්ත වශයෙන්ම, C. මේ සඳහා, llvm සඳහා පසුබිමක් සකස් කරන ලදී, එමඟින් BPF ගෘහ නිර්මාණ ශිල්පය සඳහා බයිට්කේත ජනනය කිරීමට ඉඩ සලසයි.

කුඩා ළමුන් සඳහා BPF, පළමු කොටස: දීර්ඝ BPF

BPF ගෘහනිර්මාණ ශිල්පය සැලසුම් කර ඇත්තේ, නවීන යන්ත්‍ර මත කාර්යක්ෂමව ක්‍රියාත්මක වීමටය. මෙය ප්‍රායෝගිකව ක්‍රියාත්මක කිරීම සඳහා, BPF බයිට්කේතය, කර්නලයට පූරණය කළ පසු, JIT සම්පාදකයක් ලෙස හැඳින්වෙන සංරචකයක් භාවිතයෙන් ස්වදේශීය කේතයට පරිවර්තනය කරනු ලැබේ (Just In Time). ඊළඟට, ඔබට මතක නම්, සම්භාව්‍ය BPF හි වැඩසටහන කර්නලයට පටවා ඇති අතර සිදුවීම් ප්‍රභවයට පරමාණුක වශයෙන් අමුණා ඇත - තනි පද්ධති ඇමතුමක සන්දර්භය තුළ. නව ගෘහ නිර්මාණ ශිල්පය තුළ, මෙය අදියර දෙකකින් සිදු වේ - පළමුව, පද්ධතිය ඇමතුමක් භාවිතයෙන් කේතය කර්නලය තුළට පටවනු ලැබේ. bpf(2)පසුව, පසුව, වැඩසටහනේ වර්ගය අනුව වෙනස් වන වෙනත් යාන්ත්‍රණ හරහා, වැඩසටහන සිදුවීම් මූලාශ්‍රයට අනුයුක්ත කරයි.

මෙහිදී පාඨකයාට ප්රශ්නයක් තිබිය හැකිය: එය කළ හැකිද? එවැනි කේතයක් ක්රියාත්මක කිරීමේ ආරක්ෂාව සහතික කරන්නේ කෙසේද? verifier ලෙස හඳුන්වන BPF වැඩසටහන් පූරණය කිරීමේ අදියර මගින් ක්‍රියාත්මක කිරීමේ ආරක්ෂාව අපට සහතික කෙරේ (ඉංග්‍රීසියෙන් මෙම අදියර verifier ලෙස හැඳින්වේ, මම දිගටම ඉංග්‍රීසි වචනය භාවිතා කරමි):

කුඩා ළමුන් සඳහා BPF, පළමු කොටස: දීර්ඝ BPF

Verifier යනු ස්ථිතික විශ්ලේෂකයක් වන අතර එය වැඩසටහනක් කර්නලයේ සාමාන්‍ය ක්‍රියාකාරිත්වයට බාධා නොකරන බව සහතික කරයි. මාර්ගය වන විට, වැඩසටහනට පද්ධතියේ ක්‍රියාකාරිත්වයට බාධා කළ නොහැකි බව මින් අදහස් නොවේ - BPF වැඩසටහන්, වර්ගය අනුව, කර්නල් මතකයේ කොටස් කියවීමට සහ නැවත ලිවීමට, ශ්‍රිතවල අගයන් ආපසු ලබා දීමට, කප්පාදු කිරීමට, එකතු කිරීමට, නැවත ලිවීමට හැකිය. සහ ඉදිරි ජාල පැකට් පවා. BPF වැඩසටහනක් ක්‍රියාත්මක කිරීමෙන් කර්නලය බිඳ වැටෙන්නේ නැති බවත්, රීතිවලට අනුව ලිවීමට ප්‍රවේශය ඇති වැඩසටහනකට, උදාහරණයක් ලෙස, පිටතට යන පැකට්ටුවක දත්ත, පැකට්ටුවෙන් පිටත කර්නල් මතකය නැවත ලිවීමට නොහැකි බවත් Verifier සහතික කරයි. අපි BPF හි අනෙකුත් සියලුම සංරචක සමඟ දැන හඳුනා ගැනීමෙන් පසුව, අපි අනුරූප කොටසේ තව ටිකක් සවිස්තරාත්මකව Verifier දෙස බලමු.

ඉතින් අපි මෙතෙක් ඉගෙන ගෙන ඇත්තේ කුමක්ද? පරිශීලකයා C වලින් වැඩසටහනක් ලියයි, එය පද්ධති ඇමතුමක් භාවිතයෙන් කර්නලයට පූරණය කරයි bpf(2), එහිදී එය සත්‍යාපනය කරන්නෙකු විසින් පරීක්ෂා කර ස්වදේශීය බයිට්කේතයට පරිවර්තනය කරයි. එවිට එම හෝ වෙනත් පරිශීලකයෙකු විසින් වැඩසටහන සිදුවීම් මූලාශ්‍රයට සම්බන්ධ කරන අතර එය ක්‍රියාත්මක වීමට පටන් ගනී. හේතු කිහිපයක් නිසා ආරම්භය සහ සම්බන්ධතාවය වෙන් කිරීම අවශ්ය වේ. පළමුව, verifier ධාවනය කිරීම සාපේක්ෂව මිල අධික වන අතර එකම වැඩසටහන කිහිප වතාවක් බාගත කිරීමෙන් අපි පරිගණකයේ කාලය නාස්ති කරමු. දෙවනුව, වැඩසටහනක් සම්බන්ධ වන්නේ කෙසේද යන්න එහි වර්ගය මත රඳා පවතින අතර, වසරකට පෙර සංවර්ධනය කරන ලද එක් "විශ්වීය" අතුරු මුහුණතක් නව ආකාරයේ වැඩසටහන් සඳහා සුදුසු නොවේ. (දැන් ගෘහ නිර්මාණ ශිල්පය වඩාත් පරිණත වෙමින් පවතින නමුත්, මෙම අතුරු මුහුණත මට්ටමින් ඒකාබද්ධ කිරීමට අදහසක් ඇත. libbpf.)

අප තවමත් පින්තූර සමඟ අවසන් නැති බව අවධානයෙන් සිටින පාඨකයාට වැටහෙනු ඇත. ඇත්ත වශයෙන්ම, සම්භාව්‍ය BPF හා සසඳන විට BPF මූලික වශයෙන් පින්තූරය වෙනස් කරන්නේ මන්දැයි ඉහත සියල්ල පැහැදිලි නොකරයි. යෙදුමේ විෂය පථය සැලකිය යුතු ලෙස පුළුල් කරන නවෝත්පාදන දෙකක් වන්නේ හවුල් මතකය සහ කර්නල් උපකාරක කාර්යයන් භාවිතා කිරීමේ හැකියාවයි. BPF හි, හවුල් මතකය ක්‍රියාත්මක කරනු ලබන්නේ ඊනියා සිතියම් - විශේෂිත API සමඟ බෙදාගත් දත්ත ව්‍යුහයන් භාවිතා කරමිනි. ඔවුන්ට මෙම නම ලැබී ඇත්තේ දිස්වන පළමු වර්ගයේ සිතියම හැෂ් වගුවක් නිසා විය හැකිය. එවිට අරා දිස් විය, දේශීය (CPU එකකට) හැෂ් වගු සහ දේශීය අරා, සෙවුම් ගස්, BPF වැඩසටහන් සඳහා පොයින්ටර් අඩංගු සිතියම් සහ තවත් බොහෝ දේ. දැන් අපට සිත්ගන්නා කරුණ නම්, BPF වැඩසටහන් වලට දැන් ඇමතුම් අතර පැවතිය හැකි අතර එය වෙනත් වැඩසටහන් සමඟ සහ පරිශීලක අවකාශය සමඟ බෙදාගැනීමේ හැකියාව ඇත.

පද්ධති ඇමතුමක් භාවිතයෙන් පරිශීලක ක්‍රියාවලි වලින් සිතියම් වෙත ප්‍රවේශ වේ bpf(2), සහ උපකාරක ශ්‍රිත භාවිතයෙන් කර්නලය තුළ ක්‍රියාත්මක වන BPF වැඩසටහන් වලින්. එපමණක් නොව, සිතියම් සමඟ වැඩ කිරීමට පමණක් නොව, අනෙකුත් කර්නල් හැකියාවන් වෙත ප්රවේශ වීමට උපකාරකයන් පවතී. උදාහරණයක් ලෙස, BPF වැඩසටහන් වලට පැකට් වෙනත් අතුරුමුහුණත් වෙත යොමු කිරීමට, perf සිද්ධීන් උත්පාදනය කිරීමට, කර්නල් ව්‍යුහයන්ට ප්‍රවේශ වීමට සහ යනාදිය සඳහා උපකාරක ශ්‍රිත භාවිතා කළ හැක.

කුඩා ළමුන් සඳහා BPF, පළමු කොටස: දීර්ඝ BPF

සාරාංශයක් ලෙස, BPF අත්තනෝමතික, එනම්, සත්‍යාපක-පරීක්ෂා කළ, පරිශීලක කේතය කර්නල් අවකාශයට පැටවීමේ හැකියාව සපයයි. මෙම කේතයට ඇමතුම් අතර තත්වය සුරැකිය හැකි අතර පරිශීලක අවකාශය සමඟ දත්ත හුවමාරු කර ගත හැකි අතර, මෙම වර්ගයේ වැඩසටහන් මගින් අවසර දී ඇති කර්නල් උප පද්ධති වෙත ප්‍රවේශය ද ඇත.

මෙය දැනටමත් කර්නල් මොඩියුල මගින් සපයනු ලබන හැකියාවන්ට සමාන වේ, එයට සාපේක්ෂව BPF හි යම් වාසි ඇත (ඇත්ත වශයෙන්ම, ඔබට සමාන යෙදුම් සංසන්දනය කළ හැක්කේ, උදාහරණයක් ලෙස, පද්ධති ලුහුබැඳීම - ඔබට BPF සමඟ අත්තනෝමතික ධාවකයක් ලිවිය නොහැක). ඔබට අඩු ප්‍රවේශ සීමාවක් සටහන් කළ හැකිය (BPF භාවිතා කරන සමහර උපයෝගිතා සඳහා පරිශීලකයාට කර්නල් ක්‍රමලේඛන කුසලතා හෝ සාමාන්‍යයෙන් ක්‍රමලේඛන කුසලතා තිබීම අවශ්‍ය නොවේ), ධාවන කාල ආරක්ෂාව (ලියන විට පද්ධතිය නොකැඩූ අය සඳහා අදහස් දැක්වීමේදී ඔබේ අත ඔසවන්න. හෝ පරීක්ෂණ මොඩියුල), පරමාණුකත්වය - මොඩියුල නැවත පූරණය කිරීමේදී අක්‍රීය කාලයක් පවතින අතර, BPF උප පද්ධතිය කිසිදු සිදුවීමක් මග හැරෙන්නේ නැති බව සහතික කරයි (සාධාරණ ලෙස, මෙය BPF වැඩසටහන් වල සියලුම වර්ග සඳහා සත්‍ය නොවේ).

එවැනි හැකියාවන් තිබීම BPF කර්නලය පුළුල් කිරීම සඳහා විශ්වීය මෙවලමක් බවට පත් කරයි, එය ප්‍රායෝගිකව සනාථ වේ: BPF වෙත වැඩි වැඩියෙන් නව වැඩසටහන් එකතු කරනු ලැබේ, වැඩි වැඩියෙන් විශාල සමාගම් BPF භාවිතා කරන්නේ සටන් සේවාදායකයන් 24 × 7, වැඩි වැඩියෙන්. ආරම්භකයින් BPF මත පදනම් වූ විසඳුම් මත ඔවුන්ගේ ව්‍යාපාරය ගොඩනඟයි. BPF සෑම තැනකම භාවිතා වේ: DDoS ප්‍රහාර වලින් ආරක්ෂා කිරීම, SDN නිර්මාණය කිරීම (උදාහරණයක් ලෙස, kubernetes සඳහා ජාල ක්‍රියාත්මක කිරීම), ප්‍රධාන පද්ධති ලුහුබැඳීමේ මෙවලම සහ සංඛ්‍යාලේඛන එකතු කරන්නා ලෙස, ආක්‍රමණය හඳුනාගැනීමේ පද්ධති සහ වැලිපිල්ල පද්ධති ආදිය.

ලිපියේ දළ විශ්ලේෂණය කොටස මෙතැනින් අවසන් කර අතථ්‍ය යන්ත්‍රය සහ BPF පරිසර පද්ධතිය දෙස වඩාත් විස්තරාත්මකව බලමු.

අපගමනය: උපයෝගිතා

පහත කොටස්වල උදාහරණ ක්‍රියාත්මක කිරීමට හැකි වීම සඳහා, ඔබට අවම වශයෙන් උපයෝගිතා ගණනාවක් අවශ්‍ය විය හැක. llvm/clang bpf සහාය ඇතිව සහ bpftool. කොටසේ සංවර්ධන මෙවලම් ඔබට උපයෝගිතා එකලස් කිරීම සඳහා වන උපදෙස් මෙන්ම ඔබේ කර්නලය ද කියවිය හැකිය. අපගේ ඉදිරිපත් කිරීමේ එකඟතාවයට බාධා නොවන පරිදි මෙම කොටස පහතින් තබා ඇත.

BPF අතථ්‍ය යන්ත්‍ර රෙජිස්ටර් සහ උපදෙස් පද්ධතිය

BPF හි ගෘහ නිර්මාණ ශිල්පය සහ විධාන පද්ධතිය සංවර්ධනය කරන ලද්දේ වැඩසටහන් C භාෂාවෙන් ලියා ඇති අතර කර්නලයට පැටවීමෙන් පසු ස්වදේශීය කේතයට පරිවර්තනය කිරීම සැලකිල්ලට ගනිමිනි. එබැවින්, නවීන යන්ත්‍රවල හැකියාවන් පිළිබඳ ගණිතමය අර්ථයෙන්, මංසන්ධිය දෙස බැලීමෙන් ලේඛන ගණන සහ විධාන කට්ටලය තෝරා ගන්නා ලදී. මීට අමතරව, වැඩසටහන් සඳහා විවිධ සීමාවන් පනවා ඇත, උදාහරණයක් ලෙස, මෑතක් වන තුරුම ලූප සහ උපසිරැසි ලිවීමට නොහැකි වූ අතර, උපදෙස් ගණන 4096 ට සීමා විය (දැන් වරප්රසාදිත වැඩසටහන් මිලියනයක් දක්වා උපදෙස් පැටවිය හැක).

BPF සතුව පරිශීලක-ප්‍රවේශ විය හැකි 64-bit රෙජිස්ටර් එකොළහක් ඇත r0-r10 සහ වැඩසටහන් කවුන්ටරයක්. ලියාපදිංචි කරන්න r10 රාමු දර්ශකයක් අඩංගු වන අතර එය කියවීමට පමණි. වැඩසටහන් වලට ධාවන වේලාවේදී 512-බයිට් තොගයකට ප්‍රවේශය සහ සිතියම් ආකාරයෙන් අසීමිත හවුල් මතක ප්‍රමාණයක් ඇත.

BPF වැඩසටහන් වලට විශේෂිත වූ ක්‍රමලේඛ-වර්ගයේ කර්නල් උපකාරක කට්ටලයක් සහ, වඩාත් මෑතක දී, සාමාන්‍ය ක්‍රියාකාරකම් ක්‍රියාත්මක කිරීමට අවසර ඇත. හැඳින්වෙන සෑම ශ්‍රිතයක්ම රෙජිස්ටර් වලින් සම්මත කර ඇති තර්ක පහක් දක්වා ගත හැක r1-r5, සහ ආපසු ලැබෙන අගය වෙත යවනු ලැබේ r0. කාර්යයෙන් ආපසු පැමිණීමෙන් පසු, රෙජිස්ටර් වල අන්තර්ගතය සහතික කෙරේ r6-r9 වෙනස් වෙන්නේ නැහැ.

කාර්යක්ෂම වැඩසටහන් පරිවර්තනය සඳහා, ලියාපදිංචි වේ r0-r11 මක්නිසාද යත් වත්මන් ගෘහනිර්මාණ ශිල්පයේ ABI ලක්ෂණ සැලකිල්ලට ගනිමින් සියලුම සහාය දක්වන ගෘහ නිර්මාණ ශිල්පය සැබෑ ලේඛනවලට අනන්‍ය ලෙස සිතියම්ගත කර ඇත. උදාහරණයක් ලෙස, සඳහා 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 වැඩසටහන් ක්‍රියාත්මක කිරීමේ ප්‍රති result ලය සහ ලේඛනයේ නැවත ලබා දීමට ද භාවිතා වේ r1 වැඩසටහන සන්දර්භය වෙත දර්ශකයක් ලබා දෙයි - වැඩසටහනේ වර්ගය මත පදනම්ව, මෙය උදාහරණයක් ලෙස ව්‍යුහයක් විය හැකිය struct xdp_md (XDP සඳහා) හෝ ව්යුහය struct __sk_buff (විවිධ ජාල වැඩසටහන් සඳහා) හෝ ව්යුහය struct pt_regs (විවිධ ආකාරයේ ලුහුබැඳීමේ වැඩසටහන් සඳහා) ආදිය.

එබැවින්, අපට සිතියම් ආකාරයෙන් රෙජිස්ටර්, කර්නල් උපකාරක, තොගයක්, සන්දර්භ දර්ශකයක් සහ හවුල් මතකයක් තිබුණි. ගමනේදී මේ සියල්ල අත්‍යවශ්‍ය බව නොවේ, නමුත් ...

අපි විස්තරය දිගටම කරගෙන ගොස් මෙම වස්තූන් සමඟ වැඩ කිරීම සඳහා විධාන පද්ධතිය ගැන කතා කරමු. සෑම (සියල්ලම පාහේ) BPF උපදෙස් වලට ස්ථාවර 64-bit විශාලත්වයක් ඇත. ඔබ 64-bit Big Endian යන්ත්‍රයක එක් උපදෙස් දෙස බැලුවහොත් ඔබට පෙනෙනු ඇත

කුඩා ළමුන් සඳහා BPF, පළමු කොටස: දීර්ඝ BPF

එය Code - මෙය උපදෙස් වල කේතනය වේ, Dst/Src පිළිවෙලින් ග්‍රාහකයේ සහ ප්‍රභවයේ කේතීකරණ වේ, Off - 16-bit අත්සන් කරන ලද indentation, සහ Imm සමහර උපදෙස් වල භාවිතා කරන ලද 32-බිට් අත්සන් කරන ලද පූර්ණ සංඛ්‍යාවකි (cBPF නියතය K හා සමානයි). කේතනය කිරීම Code වර්ග දෙකෙන් එකක් ඇත:

කුඩා ළමුන් සඳහා BPF, පළමු කොටස: දීර්ඝ BPF

උපදෙස් පන්ති 0, 1, 2, 3 මතකය සමඟ වැඩ කිරීම සඳහා විධාන නිර්වචනය කරයි. ඔව්හු ලෙස හැඳින්වේ, BPF_LD, BPF_LDX, BPF_ST, BPF_STX, පිළිවෙලින්. පන්ති 4, 7 (BPF_ALU, BPF_ALU64) ALU උපදෙස් මාලාවක් සාදයි. පන්ති 5, 6 (BPF_JMP, BPF_JMP32) පැනීමේ උපදෙස් අඩංගු වේ.

BPF උපදෙස් පද්ධතිය අධ්‍යයනය කිරීමේ වැඩිදුර සැලැස්ම පහත පරිදි වේ: සියලුම උපදෙස් සහ ඒවායේ පරාමිතීන් ඉතා සූක්ෂම ලෙස ලැයිස්තුගත කරනවා වෙනුවට, අපි මෙම කොටසේ උදාහරණ කිහිපයක් දෙස බලමු, ඒවායින් උපදෙස් ඇත්ත වශයෙන්ම ක්‍රියාත්මක වන ආකාරය සහ කෙසේද යන්න පැහැදිලි වනු ඇත. BPF සඳහා ඕනෑම ද්විමය ගොනුවක් අතින් විසුරුවා හරින්න. ලිපියේ පසුව කරුණු තහවුරු කිරීම සඳහා, අපි Verifier, JIT සම්පාදකය, සම්භාව්‍ය BPF පරිවර්තනය, මෙන්ම සිතියම් අධ්‍යයනය කරන විට, ඇමතුම් කාර්යයන් යනාදිය පිළිබඳ කොටස්වල තනි උපදෙස් ද හමුවෙමු.

අපි තනි උපදෙස් ගැන කතා කරන විට, අපි මූලික ගොනු වෙත යොමු කරමු bpf.h и bpf_common.h, BPF උපදෙස් වල සංඛ්‍යාත්මක කේත නිර්වචනය කරයි. ඔබේම සහ/හෝ ද්වීමය විග්‍රහ කිරීමේදී වාස්තු විද්‍යාව හැදෑරීමේදී, ඔබට සංකීර්ණත්වය අනුව වර්ග කර ඇති පහත මූලාශ්‍රවල අර්ථ ශාස්ත්‍රය සොයාගත හැක: නිල නොවන eBPF පිරිවිතර, BPF සහ XDP යොමු මාර්ගෝපදේශය, උපදෙස් කට්ටලය, ලේඛනගත කිරීම/ජාලකරණය/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-bit අගයෙහි දෙවන කොටස ගබඩා කිරීම සඳහා යාබද උපදෙස් දෙකක් භාවිතා කරනු ලැබේ 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 වැඩසටහන පූරණය කිරීමෙන් පසු මෙම සිතියම් එකකින් වැඩි වේ, i.e. ඔවුන්ගේ ගොනු විස්තර පරිශීලක ක්‍රියාවලියෙන් වසා දැමිය හැක refcount බිංදු බවට පත් නොවනු ඇත:

කුඩා ළමුන් සඳහා BPF, පළමු කොටස: දීර්ඝ BPF

වැඩසටහනක් සාර්ථකව පූරණය කිරීමෙන් පසු, අපි සාමාන්‍යයෙන් එය යම් ආකාරයක ඉවෙන්ට් ජෙනරේටරයකට අමුණන්නෙමු. උදාහරණයක් ලෙස, එන පැකට් සැකසීමට හෝ සමහරක් වෙත සම්බන්ධ කිරීමට අපට එය ජාල අතුරු මුහුණතක් මත තැබිය හැකිය tracepoint හරය තුළ. මෙම අවස්ථාවේදී, යොමු කවුන්ටරය ද එකකින් වැඩි වන අතර, loader වැඩසටහනේ ගොනු විස්තරය වසා දැමීමට අපට හැකි වනු ඇත.

අපි දැන් bootloader එක shutdown කලොත් මොකද වෙන්නේ? එය සිදුවීම් උත්පාදක (කොක්ක) වර්ගය මත රඳා පවතී. ලෝඩරය අවසන් වූ පසු සියලුම ජාල කොකු පවතිනු ඇත, මේවා ඊනියා ගෝලීය කොකු වේ. තවද, උදාහරණයක් ලෙස, ලුහුබැඳීමේ වැඩසටහන් ඒවා නිර්මාණය කළ ක්‍රියාවලිය අවසන් වූ පසු මුදා හරිනු ඇත (එබැවින් දේශීය ලෙස හැඳින්වේ, “දේශීය සිට ක්‍රියාවලියට”). තාක්ෂණික වශයෙන්, දේශීය කොකු සෑම විටම පරිශීලක අවකාශයේ අනුරූප ගොනු විස්තරයක් ඇති අතර එම නිසා ක්‍රියාවලිය වසා ඇති විට වසා දමයි, නමුත් ගෝලීය කොකු එසේ නොවේ. පහත රූපයේ, රතු කුරුස භාවිතා කරමින්, ලෝඩර් වැඩසටහන අවසන් කිරීම දේශීය හා ගෝලීය කොකු සම්බන්ධයෙන් වස්තූන්ගේ ආයු කාලයට බලපාන ආකාරය පෙන්වීමට මම උත්සාහ කරමි.

කුඩා ළමුන් සඳහා 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". මෙයින් පසු bootloader වැඩසටහන 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

අපිට සාමාන්‍ය විදියට file object එක delete කරන්න පුළුවන් unlink(2) ඊට පසු, අදාළ වැඩසටහන මකා දැමෙනු ඇත:

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

වස්තූන් මකා දැමීම

වස්තු මකා දැමීම ගැන කතා කරන විට, අපි වැඩසටහන කොක්කෙන් (සිදුවීම් උත්පාදක යන්ත්රය) විසන්ධි කළ පසු, එක නව සිදුවීමක්වත් එය දියත් නොකරන බව පැහැදිලි කිරීම අවශ්ය වේ, කෙසේ වෙතත්, වැඩසටහනේ සියලුම වත්මන් අවස්ථා සාමාන්ය අනුපිළිවෙලින් සම්පූර්ණ කරනු ඇත. .

සමහර වර්ගවල BPF වැඩසටහන් ඔබට පියාසර කිරීමේදී වැඩසටහන ප්‍රතිස්ථාපනය කිරීමට ඉඩ සලසයි, i.e. අනුපිළිවෙල පරමාණුකත්වය සපයයි 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 - දර්ශකය අනුව වස්තුවේ ප්රමාණය, i.e. සාමාන්යයෙන් මෙය sizeof(*attr). කර්නලය 5.8 හි පද්ධති ඇමතුම bpf විවිධ විධාන 34කට සහය දක්වයි, සහ අර්ථ දැක්වීම union bpf_attr පේළි 200 ක් අල්ලා ගනී. නමුත් අපි මේ ගැන බිය නොවිය යුතුය, මන්ද අපි ලිපි කිහිපයකින් විධාන සහ පරාමිති පිළිබඳව හුරුපුරුදු වනු ඇත.

අපි කණ්ඩායමෙන් පටන් ගනිමු BPF_PROG_LOAD, BPF වැඩසටහන් නිර්මාණය කරන - BPF උපදෙස් මාලාවක් ගෙන එය කර්නලයට පූරණය කරයි. පූරණය වන මොහොතේ, සත්‍යාපකය දියත් කරනු ලැබේ, පසුව JIT සම්පාදකය සහ සාර්ථක ක්‍රියාත්මක කිරීමෙන් පසු, වැඩසටහන් ගොනු විස්තරය පරිශීලකයා වෙත ආපසු ලබා දෙනු ලැබේ. මීළඟට ඔහුට මොකද වෙන්නේ කියලා අපි කලින් කොටසින් දැක්කා BPF වස්තූන්ගේ ජීවන චක්‍රය ගැන.

අපි දැන් සරල BPF වැඩසටහනක් පූරණය කරන අභිරුචි වැඩසටහනක් ලියන්නෙමු, නමුත් පළමුව අපට පූරණය කිරීමට අවශ්‍ය කුමන ආකාරයේ වැඩසටහනක් දැයි තීරණය කළ යුතුය - අපට තෝරා ගැනීමට සිදුවේ. වර්ගය සහ මෙම වර්ගයේ රාමුව තුළ, verifier පරීක්ෂණය සමත් වන වැඩසටහනක් ලියන්න. කෙසේ වෙතත්, ක්රියාවලිය සංකීර්ණ නොකිරීමට, මෙන්න සූදානම් කළ විසඳුමක්: අපි වැනි වැඩසටහනක් ගන්නෙමු 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 BPF ගෘහ නිර්මාණ ශිල්පයේ ද්විමය කේත වලින් බයිට් 16 ක් - උපදෙස් දෙකක් ගනී, නමුත් එහි ස්වදේශික ආකාරයෙන් (x86_64) එය දැනටමත් බයිට් 40 කි. අපගේ වැඩසටහන එහි මුල් ස්වරූපයෙන් බලමු:

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

පුදුමයක් නැත. දැන් අපි JIT සම්පාදකයෙන් උත්පාදනය කරන ලද කේතය දෙස බලමු:

# 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), නමුත් සාධාරණ ලෙස, අපගේ වැඩසටහන ඉතා සරල වන අතර, සුළු නොවන වැඩසටහන් සඳහා JIT සම්පාදකය විසින් එකතු කරන ලද පෙරවදන සහ කථාංගය අවශ්‍ය වේ.

සිතියම්

BPF වැඩසටහන් වලට අනෙකුත් BPF වැඩසටහන් වලට සහ පරිශීලක අවකාශයේ ඇති වැඩසටහන් වලට ප්‍රවේශ විය හැකි ව්‍යුහගත මතක ප්‍රදේශ භාවිතා කළ හැක. මෙම වස්තූන් සිතියම් ලෙස හඳුන්වනු ලබන අතර මෙම කොටසේදී අපි පද්ධති ඇමතුමක් භාවිතයෙන් ඒවා හසුරුවන්නේ කෙසේදැයි පෙන්වමු bpf.

සිතියම් වල හැකියාවන් හවුල් මතකයට ප්‍රවේශ වීමට පමණක් සීමා නොවන බව අපි වහාම කියමු. උදාහරණයක් ලෙස, BPF වැඩසටහන් සඳහා පොයින්ටර් හෝ ජාල අතුරුමුහුණත් සඳහා පොයින්ටර්, perf සිදුවීම් සමඟ වැඩ කිරීම සඳහා සිතියම් ආදිය අඩංගු විශේෂ කාර්ය සිතියම් ඇත. පාඨකයා ව්යාකූල නොවන පරිදි අපි ඔවුන් ගැන මෙහි කතා නොකරමු. මෙය හැර, අපගේ උදාහරණ සඳහා මෙය වැදගත් නොවන බැවින්, අපි සමමුහුර්ත කිරීමේ ගැටළු නොසලකා හරිමු. පවතින සිතියම් වර්ගවල සම්පූර්ණ ලැයිස්තුවක් සොයාගත හැකිය <linux/bpf.h>, සහ මෙම කොටසේදී අපි ඓතිහාසික වශයෙන් පළමු වර්ගය වන හැෂ් වගුව උදාහරණයක් ලෙස ගනිමු BPF_MAP_TYPE_HASH.

ඔබ හැෂ් වගුවක් නිර්මාණය කරන්නේ නම්, කියන්න, C++, ඔබ කියයි unordered_map<int,long> woo, එහි රුසියානු භාෂාවෙන් අදහස් වන්නේ "මට මේසයක් අවශ්යයි woo අසීමිත ප්‍රමාණය, යතුරු වර්ගයට අයත් වේ int, සහ අගයන් වර්ගය වේ long" BPF හැෂ් වගුවක් නිර්මාණය කිරීම සඳහා, අපි මේසයේ උපරිම ප්‍රමාණය සඳහන් කළ යුතු අතර, යතුරු වර්ග සහ අගයන් සඳහන් කරනවා වෙනුවට, ඒවායේ ප්‍රමාණයන් බයිට් වලින් සඳහන් කිරීමට අවශ්‍ය වෙනවා හැර, අපි එකම දේ කළ යුතුයි. . සිතියම් නිර්මාණය කිරීමට විධානය භාවිතා කරන්න 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, එහි අපි කියනවා “මට යතුරු සහ ප්‍රමාණයේ අගයන් සහිත හැෂ් වගුවක් අවශ්‍යයි 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 යනු අපගේ වස්තුවේ ගෝලීය ID වේ. විධානය භාවිතයෙන් පවතින සිතියමක් විවෘත කිරීමට පද්ධතියේ ඕනෑම වැඩසටහනකට මෙම හැඳුනුම්පත භාවිතා කළ හැක 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 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 (එක් වරක් සම්පාදනය කරන්න, සෑම තැනකම ධාවනය කරන්න) - විවිධ API මත ධාවනය කිරීමේ හැකියාව ඇතිව, එක් කර්නලයකින් තවත් කර්නලයකට ගෙන යා හැකි 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 වැඩසටහන් සිතියම්, කර්නල් සහායකයින්, BTF යනාදිය සමඟ අන්තර්ක්‍රියා කරන ආකාරය නිවැරදිව පහත කොටස් වලින් වඩාත් පහසුවෙන් පැහැදිලි කිරීමට මෙය අපට ඉඩ සලසයි.

සාමාන්යයෙන් භාවිතා කරන ව්යාපෘති libbpf Git උප මොඩියුලයක් ලෙස GitHub ගබඩාවක් එක් කරන්න, අපි එයම කරන්නෙමු:

$ 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, slashට පෙර අපි 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, මෙම විකල්පය නොමැතිව ඔබ අනාගතයේ දී විස්මයන් සඳහා විය හැකිය. අපි බලමු අපේ code එක, අපිට අවශ්‍ය program එක ලියන්න අපි සමත් වුනාද?

$ 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 අපට විකල්ප දෙකක් ඉදිරිපත් කරයි - පහළ මට්ටමේ API හෝ ඉහළ මට්ටමේ API භාවිතා කරන්න. BPF වැඩසටහන් ලිවීම, පූරණය කිරීම සහ සම්බන්ධ කිරීම ඔවුන්ගේ පසුකාලීන අධ්‍යයනය සඳහා අවම උත්සාහයකින් ඉගෙන ගැනීමට අවශ්‍ය බැවින් අපි දෙවන මාර්ගයට යන්නෙමු.

පළමුව, අපගේ වැඩසටහනේ "ඇටසැකිල්ල" එහි ද්විමය වෙතින් එකම උපයෝගීතාවයෙන් උත්පාදනය කළ යුතුය bpftool - BPF ලෝකයේ ස්විස් පිහිය (BPF හි නිර්මාතෘ සහ නඩත්තු කරන්නන්ගෙන් කෙනෙකු වන ඩැනියෙල් බෝක්මන් ස්විස් ජාතික බැවින් එය වචනාර්ථයෙන් ගත හැකිය):

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

ගොනුවේ xdp-simple.skel.h අපගේ වැඩසටහනේ ද්විමය කේතය සහ කළමනාකරණය සඳහා වන කාර්යයන් අඩංගු වේ - අපගේ වස්තුව පැටවීම, ඇමිණීම, මකා දැමීම. අපගේ සරල නඩුවේදී මෙය overkill ලෙස පෙනේ, නමුත් වස්තු ගොනුවේ බොහෝ BPF වැඩසටහන් සහ සිතියම් අඩංගු වන අවස්ථාවකද එය ක්‍රියා කරන අතර මෙම යෝධ ELF පූරණය කිරීමට අපට අවශ්‍ය වන්නේ ඇටසැකිල්ල ජනනය කර අප විසින් අභිරුචි යෙදුමෙන් කාර්යයන් එකක් හෝ දෙකක් ඇමතීමට පමණි. ලියන්නේ අපි දැන් ඉදිරියට යමු.

හරියටම කිවහොත්, අපගේ loader වැඩසටහන සුළුපටු ය:

#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;
};

අපට පහත මට්ටමේ API එකක සලකුණු මෙහි දැකිය හැක: ව්‍යුහය 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 වැඩසටහන් වලට කර්නල් ව්‍යුහයන් වෙත ප්‍රවේශ වීමට, සිතියම් කළමනාකරණය කිරීමට සහ "සැබෑ ලෝකය" සමඟ සන්නිවේදනය කිරීමට ඉඩ සලසයි - perf සිදුවීම් නිර්මාණය කිරීම, දෘඩාංග පාලනය කිරීම (උදාහරණයක් ලෙස, පැකට් යළි-යොමුවීම්) ආදිය.

උදාහරණය: 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 උපකාරක ශ්‍රිත නිර්වචන Linux පද්ධති ඇමතුම් අර්ථ දැක්වීම් වලට සමාන වේ. මෙහිදී, උදාහරණයක් ලෙස, තර්ක නොමැති ශ්‍රිතයක් අර්ථ දක්වා ඇත. (විවාද තුනක් ගන්නා ශ්‍රිතයක් සාර්ව භාවිතයෙන් අර්ථ දක්වා ඇත 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_proto, XDP මෙම ශ්‍රිතයට සහය දක්වන්නේද නැද්ද යන්න සහකාර ශ්‍රිත ID මගින් තීරණය කරයි. අපගේ කාර්යය වේ සහයෝගය දක්වයි:

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 macro භාවිතා කරමින් 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
};

එනම්, එක් එක් වර්ගයේ BPF වැඩසටහන සඳහා, එම වර්ගයේ දත්ත ව්‍යුහයකට දර්ශකයක් අර්ථ දක්වා ඇත struct bpf_verifier_ops, අගය සමඟ ආරම්භ කරන ලද _name ## _verifier_ops, එනම්, xdp_verifier_ops සඳහා xdp. ව්යුහය 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 ආපසු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;
}

අපගේ වැඩසටහන එය ක්‍රියාත්මක වන CPU අංකය මුද්‍රණය කරයි. අපි එය සම්පාදනය කර කේතය දෙස බලමු:

$ 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 අපි string ලියන්නෙමු running on CPU%un, ඉන්පසු 8 වන පේළියේ අපි හුරුපුරුදු එක ධාවනය කරමු bpf_get_smp_processor_id. පේළි 9-12 මත අපි උපකාරක තර්ක සකස් කරමු bpf_printk - ලියාපදිංචි කරයි r1, r2, r3. ඇයි දෙන්නෙක් නෙවෙයි තුන්දෙනෙක් ඉන්නේ? නිසා bpf_printkමේක macro wrapper එකක් සැබෑ උපකාරකයා වටා 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

අපි බාගත කළ වැඩසටහනේ ID 669 ඇති අතර අතුරු මුහුණතේ එකම හැඳුනුම්පත අපට පෙනේ lo. අපි පැකේජ කිහිපයක් යවන්නෙමු 127.0.0.1 (ඉල්ලීම + පිළිතුර):

$ ping -c1 localhost

සහ දැන් අපි debug virtual file එකේ අන්තර්ගතය බලමු /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 අරාවේ අනුරූප ප්‍රවේශයට අපට දර්ශකයක් ලැබේ, එය අපි එකකින් වැඩි කරමු. රුසියානු භාෂාවට පරිවර්තනය කර ඇත: අපි CPU විසින් පැමිණෙන පැකට් සැකසූ සංඛ්‍යාලේඛන ගණනය කරමු. වැඩසටහන ක්රියාත්මක කිරීමට උත්සාහ කරමු:

$ 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 { ... }...

Program Assembler එක බැලුවොත් අපිට පේනවා ඒ අගය &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]
...

මේ අනුව, අපගේ loader වැඩසටහන දියත් කරන අවස්ථාවේදී, වෙත සබැඳිය බව අපට නිගමනය කළ හැකිය &woo පුස්තකාලයක් සහිත යමක් මගින් ප්‍රතිස්ථාපනය විය libbpf. මුලින්ම අපි output එක බලමු 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_xattr සිට libbpf/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, සහ අපගේ සිතියමේ ගොනු විස්තරයට පළමු IMM සහ, එය සමාන නම්, උදාහරණයක් ලෙස, 0xdeadbeef, එවිට ප්රතිඵලයක් ලෙස අපට උපදෙස් ලැබෙනු ඇත

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

සිතියම් තොරතුරු නිශ්චිත පූරණය කරන ලද BPF වැඩසටහනකට මාරු කරනු ලබන්නේ මෙලෙසය. මෙම අවස්ථාවේදී, සිතියම භාවිතයෙන් නිර්මාණය කළ හැකිය BPF_MAP_CREATE, සහ ID භාවිතයෙන් විවෘත කර ඇත 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 sockets, මෙයද බලන්න ආර්එෆ්සී 3549. ක්රියාත්මක කිරීමට සරලම ක්රමය xdp_attach වෙතින් කේතය පිටපත් කරයි libbpf, එනම්, ගොනුවෙන් netlink.c, අපි කළේ එය ටිකක් කෙටි කර:

නෙට්ලින්ක් සොකට් ලෝකයට සාදරයෙන් පිළිගනිමු

නෙට්ලින්ක් සොකට් වර්ගයක් විවෘත කරන්න 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 මත ධාවනය විය.)

llvm/clang

BPF LLVM සමඟ මිත්‍රශීලී වන අතර, මෑතකදී BPF සඳහා වැඩසටහන් gcc භාවිතයෙන් සම්පාදනය කළ හැකි වුවද, දැනට පවතින සියලුම සංවර්ධන කටයුතු LLVM සඳහා සිදු කෙරේ. එමනිසා, පළමුවෙන්ම, අපි වත්මන් අනුවාදය ගොඩනඟමු clang git වෙතින්:

$ 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 කර්නල් ලැයිස්තුගත කර ඇති ඒවායින් වඩාත්ම අස්ථායී වේ).

කර්නල් වින්‍යාස ගොනු කළමනාකරණය කරන්නේ කෙසේද යන්න ගැන කතා කිරීම මෙම ලිපියේ විෂය පථයෙන් ඔබ්බට ය - මෙය කරන්නේ කෙසේදැයි ඔබ දැනටමත් දන්නා බව උපකල්පනය කෙරේ. ඉගෙනීමට සූදානම් තමන්ගේම මත. කෙසේ වෙතත්, ඔබට ක්‍රියාකාරී BPF-සක්‍රීය පද්ධතියක් ලබා දීමට පහත උපදෙස් අඩු වැඩි වශයෙන් ප්‍රමාණවත් විය යුතුය.

ඉහත කර්නල් වලින් එකක් බාගන්න:

$ 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 eth0 වෙනුවට ip addr show dev eth0.

නිගමනය

BPF මඟින් ඔබට ඵලදායි ලෙස මැනීමට සහ හරයේ ක්‍රියාකාරීත්වය වෙනස් කිරීමට මැක්කන් සපත්තු කිරීමට ඉඩ සලසයි. UNIX හි හොඳම සම්ප්‍රදායන් තුළ පද්ධතිය ඉතා සාර්ථක විය: කර්නලය (නැවත) ක්‍රමලේඛනය කිරීමට ඔබට ඉඩ සලසන සරල යාන්ත්‍රණයක් විශාල පිරිසකට සහ සංවිධානවලට අත්හදා බැලීමට ඉඩ සලසයි. තවද, අත්හදා බැලීම් මෙන්ම BPF යටිතල ව්‍යුහය සංවර්ධනය කිරීම අවසන් වී නැතත්, පද්ධතියට දැනටමත් ස්ථාවර ABI එකක් ඇති අතර එමඟින් ඔබට විශ්වාසදායක සහ වඩාත්ම වැදගත් ව්‍යාපාරික තර්කනය ගොඩනැගීමට ඉඩ සලසයි.

මගේ මතය අනුව, තාක්ෂණය මෙතරම් ජනප්‍රිය වී ඇත්තේ එක් අතකින් එය භාවිතා කළ හැකි නිසා බව සටහන් කිරීමට කැමැත්තෙමි. играть (යන්ත්‍රයක ගෘහනිර්මාණ ශිල්පය එක් සන්ධ්‍යාවකින් අඩු වැඩි වශයෙන් අවබෝධ කර ගත හැක), අනෙක් අතට, එහි පෙනුමට පෙර (අලංකාර ලෙස) විසඳිය නොහැකි ගැටළු විසඳීමට. මෙම සංරචක දෙක එක්ව මිනිසුන්ට අත්හදා බැලීමට හා සිහින දැකීමට බල කරයි, එය වඩ වඩාත් නව්‍ය විසඳුම් මතුවීමට හේතු වේ.

මෙම ලිපිය, විශේෂයෙන් කෙටි නොවූවත්, BPF ලෝකයට හැඳින්වීමක් පමණක් වන අතර, "උසස්" විශේෂාංග සහ ගෘහ නිර්මාණ ශිල්පයේ වැදගත් කොටස් විස්තර නොකරයි. ඉදිරි සැලැස්ම මේ වගේ දෙයක්: මීළඟ ලිපිය BPF වැඩසටහන් වර්ග පිළිබඳ දළ විශ්ලේෂණයක් වනු ඇත (5.8 කර්නලය තුළ වැඩසටහන් වර්ග 30ක් සහය දක්වයි), ඉන්පසු අපි අවසානයේ කර්නල් ලුහුබැඳීමේ වැඩසටහන් භාවිතයෙන් සැබෑ BPF යෙදුම් ලියන්නේ කෙසේදැයි බලමු. උදාහරණයක් ලෙස, පසුව BPF ගෘහ නිර්මාණ ශිල්පය පිළිබඳ වඩාත් ගැඹුරු පාඨමාලාවක් සඳහා කාලය එළඹ ඇත, පසුව BPF ජාලකරණය සහ ආරක්ෂක යෙදුම් පිළිබඳ උදාහරණ.

මෙම ලිපි මාලාවේ පෙර ලිපි

  1. කුඩා ළමුන් සඳහා BPF, ශුන්‍ය කොටස: සම්භාව්‍ය BPF

සබැඳි

  1. BPF සහ XDP යොමු මාර්ගෝපදේශය — Cilium වෙතින් BPF පිළිබඳ ලියකියවිලි, හෝ වඩාත් නිවැරදිව BPF හි නිර්මාතෘ සහ නඩත්තු කරන්නන්ගෙන් එක් අයෙකු වන Daniel Borkman වෙතින්. මෙය පළමු බැරෑරුම් විස්තර වලින් එකකි, එය අනෙක් ඒවාට වඩා වෙනස් වන්නේ ඩැනියෙල් ඔහු ලියන දේ හරියටම දන්නා අතර එහි කිසිදු වැරැද්දක් නොමැති බැවිනි. විශේෂයෙන්ම, මෙම ලේඛනය XDP සහ TC වර්ගයේ BPF වැඩසටහන් සමඟ හොඳින් දන්නා උපයෝගීතාව භාවිතා කරන ආකාරය විස්තර කරයි. ip පැකේජයෙන් iproute2.

  2. ලේඛනගත කිරීම/ජාලකරණය/filter.txt — සම්භාව්‍ය සහ පසුව දීර්ඝ කරන ලද BPF සඳහා ලියකියවිලි සහිත මුල් ගොනුව. ඔබට එකලස් කිරීමේ භාෂාව සහ තාක්ෂණික වාස්තුවිද්‍යාත්මක තොරතුරු සොයා බැලීමට අවශ්‍ය නම් හොඳ කියවීමක්.

  3. ෆේස්බුක් වෙතින් BPF ගැන බ්ලොග්. ඇලෙක්සෙයි ස්ටාරොවොයිටොව් (ඊබීපීඑෆ් හි කතුවරයා) සහ ඇන්ඩ්‍රි නක්‍රිකෝ - (නඩත්තු කරන්නා) එහි ලියා ඇති පරිදි එය කලාතුරකින් යාවත්කාලීන වේ, නමුත් සුදුසු ය. libbpf).

  4. bpftool හි රහස්. bpftool භාවිතා කිරීමේ උදාහරණ සහ රහස් සමඟ Quentin Monnet වෙතින් විනෝදාත්මක twitter ත්‍රෙඩ් එකක්.

  5. BPF වෙත කිමිදෙන්න: කියවීමේ ද්‍රව්‍ය ලැයිස්තුවක්. Quentin Monnet වෙතින් BPF ලියකියවිලි වෙත යෝධ (සහ තවමත් පවත්වාගෙන යන) සබැඳි ලැයිස්තුවක්.

මූලාශ්රය: www.habr.com

අදහස් එක් කරන්න