చిన్న పిల్లలకు BPF, మొదటి భాగం: పొడిగించిన BPF

ప్రారంభంలో ఒక సాంకేతికత ఉంది మరియు దానిని BPF అని పిలిచేవారు. మేము ఆమె వైపు చూశాము మునుపటి, ఈ సిరీస్ యొక్క పాత నిబంధన వ్యాసం. 2013లో, అలెక్సీ స్టార్వోయిటోవ్ మరియు డేనియల్ బోర్క్‌మాన్ కృషితో, ఆధునిక 64-బిట్ మెషీన్‌ల కోసం ఆప్టిమైజ్ చేయబడిన దాని యొక్క మెరుగైన వెర్షన్ అభివృద్ధి చేయబడింది మరియు లైనక్స్ కెర్నల్‌లో చేర్చబడింది. ఈ కొత్త టెక్నాలజీని క్లుప్తంగా ఇంటర్నల్ BPF అని పిలిచారు, తర్వాత ఎక్స్‌టెండెడ్ BPF అని పేరు మార్చారు మరియు ఇప్పుడు, చాలా సంవత్సరాల తర్వాత, అందరూ దీనిని BPF అని పిలుస్తున్నారు.

స్థూలంగా చెప్పాలంటే, BPF మిమ్మల్ని Linux కెర్నల్ స్పేస్‌లో ఏకపక్ష వినియోగదారు అందించిన కోడ్‌ని అమలు చేయడానికి అనుమతిస్తుంది మరియు కొత్త ఆర్కిటెక్చర్ చాలా విజయవంతమైంది, దాని అన్ని అప్లికేషన్‌లను వివరించడానికి మాకు డజను మరిన్ని కథనాలు అవసరం. (దిగువ పనితీరు కోడ్‌లో మీరు చూడగలిగినట్లుగా, డెవలపర్‌లు బాగా చేయని ఏకైక విషయం, మంచి లోగోను సృష్టించడం.)

ఈ కథనం 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 యంత్రాల ఆగమనానికి ప్రతిస్పందనగా అభివృద్ధి చేయబడింది మరియు సమర్థవంతమైన ప్యాకెట్ ఫిల్టరింగ్ సమస్యను పరిష్కరించింది. ఆర్కిటెక్చర్ చాలా విజయవంతమైంది, బర్కిలీ UNIXలో చురుకైన తొంభైలలో జన్మించినందున, ఇది ఇప్పటికే ఉన్న చాలా ఆపరేటింగ్ సిస్టమ్‌లకు పోర్ట్ చేయబడింది, క్రేజీ ఇరవైలలో మనుగడ సాగించింది మరియు ఇప్పటికీ కొత్త అప్లికేషన్‌లను కనుగొంటోంది.

64-బిట్ మెషీన్‌లు, క్లౌడ్ సేవలు మరియు SDNని రూపొందించడానికి అవసరమైన సాధనాల యొక్క సర్వవ్యాప్తికి ప్రతిస్పందనగా కొత్త BPF అభివృద్ధి చేయబడింది (Sతరచుగా-dనిర్వచించబడింది nఎట్వర్కింగ్). క్లాసిక్ BPFకి మెరుగైన ప్రత్యామ్నాయంగా కెర్నల్ నెట్‌వర్క్ ఇంజనీర్లచే అభివృద్ధి చేయబడింది, కొత్త BPF అక్షరాలా ఆరు నెలల తర్వాత Linux సిస్టమ్‌లను గుర్తించే కష్టమైన పనిలో అప్లికేషన్‌లను కనుగొంది మరియు ఇప్పుడు, అది కనిపించిన ఆరు సంవత్సరాల తర్వాత, మాకు పూర్తి తదుపరి కథనం అవసరం. వివిధ రకాల ప్రోగ్రామ్‌లను జాబితా చేయండి.

నవ్వోచ్చే చిత్రాలు

దాని ప్రధాన భాగంలో, BPF అనేది శాండ్‌బాక్స్ వర్చువల్ మెషీన్, ఇది భద్రతను రాజీ పడకుండా కెర్నల్ స్థలంలో “ఏకపక్ష” కోడ్‌ను అమలు చేయడానికి మిమ్మల్ని అనుమతిస్తుంది. BPF ప్రోగ్రామ్‌లు వినియోగదారు స్థలంలో సృష్టించబడతాయి, కెర్నల్‌లోకి లోడ్ చేయబడతాయి మరియు కొన్ని ఈవెంట్ మూలానికి కనెక్ట్ చేయబడతాయి. ఒక ఈవెంట్, ఉదాహరణకు, నెట్‌వర్క్ ఇంటర్‌ఫేస్‌కి ప్యాకెట్‌ని డెలివరీ చేయడం, కొన్ని కెర్నల్ ఫంక్షన్‌ను ప్రారంభించడం మొదలైనవి కావచ్చు. ప్యాకేజీ విషయంలో, BPF ప్రోగ్రామ్ ప్యాకేజీ యొక్క డేటా మరియు మెటాడేటాకు యాక్సెస్‌ను కలిగి ఉంటుంది (ప్రోగ్రామ్ రకాన్ని బట్టి చదవడం మరియు వ్రాయడం కోసం); కెర్నల్ ఫంక్షన్‌ను అమలు చేసే సందర్భంలో, వాదనలు కెర్నల్ మెమరీకి పాయింటర్‌లతో సహా ఫంక్షన్, మొదలైనవి.

ఈ ప్రక్రియను నిశితంగా పరిశీలిద్దాం. ప్రారంభించడానికి, క్లాసిక్ BPF నుండి మొదటి వ్యత్యాసం గురించి మాట్లాడుదాం, దీని కోసం ప్రోగ్రామ్‌లు అసెంబ్లర్‌లో వ్రాయబడ్డాయి. కొత్త సంస్కరణలో, ఆర్కిటెక్చర్ విస్తరించబడింది, తద్వారా ప్రోగ్రామ్‌లను ఉన్నత-స్థాయి భాషలలో వ్రాయవచ్చు, ప్రాథమికంగా, C. దీని కోసం, llvm కోసం బ్యాకెండ్ అభివృద్ధి చేయబడింది, ఇది BPF ఆర్కిటెక్చర్ కోసం బైట్‌కోడ్‌ను రూపొందించడానికి మిమ్మల్ని అనుమతిస్తుంది.

చిన్న పిల్లలకు BPF, మొదటి భాగం: పొడిగించిన BPF

BPF ఆర్కిటెక్చర్ ఆధునిక యంత్రాలపై సమర్ధవంతంగా అమలు చేయడానికి కొంతవరకు రూపొందించబడింది. ఆచరణలో ఈ పని చేయడానికి, BPF బైట్‌కోడ్, కెర్నల్‌లోకి ఒకసారి లోడ్ చేయబడి, JIT కంపైలర్ (JIT కంపైలర్) అనే భాగాన్ని ఉపయోగించి స్థానిక కోడ్‌లోకి అనువదించబడుతుంది.JUst In Time). తర్వాత, మీరు గుర్తుంచుకుంటే, క్లాసిక్ BPFలో ప్రోగ్రామ్ కెర్నల్‌లోకి లోడ్ చేయబడింది మరియు ఈవెంట్ సోర్స్‌కు పరమాణుపరంగా జోడించబడింది - ఒకే సిస్టమ్ కాల్ సందర్భంలో. కొత్త ఆర్కిటెక్చర్‌లో, ఇది రెండు దశల్లో జరుగుతుంది - ముందుగా, సిస్టమ్ కాల్‌ని ఉపయోగించి కోడ్ కెర్నల్‌లోకి లోడ్ చేయబడుతుంది. bpf(2)ఆపై, తరువాత, ప్రోగ్రామ్ యొక్క రకాన్ని బట్టి మారే ఇతర యంత్రాంగాల ద్వారా, ప్రోగ్రామ్ ఈవెంట్ మూలానికి జోడించబడుతుంది.

ఇక్కడ పాఠకుడికి ఒక ప్రశ్న ఉండవచ్చు: ఇది సాధ్యమేనా? అటువంటి కోడ్ అమలు భద్రత ఎలా హామీ ఇవ్వబడుతుంది? వెరిఫైయర్ అని పిలువబడే BPF ప్రోగ్రామ్‌లను లోడ్ చేసే దశ ద్వారా అమలు భద్రత మాకు హామీ ఇవ్వబడుతుంది (ఇంగ్లీష్‌లో ఈ దశను వెరిఫైయర్ అంటారు మరియు నేను ఆంగ్ల పదాన్ని ఉపయోగించడం కొనసాగిస్తాను):

చిన్న పిల్లలకు BPF, మొదటి భాగం: పొడిగించిన BPF

వెరిఫైయర్ అనేది స్టాటిక్ ఎనలైజర్, ఇది ప్రోగ్రామ్ కెర్నల్ యొక్క సాధారణ ఆపరేషన్‌కు అంతరాయం కలిగించదని నిర్ధారిస్తుంది. మార్గం ద్వారా, ప్రోగ్రామ్ సిస్టమ్ యొక్క ఆపరేషన్‌లో జోక్యం చేసుకోదని దీని అర్థం కాదు - BPF ప్రోగ్రామ్‌లు, రకాన్ని బట్టి, కెర్నల్ మెమరీ విభాగాలను చదవవచ్చు మరియు తిరిగి వ్రాయవచ్చు, ఫంక్షన్ల విలువలను తిరిగి ఇవ్వవచ్చు, ట్రిమ్ చేయవచ్చు, జోడించవచ్చు, తిరిగి వ్రాయవచ్చు. మరియు నెట్‌వర్క్ ప్యాకెట్‌లను కూడా ఫార్వార్డ్ చేయండి. BPF ప్రోగ్రామ్‌ను అమలు చేయడం వలన కెర్నల్ క్రాష్ చేయబడదని మరియు నిబంధనల ప్రకారం రైట్ యాక్సెస్ ఉన్న ప్రోగ్రామ్, ఉదాహరణకు, అవుట్‌గోయింగ్ ప్యాకెట్ యొక్క డేటా, ప్యాకెట్ వెలుపల కెర్నల్ మెమరీని ఓవర్‌రైట్ చేయలేదని వెరిఫైయర్ హామీ ఇస్తుంది. మేము BPF యొక్క అన్ని ఇతర భాగాలతో పరిచయం పొందిన తర్వాత సంబంధిత విభాగంలో వెరిఫైయర్‌ని కొంచెం వివరంగా పరిశీలిస్తాము.

కాబట్టి మనం ఇప్పటివరకు ఏమి నేర్చుకున్నాము? వినియోగదారు C లో ప్రోగ్రామ్‌ను వ్రాస్తాడు, సిస్టమ్ కాల్‌ని ఉపయోగించి దానిని కెర్నల్‌లోకి లోడ్ చేస్తాడు bpf(2), ఇక్కడ ఇది వెరిఫైయర్ ద్వారా తనిఖీ చేయబడుతుంది మరియు స్థానిక బైట్‌కోడ్‌లోకి అనువదించబడుతుంది. అప్పుడు అదే లేదా మరొక వినియోగదారు ప్రోగ్రామ్‌ను ఈవెంట్ మూలానికి కనెక్ట్ చేస్తారు మరియు అది అమలు చేయడం ప్రారంభిస్తుంది. అనేక కారణాల వల్ల బూట్ మరియు కనెక్షన్‌ని వేరు చేయడం అవసరం. ముందుగా, వెరిఫైయర్‌ను అమలు చేయడం చాలా ఖరీదైనది మరియు అదే ప్రోగ్రామ్‌ను చాలాసార్లు డౌన్‌లోడ్ చేయడం ద్వారా మేము కంప్యూటర్ సమయాన్ని వృధా చేస్తాము. రెండవది, ప్రోగ్రామ్ ఎలా కనెక్ట్ చేయబడిందో దాని రకాన్ని బట్టి ఉంటుంది మరియు ఒక సంవత్సరం క్రితం అభివృద్ధి చేయబడిన ఒక “యూనివర్సల్” ఇంటర్‌ఫేస్ కొత్త రకాల ప్రోగ్రామ్‌లకు తగినది కాకపోవచ్చు. (ఇప్పుడు ఆర్కిటెక్చర్ మరింత పరిణతి చెందుతోంది, ఈ ఇంటర్‌ఫేస్‌ని స్థాయిలో ఏకీకృతం చేయాలనే ఆలోచన ఉంది 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 ప్రతిచోటా ఉపయోగించబడుతుంది: DDoS దాడుల నుండి రక్షించడంలో, SDNని సృష్టించడం (ఉదాహరణకు, kubernetes కోసం నెట్‌వర్క్‌లను అమలు చేయడం), ప్రధాన సిస్టమ్ ట్రేసింగ్ టూల్ మరియు స్టాటిస్టిక్స్ కలెక్టర్‌గా, చొరబాట్లను గుర్తించే వ్యవస్థలు మరియు శాండ్‌బాక్స్ సిస్టమ్‌లు మొదలైన వాటిలో.

కథనం యొక్క స్థూలదృష్టి భాగాన్ని ఇక్కడ ముగించి, వర్చువల్ మిషన్ మరియు BPF పర్యావరణ వ్యవస్థను మరింత వివరంగా చూద్దాం.

డైగ్రెషన్: యుటిలిటీస్

కింది విభాగాలలో ఉదాహరణలను అమలు చేయడానికి, మీకు కనీసం అనేక వినియోగాలు అవసరం కావచ్చు llvm/clang bpf మద్దతుతో మరియు bpftool. విభాగంలో అభివృద్ధి సాధనాలు మీరు యుటిలిటీలను, అలాగే మీ కెర్నల్‌ను అసెంబ్లింగ్ చేయడానికి సూచనలను చదవవచ్చు. మా ప్రదర్శన యొక్క సామరస్యానికి భంగం కలిగించకుండా ఈ విభాగం క్రింద ఉంచబడింది.

BPF వర్చువల్ మెషిన్ రిజిస్టర్‌లు మరియు ఇన్‌స్ట్రక్షన్ సిస్టమ్

ప్రోగ్రామ్‌లు సి భాషలో వ్రాయబడతాయి మరియు కెర్నల్‌లోకి లోడ్ చేసిన తర్వాత స్థానిక కోడ్‌లోకి అనువదించబడతాయనే వాస్తవాన్ని పరిగణనలోకి తీసుకొని BPF యొక్క ఆర్కిటెక్చర్ మరియు కమాండ్ సిస్టమ్ అభివృద్ధి చేయబడింది. అందువల్ల, ఆధునిక యంత్రాల సామర్థ్యాల గణిత కోణంలో, ఖండనను దృష్టిలో ఉంచుకుని రిజిస్టర్ల సంఖ్య మరియు ఆదేశాల సమితి ఎంపిక చేయబడ్డాయి. అదనంగా, ప్రోగ్రామ్‌లపై వివిధ పరిమితులు విధించబడ్డాయి, ఉదాహరణకు, ఇటీవలి వరకు లూప్‌లు మరియు సబ్‌ట్రౌటీన్‌లను వ్రాయడం సాధ్యం కాదు మరియు సూచనల సంఖ్య 4096కి పరిమితం చేయబడింది (ఇప్పుడు ప్రత్యేక ప్రోగ్రామ్‌లు మిలియన్ సూచనల వరకు లోడ్ చేయగలవు).

BPF పదకొండు వినియోగదారు యాక్సెస్ చేయగల 64-బిట్ రిజిస్టర్‌లను కలిగి ఉంది 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 ప్రోగ్రామ్ అమలు ఫలితాన్ని మరియు రిజిస్టర్‌లో తిరిగి ఇవ్వడానికి కూడా ఉపయోగించబడుతుంది r1 ప్రోగ్రామ్ సందర్భానికి పాయింటర్‌గా పంపబడుతుంది - ప్రోగ్రామ్ రకాన్ని బట్టి, ఇది ఉదాహరణకు, నిర్మాణం కావచ్చు struct xdp_md (XDP కోసం) లేదా నిర్మాణం struct __sk_buff (వివిధ నెట్‌వర్క్ ప్రోగ్రామ్‌ల కోసం) లేదా నిర్మాణం struct pt_regs (వివిధ రకాల ట్రేసింగ్ ప్రోగ్రామ్‌ల కోసం) మొదలైనవి.

కాబట్టి, మేము మ్యాప్‌ల రూపంలో రిజిస్టర్‌లు, కెర్నల్ హెల్పర్‌లు, స్టాక్, కాంటెక్స్ట్ పాయింటర్ మరియు షేర్డ్ మెమరీని కలిగి ఉన్నాము. పర్యటనలో ఇవన్నీ ఖచ్చితంగా అవసరమని కాదు, కానీ ...

వివరణను కొనసాగించండి మరియు ఈ వస్తువులతో పనిచేయడానికి కమాండ్ సిస్టమ్ గురించి మాట్లాడండి. అన్నీ (దాదాపు అన్ని) BPF సూచనలు స్థిరమైన 64-బిట్ పరిమాణాన్ని కలిగి ఉంటాయి. మీరు 64-బిట్ బిగ్ ఎండియన్ మెషీన్‌లో ఒక సూచనను చూస్తే మీరు చూస్తారు

చిన్న పిల్లలకు BPF, మొదటి భాగం: పొడిగించిన BPF

ఇది Code - ఇది సూచనల ఎన్‌కోడింగ్, Dst/Src రిసీవర్ మరియు మూలం యొక్క ఎన్‌కోడింగ్‌లు వరుసగా, Off - 16-బిట్ సంతకం ఇండెంటేషన్, మరియు Imm కొన్ని సూచనలలో (cBPF స్థిరాంకం K మాదిరిగానే) ఉపయోగించిన 32-బిట్ సంతకం పూర్ణాంకం. ఎన్కోడింగ్ 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 కోసం ఏదైనా బైనరీ ఫైల్‌ను మాన్యువల్‌గా విడదీయండి. కథనంలో తదుపరి విషయాలను ఏకీకృతం చేయడానికి, మేము వెరిఫైయర్, JIT కంపైలర్, క్లాసిక్ BPF యొక్క అనువాదం, అలాగే మ్యాప్‌లను అధ్యయనం చేస్తున్నప్పుడు, కాలింగ్ ఫంక్షన్‌లు మొదలైన వాటి గురించిన విభాగాలలోని వ్యక్తిగత సూచనలను కూడా కలుస్తాము.

మేము వ్యక్తిగత సూచనల గురించి మాట్లాడినప్పుడు, మేము కోర్ ఫైళ్ళను సూచిస్తాము bpf.h и bpf_common.h, ఇది BPF సూచనల సంఖ్యా సంకేతాలను నిర్వచిస్తుంది. మీ స్వంత మరియు/లేదా బైనరీలను అన్వయించడంపై నిర్మాణాన్ని అధ్యయనం చేస్తున్నప్పుడు, మీరు సంక్లిష్టత క్రమంలో క్రమబద్ధీకరించబడిన క్రింది మూలాలలో అర్థశాస్త్రాన్ని కనుగొనవచ్చు: అనధికారిక eBPF స్పెసిఫికేషన్, BPF మరియు XDP రిఫరెన్స్ గైడ్, ఇన్‌స్ట్రక్షన్ సెట్, డాక్యుమెంటేషన్/నెట్‌వర్కింగ్/filter.txt మరియు, వాస్తవానికి, Linux సోర్స్ కోడ్‌లో - వెరిఫైయర్, 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 ప్రోగ్రామ్‌లు ఫ్లైలో ప్రోగ్రామ్‌ను భర్తీ చేయడానికి మిమ్మల్ని అనుమతిస్తాయి, అనగా. సీక్వెన్స్ అటామిసిటీని అందిస్తాయి 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 సూచనల సమితిని తీసుకొని దానిని కెర్నల్‌లోకి లోడ్ చేస్తుంది. లోడ్ అవుతున్న సమయంలో, వెరిఫైయర్ ప్రారంభించబడుతుంది, ఆపై JIT కంపైలర్ మరియు విజయవంతంగా అమలు చేయబడిన తర్వాత, ప్రోగ్రామ్ ఫైల్ డిస్క్రిప్టర్ వినియోగదారుకు తిరిగి ఇవ్వబడుతుంది. అతని తర్వాత ఏమి జరుగుతుందో మునుపటి విభాగంలో చూశాము 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 వీరి గ్లోబల్ ID 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 ప్రోగ్రామ్‌లకు పాయింటర్‌లు లేదా నెట్‌వర్క్ ఇంటర్‌ఫేస్‌లకు పాయింటర్లు, పెర్ఫ్ ఈవెంట్‌లతో పని చేయడానికి మ్యాప్‌లు మొదలైనవి. పాఠకులను గందరగోళానికి గురిచేయకుండా మేము వాటి గురించి ఇక్కడ మాట్లాడము. ఇది కాకుండా, మేము సమకాలీకరణ సమస్యలను విస్మరిస్తాము, ఎందుకంటే ఇది మా ఉదాహరణలకు ముఖ్యమైనది కాదు. అందుబాటులో ఉన్న మ్యాప్ రకాల పూర్తి జాబితాను కనుగొనవచ్చు <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. సిస్టమ్‌లోని ఏదైనా ప్రోగ్రామ్ ఆదేశాన్ని ఉపయోగించి ఇప్పటికే ఉన్న మ్యాప్‌ను తెరవడానికి ఈ 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: ఇప్పటికే ఉన్న మ్యాప్‌ను దాని గ్లోబల్ 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, అనేక (ఎక్కువ లేదా తక్కువ అర్థరహితమైన) పరీక్ష ప్రోగ్రామ్‌లను వ్రాసి, అవన్నీ ఎలా పనిచేస్తాయో వివరంగా విశ్లేషిద్దాం. మ్యాప్‌లు, కెర్నల్ హెల్పర్‌లు, BTF మొదలైన వాటితో BPF ప్రోగ్రామ్‌లు ఎలా ఇంటరాక్ట్ అవుతాయో ఖచ్చితంగా ఈ క్రింది విభాగాలలో మరింత సులభంగా వివరించడానికి ఇది మమ్మల్ని అనుమతిస్తుంది.

సాధారణంగా ఉపయోగించే ప్రాజెక్ట్‌లు 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, స్లాష్‌కు ముందు మేము ప్రోగ్రామ్ రకం 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 మాకు రెండు ఎంపికలను అందిస్తుంది - దిగువ-స్థాయి API లేదా ఉన్నత-స్థాయి APIని ఉపయోగించండి. మేము రెండవ మార్గంలో వెళ్తాము, ఎందుకంటే BPF ప్రోగ్రామ్‌లను వారి తదుపరి అధ్యయనం కోసం తక్కువ ప్రయత్నంతో వ్రాయడం, లోడ్ చేయడం మరియు కనెక్ట్ చేయడం ఎలాగో తెలుసుకోవాలనుకుంటున్నాము.

ముందుగా, అదే యుటిలిటీని ఉపయోగించి దాని బైనరీ నుండి మన ప్రోగ్రామ్ యొక్క "అస్థిపంజరం"ని రూపొందించాలి bpftool — BPF ప్రపంచంలోని స్విస్ కత్తి (దీనిని అక్షరాలా తీసుకోవచ్చు, ఎందుకంటే BPF సృష్టికర్తలు మరియు నిర్వాహకులలో ఒకరైన డేనియల్ బోర్క్‌మాన్ స్విస్):

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

ఫైల్‌లో xdp-simple.skel.h మా ప్రోగ్రామ్ యొక్క బైనరీ కోడ్ మరియు నిర్వహణ కోసం విధులను కలిగి ఉంటుంది - లోడ్ చేయడం, జోడించడం, మా వస్తువును తొలగించడం. మా సాధారణ సందర్భంలో ఇది ఓవర్‌కిల్ లాగా కనిపిస్తుంది, కానీ ఆబ్జెక్ట్ ఫైల్‌లో అనేక BPF ప్రోగ్రామ్‌లు మరియు మ్యాప్‌లు ఉన్న సందర్భంలో కూడా ఇది పని చేస్తుంది మరియు ఈ జెయింట్ ELFని లోడ్ చేయడానికి మనం అస్థిపంజరాన్ని రూపొందించి, కస్టమ్ అప్లికేషన్ నుండి ఒకటి లేదా రెండు ఫంక్షన్‌లను కాల్ చేయాలి. ఇప్పుడు వెళ్దాం అని వ్రాస్తున్నారు.

ఖచ్చితంగా చెప్పాలంటే, మా లోడర్ ప్రోగ్రామ్ అల్పమైనది:

#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. ఆమె IDని కనుగొనండి:

# 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 ఒక మాక్రో ఉపయోగించి 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 లో మేము స్ట్రింగ్ వ్రాస్తాము 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

మేము డౌన్‌లోడ్ చేసిన ప్రోగ్రామ్ ID 669ని కలిగి ఉంది మరియు మేము ఇంటర్‌ఫేస్‌లో అదే IDని చూస్తాము 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 మేము శ్రేణిలోని సంబంధిత ఎంట్రీకి పాయింటర్‌ని పొందుతాము, దానిని మనం ఒకటి పెంచుతాము. రష్యన్‌లోకి అనువదించబడింది: ఇన్‌కమింగ్ ప్యాకెట్‌లను 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 { ... }...

మేము ప్రోగ్రామ్ అసెంబ్లర్‌ను చూస్తే, దాని విలువ మనకు కనిపిస్తుంది &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_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ని సృష్టించిన వ్యక్తులు ఆన్‌లైన్ లైనక్స్ కమ్యూనిటీకి చెందినవారు, అంటే వారు తమకు బాగా తెలిసిన దాన్ని ఉపయోగించారు (కానీ కాదు సాధారణ వ్యక్తులు) కెర్నల్‌తో పరస్పర చర్య చేయడానికి ఇంటర్‌ఫేస్: నెట్‌లింక్ సాకెట్లు, ఇది కూడ చూడు RFC3549. అమలు చేయడానికి సులభమైన మార్గం 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/cang

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.)

పహోల్ మరియు 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 అభివృద్ధి Linux నెట్‌వర్కింగ్ కమ్యూనిటీలో జరుగుతుంది మరియు అందువల్ల అన్ని మార్పులు త్వరగా లేదా తరువాత Linux నెట్‌వర్కింగ్ నిర్వహణదారు డేవిడ్ మిల్లర్ ద్వారా జరుగుతాయి. వాటి స్వభావాన్ని బట్టి - సవరణలు లేదా కొత్త ఫీచర్లు - నెట్‌వర్క్ మార్పులు రెండు కోర్లలో ఒకదానిలోకి వస్తాయి - 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, Linux కెర్నల్‌లో భాగంగా సరఫరా చేయబడింది. ఇది 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 సృష్టికర్తలు మరియు నిర్వాహకులలో ఒకరైన డేనియల్ బోర్క్‌మాన్ నుండి. ఇది మొదటి తీవ్రమైన వివరణలలో ఒకటి, ఇది ఇతరుల నుండి భిన్నంగా ఉంటుంది, దీనిలో డేనియల్ అతను ఏమి వ్రాస్తున్నాడో ఖచ్చితంగా తెలుసు మరియు అక్కడ ఎటువంటి తప్పులు లేవు. ప్రత్యేకించి, ఈ పత్రం బాగా తెలిసిన యుటిలిటీని ఉపయోగించి XDP మరియు TC రకాల BPF ప్రోగ్రామ్‌లతో ఎలా పని చేయాలో వివరిస్తుంది ip ప్యాకేజీ నుండి iproute2.

  2. డాక్యుమెంటేషన్/నెట్‌వర్కింగ్/filter.txt — క్లాసిక్ మరియు ఆపై పొడిగించిన BPF కోసం డాక్యుమెంటేషన్‌తో కూడిన అసలు ఫైల్. మీరు అసెంబ్లీ భాష మరియు సాంకేతిక నిర్మాణ వివరాలను లోతుగా పరిశోధించాలనుకుంటే మంచి పఠనం.

  3. facebook నుండి BPF గురించి బ్లాగ్. అలెక్సీ స్టార్వోయిటోవ్ (eBPF రచయిత) మరియు Andrii Nakryiko - (నిర్వహించేవాడు) వ్రాసినట్లు ఇది చాలా అరుదుగా నవీకరించబడింది, కానీ సముచితంగా ఉంది libbpf).

  4. bpftool యొక్క రహస్యాలు. క్వెంటిన్ మొన్నెట్ నుండి వినోదభరితమైన ట్విట్టర్ థ్రెడ్ ఉదాహరణలు మరియు bpftoolని ఉపయోగించే రహస్యాలు.

  5. BPFలోకి ప్రవేశించండి: పఠన సామగ్రి జాబితా. క్వెంటిన్ మొన్నెట్ నుండి BPF డాక్యుమెంటేషన్‌కు లింక్‌ల యొక్క పెద్ద (మరియు ఇప్పటికీ నిర్వహించబడుతున్న) జాబితా.

మూలం: www.habr.com

ఒక వ్యాఖ్యను జోడించండి