一.简要说明:

1.netmap API主要为两个头文件netmap.h 和netmap_user.h ,当解压下载好的netmap程序后,在./netmap/sys/net/目录下,本文主要对这两个头文件进行分析。

2.我们从netmap_user.h头文件开始看起。

二.likely()和unlikely()

这两个宏定义是对编译器做优化的,并不会对变量做什么改变。后面看到这两个宏的调用自动忽略就好了。

  1. #ifndef likely
  2. #define likely(x) __builtin_expect(!!(x), 1)
  3. #define unlikely(x) __builtin_expect(!!(x), 0)
  4. #endif /* likely and unlikely */

三.netmap.h头文件

1.netmap.h被netmap_user.h调用,里面定义了一些宏和几个主要的结构体,如nmreq{}, netmap_if{}, netmap_ring{},  netmap_slot{}。

2.一个网卡(或者网络接口)只有一个netmap_if{}结构,在使用mmap()申请的共享内存中,通过netmap_if{}结构可以访问到任何一个发送/接收环(也就是netmap_ring{}结构,一个netmap_if{}可以对应多发送/接收环,这应该和物理硬件有关 ,我在虚拟机下只有一对环,在真实主机上有两队环)。

3.找到netmap_ring{}的地址后,我们就可以找到环中每一个buffer的地址(buffer里面存储的是将要发送/接收的数据包)。后面会讲解这是如何实现的。

4.通过一个nifp是如何访问到多个收/发环的,通过一个ring如何找到多个不同的buffer地址的,其实都是通过存储这些结构体相邻的后面一部分空间实现。(申请共享内存的时候,这些均已被设计好)

四.几个重要的宏定义

1._NETMAP_OFFSET

  1. #define _NETMAP_OFFSET(type, ptr, offset) \
  2. ((type)(void *)((char *)(ptr) + (offset)))

解释:该宏定义的作用是将ptr指针(强转成char *类型)向右偏移offset个字节,再将其转化为指定的类型type。

2.NETMAP_IF

  1. #define NETMAP_IF(_base, _ofs) _NETMAP_OFFSET(struct netmap_if *, _base, _ofs)

解释:该宏定义将_base指针向右偏移_ofs个字节后,强转为netmap_if *类型返回。在nemap中通过此宏得到d->nifp的地址。

3.NETMAP_TXRING

  1. #define NETMAP_TXRING(nifp, index) _NETMAP_OFFSET(struct netmap_ring *, \
  2. nifp, (nifp)->ring_ofs[index] )

解释:1.通过该宏定义,可以找到nifp的第index个发送环的地址(index是从0开始的),ring_ofs[index]为偏移量,由内核生成。

2.其中,我们注意到struct netmap_if{}最后面只定义了const ssize_t ring_ofs[0],实际上其它的netmap环的偏移量都写在了该结构体后面的内存地址里面,直接访问就可以了。

4.NETMAP_RXRING

  1. #define NETMAP_RXRING(nifp, index) _NETMAP_OFFSET(struct netmap_ring *, \
  2. nifp, (nifp)->ring_ofs[index + (nifp)->ni_tx_rings + ] )

解释:通过该宏定义,可以找到nifp的第index个接收环的地址,其中(nifp)->ring_ofs[]里面的下标为index+(nifp)->ni_tx_rings+1,正好与发送环的偏移量区间隔开1个。(我想这应该是作者特意设计的)

5.NETMAP_BUF

  1. #define NETMAP_BUF(ring, index) \
  2. ((char *)(ring) + (ring)->buf_ofs + ((index)*(ring)->nr_buf_size))

解释:1.通过该宏定义,可以找到ring这个环的第index个buffer的地址(buffer里面存的就是我们接收/将发送的完整数据包),每个buffer占的长度是2048字节(在(ring)->nr_buf_size也给出了)。

2.其中(ring) ->buf_ofs是固定的偏移量,不同的环这个值不相同,但所有的(char *)(ring)+(ring)->buf_ofs会指向同一个地址,也就是存放buffer的连续内存的开始地址(d->buf_start会指向该地址)。

6.NETMAP_BUF_IDX

  1. #define NETMAP_BUF_IDX(ring, buf) \
  2. ( ((char *)(buf) - ((char *)(ring) + (ring)->buf_ofs) ) / \
  3. (ring)->nr_buf_size )

解释:在讲NETMAP_BUF的时候我们说(char *)(ring) + (ring)->buf_ofs)总会指向存放buffer的起始位置(无论是哪一个环),在这段内存中将第一个buffer下标标记为0的话,NETMAP_BUF_IDX计算的恰好是指针buf所指buffer的下标。

上面几个宏一时没弄懂也没关系,下面调用的时候还会提的。

五.nm_open函数

1.调用nm_open函数时,如:nmr = nm_open("netmap:eth0", NULL, 0, NULL); nm_open()会对传递的ifname指针里面的字符串进行分析,提取出网络接口名。

2.nm_open()会对struct nm_desc *d申请内存空间,并 通过d->fd = open(NETMAP_DEVICE_NAME, O_RDWR);打开一个特殊的设备/dev/netmap来创建文件描述符d->fd。

3.通过ioctl(d->fd, NIOCREGIF, &d->req)语句,将d->fd绑定到一个特殊的接口,并对d->req结构体里面的成员做初始化,包括a.在共享内存区域中 nifp 的偏移,b.共享区域的大小nr_memsize,c.tx/rx环的大小nr_tx_slots/nr_rx_slots(大小为256),d.tx/rx环的数量nr_tx_rings、nr_rx_rings(视硬件性能而定)等。

4.接着在if ((!(new_flags & NM_OPEN_NO_MMAP) || parent) && nm_mmap(d, parent))语句中调用nm_mmap函数,继续给d指针指向的内存赋值。

六.nm_mmap函数

nm_mmap()源码:

  1. static int nm_mmap(struct nm_desc *d, const struct nm_desc *parent)
  2. {
  3. //XXX TODO: check if mmap is already done
  4.  
  5. if (IS_NETMAP_DESC(parent) && parent->mem && parent->req.nr_arg2 == d->req.nr_arg2)
  6. {
  7. /* do not mmap, inherit from parent */
  8. D("do not mmap, inherit from parent");
  9. d->memsize = parent->memsize;
  10. d->mem = parent->mem;
  11. } else
  12. {
  13. /* XXX TODO: 检查如果想申请的内存太大 (or there is overflow) */
  14. d->memsize = d->req.nr_memsize; /* 将需要申请的内存大小赋值给d->memsize */
  15. d->mem = mmap(, d->memsize, PROT_WRITE | PROT_READ, MAP_SHARED, d->fd, ); /* 申请共享内存 */
  16. if (d->mem == MAP_FAILED)
  17. {
  18. goto fail;
  19. }
  20. d->done_mmap = ;
  21. }
  22. {
  23. struct netmap_if *nifp = NETMAP_IF(d->mem, d->req.nr_offset); /*通过d->req.nr_offset这个偏移量的到nifp的地址,NETMAP_IF前面说过*/
  24. int i;
  25. /*
  26. *for(i=0; i<=2; i++)
  27. * printf("ring_ofs[%d]:0x%x\n",i,nifp->ring_ofs[i]); // 这里是我自己加的,为了手动计算收/发环的偏移量
  28. */
  29. struct netmap_ring *r = NETMAP_RXRING(nifp,); //对nifp,找接收包的环r,因为index为0,所以省略了
  30.  
  31. *(struct netmap_if **) (uintptr_t) &(d->nifp) = nifp; //对d->nifp赋值,虽然d->nifp使用const定义的,但对其取地址再强值类型转换后,依然可以对其指向的空间进行操作
  32. *(struct netmap_ring **) (uintptr_t) &d->some_ring = r; //同理,对d->some_ring进行赋值,此处指向了第一个接受(rx)环。
  33. //printf("buf_ofs:0x%x\n", (u_int)r->buf_ofs);
  34. *(void **) (uintptr_t) &d->buf_start = NETMAP_BUF(r, );//计算第一个buffer的地址,并存入d->buf_start指针中
  35. *(void **) (uintptr_t) &d->buf_end = (char *) d->mem + d->memsize; //计算共享区间的最后一个地址,赋值给d->buf_end
  36. }
  37.  
  38. return ;
  39.  
  40. fail: return EINVAL;
  41. }

其中:

1.nifp为申请的共享内存首地址d->mem向右偏移d->req.nr_offset(该值在调用前面的ioctl()时得到)得到。并且一个网络接口(网卡)只对应一个nifp。(使用宏NETMAP_IF计算)

2.得到的nifp的地址,nifp结构体里最后定义的ring_ofs[0]以及接下来内存中的ring_ofs[1],ring_ofs[2]...,这些内存中存储的是访问每一个环(tx or rx ring)的偏移量,通过这个偏移量我们可以得到每一个环的地址(使用宏NETMAP_RXRING/NETMAP_TXRING进行计算)。

3.得到每个收/发环的地址了,netmap_ring结构体最后面有一个struct netmap_slot slot[0];,通过slot[0],后面内存的slot[1],slot[2],slot[3]...,取出里面的偏移量就可以得到每一个buffer(也叫数据包槽)的地址了(使用宏NETMAP_BUF计算得到)。    到这里,netmap如何访问到内存槽中的每一个buffer的,我们都知道了。实际上netmap运行的数据结构就和下图描述的一样:

4.在struct nm_desc中,nifp,some_ring,buf_start,buf_end等指针都定义为const的,但我们通过对其取地址再强转指针的方式去往这些指针指向的内存中赋值。

注:在nm_mmap()中使用mmap()申请共享的时候,这些数据结构里数据的设计是内核模块就已写好了的,我们在这里其实是在做验证。

七.nm_nextpkt函数

1.nm_nextpkt()是用来接收网卡上到来的数据包的函数。

2.nm_nextpkt()会将所有rx环都检查一遍,当发现有一个rx环有需要接收的数据包时,得到这个数据包的地址,并返回。所以nm_nextpkt()每次只能取一个数据包。

nm_nextpkt()源代码:

  1. static u_char *nm_nextpkt(struct nm_desc *d, struct nm_pkthdr *hdr)
  2. {
  3. int ri = d->cur_rx_ring; //当前的接收环的编号
  4. do
  5. {
  6. /* compute current ring to use */
  7. struct netmap_ring *ring = NETMAP_RXRING(d->nifp, ri); //得到当前rx环的地址
  8. if (!nm_ring_empty(ring)) //判断环里是否有新到的包
  9. {
  10. u_int i = ring->cur; //当前该访问哪个槽(buffer)了
  11. u_int idx = ring->slot[i].buf_idx; //得到第i个buffer的下标
  12. //printf("%d\n", idx);
  13. u_char *buf = (u_char *) NETMAP_BUF(ring, idx); //得到存有到来数据包的地址
  14.  
  15. // __builtin_prefetch(buf);
  16. hdr->ts = ring->ts;
  17. hdr->len = hdr->caplen = ring->slot[i].len;
  18. ring->cur = nm_ring_next(ring, i); //ring->cur向后移动一位
  19. /* we could postpone advancing head if we want
  20. * to hold the buffer. This can be supported in
  21. * the future.
  22. */
  23. ring->head = ring->cur;
  24. d->cur_rx_ring = ri; //将当前环(d->cur_rx_ring)指向第ri个(因为可能有多个环)。
  25. return buf; //将数据包地址返回
  26. }
  27. ri++;
  28. if (ri > d->last_rx_ring) //如果ri超过了rx环的数量,则再从第一个rx环开始检测是否有包到来。
  29. ri = d->first_rx_ring;
  30. } while (ri != d->cur_rx_ring);
  31. return NULL; /* 什么也没发现 */
  32. }

八.nm_inject函数

1.nm_inject()是用来往共享内存中写入待发送的数据包数据的。数据包经共享内存拷贝到网卡,然后发送出去。所以nm_inject()是用来发包的。

2.nm_inject()也会查找所有的发送环(tx环),找到一个可以发送的槽,就将数据包写入并返回,所以每次函数调用也只能发送一个包。

源代码:

  1. static int nm_inject(struct nm_desc *d, const void *buf, size_t size)
  2. {
  3. u_int c, n = d->last_tx_ring - d->first_tx_ring + ;
  4.  
  5. for (c = ; c < n; c++)
  6. {
  7. /* 计算当前的环去使用(compute current ring to use) */
  8. struct netmap_ring *ring;
  9. uint32_t i, idx;
  10. uint32_t ri = d->cur_tx_ring + c; //该访问第几个tx环了
  11.  
  12. if (ri > d->last_tx_ring) //当超过访问的tx环的下标范围时,从头开始访问
  13. ri = d->first_tx_ring;
  14. ring = NETMAP_TXRING(d->nifp, ri); //得到当前tx环的地址
  15. if (nm_ring_empty(ring)) //如果当前tx环是满的(ring->cur=ring->tail表示没地方存数据包了),就跳过
  16. {
  17. continue;
  18. }
  19. i = ring->cur; //当前要往哪个槽(槽指向buffer)中写入数据
  20. idx = ring->slot[i].buf_idx; //得到这个槽相对于buffer起始地址(d->buf_start)的下标编号
  21. ring->slot[i].len = size; //size为待发送数据包的长度
  22. nm_pkt_copy(buf, NETMAP_BUF(ring, idx), size); //将buf里存的数据包拷贝给ring这个环的第i个槽
  23. d->cur_tx_ring = ri;
  24. ring->head = ring->cur = nm_ring_next(ring, i); //将head和cur指向下一个槽
  25. return size;
  26. }
  27. return ; /* 失败 */
  28. }

九.nm_close函数

1.nm_close函数就是回收动态内存,回收共享内存,关闭文件描述符什么的了。

源代码:

  1. static int nm_close(struct nm_desc *d)
  2. {
  3. /*
  4. * ugly trick to avoid unused warnings
  5. */
  6. static void *__xxzt[] __attribute__ ((unused)) =
  7. { (void *) nm_open, (void *) nm_inject, (void *) nm_dispatch, (void *) nm_nextpkt };
  8.  
  9. if (d == NULL || d->self != d)
  10. return EINVAL;
  11. if (d->done_mmap && d->mem)
  12. munmap(d->mem, d->memsize); //释放申请的共享内存
  13. if (d->fd != -)
  14. {
  15. close(d->fd); //关闭文件描述符
  16. }
  17.  
  18. bzero(d, sizeof(*d)); //将d指向的空间全部置0
  19. free(d); //释放指针d指向的空间
  20. return ;
  21. }

012.对netmap API的解读的更多相关文章

  1. Node.js API 初解读(一)

    Node.JS API 初解读 Version: NodeJs v6.2.0 一. Assert 1.简介 Assert模块主要用于断言.如果表达式不符合预期,就抛出一个错误. 该模块用于编写程序的单 ...

  2. 010 使用netmap API接管网卡,接收数据包,回应ARP请求

    一.本文目的: 上一节中,我们已经在CentOS 6.7 上安装好了netmap,也能接收和发送包了,这节我们来调用netmap中的API,接管网卡,对网卡上收到的数据包做分析,并回应ARP请求. 二 ...

  3. RESTful API 架构解读

    RESTful API 架构解读 首先我们还是先介绍下 RESTful api 的来龙去脉. 首先, RESTful (下文都简称 RESTful api 为 RESTful ) 1.RESTful ...

  4. Node.js API 初解读(三)

    目录 Node.JS API 初解读三 Node.JS API 初解读三 Version: NodeJs v6.2.0 一. DNS (Domain Name Server) [域名服务器] 1.简介 ...

  5. FastDFS的配置、部署与API使用解读(2)以字节方式上传文件的客户端代码(转)

    本文来自 诗商·柳惊鸿 Poechant CSDN博客,转载请注明源地址:FastDFS的配置.部署与API使用解读(2)上传文件到FastDFS分布式文件系统的客户端代码 在阅读本文之前,请您先通过 ...

  6. FastDFS的配置、部署与API使用解读(1)Get Started with FastDFS(转)

    转载请注明来自:诗商·柳惊鸿CSDN博客,原文链接:FastDFS的配置.部署与API使用解读(1)入门使用教程 1.背景 FastDFS是一款开源的.分布式文件系统(Distributed File ...

  7. Node.js API 初解读(二)

    四. Cluster 1.简介 在介绍 Cluster 之前.我们需要知道 node的 一些基本特性,比如说 都知道的 nodejs最大的特点就是单进程.无阻塞运行,并且是异步事件驱动的. 那么随之而 ...

  8. fetch API 简单解读

    http://f2e.souche.com/blog/fetch-api-jie-du/?utm_source=tuicool&utm_medium=referral 在我们日常的前端开发中, ...

  9. FastDFS的配置、部署与API使用解读(8)FastDFS多种文件上传接口详解(转)

    1.StorageClient与StorageClient1的区别 相信使用happy_fish的FastDFS的童鞋们,一定都熟悉StorageClient了,或者你熟悉的是StorageClien ...

随机推荐

  1. 以太坊智能合约Hello World示例程序

    简介 以太坊(Ethereum)是一提供个智能合约(smart contract)功能的公共区块链(BlockChain)平台. 本文介绍了一个简单的以太坊智能合约的开发过程. 开发环境 在以太坊上开 ...

  2. Sublime插件支持Sass编译和Babel解析ES6 & .sublime-build文件初探

    用Sublime Text蛮久了,配置配来配去的,每次换电脑都得重头再配过,奈何人老了脑子不中用了,得好好整理一些,下次换电脑就有得参考了.. 同事说,他的WebStorm简直太方便,自身集成了很多方 ...

  3. SQL Server 2005 数据库 可疑状态

    KJDY数据库名称 ALTER DATABASE KJDY SET EMERGENCY ---修改数据库为 紧急模式 ALTER DATABASE KJDY SET SINGLE_USER ---单用 ...

  4. combox

    通过combox控件本身的item添加了选项后,该控件在启动后SelectedIndex默认值是-1,所以最好是在窗体加载的时候初始化城SelectedIndex=0 另外如果是窗体加载时item从数 ...

  5. 生成树形结构的json字符串代码(c#)供前端angular tree使用.

    框架是使用EF6.0.可以针对返回的值使用Newtonsoft.Json.dll(百度搜一下)来对返回的值序列化为json字符串,如果对以下值那就是使用JsonConvert.SerializeObj ...

  6. MVC 自定义Htmlhelper扩展

    在MVC中,我们不仅可以使用它原来的方法,我们还可以自定义,这不不仅加大了我们开发的效率,同时使界面更简洁. 具体什么是扩展方法,你可以这样理解,必须是静态且在形参中第一个参数是以this开头,大概先 ...

  7. 关于WebBrowser访问百度地图

    前段时间遇到一个困惑用WebBrowser访问百度地图的时候,百度会自动转至让下载sdk的页面,经过一个仁兄的点拨,可以改变WebBrowser的agent来骗过网站.经过试验成功.贴源码如下: st ...

  8. 【linux草鞋应用编程系列】_6_ 重定向和VT100编程

    一.文件重定向     我们知道在linux shell 编程的时候,可以使用文件重定向功能,如下所示: [root@localhost pipe]# echo "hello world&q ...

  9. Apache Lucene(全文检索引擎)—创建索引

    目录 返回目录:http://www.cnblogs.com/hanyinglong/p/5464604.html 本项目Demo已上传GitHub,欢迎大家fork下载学习:https://gith ...

  10. jQuery用户数字评分效果

    效果预览:http://hovertree.com/texiao/jquery/5.htm HTML文件代码: <!DOCTYPE html> <html xmlns="h ...