C++ 三大函数该写就要写

  记得C++ 沉思录上说过,如果数据成员有指针类型的,最好把三大函数补全,不然的话容易产生浅拷贝和内存泄露。这里给出一个简单的例子。

#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>

using namespace std;

class CDemo
{
public:
       CDemo():str(new char[1])
       { 
        str[0] = '/0';
        cout<< "Construct"<<endl; 
       };
       CDemo(const char *s) : str(new char[strlen(s) + 1])
       {
          strcpy(str,s);
       }
       CDemo( const CDemo & D) : str(new char [strlen(D.c_str()) + 1])
       {
              strcpy(str,D.c_str());
       }
       ~CDemo()
       {
              if(str != NULL)
              {
                     delete [] str;
                     str = nullptr;
                     cout<< "Deconstruct!"<<endl;
              }
              cout<< "Deconstruct finish"<<endl;
       }
       CDemo & operator = ( CDemo CD)
       {
               swap(str, CD.str);
               return * this;
       }

       char *c_str() const
       {
          return str;
       }

private:
        char *str;
};

int main()
{
       CDemo   d1("trend micro");
       CDemo d2;
       d2 = d1;
       vector<CDemo> * a1 = new vector<CDemo>();
       a1->push_back(d1);
       cout<< "d1: "<<d1.c_str()<<endl;
       cout<< "a1: "<<a1->front().c_str()<<endl;
       strcpy(a1->front().c_str(), "change"); //如果没有拷贝构造函数,则这里会出错
       cout<<a1->front().c_str()<<endl;
       delete a1; //如果不定义拷贝构造函数将出现push_back的时候的浅拷贝情况,析构a1的时候要
        //delete 类中的str,导致str被delete两次
       return 0;
}

 

 

Intel 82599 ixgbe & ixgbevf CNA 卡驱动分析03——部分功能代码分析

I/O Operations and Activities
 
     使用SR-IOV 的根本原因就是使得虚拟机中的一个驱动可以直接访问PCI进行I/O操作,并能够在虚拟机之间共享设备。Intel VF 驱动了解自己运行在一个虚拟化的环境中拥有优先的PCI资源。
     
     可用的资源包括基本的接收和发送以太网包的能力,Intel VF 还提供了额外的硬件包括:
     ·状态信息:
          ·链路速度
          ·链路状态
          ·复用模式
     ·统计数据包括:
          ·收到的包计数
          ·发送的包计数
          ·收到的八位组计数
          ·发送的八位组计数
          ·收到的多播包计数
          ·功能级的复位(Function Level Reset)
          ·VLAN 标号插入
          ·校验和插入
          
 
Actions taken via Mailbox system   – VF to PF
 
VF 暴露给VF驱动的PCI资源并不一定满足所有PF 驱动的需求,比如VLAN 标识配置和多播地址配置。
 
在这种情况下,VF驱动利用信箱 系统来向PF驱动传递消息,这样来使得PF驱动进行需要的操作。
 
现在定义的可以使用信箱机制实现的行为包括:
·VF复位
·配置VF MAC 地址
·设置多播地址
·设置VLAN过滤器
·设置最大包长
·信箱消息的ID实在ixgbe_mbx.h中定义的,适用于ixgbe PF 驱动和 ixgbevf VF 驱动
 
Virtual Function 复位:
 
消息ID:IXGBE_VF_RESET
驱动在执行了功能级的复位(Function Level Reset)后就会把这个消息发送给PF驱动。
 
一个例子:
     文件:ixgbe_vf.c
     函数:ixgbe_reset_hw_vf
     msgbuf[0] = IXGBE_VF_RESET;
     mbx->ops.write_posted(hw,msgbuf,1,0);

 

 
当PF驱动接收到消息,就会进行回复,并发送回MAC地址给VF。
 
 
Configuring a MAC Address
 
消息ID: IXGBE_VF_SET_MAC_ADDR
当VF驱动想要定义自己的MAC地址时,就会发送该消息(而不是使用当PF初始化时分配给VF的默认MAC地址)。
 
实例:
     文件:ixgbex_vf.c
     函数:ixgbe_set_rar_vf
     msbuf[0] = IXGBE_VF_SET_MAC_ADDR;
     memcpy(msg_addr, addr,6);
     ret_val = mbx->ops.write_posted(hw, msbug, 3);

 

 
Setting Multicast Address
 
消息ID:IXGBE_VF_SET_MULTICAST
当VF驱动需要设置一个多播地址来过滤达到的包时就会发送该消息。
 
实例:
          文件:ixgbe_vf.c
          函数:ixgbe_update_mc_addr_list_vf
          cnt = (mc_addr_count > 30) ? 30 : mc_addr_count;
          msgbuf[0] = IXGBE_VF_SET_MULTICAST;
          msgbuf[0] |= cnt << IXGBE_VT_MSGINFO_SHIFT;
          
          for( i = 0; i < cnt; i++)
          {
               vector = ixgbe_mta_vector(hw, next(hw, &mc_addr_list, &vmdq));
               hw_dbg(hw, "Hash value = 0x%03x/n",vector);
               vector_list[i] = (u16) vector;
          }
 
          mbx->ops.write_posted(hw, msgbuf, IXGBE_VFMAILBOX_SIZE, 0);

 

 
Setting VLAN Filter
 
消息ID:IXGBE_VF_VLAN
VF驱动想要设置一个VLAN 标识来过滤到达的包就会发送该消息
 
实例:
     文件:ixgbe_vf.c
     函数:ixgbe_set_vfta_vf
     msgbuf[0] = IXGBE_VF_SET_VLAN;
     msgbuf[1] = vlan;
     
     msgbuf[0] |= vlan_on << IXGBE_VTMSGINFO_SHIFT;
     mbx-ops.write_posted(hw, msgbuf, 2, 0);

 

 
PF to VF Mailbox Messages
 
 
Physical Function Driver
     
     该驱动负责物理资源和针对VF配置的一些处理。
     
     当驱动在探测发现设备的时候,在驱动初始化执行的众多任务中,有一项就是将自己注册为一个SR-IOV设备。
     文件:ixgbe_main.c
     函数:__devinit ixgbe_probe_vf
     
     err = pci_enable_sriov(adapter->pdev, adapter->num_vfs);
     这个函数调用将82599注册为一个SR-IOV设备,表明支持特定数量的VF。
     
Default Configuration
     
     在驱动初始化阶段进行了很多项默认配置。这些默认配置包括VF的个数,VF流量配置,VFMAC地址分配。
 
     
Assignment of Queue’s to Pools
     
     82599 PF 驱动 默认支持配置63个VF。每个池内有两个队列对与其相关,总共4个队列
     文件:ixgbe_main.c
     函数:ixgbe_up_complete
     if ( adapter->flags & IXGBE_FLAG_SRIOV_ENABLED )
     {
          gpie &= ~IXGBE_GPIE_VTMODE_MASK;
          gpie |=  IXGBE_GPIE_VTMODE_64;
     }
 
     IXGBE_WRITE_REG(hw, IXGBE_GPID, gpie);

 

Intel 82599 ixgbe &amp; ixgbevf CNA 卡驱动分析03——部分功能代码分析

 
     这段代码对PCIe 控制寄存(GCR_EXT-0x11050)器进行了配置,默认支持64个VF。
     
     文件:ixgbe_main.c
     函数:ixgbe_up_complete
     if ( adapter->flags & IXGBE_FLGA_SRIOV_ENABLED)
     {
          gpie &= ~IXGBE_GPIE_VTMODE_MASK;
          gpie |= IXGBE_GPIE_VTMODE_64;
     }

 

     IXGBE_WRITE_REG(hw, IXGBE_GPIE,gpie);
     该代码片段配置 VT_Mode (15:14)比特,目的是对中断寄存器(GPIE-0x00898)进行配置.
 
     

Intel 82599 ixgbe &amp; ixgbevf CNA 卡驱动分析03——部分功能代码分析

 
 
Enabling VF to VF Bridging 
 
VF 到 VF 的桥接在函数ixgbe_configure_rx() 函数中实现,该函数位于ixgbe_main.c
该函数使能了PF DMA 传输交换控制寄存器(PFDTXGSWC)的回环使能位(LBE)
 
文件:ixgbe_main.c
函数:ixgbe_configure_rx
 
          #ifdef     CONFIG_PCI_IOV
                        if( adapter->flags & IXGBE_FLAG_SRIOV_ENABLED)
                         {
                              IXGBE_WRITE_REG(hw, IXGBE_PFDTXGSWC, IXGBE_PFDTXGSWC_VT_LBEN);
                              ixgbe_set_vmolr(hw, adapter->num_vfs);
                         }
          #endif

 

 
          该代码片段同时也使能了VF的流量,通过ixgbe_set_vmolr函数的调用。
 
Default Pool
     
          当一些包不会送到某个VF则会送到默认池去处理。这个池资源是为PF准备的。默认池是不分配给VF的池。如果有32个池资源已经进行了配置,第33个池就会配配置为默认池。
 
          文件:ixgbe_main.c
          函数:ixgbe_configure_rx
                    
                    if(adapter->num_vfs )
                    {
                         vt_reg_bits &= ~IXGBE_VT_CTL_POOL_MASK;
                         vt_reg_bits |= (adapter->num_vfs << IXGBE_VT_CTL_POOL_SHIFT);
                    }
 
                    u32     vt_reg;
                    u32     vt_reg_bits;
                    if ( hw->mac.type = ixgbe_mac_82599EB)
                    {
                         vt_reg = IXGBE_VT_CTL;
                         vt_reg_bits = IXGBE_VMD_CTL_VMDQ_EN | IXGBE_VT_CTL_REPLEN
                         
                         if ( adapter->num_vfs )
                         {
                              vt_reg_bits &= ~IXGBE_VT_CTL_POOL_MASK; 
                              vt_reg_bits |= (adapter->num_vfs << IXGBE_VT_CTL_POOL_SHIFT); 

                          }
                      } 
                      else {
                           vt_reg = IXGBE_VMD_CTL;
                           vt_reg_bits = IXGBE_VMD_CTL_VMDQ_EN;
                     }
                     vmdctl = IXGBE_READ_REG(hw, vt_reg);
                     IXGBE_WRITE_REG(hw, vt_reg, vmdctl | vt_reg_bits); 

 

                     驱动操作PFVTCTL(0x051B0)寄存器来配置默认池。主要操作DEF_PF 位(12:7)
       

Intel 82599 ixgbe &amp; ixgbevf CNA 卡驱动分析03——部分功能代码分析

 
Replication Enable
 
Broadcast Accept Mode
 
 
Accept Packet Matching PFUTA Table
     
     这里允许VF接收一个域PF 单波表(PFUTA 0x0F400)中某个单波地址入口匹配的包。
     文件:ixgbe_sriov.c
     函数:ixgbe_set_vmolr
               
               u32 vmolr = IXGBE_READ_REG(hw, IXGBE_VMOLR(vf));
               vmolr |= (IXGBE_VMOLR_AUPE | IXGBE_VMOLR_ROMPE | IXGBE_VMOLR_ROPE |      
                              IXGBE_VMOLR_BAM);
               IXGBE_WRITE_REG(hw, IXGBE_VMOLR(vf), vmolr);

 

     
          驱动操作PF VM L2 控制寄存器(PFVML2FLT 0x0F000 + 4 *n [n=0..63] )的ROPE域(第26比特)使能作者禁止VF 接收赖在PFUTA表的包。
 
Accept Packets Matching MTA Table
          
     该功能允许VF接收在多播表阵列(UTA 0xA000)中匹配多播地址入口的包。
     默认是接收。
     文件:ixgbe_sriov.c
     函数:ixgbe_set_vmolr
               
               u32 vmolr = IXGBE_READ_REG(hw, IXGBE_VMOLR(vf));
               vmolr |= (IXGBE_VMOLR_AUPE | IXGBE_VMOLR_ROMPE |
                              IXGBE_VMOLR_ROPE | IXGBE_VMOLR_BAM);
               IXGBE_WRITE_REG(hw, IXGBE_VMOLR(vf), vmolr);

 

     驱动操作 PF VM L2 控制寄存器(PFVML2FLT 0x0F000 + 4 *n [n = 0..63]的ROMPE域(第25bit)注册使能或者禁止接收来自MTA 表的包。
 

Intel 82599 ixgbe &amp; ixgbevf CNA 卡驱动分析03——部分功能代码分析

 
 
Accept Untagged Packets Enable
 
     这种方式允许VF接收一个MAC地址匹配但是VLAN 标识不一定要匹配的包。
     默认情况下,未加标识的包的接收功能已经使能。
     文件:ixgbe_sriov.c
     函数:ixgbe_set_vmolr
     
          u32 vmolr = IXGBE_READ_REG(hw, IXGBE_VMOLR(vf));
          vmolr |= ( IXGBE_VMOLR_AUPE | IXGBE_VMOLR_ROMPE | 
                          IXGBE_VMOLR_ROPE | IXGBE_VMOLR_BAM);
          IXGBE_WRTIE_REG(hw, IXGBE_VMOLR(vf),vmolr);

 

     驱动操作PF VM L2 控制寄存器(PFVML2FLT 0x0F000 + 4*n [n=0..63])注册使能或禁止VF接收未加标识的包
 
     

Intel 82599 ixgbe &amp; ixgbevf CNA 卡驱动分析03——部分功能代码分析

 
 
Strip VLAN Tag for Incoming Packets
 
这允许一个VF 剥去一个通过L2 过滤传过来的包的VLAN 标识
默认情况下,这项功能是开启的。
 
     文件:ixgbe_main.c
     函数:ixgbe_vlan_rx_register
               for( i = 0; i < adpater->num_rx_queues; i++)
               {
                    j = adpter->rx_ring[i]->reg_idx;
                    ctrl = IXGBE_READ_REG(&adapter->hw, IXGBE_RXDCTL(j));
                    ctrl |= IXGBE_RXDCTL_VME;
                    IXGBE_WRITE_REG(&adapter->hw, IXGBE_RXDCTL(j), ctrl);
               }

 

    驱动操作接收描述符控制寄存器(RXDCTL[n] (0x01208 + 0x40*n, n= 0..63 and 0x0D028 + 0x40*(n-64)), n=64..127; RW)的VLAN 模式使能位(第30bit)注册使能或禁止剥除到来包的VLAN 标识。

Intel 82599 ixgbe &amp; ixgbevf CNA 卡驱动分析03——部分功能代码分析

 
 
VF MAC 地址赋值
 
PF 驱动使用random_ether_add()函数(由内核提供【哪个内核,操作系统还是Hypervisor?】)动态的给每一个VF赋予一个MAC地址。下面的代码片段是一个展示示例:
          文件:ixgbe_sriov.c
          函数:ixgbe_vf_configuration
               random_ether_addr(vf_mac_addr);
               memcpy( adapter->vfinfo[vfn].vf_mac_addresses, vf_mac_addr,6);

 

     __devinit ixgbe_probe() 例程为每一个创建的VF调用ixgbe_vf_configuration() 一次。VF获得MAC地址当进行功能级的复位时,VF通过信箱发送IXGBE_VF_RESET消息到PF。PF发回VF  MAC地址作为回复。
     此外,VF驱动亦可以指定自己的MAC地址使用IXGBE_VF_SET_MAC消息。
 

Intel 82599 ixgbe & ixgbevf CNA 卡驱动分析02——VF/PF/MailBox

Physical Function Driver
     
     PF 驱动是一个专门管理SR-IOV设备全局功能驱动,而且还要配置相关共享资源。PF 驱动 随着Hypervisor 的不同而不同,一般需要具有比普通虚拟机更高的权限才能对其进行操作。PF驱动包含了所有传统驱动的功能,使得Hypervisor能够访问设备I/O资源。也可以通过调用PF驱动执行相关操作从而影响整个设备。PF驱动必须在VF驱动之前加载,而且需要等VF驱动卸载之后才能卸载。
     
     Intel SR-IOV 驱动包含了所有 Intel 以太网卡的功能,并且还有下面使用SR-IOV时拥有的特殊功能:
     ·给每个VF生成一个MAC 地址
     ·通过信箱系统与VF驱动通信:
          ·通过VF驱动配置VLAN过滤器
          ·通过VF驱动配置多播地址
          ·通过VF驱动配置最大包长
          ·处理VF驱动资源复位请求
 
 
Virutal Function Driver
 
     标准设备驱动(驱动不会意识到自己所处的虚拟化环境)期望获知如何控制设备和设备是如何工作的。在虚拟化环境下,一个标准的驱动一般与一个软件间接层进行交互,这个软件间接层模拟了底层的物理硬件设备。大多数情况下,该驱动不会意识到这个间接层的存在。
 
     通过直接赋值,我们期望改变本地共享设备的功能。VF 接口并没有包含完整的PCIe控制功能,而且一般不能直接控制共享设备和资源,比如设置以太网连接速率。这时候有必要告知VF 驱动,使其意识到自己所处的虚拟环境。当VF驱动意识到自己所处的虚拟化环境之后,就能够直接与硬件进行数据交互。同时这些VF驱动也能够知道这些设备都是共享设备,依赖于PF驱动提供的服务来进行操作,这些操作都具有全局效果,比如初始化和下一级结构的控制。
 
PF Driver – VF Driver Communication
 
     设备共享需要一个功能就是VF驱动与PF驱动通信,这样VF驱动就能够请求具有全局功能的操作。这个信道需要具有传递信息和产生中断的能力。
 
     SR-IOV标准并没有定义这条通信线路的实现机制。Intel 选择了通过使用一系列硬件信箱和门铃寄存器来为每个VF实现这个通信信道。
     
     Intel选择使用一件信箱的根本原因是为了确保实现一个独立于Hypervisor且持久可靠的通信机制。然而, 基于软件的信道也能够用来实现该通信功能,这个功能需要由Hypervisor子系统提供。这些信道的实现各个生产商并不相同,需要客户自己启动这些支持。硬件信道则不同,总是能够工作,除非被Hypervisor禁用。现在看来,并不是所用Hypervisor生产商都提供基于软件的消息机制,因此Intel 相关度去哦那个提供了利用硬件信道的API。
 
 
Example Receive Flow
 
     一个包是如何被一个虚拟机接收和发送的:

Intel 82599 ixgbe &amp; ixgbevf CNA 卡驱动分析02——VF/PF/MailBox

 
第一、二步:包到达,被送往L2 进行分类和交换
第三步:       根据目的MAC地址进行分类,这时候,改包与 缓冲池1匹配
第四步:       网卡发起DMA操作,将一个包传递给一个VM
第五步:       DMA操作到达Intel 芯片集,在这里VT-d(由Hypervisor 配置)进行DMA操作所需的地址翻译;结果使得该包     
                    被直接送入到虚拟机的VF驱动缓冲里面
第六步:       网卡发起了MSI-X中断,表明该接收操作已经完成。该中断由Hypervisor接收。
第七步:       Hypervisor向虚拟机注入一个虚拟中断表明传输已经结束,这时候虚拟机的VF驱动就开始处理该包。
 
 
Mailbox Communication System
 
     有时候VF驱动必须与PF驱动通信,以便完成一些工作,这些工作在VF提供的PCI资源基础上是无法完成的。
     
     例如:当VF驱动想要定义一个VLAN过滤器。这个功能在VF中并没有暴露,因此VF驱动也不能直接配置VLAN过滤器。
 
     VF驱动能够代替VF 向PF驱动发起这种类型的配置请求。
 
     

Intel 82599 ixgbe &amp; ixgbevf CNA 卡驱动分析02——VF/PF/MailBox

 
 
Virtual Function Mailbox
 
     当一个虚拟机获得访问VF的权限后,VF 资源中的信箱功能就会暴露给该虚拟机。这个功能相当简单,这是一系列的缓冲区,可以从这些缓冲区中读取或者写入信息,并增加一个寄存器(VFMailbox)用于提供PF和VF之间的同步功能。
 Intel 82599 ixgbe &amp; ixgbevf CNA 卡驱动分析02——VF/PF/MailBox
     Intel 82599 ixgbe &amp; ixgbevf CNA 卡驱动分析02——VF/PF/MailBox
     VF和PF都可以访问该缓冲区,可以通过这个缓冲区来传递信息。因为信箱缓冲和VFMailbox寄存器是VF资源的一部分,因此这些资源对于特定的VF来说是独占的。这意味着针对特定VF的VF驱动不能对分配给其他虚拟机的信箱进行写入。
 
     更多的关于信箱缓冲的信息可以参见:Intel ®82559 10Gigabit Ethernet Controller Datasheet。
 
 
Physical Function Mailbox
     
     PF 驱动能够访问所有的VF信箱,通过VF信箱的信箱内(VFMBMEM:VF Mailbox Mailbox Memory)存和PF信箱(Physical Function Mailbox)寄存器阵列.
     

Intel 82599 ixgbe &amp; ixgbevf CNA 卡驱动分析02——VF/PF/MailBox

     当一个VF驱动向VFMBMEM缓冲写入信息并在VFMailbox寄存器中设置适当的比特位之后,就向给PF产生一个中断。PF驱动获取这些信息,并给予这条消息进行回复。
 
     这时候,PF驱动会使用信箱发送一个异步消息给VF,这就是IXGBE_PF_CONTORL_MSG 消息。
 
 
Virtual Function Driver
     
     Intel VF驱动实例代码是标准 Intel ixgbe 10 Gigabit Ethernet 驱动的一个修改后的版本。通过设备ID来加载。Intel VF有一个设备ID表明它们是一个VF, 这样祥光的驱动就可以被加载。
 
     Intel VF 驱动可以被分割为三个部分:
     ·操作系统界面——虚拟机操作系统可以通过该界面调用各种API
     ·I/O操作——使用SR-IOV 功能来进行I/O操作,而避免Hypervisor的干预
     ·配置任务——配置像VLAN过滤器等需要与PF驱动进行通信的任务
 
注:PF & VF 有时候只实际的PCIe 物理功能模块,有时候就是泛指,注意区别

Intel 82599 ixgbe & ixgbevf CNA 卡驱动分析01——SR-IOV

SR-IOV Overview
 

当在主机接口之后使用普通共享设备时,本地共享设备会为每个接口提供单独的内存空间,工作队列,中断和命令处理。这些共享资源也需要被管理,它们会向Hypervisor中可信分区提供一系列用于管理自己的寄存器。

 
当拥有独立的工作队列和命令处理机制后,这些设备就可以同时从多种资源接受命令,并将这些命令智能的融合在一起,在传递给下一级结构之前。
 
虚拟化软件不用再对I/O请求进行复用,这减少了软件的压力。
 
本地共享设备能够通过很多方式实现,可以按照标准实现也可以使用其他专门的实现方式。
 
因为大多数这些设备都是通过PCI 访问的,PCI-SIG 决定定义一种实现标准用语创建和管理本地共享设备,也就是现在的Single Root I/O Virtualization 和 Sharing (SR-IOV) specification.
 

Intel 82599 ixgbe &amp; ixgbevf CNA 卡驱动分析01——SR-IOV

SR-IOV Goal
 
PCI-SIG SR-IOV 标准目的在于标准化化在虚拟环境中共享一个 I/O 设备的方式。
 
这个目标绕开了Hypervisor 的参与,提供了独立的内存空间、中断、DMA 流给每一个虚拟机用于数据的移动。
 
SR-IOV 引入了两种新的function 类型:
     
     Physical Function(PF):这是一个拥有所用PCIe 功能的fucntion,当然也包含了SR-IOV 扩展的能力
     Virtual Function (VF): 这是一个轻量级的PCIe function,包含了数据移动所需的资源。
High Level Overview of PCI-SIG SR-IOV
 
     虚拟化方法中的直接赋值提供了非常快速的I/O。 然而,这种方式使得I/O设备不能被共享。SR-IOV 标准提供了一个机制,通过这个机制使得一个单根的功能模块(比如一个以太网端口)看起来像是多个单独的物理设备。
 
     一个支持SR-IOV 的设备能够通过配置(一般由Hypervisor来配置)在PCI的配置空间看起来像是拥有多个功能。每个功能都拥有自己的配置空间,拥有自己的基地址寄存器(BAR)。
 

Intel 82599 ixgbe &amp; ixgbevf CNA 卡驱动分析01——SR-IOV

     支持SR-IOV 的设备为每个独立的虚拟功能提供了一个配置号,每个虚拟空能都拥有独立的PCI 配置空间。 Hypervisor 可以将一个活多个虚拟功能分配给一个虚拟机。内存地址翻译技术比如Intel VT-d 提供的硬件辅助技术使得可以针对每个虚拟机进行DMA传输。
     SR-IOV 标准详细列出了PCI配置空间信息该如何呈现。这些信息与标准PCI设备的信息有些不同。Hypervisor必须知道如何访问和读取这些信息,以便使得VM可以访问它们。
     
 
High Level Architecture:
 

Intel 82599 ixgbe &amp; ixgbevf CNA 卡驱动分析01——SR-IOV

     在虚拟化的环境中,系统中包含SR-IOV设备,Hypervisor都必须将PCI 配置空间的信息传递给虚拟机的I/O设备。对于SR-IOV 来说,Hypervisor 将实际的配置空间信息传递给特定的VF,允许虚拟机中的VF 驱动访问这些VF资源。
 
     SR-IOV标准的一部分规定了一个支持SR-IOV的设备需要如何表明自身具有SR-IOV的能力,PCI配置空间的虚拟VF信息是如何被存储,可以怎样被访问。这是SR-IOV特有的机制。Hypervisor必须知道如何读取和分析这些信息。Linux和Xen内核已经更新,现已能够使用这项新功能。
 
    VT-d 使得一个I/O设备通过DMA重映射操作可以直接分配给一个虚拟机。Hypervisor 必须使用I/O地址来进行配置,以便进行重映射。
 
注:1. 文章其实是对 Intel 提供的文档和代码的理解和翻译,
  2. 这里没有讲SR-IOV具体是一个什么东西(或者说讲的太简略),如果有一定基础的朋友会知道,之后可能有会有专门的文章补充介绍

链表基本操作

/*   注意C++ 的内存管理的复杂性
     尤其是在merge() 之中,当融合之后如何保证被合并了的链表之后的对象的析构函数会出错,不会被delete两次
     还有就是友元函数的模板写法
*/ #include <iostream> #include <typeinfo> using namespace std; template<class DataType> struct Node { DataType data; Node<DataType> *next; }; template<class DataType> class Linklist; template<class DataType> Linklist<DataType> &Merge(Linklist<DataType> &, Linklist<DataType> &); template<class DataType> class Linklist { public: Linklist(); Linklist(DataType a[], int n); void insert(DataType x, int i); DataType Delete( int i); DataType get( int i); int length(); void reverse(); void sort(); ~Linklist(); void print(); friend Linklist<DataType> & Merge<DataType>(Linklist<DataType> &L1, Linklist<DataType> &L2); private: Node<DataType> *first; }; template<class DataType> Linklist<DataType>::Linklist() { first = new Node<DataType>(); first->next = NULL; } template<class DataType> Linklist<DataType>::Linklist(DataType a[], int n) { first = new Node<DataType>(); first->next = NULL; Node<DataType> * temp = NULL; Node<DataType> * s = NULL; s = first; for( int i = 0; i < n; i++) { temp = new Node<DataType>(); temp->data = a[i]; s->next = temp; s = temp; } s->next = NULL; } template<class DataType> void Linklist<DataType>::insert(DataType x,int index) { Node<DataType> *p = first; int count = 0; while( p != NULL && count < index -1) { p = p->next; count++; } if(p == NULL) { cout<< "wrong place/n"; return ; } else { Node<DataType> *s = new Node<DataType>(); s->data = x; s->next = p->next; p->next = s; } } template<class DataType> int Linklist<DataType>::length() { Node<DataType> *temp = first; int count = 0; while(temp != NULL) { count++; temp = temp->next; } return count; } template<class DataType> DataType Linklist<DataType>::Delete( int index) { Node<DataType> *p= first; Node<DataType> *q=NULL; DataType x; int count = 0; while( p != NULL && count < index - 1) { p = p->next; count++; } if(p == NULL || p->next == NULL) { throw "wrong positon/n"; } else { q = p->next; x = p->data; p->next = q->next; delete q; return x; } } template<class DataType> DataType Linklist<DataType>::get( int index) { Node<DataType> *p = first; int count = 0; while(p != NULL && count < index -1) { p = p->next; count++; } if( p->next == NULL) { throw "wrong positon /n"; } return p->next->data; } template<class DataType> void Linklist<DataType>::print() { Node<DataType> *temp = first->next; while(temp!= NULL) { cout<< temp->data<< '/t'; temp = temp->next; } cout<<endl; //cout<<"print over!"<<endl; } template<class DataType> Linklist<DataType>::~Linklist() { Node<DataType> *temp = NULL; while( first!= NULL) { temp = first; first = first->next; cout<< "In: "<<typeid (*this ).name()<<"/t"<<temp->data<<endl; delete temp; temp = NULL; } } template<class DataType> void Linklist<DataType>::reverse() { Node<DataType> *pre = first->next; Node<DataType> *cur = pre->next; Node<DataType> *post; while(cur != NULL) { post = cur->next; cur->next = pre; pre = cur; cur = post; } first->next->next = NULL; first->next = pre; } template<class DataType> void Linklist<DataType>::sort() { Node<DataType> *cur1 = first->next; while(cur1 != NULL) { Node<DataType> *cur2 = cur1->next; Node<DataType> *min = cur1; while(cur2 != NULL) { if( cur2->data < min->data) { min = cur2; } cur2 = cur2->next; } DataType tmp = cur1->data; cur1->data = min->data; min->data = tmp; cur1 = cur1->next; } } template<class DataType> Linklist<DataType> & Merge(Linklist<DataType> &L1, Linklist<DataType> &L2) { Node<DataType> *L1_head = L1.first->next; Node<DataType> *L2_head = L2.first->next; Node<DataType> *tmp = new Node<DataType>(); L1.sort(); L2.sort(); while(L1_head != NULL && L2_head != NULL) { if(L1_head->data <= L2_head->data) { tmp->next = L1_head; tmp = tmp->next; L1_head = L1_head->next; } else { tmp->next = L2_head; tmp = tmp->next; L2_head = L2_head->next; } } if(L1_head != NULL) { tmp->next = L1_head; } if( L2_head != NULL) { tmp->next = L2_head; } if(L1.first->next->data <= L2.first->next->data) { cout<< "L2 Delte/n"; Linklist<DataType> &L = L1; delete L2.first; L2.first = NULL; return L; } else { cout<< "L1 Delte /n"; Linklist<DataType> &L = L2; delete L1.first; L1.first = NULL; return L; } //return L1.first->next->data < L2.first->next->data ? L1 : L2; } int main() { int a[] = {10,20,30,40,50}; int b[] = {1,20,7,5}; int sizea = sizeof(a) / sizeof(a[0]); int sizeb = sizeof(b) / sizeof(b[0]); Linklist< int> L(a,sizea); Linklist< int> L1(b,sizeb); // L.print(); // L.Delete(3); // L.print(); // cout<<L.get(3)<<endl; // L.insert(9,3); // L.print(); // cout<<"Original: /n"; // L.print(); // cout<<L.get(2)<<endl; // L.reverse(); // cout<<"After Reverse: /n"; // L.print(); // cout<<L.get(2)<<endl; // L.sort(); // cout<<"After sort/n"; L.print(); L1.print(); Merge(L,L1).print(); return 0; }

听说某公司要求20分钟写完…

bind与lambda

  bind() 和 lambda 表达式都可以实现类似的功能,在使用的时候有时候不知道选择哪一个。这里来做一个简单的总结。主要参考了一下的文章:

http://stackoverflow.com/questions/1930903/bind-vs-lambda

http://www.gockelhut.com/c++/articles/lambda_vs_bind

1. bind() 在多层嵌套的时候会使代码非常难看懂(参见文章一)

2. lambda 不支持“多态性”(其实就是泛型),需要在定义的时候指定参数类型

3. lambda 运行速度会比bind() 函数快很多

4. lambda 可以通过 std::function 实现“多态”特性

下面是一个例子,表明lambda 的运行时间比bind 的情况要快很多:

#include <cstdint>
#include <chrono>
#include <iostream>
#include <string>
#include <functional>
#include <thread>

class timer
{
public:
    typedef std::chrono::high_resolution_clock clock;
    typedef clock::time_point                  time_point;
    typedef clock::duration                    duration;
    
public:
    timer()
    {
        reset();
    }
    
    void reset()
    {
        _starttime = clock::now();
    }

    duration elapsed() const
    {
        return clock::now() - _starttime;
    }
protected:
    time_point _starttime;
};

template <typename T>
void volatile_write(const T& x)     
{
    volatile T* p = new T;
    *p = x;
    delete p;
}

template <typename Function>
void run_test(const std::string& name, Function func)
{
    std::cout << name;
    timer t;                //初始化的时候已经开始计时
    volatile_write(func());
    timer::duration duration = t.elapsed();
    std::cout << '/t' << duration.count() << std::endl;
}

template <typename Function>
void do_test_loop(Function func, const uint64_t upper_limit = 100000)
{
    for (uint64_t i = 0; i < upper_limit; ++i)
        func(i);
}

uint64_t test_accumulate_lambda()
{
    uint64_t x = 0;
    auto accumulator = [&x] (uint64_t i) { x += i; };
    do_test_loop(accumulator);
    return x;
}

void test_accumulate_bind_function(uint64_t& x, uint64_t i)
{
    x += i;
}

uint64_t test_accumulate_bind()
{
    namespace arg = std::placeholders;
    
    uint64_t x = 0;
    std::function<void (uint64_t)> accumulator = std::bind(&test_accumulate_bind_function, std::ref(x), arg::_1);
    do_test_loop(accumulator);
    return x;
}
uint64_t test_accumulate_bind_auto()
{
    namespace arg = std::placeholders;
    
    uint64_t x = 0;
    auto accumulator = std::bind(&test_accumulate_bind_function, std::ref(x), arg::_1);
    do_test_loop(accumulator);
    return x;
}

uint64_t test_accumulate_bound_lambda()
{
    uint64_t x = 0;
    std::function<void (uint64_t)> accumulator = [&x] (uint64_t i) { x += i; };
    do_test_loop(accumulator);
    return x;
}


int main()
{
   
    run_test("Accumulate (lambda)            ", &test_accumulate_lambda);
    run_test("Accumulate (bind)              ", &test_accumulate_bind);
    run_test("Accumulate (bound lambda)      ", &test_accumulate_bound_lambda);
    run_test("Accumulate (bind_auto)        ", &test_accumulate_bind_auto);
    return 0;
}

运行结果:

C:/Windows/system32/cmd.exe /c lambda_vs_bind.exe
Accumulate (lambda) 20001
Accumulate (bind) 110007
Accumulate (bound lambda) 50003
Accumulate (bind_auto) 90005
Hit any key to close this window…

 

从运行结果可以看出,使用lambda比bind快一个数量级,使用auto 修饰函数对象比用std::function<>速度也略有提升。

 

wrapper 函数 bind 和 function 使用实例

#include <random>
#include <iostream>
#include <functional>
 
void f(int n1, int n2, int n3, const int& n4, int n5)
{
    std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '/n';
}
 
int g(int n1)
{
    return n1;
}
 
struct Foo {
    void print_sum(int n1, int n2)
    {
        std::cout << n1+n2 << '/n';
    }
};
 
int main()
{
    using namespace std::placeholders;
 
    // demonstrates argument reordering and pass-by-reference
    int n = 7;
    auto f1 = std::bind(f, _2, _1, 42, std::cref(n), n);//注:cref 的意思是常量引用,也就是说不能改变该引用,也可以直接使用ref()则可以改变
    n = 10;
    f1(1, 2, 1001); // 1 is bound by _1, 2 is bound by _2, 1001 is unused
 
    // nested bind subexpressions share the placeholders
    auto f2 = std::bind(f, _3, std::bind(g, _3), _3, 4, 5);
    f2(10, 11, 12);
 
 
    // bind to a member function
    Foo foo;
    auto f3 = std::bind(&Foo::print_sum, foo, 95, _1);
    f3(5);
}

运行结果:

C:/Windows/system32/cmd.exe /c bind.exe
2 1 42 10 7
12 12 12 4 5
100
Hit any key to close this window…

说明:bind 函数里面的”_n”代表的是占位符,在实际调用函数之前表示这里有一个参数;在绑定一个类的成员函数的时候需要指明类成员函数的隐含指针“this”,因此最后一个bind 后面会有一个“foo”参数。

#include <functional>
#include <iostream> struct Foo { Foo(int num) : num_(num) {} void print_add(int i) const { std::cout << num_+i << '/n'; } int num_;}; void print_num(int i){ std::cout << i << '/n';} int main(){ // store a free function std::function<void(int)> f_display = print_num; f_display(-9); // store a lambda std::function<void()> f_display_42 = []() { print_num(42); }; f_display_42(); // store the result of a call to std::bind std::function<void()> f_display_31337 = std::bind(print_num, 31337); f_display_31337(); // store a call to a member function std::function<void(const Foo&, int)> f_add_display = &Foo::print_add; Foo foo(314159); f_add_display(foo, 1);}

运行结果:

C:/Windows/system32/cmd.exe /c function.exe
-9
42
31337
314160
Hit any key to close this window…

说明:function<> 尖括号内部用于表示函数返回值和参数(参数放在‘()’中)。

virtual 函数以外的其它选择

  复习effective C++ ,今天看到了”virtual 函数以外的其它选择”,里面介绍了Strategy 模式的两种实现方式,也介绍了std::function 和 std::bind 函数等,结合这段时间学习的进行一个总结。

首先还是先来回顾书上的内容:

问题引入:

一个游戏需要对其中人们生命值,健康状况进行监控计算,因此需要定义一个专门的函数,但是不同的人物的计算方式是不同的,也就是说这个函数需要不同的实现方式。可以使用多态,这是最基本的方法。

实例:

class GameCharacter {

public:

virtual int healthValue() const;// return character’s health rating

…                         // derived classes may redefine this

};

  由于healthValue()没有声明为pure virtual , 这暗示我们将有个计算健康指数的缺省算法。这也可能成为一个弱点,在派生类中考虑重定义该函数的时候考虑不周。

  因此,也可以使用“函数指针”作为成员变量,对于不同人物传给这个“函数指针”不同的计算函数。这就是:使用 Function Pointer实现Strategy模式。

    class GameCharacter;                       // forward declaration
    // function for the default health calculation algorithm
    int defaultHealthCalc(const GameCharacter& gc);
    class GameCharacter {
    public:
      typedef int (*HealthCalcFunc)(const GameCharacter&);
      explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf){}
      int healthValue() const
        { return healthFunc(*this); }
      ...
    private:
      HealthCalcFunc healthFunc;
    };
    //下面是同一类人中的不同实现:
    class EvilBadGuy: public GameCharacter {
   public:
    explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc) : GameCharacter(hcf)
      { ... }
    ...
  };
int loseHealthQuickly(const GameCharacter&);// health calculation
int loseHealthSlowly(const GameCharacter&);// funcs with different behavior
                                                                                              
EvilBadGuy ebg1(loseHealthQuickly); //sametype charac-ters with
EvilBadGuy ebg2(loseHealthSlowly); //different  health-related behavior
                                                                            

从上面的代码可以看出,这样的代码具有很好的扩展性,虽然都是EvilBadGuy对象,但是通过绑定函数指针的方式可以使得实现方式灵活多变。

上面的: 

typedef int (*HealthCalcFunc)(const GameCharacter&);

作用是定义 一个 返回值为 int 类型,以GameCharacter 的引用为参数的的函数的类型,以后用HealthCalcFunc 声明的函数都是这个类型。(记得以前竟然看不懂这种方式)。

 

也可以使用 tr1::function 来完成 Strategy 模式(注:tr1::fucntion 现在已经是C++11 标准的一部分,存在于std namespace 中)

    class GameCharacter;                                 // as before
    int defaultHealthCalc(const GameCharacter& gc);      // as before
    class GameCharacter {
      public:
      // HealthCalcFunc is any callable entity that can be called with
      // anything compatible with a GameCharacter and that returns 
      // anything compatible with an int; see below for details
      typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
      explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf){}
      int healthValue() const
      { return healthFunc(*this);   }
      ...
      private:
      HealthCalcFunc healthFunc;
    };

实现了上面的类之后,计算“健康状况”可以用下面的方式都行:

    short   calcHealth(const  GameCharacter &);
struct HealthCalculator { // class for health   int operator()(const GameCharacter&) const // calculation function   { ... } // objects };
class GameLevel { public:   float health(const GameCharacter&) const; // health calculation   ... // mem function; note }; // non-int return type class EvilBadGuy: public GameCharacter { // as before ... };
class EyeCandyCharacter: public GameCharacter { // another character ... // type; assume same };
// constructor as EvilBadGuy EvilBadGuy ebg1(calcHealth); // character using a // health calculation function EyeCandyCharacter ecc1(HealthCalculator()); // character using a // health calculation // function object GameLevel currentLevel; ... EvilBadGuy ebg2( // character using a   std::tr1::bind(&GameLevel::health, // health calculation   currentLevel, // member function;   _1) // see below for details );

:上面的bind函数,有两个参数第一个是currentLevel 实际上是this指针,因为我们知道类的成员函数其实都有一个隐藏的参数 this 指针,在使用bind函数的时候需要指明该参数;后面的_1 叫做占位符,我们实际传进来的参数只有一个就用这个占位符代替。

 

下面来看一下Strategy 模式的经典实现方法:

virtual 函数以外的其它选择

  这个图告诉你:GameCharacter 是某个继承体系的根类,体系中的EvilBadGuyEyeCandyCharacter 都是derived classes HealthCalcFunc 是另一个继承体系的根类,体系中的SlowHealthLoser FastHealthLoser 都是derived  class,每个GameCharacter 对象内含一个指针,指向一个来自HealthCalcFunc继承体系的对象。

    class GameCharacter;                            // forward declaration
    class HealthCalcFunc {
    public:
      ...
      virtual int calc(const GameCharacter& gc) const
      { ... }
      ...
    };
    HealthCalcFunc defaultHealthCalc;
    class GameCharacter {
    public:
      explicit GameCharacter(HealthCalcFunc *phcf = &defaultHealthCalc) : pHealthCalc(phcf){}
      int healthValue() const
      { return pHealthCalc->calc(*this);}
      ...
    private:
      HealthCalcFunc *pHealthCalc;
    
    };

当你为解决问题而寻找某个设计方法时,不放考虑virtual函数的替代方案。一下有几个替代方案:

  1. 使用non-virtual interface (NVI)手法,它以public  non-virtual成员函数包裹较低访问性(privateprotected)的virtual函数
  1. virtual函数替换为“函数指针成员变量”,这是Strategy设计模式的一部分表现形式
  2. 以“tr1 :: function”成员变量替换virtual函数,因而允许使用任何可调用实体搭配一个兼容于需求的签名式。
  1. 将继承体系内的virtual函数替换为另一个继承体系内的virtual函数。这是Strategy设计模式的传统实现手法。

 

附:UML关系图的含义:

virtual 函数以外的其它选择

 

实际上,有两种不同的 has-a 关系。一个对象可以拥有另一个对象,其中被包含的对象是包含对象的一部分——或者不是。下图 中,我表示出 Airport拥 Aircraft。Aircraft 并不是 Airport 的一部分,但仍然可以说 Airport 拥有 Aircraft,这种关系称为聚集

virtual 函数以外的其它选择

 

另一种 has-a 关系是包含,被包含对象是包含对象的一部分,这种关系也称为组合。

下图显示了 Car(轿车)拥有 Tire(轮胎),后者是它的一部分(也就是说,Car 由 Tire 和其他东西组成),这种 has-a 关系,称为组合关系(composition),用实心菱形表示。此图上还显示了 Car 使用了 GasStation(加油站)类,这种使用关系用带箭头的虚线表示,也称依赖关系(dependencyrelationship)。

virtual 函数以外的其它选择

  组合和聚集都有“一个对象包含一个或多个对象”的意思,但是,组合意味着“被包含对象是包含对象的一部分”,而聚集意味着被包含对象更像是一个集合。我们可以认为组合是一种非共享的关联,被包含对象的生存周期由包含对象控制。适当使用构造函数和析构函数在这里有助于对象的创建和销毁过程。

FCoE BB6 相关学习笔记

   FC拥有自己的独立层次结构,FC-0到FC-4对应OSI模型的1-5层,但也并非一一对应,完整协议内容请大家自行查阅标准文档。其中FC-2定义了数据通信的内容,是与网络方面息息相关的,下面介绍的内容也都是以FC-2为主。

  在FC网络中一共有三种主要的接口角色,NPort,FPort和EPort,其中N是服务器或存储等终端节点连接FC网络的的接口,F是FC交换机设备连接服务器或存储等终端节点的接口,E是FC交换机互联接口。

FCoE BB6 相关学习笔记

FC设备都拥有2个重要标识:
     WWN(World Wide Name):64bit,节点和每个接口都有各自固定的WWN且所有的WWN均是唯一的,WWN的作用是为了身份识别和安全控制,有些类似于MAC,但不做转发寻址使用。 FC ID:24bit,由8个bit的Domain ID,8bit的Area ID和8bit的Port ID组成,每个Domain ID代表一台FC Switch(由此可以算出每个FC网络最多支持256个Switch节点,减去部分保留ID,实际能够支持最多239个Switch)。终端节点的FC ID是基于接口的,每个NPort的FC ID是由直连的FC Switch动态分配。FC ID的主要作用就是供数据报文在FC网络中寻址转发。
     有了标识的Tag,那么就需要个动态协议供FC Switch互相学习了,FC网络使用FSPF(Fabric Shortest Path First)进行FC ID的寻址学习,看名字就知道其协议机制和OSPF没有什么大的区别。
   FC技术体系还有最重要的一个关键流控技术Buffer to Buffer Credits用来确保无丢包转发。BB Credits和TCP滑动窗口相似,规则很简单,两个相邻FC节点在连接初始化的时候先协商一个度量收包设备Buffer大小的数值N出来,发包设备每发一个数据报文就做N-1,收包设备每收一个报文就回一个R_RDY报文回来,发包设备每收到一个R_RDY就做N+1,当N=0时,发包设备就停止发包。这样当突发拥塞时,上游设备们都把报文存在本地缓存中等着,下游有空间时再发,可以最简单的避免丢包。BB Credits是以报文数目衡量buffer能力,与报文长度无关(FC报文最大长度2112Byte)。
     FC设备(一般指服务器,称为Initiator)在传输数据之前需要进行两步注册动作,NPort先通过FLOGI(Fabric Login)注册到最近的Fabric交换机上,获取FC ID及其他一些服务参数并初始化BB Credits。然后再通过PLOGI(Port Login)注册到远端的目的设备(一般指存

储,称为Target)的NPort上建立连接,并在P2P直连的拓扑下初始化BB Credits。

     FC从标准建立伊始就开始被研究跨传统TCP/IP/Ethernet网络传播,目前主要有iSCSI(IP SAN)、FCIP、iFCP和FCoE四条道路。其中FCIP和iFCP应用最少,iSCSI缓慢增长,FCoE后来居上。

FCoE BB6 相关学习笔记

 

  SCSI也做过一些研究,后面有专门文章。FCP(Fibre Channel Protocol)是用来协助SCSI进行寻址的协议。iSCSI、FCIP和iFCP都是依靠TCP的可靠连接确保无丢包,但封的报头多了开销很大。iSCSI由于需要全新的存储设备支持,过于激进,目前虽然有发展,但是受传统存储设备厂商制约始终很缓慢。FCIP和iFCP都是支持FC网络跨IP核心网传输时用到的网络协议,由于目前SAN还是本地组网或使用光纤直连方式的远程组网较多,此场景并不多见,因此也应用很少,其中FCIP已经成为RFC,而iFCP止步于Draft。FCoE相比较来说对上层协议改动较少,开销较低,且有利于减少服务器网络接口数量,在传统交换机厂商的大力鼓吹下当前发展最为迅猛,数据中心网络毕竟会是交换机的天下。
     FCoE基于FC模型而来,仍然使用FSPF和WWN/FC ID等FC的寻址与封装技术,只是在外层新增加了FCoE报头和Ethernet报头封装和相应的寻址动作,可以理解为类似IP和Ethernet的关系。

FCoE BB6 相关学习笔记

看一下FCoE的帧结构:

FCoE BB6 相关学习笔记

  寻址稍微说一下,FCoE使用FIP(FCoE Initialization Protocol)进行初始化连接,FIP运行于VFPort和VNPort之间或VEPort之间,所谓的V就是前面介绍FC的接口角色中的名称前面加了个Virtual。
 
     FCoE使用FIP(FCoE Initialization Protocol)进行初始化连接,FIP运行于VFPort和VNPort之间或VEPort之间,所谓的V就是前面介绍FC的接口角色中的名称前面加了个Virtual。FIP在接口使能后一共做了三件事:

1. FIP VLAN Discovery 过程

FIP VLAN Discovery 过程用于发现 FIP 协议以及后来的 FCoE 中使用的 FCoE VLAN 。
ENode 首先向一个所谓的 All-FCF-MACs 的 MAC 地址发送 FIP VLAN Discovery Request 。本地 VLAN 上的所有的 FCFs (Fibre Channel Forwarder) 都会监听这个 MAC 地址,并对该地址上的 FIP VLAN Discovery 进行响应, 向 ENode 发送 ENode 可以进行 VN_Port Login 的 FCoE VLAN 。

FIP VLAN Discovery 是一个可选的步骤,它的作用仅仅是告诉 ENode 可用的 VLAN, 但并不会引发 ENode 对 VLAN 进行选择。

2. FIP FCF Discovery

FIP FCF Discovery 用于发现那些 FCFs 可以接受 ENodes 的 LOGIN。 FCFs 会定时地向 FCoE VLAN 上的 ALL-ENode-MACS 发送 FIP FCF Discovery Advertisement 。 All-ENode-MACs 与前面的 All_FCF-MACs 对应,该地址被所有的 ENodes 监听。 这些 Advertisement 中包含了 FCF 的 MAC 地址,以及一些建立链接所需的其他参数。
FCF 会定时的发送这些 Advertisement,然而对于新添加的 ENodes 来讲, 这些 ENodes 一般不愿意在启动之后傻等着“收看广告”,对此,ENode 可以向 All-FCF-MACs 发送 solicit unicast Advertisement 。FCFs 在接收到这个单播之后,会向发送的 ENode 发送一个 unicast FIP FCF Discovery Advertisement。

ENode 在收集到了足够的信息之后,就可以决定和哪一个 FCF 建立连接了。

3. FIP FLOGI 以及 FDISC

ENode 在发现了所有的 FCFs 并选了了待 Login 的 FCF 之后, FIP 过程最后的一步就是通知所选择的 FCF: 我要和你的 VF_Port 建立虚拟连接了! 这个过程中可能会涉及到多个 FIP 数据包,并最终为 N_Port 分配 FC_ID。

FCoE BB6 相关学习笔记

  FIP 协议交互和虚拟链接的建立一般来讲,这两个协议的发起者都是 Initiator, 但某些情况下, FCFs 也可能会主动发送一些广播信息。图中,红色部分采用的是 FIP 协议,而绿色部分,则是建立链接以后,使用的是 FCoE 协议。
 
FIP帧格式:

FCoE BB6 相关学习笔记

  FCF(Fibre Channel Forwarder)是FCoE里面重要的角色,可以是软件或者芯片硬件实现,需要占用Domain ID,处理FCoE交换机中所有与FC相关的工作,如封装解封装和FLOGI等。

FCoE BB6 相关学习笔记

  Enode是指网络中所有以FCoE形式转发报文的节点设备,可以是服务器CAN网卡、FCoE交换机和支持FCoE的存储设备。FCoE外层封装的Ethernet报头中MAC地址在Enode间是逐跳的,而FC ID才是端到端的。

下图是一个 ENode 的 VN_Port 与 FCFs 的 VF_Port 的链接示意图:

FCoE BB6 相关学习笔记

 

FCoE VN_Port to VF_Port Example

 
图中,蓝色的实线表示 Ethernet 连接,绿色的实线表示 Fibre Chanle连接,而红色的虚线则表示虚拟的 VN_Port 和 VF_Port 的连接。每个 ENode 和 FCF 都有唯一的一条网线接入 Ethernet, 而在这些 Ethernet 连接之上,则可以虚拟出多个 VN_Port 与 VF_Port 的连接。

VE_Port to VE_Port Virtual Links :

 

FCoE BB6 相关学习笔记

 

图中线条颜色的含义与前面的图类似,不过红色的虚线在这里表示了 VE_Port 和 VE_Port 之间的虚拟链接。 从图中可以看出,每个 FCF 可以拥有多个 VE_Port,通过 Ethernet 连接,实现了多个 VE_Port 的虚拟链接。
从前面的两附图可以看出:
  • 一个 ENode 可以通过一个无损 EtherNet 与多个 FCF 建立虚拟链接。 
  • 一个 FCF 可以通过一个无损 Ethernet 与多个 Enode 建立虚拟链接。 
  • 一个 FCF 可以通过无损 Ethernet 与多个其他的 FCF 建立虚拟链接。 
ENode 模型如下图所示:

 

FCoE BB6 相关学习笔记

ENode Model

对于每一个 NIC,都对应惟一的一个 FCoE Controller, Linux 中, 当为指定的网卡创建 FCoE 实例的时候,会自动创建响应的 FCoE Controller 。

FCoE Controller 的主要作用包括:

  • 发起 FIP VLAN Discovery 以搜索可用的 VLANs (可选);
  • 对 FCF 发起 FIP discovery 以所搜该 FCF 上可用的 VF_Port。
  • 向 FCF 发起 FIP FLOGI Exchange , 如果该 FLOGI 成功,则还需要 VN_Port/FCoE_LEP 对。
  • 发起 FIP NPIV FDISC Exchange (可选)。
  • 当需要显示的从 FCF 中 Logout 时候,发起 FIP LOGO Exchanges。
  • 当 VN_Port 从 FCF 中 logout 后,反实例化 VN_Port/FCoE_LEP (也就是删除响应的数据结构等等)。
  • 收到 FIP Clear Virtual Link 请求的时候,反实例化 VN_Port/FCoE_LEP 。
  • 后台发送 FIP Keep Alive 。
  • 监视 VN_Port/FCoE_LEP 的状态;
  • 监视 FCF 上的 VF_Port 状态。
     FCoE 网卡需要能够与多个VF_Port 进行FIP交互,发送FLOGI 和 FDISC 帧。FCoE_LET 的作用就是将FC帧封装为FCoE帧,将接收到的FCoE 帧 解封装为FC帧。


PFC——基于优先级的流量控制

  通过以太网传输的光纤通道流量(FCoE)就是要求无丢包服务的一个典型示例。为了使以太网能够满足应用的无丢包要求,需要制定一种方法来通过以太网提供无损服务。基于优先级的流量控制正是在这种背景下出现。

     基于优先级的流量控制(PFC)是对暂停机制的一种增强(如下图)。当前以太网暂停选择(IEEE 802.3 Annex 31B)也能达到无丢包的要求,但它会阻止一条链路上的所有流量,本质上来讲,它会暂停整条链路。PFC 允许在一条以太网链路上创建 8 个虚拟通道,并为每条虚拟通道指定一个 IEEE 802.1P 优先等级,允许单独暂停和重启其中任意一条虚拟通道,同时允许其它虚拟通道的流量无中断通过。这一方法使网络能够为单个虚拟链路创建无丢包类别的服务,使其能够与同一接口上的其它流量类型共存。

PFC——基于优先级的流量控制

  

  如图所示,在交换机端口的 8 个队列进行 buffer 的分配,形成了网络中 8 个虚拟化通道,数据流带上自身的通道标签(802.1P 进行标识),buffer 大小使得各队列有不同的数据缓存能力。 一旦出现瞬时拥塞,即某个设备的队列缓存消耗较快,超过一定阈值(可设定为端口队列的 1/2、3/4 等比例),设备即向数据进入的方向(上游设备)发送反压信息,上游设备接收到反压信息,会根据反压信息指示停止发送或延迟发送数据,并将数据存储在本地端口 buffer,如果本地端口 buffer 消耗超过阈值,则继续向上游反压,如此一级级反压,直到网络终端设备,从而消除网络节点因拥塞造成的丢包。使新一代以太网技术可以满足存储业务不丢包的要求(为光纤通道流量,也就是存储流量,分配一个 IEEE 802.1P 优先等级,并为该优先等级启用 PFC)。

  

交互流程:
 
使用端口的 PFC(Priority-Based Flow Control,基于优先级流量控制)功能,可以基于 802.1P  优先级对报文进行流量控制。
当本端发生拥塞时,交换机会根据本端收到报文的 802.1P  优先级进行判别,从而确定对报文的处理方式。
· 如果此时收到报文的 802.1P  优先级有使能 PFC,则接收该报文,并向对端发送 PFCPAUSE 帧,通知对端设备暂时停止发送该类报文。对端设备在接收到 PFC PAUSE 帧后,将暂时停止向本端发送该类报文,暂停时间长短信息由 PFC PAUSE 帧所携带。当拥塞仍然存在时,此过程将重复进行,直至拥塞解除。
· 如果此时收到报文的 802.1P  优先级没有使能 PFC,则直接将报文丢弃。 

当对端接收到 PFC  PAUSE 帧时,会根据本端的 802.1P  优先级的配置信息和状态信息,确定对报文的处理方式。 

· 若对端有开启相应优先级的 PFC 功能,且尚未暂停发送相应优先级的报文,则暂停发送相应优先级的报文,并根据 PFC PAUSE 帧中对应的暂停时间启动定时器。当定时器到期后,将恢复相应优先级报文的发送。 
· 若对端有开启相应优先级的 PFC 功能,且已经暂停发送相应优先级的报文,则根据 PFC  PAUSE 帧中对应的暂停时间更新对应定时器的到期时间。
· 若 PFC  PAUSE 帧中对应的暂停时间为 0,则相当于使对应的暂停定时器立即到期,立即恢复相应优先级报文的发送。
· 若 PFC  PAUSE 帧中对应的暂停时间不为 0,则相当于复位对应的暂停定时器。也就是说,只要本端一直拥塞,则对端会因不断收到 PFC PAUSE 帧而持续暂停发送相应优先级的报文。 
· 若对端没有开启相应优先级的 PFC 功能,则不会暂停发送相应优先级的报文。