012.对netmap API的解读
一.簡要說明:
? ? 1.netmap API主要為兩個頭文件netmap.h 和netmap_user.h ,當(dāng)解壓下載好的netmap程序后,在./netmap/sys/net/目錄下,本文主要對這兩個頭文件進行分析。
? ? 2.我們從netmap_user.h頭文件開始看起。
?
二.likely()和unlikely()
? ? 這兩個宏定義是對編譯器做優(yōu)化的,并不會對變量做什么改變。后面看到這兩個宏的調(diào)用自動忽略就好了。
#ifndef likely #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #endif /* likely and unlikely */?
三.netmap.h頭文件
? ? 1.netmap.h被netmap_user.h調(diào)用,里面定義了一些宏和幾個主要的結(jié)構(gòu)體,如nmreq{},?netmap_if{}, netmap_ring{}, ?netmap_slot{}。
? ? 2.一個網(wǎng)卡(或者網(wǎng)絡(luò)接口)只有一個netmap_if{}結(jié)構(gòu),在使用mmap()申請的共享內(nèi)存中,通過netmap_if{}結(jié)構(gòu)可以訪問到任何一個發(fā)送/接收環(huán)(也就是netmap_ring{}結(jié)構(gòu),一個netmap_if{}可以對應(yīng)多發(fā)送/接收環(huán),這應(yīng)該和物理硬件有關(guān) ,我在虛擬機下只有一對環(huán),在真實主機上有兩隊環(huán))。
? ? 3.找到netmap_ring{}的地址后,我們就可以找到環(huán)中每一個buffer的地址(buffer里面存儲的是將要發(fā)送/接收的數(shù)據(jù)包)。后面會講解這是如何實現(xiàn)的。
? ? 4.通過一個nifp是如何訪問到多個收/發(fā)環(huán)的,通過一個ring如何找到多個不同的buffer地址的,其實都是通過存儲這些結(jié)構(gòu)體相鄰的后面一部分空間實現(xiàn)。(申請共享內(nèi)存的時候,這些均已被設(shè)計好)
?
四.幾個重要的宏定義
1._NETMAP_OFFSET
#define _NETMAP_OFFSET(type, ptr, offset) \((type)(void *)((char *)(ptr) + (offset)))解釋:該宏定義的作用是將ptr指針(強轉(zhuǎn)成char *類型)向右偏移offset個字節(jié),再將其轉(zhuǎn)化為指定的類型type。
2.NETMAP_IF
#define NETMAP_IF(_base, _ofs) _NETMAP_OFFSET(struct netmap_if *, _base, _ofs)解釋:該宏定義將_base指針向右偏移_ofs個字節(jié)后,強轉(zhuǎn)為netmap_if *類型返回。在nemap中通過此宏得到d->nifp的地址。
3.NETMAP_TXRING
#define NETMAP_TXRING(nifp, index) _NETMAP_OFFSET(struct netmap_ring *, \nifp, (nifp)->ring_ofs[index] )解釋:1.通過該宏定義,可以找到nifp的第index個發(fā)送環(huán)的地址(index是從0開始的),ring_ofs[index]為偏移量,由內(nèi)核生成。 ??
? ? ? ? 2.其中,我們注意到struct netmap_if{}最后面只定義了const ssize_t ring_ofs[0],實際上其它的netmap環(huán)的偏移量都寫在了該結(jié)構(gòu)體后面的內(nèi)存地址里面,直接訪問就可以了。
4.NETMAP_RXRING
#define NETMAP_RXRING(nifp, index) _NETMAP_OFFSET(struct netmap_ring *, \nifp, (nifp)->ring_ofs[index + (nifp)->ni_tx_rings + 1] )解釋:通過該宏定義,可以找到nifp的第index個接收環(huán)的地址,其中(nifp)->ring_ofs[]里面的下標(biāo)為index+(nifp)->ni_tx_rings+1,正好與發(fā)送環(huán)的偏移量區(qū)間隔開1個。(我想這應(yīng)該是作者特意設(shè)計的)
5.NETMAP_BUF
#define NETMAP_BUF(ring, index) \((char *)(ring) + (ring)->buf_ofs + ((index)*(ring)->nr_buf_size))解釋:1.通過該宏定義,可以找到ring這個環(huán)的第index個buffer的地址(buffer里面存的就是我們接收/將發(fā)送的完整數(shù)據(jù)包),每個buffer占的長度是2048字節(jié)(在(ring)->nr_buf_size也給出了)。
? ? ? ? 2.其中(ring) ->buf_ofs是固定的偏移量,不同的環(huán)這個值不相同,但所有的(char *)(ring)+(ring)->buf_ofs會指向同一個地址,也就是存放buffer的連續(xù)內(nèi)存的開始地址(d->buf_start會指向該地址)。
6.NETMAP_BUF_IDX
#define NETMAP_BUF_IDX(ring, buf) \( ((char *)(buf) - ((char *)(ring) + (ring)->buf_ofs) ) / \(ring)->nr_buf_size )解釋:在講NETMAP_BUF的時候我們說(char *)(ring) + (ring)->buf_ofs)總會指向存放buffer的起始位置(無論是哪一個環(huán)),在這段內(nèi)存中將第一個buffer下標(biāo)標(biāo)記為0的話,NETMAP_BUF_IDX計算的恰好是指針buf所指buffer的下標(biāo)。
上面幾個宏一時沒弄懂也沒關(guān)系,下面調(diào)用的時候還會提的。
?
五.nm_open函數(shù)
? ? 1.調(diào)用nm_open函數(shù)時,如:nmr = nm_open("netmap:eth0", NULL, 0, NULL);?nm_open()會對傳遞的ifname指針里面的字符串進行分析,提取出網(wǎng)絡(luò)接口名。
? ? 2.nm_open()會對struct nm_desc *d申請內(nèi)存空間,并 通過d->fd = open(NETMAP_DEVICE_NAME, O_RDWR);打開一個特殊的設(shè)備/dev/netmap來創(chuàng)建文件描述符d->fd。
? ? 3.通過ioctl(d->fd, NIOCREGIF, &d->req)語句,將d->fd綁定到一個特殊的接口,并對d->req結(jié)構(gòu)體里面的成員做初始化,包括a.在共享內(nèi)存區(qū)域中 nifp 的偏移,b.共享區(qū)域的大小nr_memsize,c.tx/rx環(huán)的大小nr_tx_slots/nr_rx_slots(大小為256),d.tx/rx環(huán)的數(shù)量nr_tx_rings、nr_rx_rings(視硬件性能而定)等。
? ? 4.接著在if ((!(new_flags & NM_OPEN_NO_MMAP) || parent) && nm_mmap(d, parent))語句中調(diào)用nm_mmap函數(shù),繼續(xù)給d指針指向的內(nèi)存賦值。
?
六.nm_mmap函數(shù)
nm_mmap()源碼:
static int nm_mmap(struct nm_desc *d, const struct nm_desc *parent) { //XXX TODO: check if mmap is already doneif (IS_NETMAP_DESC(parent) && parent->mem && parent->req.nr_arg2 == d->req.nr_arg2){/* do not mmap, inherit from parent */D("do not mmap, inherit from parent");d->memsize = parent->memsize;d->mem = parent->mem;} else{/* XXX TODO: 檢查如果想申請的內(nèi)存太大 (or there is overflow) */d->memsize = d->req.nr_memsize; /* 將需要申請的內(nèi)存大小賦值給d->memsize */d->mem = mmap(0, d->memsize, PROT_WRITE | PROT_READ, MAP_SHARED, d->fd, 0); /* 申請共享內(nèi)存 */if (d->mem == MAP_FAILED){goto fail;}d->done_mmap = 1;}{struct netmap_if *nifp = NETMAP_IF(d->mem, d->req.nr_offset); /*通過d->req.nr_offset這個偏移量的到nifp的地址,NETMAP_IF前面說過*/int i;/**for(i=0; i<=2; i++)* printf("ring_ofs[%d]:0x%x\n",i,nifp->ring_ofs[i]); // 這里是我自己加的,為了手動計算收/發(fā)環(huán)的偏移量*/struct netmap_ring *r = NETMAP_RXRING(nifp,); //對nifp,找接收包的環(huán)r,因為index為0,所以省略了*(struct netmap_if **) (uintptr_t) &(d->nifp) = nifp; //對d->nifp賦值,雖然d->nifp使用const定義的,但對其取地址再強值類型轉(zhuǎn)換后,依然可以對其指向的空間進行操作*(struct netmap_ring **) (uintptr_t) &d->some_ring = r; //同理,對d->some_ring進行賦值,此處指向了第一個接受(rx)環(huán)。//printf("buf_ofs:0x%x\n", (u_int)r->buf_ofs);*(void **) (uintptr_t) &d->buf_start = NETMAP_BUF(r, 0);//計算第一個buffer的地址,并存入d->buf_start指針中*(void **) (uintptr_t) &d->buf_end = (char *) d->mem + d->memsize; //計算共享區(qū)間的最后一個地址,賦值給d->buf_end }return 0;fail: return EINVAL; }其中:
? ? 1.nifp為申請的共享內(nèi)存首地址d->mem向右偏移d->req.nr_offset(該值在調(diào)用前面的ioctl()時得到)得到。并且一個網(wǎng)絡(luò)接口(網(wǎng)卡)只對應(yīng)一個nifp。(使用宏NETMAP_IF計算)
? ? 2.得到的nifp的地址,nifp結(jié)構(gòu)體里最后定義的ring_ofs[0]以及接下來內(nèi)存中的ring_ofs[1],ring_ofs[2]...,這些內(nèi)存中存儲的是訪問每一個環(huán)(tx or rx ring)的偏移量,通過這個偏移量我們可以得到每一個環(huán)的地址(使用宏NETMAP_RXRING/NETMAP_TXRING進行計算)。
? ? 3.得到每個收/發(fā)環(huán)的地址了,netmap_ring結(jié)構(gòu)體最后面有一個struct netmap_slot slot[0];,通過slot[0],后面內(nèi)存的slot[1],slot[2],slot[3]...,取出里面的偏移量就可以得到每一個buffer(也叫數(shù)據(jù)包槽)的地址了(使用宏NETMAP_BUF計算得到)。 ? ?到這里,netmap如何訪問到內(nèi)存槽中的每一個buffer的,我們都知道了。實際上netmap運行的數(shù)據(jù)結(jié)構(gòu)就和下圖描述的一樣:
? ? 4.在struct nm_desc中,nifp,some_ring,buf_start,buf_end等指針都定義為const的,但我們通過對其取地址再強轉(zhuǎn)指針的方式去往這些指針指向的內(nèi)存中賦值。
? ? 注:在nm_mmap()中使用mmap()申請共享的時候,這些數(shù)據(jù)結(jié)構(gòu)里數(shù)據(jù)的設(shè)計是內(nèi)核模塊就已寫好了的,我們在這里其實是在做驗證。
七.nm_nextpkt函數(shù)
? ? 1.nm_nextpkt()是用來接收網(wǎng)卡上到來的數(shù)據(jù)包的函數(shù)。
? ? 2.nm_nextpkt()會將所有rx環(huán)都檢查一遍,當(dāng)發(fā)現(xiàn)有一個rx環(huán)有需要接收的數(shù)據(jù)包時,得到這個數(shù)據(jù)包的地址,并返回。所以nm_nextpkt()每次只能取一個數(shù)據(jù)包。
nm_nextpkt()源代碼:
static u_char *nm_nextpkt(struct nm_desc *d, struct nm_pkthdr *hdr) {int ri = d->cur_rx_ring; //當(dāng)前的接收環(huán)的編號do{/* compute current ring to use */struct netmap_ring *ring = NETMAP_RXRING(d->nifp, ri); //得到當(dāng)前rx環(huán)的地址if (!nm_ring_empty(ring)) //判斷環(huán)里是否有新到的包 {u_int i = ring->cur; //當(dāng)前該訪問哪個槽(buffer)了u_int idx = ring->slot[i].buf_idx; //得到第i個buffer的下標(biāo)//printf("%d\n", idx);u_char *buf = (u_char *) NETMAP_BUF(ring, idx); //得到存有到來數(shù)據(jù)包的地址// __builtin_prefetch(buf);hdr->ts = ring->ts;hdr->len = hdr->caplen = ring->slot[i].len;ring->cur = nm_ring_next(ring, i); //ring->cur向后移動一位/* we could postpone advancing head if we want* to hold the buffer. This can be supported in* the future.*/ring->head = ring->cur;d->cur_rx_ring = ri; //將當(dāng)前環(huán)(d->cur_rx_ring)指向第ri個(因為可能有多個環(huán))。return buf; //將數(shù)據(jù)包地址返回 }ri++;if (ri > d->last_rx_ring) //如果ri超過了rx環(huán)的數(shù)量,則再從第一個rx環(huán)開始檢測是否有包到來。ri = d->first_rx_ring;} while (ri != d->cur_rx_ring);return NULL; /* 什么也沒發(fā)現(xiàn) */ }?
八.nm_inject函數(shù)
? ? 1.nm_inject()是用來往共享內(nèi)存中寫入待發(fā)送的數(shù)據(jù)包數(shù)據(jù)的。數(shù)據(jù)包經(jīng)共享內(nèi)存拷貝到網(wǎng)卡,然后發(fā)送出去。所以nm_inject()是用來發(fā)包的。
? ? 2.nm_inject()也會查找所有的發(fā)送環(huán)(tx環(huán)),找到一個可以發(fā)送的槽,就將數(shù)據(jù)包寫入并返回,所以每次函數(shù)調(diào)用也只能發(fā)送一個包。
源代碼:
static int nm_inject(struct nm_desc *d, const void *buf, size_t size) {u_int c, n = d->last_tx_ring - d->first_tx_ring + 1;for (c = 0; c < n; c++){/* 計算當(dāng)前的環(huán)去使用(compute current ring to use) */struct netmap_ring *ring;uint32_t i, idx;uint32_t ri = d->cur_tx_ring + c; //該訪問第幾個tx環(huán)了if (ri > d->last_tx_ring) //當(dāng)超過訪問的tx環(huán)的下標(biāo)范圍時,從頭開始訪問ri = d->first_tx_ring;ring = NETMAP_TXRING(d->nifp, ri); //得到當(dāng)前tx環(huán)的地址if (nm_ring_empty(ring)) //如果當(dāng)前tx環(huán)是滿的(ring->cur=ring->tail表示沒地方存數(shù)據(jù)包了),就跳過 {continue;}i = ring->cur; //當(dāng)前要往哪個槽(槽指向buffer)中寫入數(shù)據(jù)idx = ring->slot[i].buf_idx; //得到這個槽相對于buffer起始地址(d->buf_start)的下標(biāo)編號ring->slot[i].len = size; //size為待發(fā)送數(shù)據(jù)包的長度nm_pkt_copy(buf, NETMAP_BUF(ring, idx), size); //將buf里存的數(shù)據(jù)包拷貝給ring這個環(huán)的第i個槽d->cur_tx_ring = ri;ring->head = ring->cur = nm_ring_next(ring, i); //將head和cur指向下一個槽return size;}return 0; /* 失敗 */ }?
九.nm_close函數(shù)
? ? 1.nm_close函數(shù)就是回收動態(tài)內(nèi)存,回收共享內(nèi)存,關(guān)閉文件描述符什么的了。
源代碼:
static int nm_close(struct nm_desc *d) {/** ugly trick to avoid unused warnings*/static void *__xxzt[] __attribute__ ((unused)) ={ (void *) nm_open, (void *) nm_inject, (void *) nm_dispatch, (void *) nm_nextpkt };if (d == NULL || d->self != d)return EINVAL;if (d->done_mmap && d->mem)munmap(d->mem, d->memsize); //釋放申請的共享內(nèi)存if (d->fd != -1){close(d->fd); //關(guān)閉文件描述符 }bzero(d, sizeof(*d)); //將d指向的空間全部置0free(d); //釋放指針d指向的空間return 0; }?
轉(zhuǎn)載于:https://www.cnblogs.com/ruo-yu/p/5106213.html
總結(jié)
以上是生活随笔為你收集整理的012.对netmap API的解读的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavaScript跳转到页面某个锚点#
- 下一篇: selenium启动 IE11方法