ebpf學(xué)習(xí)(2)

本篇介紹

在使用ebpf時(shí),如何在用戶態(tài)和內(nèi)核態(tài)傳遞數(shù)據(jù)呢? 本篇介紹一個(gè)方法,就是使用map.

map 的基礎(chǔ)操作

創(chuàng)建

用戶態(tài)和內(nèi)核態(tài)均可以創(chuàng)建map, 最直接的方法使用bpf系統(tǒng)調(diào)用, 不過(guò)這個(gè)api 并不是posix api, 因此需要通過(guò)syscall才行, 如下就是libbpf中的調(diào)用方式:

static inline int sys_bpf(enum bpf_cmd cmd, union bpf_attr *attr,
              unsigned int size)
{
    return syscall(__NR_bpf, cmd, attr, size);
}

在bpf.h 中也提供了一個(gè)接口, 如下

LIBBPF_API int bpf_map_create(enum bpf_map_type map_type,
                              const char *map_name,
                              __u32 key_size,
                              __u32 value_size,
                              __u32 max_entries,
                              const struct bpf_map_create_opts *opts);

這兒需要注意下,以前的接口名字是bpf_create_map, 甚至一些bpf學(xué)習(xí)材料也是這個(gè)名字, 后來(lái)修改成了bpf_map_create, 同時(shí)接口參數(shù)也變了, 在寫代碼時(shí)需要按照在對(duì)應(yīng)libbpf 庫(kù)的bpf.h中進(jìn)行確認(rèn).

bpf_map_type 支持的類型如下:

enum bpf_map_type {
    BPF_MAP_TYPE_UNSPEC,
    BPF_MAP_TYPE_HASH,
    BPF_MAP_TYPE_ARRAY,
    BPF_MAP_TYPE_PROG_ARRAY,
    BPF_MAP_TYPE_PERF_EVENT_ARRAY,
    BPF_MAP_TYPE_PERCPU_HASH,
    BPF_MAP_TYPE_PERCPU_ARRAY,
    BPF_MAP_TYPE_STACK_TRACE,
    BPF_MAP_TYPE_CGROUP_ARRAY,
    BPF_MAP_TYPE_LRU_HASH,
    BPF_MAP_TYPE_LRU_PERCPU_HASH,
    BPF_MAP_TYPE_LPM_TRIE,
    BPF_MAP_TYPE_ARRAY_OF_MAPS,
    BPF_MAP_TYPE_HASH_OF_MAPS,
    BPF_MAP_TYPE_DEVMAP,
    BPF_MAP_TYPE_SOCKMAP,
    BPF_MAP_TYPE_CPUMAP,
    BPF_MAP_TYPE_XSKMAP,
    BPF_MAP_TYPE_SOCKHASH,
    BPF_MAP_TYPE_CGROUP_STORAGE,
    BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
    BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE,
    BPF_MAP_TYPE_QUEUE,
    BPF_MAP_TYPE_STACK,
    BPF_MAP_TYPE_SK_STORAGE,
    BPF_MAP_TYPE_DEVMAP_HASH,
    BPF_MAP_TYPE_STRUCT_OPS,
    BPF_MAP_TYPE_RINGBUF,
    BPF_MAP_TYPE_INODE_STORAGE,
    BPF_MAP_TYPE_TASK_STORAGE,
    BPF_MAP_TYPE_BLOOM_FILTER,
};

map_name名字中不能包含空格,出現(xiàn)空格會(huì)被報(bào)錯(cuò)誤22. opts 用不到的話,寫成NULL就行.
綜合上述信息,創(chuàng)建map的例子如下:

  int fd = bpf_map_create(BPF_MAP_TYPE_HASH, "mymap", sizeof(int), sizeof(int), 100, NULL);

更新

在用戶態(tài)和內(nèi)核態(tài)都可以更新map,對(duì)應(yīng)的方法都是bpf_map_update_elem, 不過(guò)內(nèi)核和用戶態(tài)的參數(shù)會(huì)不一樣, 因?yàn)閮?nèi)核可以直接訪問(wèn)map結(jié)構(gòu),而用戶態(tài)只能通過(guò)fd的形式. 這兒我們先看下用戶態(tài)的函數(shù)簽名:

LIBBPF_API int bpf_map_update_elem(int fd, const void *key, const void *value,
                                   __u64 flags);

這兒的flags有幾種取值:

 *      **BPF_ANY**
 *          Create a new element or update an existing element.
 *      **BPF_NOEXIST**
 *          Create a new element only if it did not exist.
 *      **BPF_EXIST**
 *          Update an existing element.
 *      **BPF_F_LOCK**
 *          Update a spin_lock-ed map element.

查詢與刪除

map同樣也支持內(nèi)核和用戶態(tài)查詢, 方法也一樣,區(qū)別也是內(nèi)核是直接訪問(wèn)map結(jié)構(gòu),用戶態(tài)是訪問(wèn)fd,函數(shù)如下:

LIBBPF_API int bpf_map_lookup_elem(int fd, const void *key, void *value);

刪除也同理,用戶態(tài)函數(shù)如下:

LIBBPF_API int bpf_map_delete_elem(int fd, const void *key);

到了這兒用一個(gè)demo演示下map的crud操作, 代碼如下

#include <stdio.h>
#include <linux/bpf.h>
//#include <bpf/bpf_helpers.h>
#include <bpf/bpf.h>
#include <string.h>
#include <errno.h>

#ifdef SEC
#undef SEC
#endif

#define SEC(NAME) __attribute__((section(NAME), used))

int main() {
  int fd = bpf_map_create(BPF_MAP_TYPE_HASH, "mymap", sizeof(int), sizeof(int), 100, NULL);

  int key, value, result;
  key = 1, value = 2;

  result = bpf_map_update_elem(fd, &key, &value, BPF_ANY);
  printf("map update result %d with bpf any, %s\n", result, strerror(errno));

  result = bpf_map_update_elem(fd, &key, &value, BPF_EXIST);
  printf("map update result %d with %d, %s\n", result, value, strerror(errno));

  result = bpf_map_lookup_elem(fd, &key, &value);
  printf("map lookup %d value is %d, %s\n", result, value, strerror(errno));

  result = bpf_map_delete_elem(fd, &key);  
  printf("map delete %d, %s\n", result, strerror(errno));
  return 0;
}

這次就按照普通c代碼方式編譯就行,不需要編譯成bpf了.運(yùn)行效果如下:

shanks@shanks-ThinkPad-T460s:~/Documents/01code/ebpf/chapter02$ sudo ./a.out 
map update result 0 with bpf any, Success
map update result 0 with 2, Success
map lookup 0 value is 2, Success
map delete 0, Success

遍歷

bpf也支持遍歷map, 不過(guò)這個(gè)操作只在用戶態(tài)支持. 另外bpf的遍歷和我們常見(jiàn)的遍歷不太一樣,比如c++的map,我們遍歷的話,就是挨個(gè)k遍歷就行,如果在遍歷的時(shí)候伴隨著刪除操作,就需要格外小心,防止迭代器無(wú)效.
而bpf就不用擔(dān)心這個(gè)問(wèn)題,技巧就在函數(shù)簽名中:

LIBBPF_API int bpf_map_get_next_key(int fd, const void *key, void *next_key);

第一個(gè)key是開(kāi)始遍歷的k,如果指定一個(gè)在map中不存在的k,就會(huì)從頭遍歷. next_key 是下一個(gè)將要遍歷的k,如果在遍歷的時(shí)候被刪除了,就會(huì)導(dǎo)致再次從頭遍歷. 通過(guò)這個(gè)機(jī)制就不怕遍歷的時(shí)候刪除了.

我們用一個(gè)demo看下:

#include <stdio.h>
#include <linux/bpf.h>
//#include <bpf/bpf_helpers.h>
#include <bpf/bpf.h>
#include <string.h>
#include <errno.h>

#ifdef SEC
#undef SEC
#endif

#define SEC(NAME) __attribute__((section(NAME), used))

int main() {
  int fd = bpf_map_create(BPF_MAP_TYPE_HASH, "mymap", sizeof(int), sizeof(int), 100, NULL);

  int key, value;
  for (key = 1; key < 5; key ++) {
    value = key + 100;
    bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST);
    printf("insert key %d, value %d\n", key, value);
  }

  key = -1;
  value = -1;
  int next_key = 0;
  while(bpf_map_get_next_key(fd, &key, &next_key) == 0) {
    bpf_map_lookup_elem(fd, &key, &value);
    printf("key is %d, value is %d, next_key is %d\n", key, value, next_key);
    if (next_key == 4) {
      bpf_map_delete_elem(fd, &next_key);
    }
    key = next_key;
  }
  return 0;
}

輸出如下:

insert key 1, value 101
insert key 2, value 102
insert key 3, value 103
insert key 4, value 104
key is -1, value is -1, next_key is 2
key is 2, value is 102, next_key is 3
key is 3, value is 103, next_key is 4
key is 4, value is 103, next_key is 2
key is 2, value is 102, next_key is 3
key is 3, value is 103, next_key is 1

查找并刪除

如果需要查找某個(gè)值,查找到后就刪除這個(gè)值,通過(guò)前面介紹的方法也可以實(shí)現(xiàn),bpf中也提供了一個(gè)方法可以一下子做到,函數(shù)如下:

LIBBPF_API int bpf_map_lookup_and_delete_elem(int fd, const void *key,
                          void *value);

BPF map 類型介紹

BPF_MAP_TYPE_HASH: 支持動(dòng)態(tài)增刪改查
BPF_MAP_TYPE_ARRAY: 類似與數(shù)組,key 就是固定的索引,大小不能修改,也就是不支持刪除,支持修改值
BPF_MAP_TYPE_PROG_ARRAY:用來(lái)突破bpf虛擬機(jī)指令限制的方法, 在map中存放加載指令的fd.
BPF_MAP_TYPE_PERF_EVENT_ARRAY:本質(zhì)上是一個(gè)ringbuffer,用來(lái)讓內(nèi)核給用戶態(tài)傳遞trace數(shù)據(jù)
Per-CPU Hash Maps:和BPF_MAP_TYPE_HASH類似,差別在于是per cpu變量,也就是每個(gè)cpu一個(gè).
Per-CPU Array Maps:類似與BPF_MAP_TYPE_ARRAY
BPF_MAP_TYPE_STACK_TRACE:存儲(chǔ)運(yùn)行進(jìn)程的堆棧數(shù)據(jù)
BPF_MAP_TYPE_CGROUP_ARRAY:用來(lái)記錄cgroup節(jié)點(diǎn)fd,方便共享cgroup信息
BPF_MAP_TYPE_LRU_HASH and BPF_MAP_TYPE_LRU_PERCPU_HASH: 支持LRU容量管控的hash,不過(guò)percpu并不是每個(gè)cpu一個(gè)hash,而是每個(gè)cpu一個(gè)lru cache,這樣可以讓所有cpu 最常用的值得到保留
BPF_MAP_TYPE_LPM_TRIE:按照LPM(longest prefix match)算法查找最匹配的key
BPF_MAP_TYPE_ARRAY_OF_MAPS and BPF_MAP_TYPE_HASH_OF_MAPS: 支持存放map的map,不過(guò)最多就一層,不支持存放map的map的map
BPF_MAP_TYPE_DEVMAP:存放網(wǎng)絡(luò)設(shè)備的引用,用來(lái)網(wǎng)絡(luò)包重定向
BPF_MAP_TYPE_CPUMAP: 也是重來(lái)網(wǎng)絡(luò)包重定向的,不過(guò)存放的是cpu的引用
BPF_MAP_TYPE_XSKMAP:也是網(wǎng)絡(luò)包重定向,存放的是socket的引用
BPF_MAP_TYPE_SOCKMAP and BPF_MAP_TYPE_SOCKHASH:也是用來(lái)存放打開(kāi)socket的應(yīng)用,實(shí)現(xiàn)網(wǎng)絡(luò)包重定向
BPF_MAP_TYPE_CGROUP_STORAGE and BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE:不同cgroup的邏輯之間協(xié)作場(chǎng)景
BPF_MAP_TYPE_REUSEPORT_SOCKARRAY: 存儲(chǔ)可重用的socket引用
BPF_MAP_TYPE_QUEUE:就是一個(gè)queue,此時(shí)key為0,只存儲(chǔ)value就行
BPF_MAP_TYPE_STACK:就是一個(gè)stack,此時(shí)key也是0,只存儲(chǔ)value就行

BPF虛擬文件系統(tǒng)

用前面的介紹我們可以看到,bpf的map創(chuàng)建成功后返回的是fd,那應(yīng)該會(huì)在文件系統(tǒng)中存在對(duì)應(yīng)的路徑,實(shí)際上也是. BPF也支持持久化map,這樣即使創(chuàng)建這個(gè)map的進(jìn)程退出了,map信息也可以繼續(xù)保留.
接下來(lái)我們就用demo演示下:
準(zhǔn)備2個(gè)代碼,一個(gè)負(fù)責(zé)存,一個(gè)負(fù)責(zé)讀.
存map的代碼如下:


#include <stdio.h>
#include <linux/bpf.h>
#include <bpf/bpf.h>
#include <string.h>
#include <errno.h>

static const char *file_path = "/sys/fs/bpf/mapfile";

int main() {
  int fd = bpf_map_create(BPF_MAP_TYPE_HASH, "mymap", sizeof(int), sizeof(int), 100, NULL);
  printf("bpf map create result %d\n", fd);
  int key, value;
  for (key = 1; key < 5; key ++) {
    value = key + 100;
    bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST);
    printf("insert key %d, value %d\n", key, value);
  }

  int pinned = bpf_obj_pin(fd, file_path);
  printf("pin ojb at %s, result is %d\n", file_path, pinned);
  return 0;
}

執(zhí)行后,結(jié)果如下:

bpf map create result 3
insert key 1, value 101
insert key 2, value 102
insert key 3, value 103
insert key 4, value 104
pin ojb at /sys/fs/bpf/mapfile, result is 0

查看對(duì)應(yīng)的路徑,也可以看到存在期望的文件節(jié)點(diǎn):

root@shanks-ThinkPad-T460s:/sys/fs/bpf# ls -al |grep map
-rw------- 1 root root 0  8月 31 18:39 mapfile

接下來(lái)再看下讀的代碼:

#include <stdio.h>
#include <linux/bpf.h>
#include <bpf/bpf.h>
#include <string.h>
#include <errno.h>

static const char *file_path = "/sys/fs/bpf/mapfile";

int main() {
  int fd = bpf_obj_get(file_path);
  printf("bpf obj get %s, fd %d\n", file_path, fd);
  int key, value;
  key = -1;
  value = -1;
  int next_key = 0;
  while(bpf_map_get_next_key(fd, &key, &next_key) == 0) {
    bpf_map_lookup_elem(fd, &key, &value);
    printf("key is %d, value is %d, next_key is %d\n", key, value, next_key);
    key = next_key;
  }
  bpf_map_lookup_elem(fd, &key, &value);
  printf("key is %d, value is %d, next_key is %d\n", key, value, next_key);
  return 0;
}

執(zhí)行的結(jié)果如下:

bpf obj get /sys/fs/bpf/mapfile, fd 3
key is -1, value is -1, next_key is 3
key is 3, value is 103, next_key is 4
key is 4, value is 104, next_key is 2
key is 2, value is 102, next_key is 1
key is 1, value is 101, next_key is 1
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 176,283評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,953評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,714評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,186評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,410評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,776評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,210評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,642評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,878評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,654評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評(píng)論 2 373

推薦閱讀更多精彩內(nèi)容