[翻译] BPF系统调用API v14

BPF系统调用API v14

译文作者:zhangzl2013
译文链接:http://www.cnblogs.com/zhangzl2013/p/BPF_system_call_14.html
原文作者:Jonathan Corbet
原文链接:The BPF system call API,  version 14
本文有可能会被转载,从而导致评论留言的碎片化。想参与评论和探讨的同学,请找到原文或译文的原始地址,与原文或译文作者互动讨论。

Berkeley Packet Filter(BPF)方面的变化特别快。在七月份关于它的文章中,介绍了它的第二版加入了bpf()系统调用。两个月后的今天,已经是第14版了;改了许多东西,也去掉了一些功能,这样对复审代码来说补丁也能比较容易审查。现在主要的系统调用已经接近于并入主线了。现在也是时候重新审视一下这个内核即将加入的新功能,也希望它不要再改了。

BPF的开发者Alexei Starovoitov为了合并进主线做出了巨大努力——他在过去的两个月里发布了12个版本,并且做了大量的更改和测试。他对于更改建议的相应都很积极,但有些开发者觉得他有些过于固执。这病不能阻止他的工作并入主线,也不会影响最终的合并。

跟以前的版本一样,BPG功能是由一个多功能的系统调用完成的,但是现在又有了很大改动:

#include <linux/bpf.h>
    int bpf(int cmd, union bpf_attr *attr, unsigned int size);

正如Ingo Molnar建议的,最核心的变化是使用一个大的联合体结构来保存bpf()各种功能需要的各种类型的参数。联合体的使用与这个系统调用的特定命令有关。

当前补丁集中的大部分操作都与映射表的管理有关——映射表是BPF程序和用户空间共享的一个数据数组。整个过程以用BPF_MAP_CREATE创建映射表开始。在这个命令中,这个系统调用需要的信息由bpf_attr联合体中的如下结构给出:

struct {                           /* anonymous struct used by BPF_MAP_CREATE command */
       __u32             map_type;
       __u32             key_size;    /* size of key in bytes */
       __u32             value_size;  /* size of value in bytes */
       __u32             max_entries; /* max number of entries in a map */
    };

map_type域用于描述映射表的类型。计划中要包含很多种类型,包括哈希数组,普通数组,bloom filter和radix tree。目前的实现中只支持哈希类型,而且连这个类型也不是完全实现好了。key_size域和value_size域告诉代码键和值的大小分别是多少,max_entries则限制了映射表中最多能保存多少个条目。

当调用bpf()来创建映射表时,bpf_attr联合体的其他结构都得置为零,size域设置为整个联合体的大小。这个规定是所有bpf功能共有的;目的是为了以后能更好的扩展BPF。如果有新的域加入,新的程序只要提供相应的信息就行,而老的程序继续把不用的域置零,就不会有问题了。

成功创建映射表之后,bpf()函数会返回一个文件描述符,用于指代这个映射表。

有一系列的命令作用于映射表的各个条目;这些命令都利用bpf_attr联合体:

struct { /* anonymous struct used by BPF_MAP_*_ELEM commands */
__u32 map_fd;         __aligned_u64 key;         union {                 __aligned_u64 value;                 __aligned_u64 next_key;         };    };

对于这些操作,map_fd域都表示文件描述符,key域是指向相关键的指针。BPF_MAP_UPDATE_ELEM命令用于向映射表中存储一个条目;value域是要存储的数据的指针。BPF_MAP_LOOKUP_ELEM命令用于查找条目;如果此条目存在,则value域返回存储的指针。BPF_MAP_DELETE_ELEM命令用于删除条目。

枚举映射表可以使用BPF_MAP_GET_NEXT_KEY命令;它会返回指定key的下一个key。“下一个”的意义根据映射表的类型而异。如果指定的key没有找到,则next_key会被置为映射表中的第一个key,所以开始遍历时可以使用一个无意义的key作为初始值。

注意,并没有命令可以用于删除映射表。只要关闭文件描述符就可以,当所有文件描述符都关闭了,没有BPF程序引用映射表时,它就会被删除掉。

BPF_PROG_LOAD命令可以把BPF程序加载到内核中。相关结构体是:

struct {    /* anonymous struct used by BPF_PROG_LOAD command */
      __u32         prog_type;
      __u32         insn_cnt;
      __aligned_u64 insns;     /* 'const struct bpf_insn *' */
      __aligned_u64 license;   /* 'const char *' */
      __u32         log_level; /* verbosity level of eBPF verifier */
      __u32         log_size;  /* size of user buffer */
      __aligned_u64 log_buf;   /* user supplied 'char *' buffer */
  };

prog_type域表示程序将运行的上下文;它包括那些数据和辅助函数可供程序使用。BPF_PROG_TYPE_SOCKET用于使用socket的程序,BPF_PROG_TYPE_TRACING用于跟踪过滤器。程序的大小(以指令数衡量)由insn_cnt标识,insns域这个指针则指向程序本身。license域这个指针指向许可的描述信息,它未来可以用于限制非GPL程序的功能。

所有程序在加载时必须同时传递BPF验证。这个验证确保这个程序不会损害整个系统。它会防止程序随意访问数据,禁用含有循环的程序,等等。开发者如果想知道为什么程序被这个验证拒绝,可以设置一个长度为log_size的日志存储区,并设置log_buf指向它。然后再把log_level设置为非零值来真正开启日志功能。

先前的补丁集中的“fixup”数组已经去掉了。那个数组用于标识出正在引用BPF映射表文件描述符的置零;指明正在被BPF验证器内部指针使用的指令。当前版本的补丁集中针对映射表的访问定义了新指令。BPF验证器能直接识别这些指令,从而不再需要在用户空间标识出它们。

在第14版的补丁集中,BPF程序一旦加载,就不能再向它附着其他事件。一旦BPF基本功能通过审核,并入主线,那个功能才会加进去。这个时间也为期不远了,对这个API感兴趣的开发者们已经对它越来越满意了。并入3.18似乎有些激进,但是并入3.19就比较现实了。

更新:9月26号,这个系列的补丁已经被net-next tree接受了,看来它势必会出现在3.18内核中了。

-- 结束 --

posted @ 2014-10-06 23:16 zhangzl2013 阅读(...) 评论(...) 编辑 收藏