绑定确认过程

注1:本文以ZStack-cc2530-2.3.1-1.4.0里的Collector-Sensor为例。

注2:看本文先弄懂绑定基本正确的流程。

 

传感节点发送网络,并且加入网络后,就会进入MY_FIND_COLLECTOR_EVT事件,注意事件的位运算操作,MY_FIND_COLLECTOR_EVT属于ZB_USER_EVENTS事件中的一种。按如下的流程:zb_HandleOsalEvent( )--zb_BindDevice(),在zb_BindDevice()函数里,会执行如下代码:

{      

   ret = ZB_INVALID_PARAMETER;
        destination.addrMode = Addr16Bit;
        destination.addr.shortAddr = NWK_BROADCAST_SHORTADDR;
        if ( ZDO_AnyClusterMatches( 1, &commandId, sapi_epDesc.simpleDesc->AppNumOutClusters,
                                                sapi_epDesc.simpleDesc->pAppOutClusterList ) )
        {
          // Try to match with a device in the allow bind mode
          ret = ZDP_MatchDescReq( &destination, NWK_BROADCAST_SHORTADDR,
              sapi_epDesc.simpleDesc->AppProfId, 1, &commandId, 0, (cId_t *)NULL, 0 );
        }
        else if ( ZDO_AnyClusterMatches( 1, &commandId, sapi_epDesc.simpleDesc->AppNumInClusters,
                                                sapi_epDesc.simpleDesc->pAppInClusterList ) )
        {
          ret = ZDP_MatchDescReq( &destination, NWK_BROADCAST_SHORTADDR,
              sapi_epDesc.simpleDesc->AppProfId, 0, (cId_t *)NULL, 1, &commandId, 0 );
        }

        if ( ret == ZB_SUCCESS )
        {
          // Set a timer to make sure bind completes
#if ( ZG_BUILD_RTR_TYPE )
          osal_start_timerEx(sapi_TaskID, ZB_BIND_TIMER, AIB_MaxBindingTime);
#else
          // AIB_MaxBindingTime is not defined for an End Device
          osal_start_timerEx(sapi_TaskID, ZB_BIND_TIMER, zgApsDefaultMaxBindingTime);
#endif
          sapi_bindInProgress = commandId;
          return; // dont send cback event

}
需要注意的是ret = ZDP_MatchDescReq( &destination, NWK_BROADCAST_SHORTADDR, sapi_epDesc.simpleDesc->AppProfId,  1, &commandId, 0, (cId_t *)NULL, 0 );本句最终会调用无线发射信号的语句,只要信号发送出去了(不论是否收到,也不管有没有匹配的端点),得到的ret总是为ZB_SUCCESS ,于是总会这一句:osal_start_timerEx(sapi_TaskID, ZB_BIND_TIMER, zgApsDefaultMaxBindingTime);
而这一句的作用是发送完绑定请求(匹配描述符请求)后给应用层的一个确认。下面分两种情况来处理ZB_BIND_TIMER事件。

 

一、绑定不成功

       传感节点将绑定请求信息(匹配描述符请求信息)发送给采聚节点后,就会等待采聚节点发过来匹配描述符响应事件,如果绑定不成功(原因很多,比如采聚节点没有开启允许绑定,采聚节点没有匹配端点),则传感节点收不到匹配描述符响应事件,经过zgApsDefaultMaxBindingTime段时间后,传感节点就会执行ZB_BIND_TIMER事件。依次经过如下流程:SAPI_BindConfirm--zb_BindConfirm,在zb_BindConfirm函数里,有如下代码

void zb_BindConfirm( uint16 commandId, uint8 status ){
  (void)commandId;

  if ( ( status == ZB_SUCCESS ) && ( myAppState == APP_START ) )  {
    myAppState = APP_BOUND;

    myApp_StartReporting();
  }
  else  {
    osal_start_timerEx( sapi_TaskID, MY_FIND_COLLECTOR_EVT, myBindRetryDelay );
  }
}

上面程序会进入else分支,这样又会再次发送MY_FIND_COLLECTOR_EVT,在MY_FIND_COLLECTOR_EVT里,会进入绑定,这样就进入一个不断发送绑定请求的循环。

 

二、绑定成功

       如果绑定成功,传感节点会收到采聚节点发送过来的Match_desp_res消息。传感节点程序执行的流程为ZDO层的AF_INCOMING_MSG_CMD--ZDP_IncomingData---  handled = ZDO_SendMsgCBs( &inMsg );--应用层的SYS_EVENT_MSG--ZDO_CB_MSG--SAPI_ProcessZDOMsgs( (zdoIncomingMsg_t *)pMsg );--Match_Desc_rsp:在这个事件里,会执行如下这句osal_stop_timerEx(sapi_TaskID,  ZB_BIND_TIMER);这句会将ZB_BIND_TIMER事件取消。(上面程序流程执行过程比较复杂,如有不清楚的,请给我留言,我会帮你解答的。)在这个函数的下面有zb_BindConfirm( sapi_bindInProgress, ZB_SUCCESS );

void zb_BindConfirm( uint16 commandId, uint8 status ){
  (void)commandId;

  if ( ( status == ZB_SUCCESS ) && ( myAppState == APP_START ) ){
    myAppState = APP_BOUND;

    myApp_StartReporting();
  }
  else if(myAppState == APP_BOUND){
    osal_start_timerEx( sapi_TaskID, MY_FIND_COLLECTOR_EVT, myBindRetryDelay );
  }
}

这时会进入IF分支,进而通过 myApp_StartReporting();传送数据。


BINDING KuangJunBin:本文是作者根据TI Z-Stack开发文档,ZigBee Specification-2007,《Zigbee Wireless Networking》等英文资料整合和翻译而成,采用中英双语对照方便读者理解,文中翻译不当之处,望广大同行不吝赐教。推广ZigBee技术,提高国内电子行业的国际影响力,是我们无线通讯工程师的愿景。本文欢迎转载,请保留作者信息和出处,作为支持我继续努力前行的动力,谢谢! E-mail:kuangjunbin@gmail.com In the Zigbee 2006 release,the binding mechanism is implemented in all devices and is called source binding. Binding allows an application to send a packet without knowing the destination address,the APS layer determines the destination address from its binding table,and then forwards the message on to the destination application(or multiple applications)or group. ZigBee2006版本中规定,在全部节点中实现绑定机制,并将其称为源绑定绑定机制允许一个应用服务在不知道目标地址的情况下向对方(的应用服务)发送数据包。发送时使用的目标地址将由应用支持子层(APS)从绑定表中自动获得,从而能使消息顺利被目标节点的一个或多个应用服务,乃至分组接收。 Binding Table 1.Defined in RAM,but can be saved in Flash if the NV_RESTORE compiler option is used 2.Stored on source node(REFLECTOR compiler option required) 3.Entries map messages to their intended destination 4.Each entry in the binding table contains the following: typedef struct { uint16 srcIdx;//Source index uint8 srcEP;//Source endpoint uint8 dstGroupMode;//Specifies normal or group addressing uint16 dstIdx;//Destination index or group address uint8 dstEP;//Destination endpoint uint8 numClusterIds;//Number of cluster IDs in the clusterIdList below uint16 clusterIdList[MAX_BINDING_CLUSTER_IDS]; }BindingEntry_t; 绑定表 1. 绑定表存放的位置是内存中预先定义的RAM块,如果编译选项NV_RESTORE被激活,也能保存在Flash里。 2. 绑定表放置在源节点(需要激活编译选项REFLECTOR)。 3. 绑定表的条目把需要发送的消息映射到它们的目标地址上。 4. 绑定表中每个条目(entry)包括以下内容: 绑定表条目结构体的定义
<think>我们正在讨论C++的动态绑定实现原理。根据之前的对话,我们已经知道动态绑定依赖于虚函数表(vtable)和虚函数表指针(vptr)。现在,用户进一步询问动态绑定的工作机制和实现原理,我们将详细展开。 动态绑定(Dynamic Binding)是C++多态的核心,它允许在运行时根据对象的实际类型来调用相应的函数。与之相对的是静态绑定(Static Binding),它在编译时确定调用的函数。 ### 动态绑定的实现原理 动态绑定的实现基于以下关键机制: 1. **虚函数表(vtable)**: - 每个包含虚函数的类(或从包含虚函数的类派生)都有一个虚函数表。 - 虚函数表是一个函数指针数组,每个元素指向该类的一个虚函数的实现。 - 虚函数表在编译时由编译器生成,并存储在程序的只读数据段(如`.rodata`)中。 2. **虚函数表指针(vptr)**: - 每个对象在创建时,会隐式地包含一个指向其类的虚函数表的指针(vptr)。 - 该指针位于对象内存布局的起始位置(具体位置可能因编译器和平台而异,但通常是首部)。 - 当对象被构造时,构造函数会初始化这个vptr,使其指向当前类的虚函数表。 3. **动态绑定过程**: 当通过基类指针或引用调用虚函数时,编译器会生成代码来执行以下步骤: - 通过对象的vptr找到虚函数表。 - 在虚函数表中查找要调用的虚函数的地址(通常通过固定的索引,因为每个虚函数在表中的位置是固定的)。 - 使用查找到的函数地址进行调用。 ### 详细步骤分析 考虑以下代码: ```cpp class Base { public: virtual void func1() { /* Base::func1 的实现 */ } virtual void func2() { /* Base::func2 的实现 */ } int a; }; class Derived : public Base { public: virtual void func1() override { /* Derived::func1 的实现 */ } virtual void func3() { /* 新的虚函数,注意:这也会进入虚函数表 */ } int b; }; int main() { Base* basePtr = new Derived(); basePtr->func1(); // 动态绑定到Derived::func1 delete basePtr; return 0; } ``` #### 内存布局 - `Derived`对象的内存布局: ``` +----------------+ +-----------------------+ | vptr |----->| &Derived::func1 | // 重写了Base的func1 +----------------+ +-----------------------+ | Base::a | | &Base::func2 | // 未重写,所以是Base的func2 +----------------+ +-----------------------+ | Derived::b | | &Derived::func3 | // 新的虚函数 +----------------+ +-----------------------+ ``` #### 调用`basePtr->func1()`的过程 1. 从`basePtr`指向的对象中获取`vptr`(因为`vptr`位于对象起始位置,所以直接解引用`basePtr`即可得到`vptr`)。 2. 通过`vptr`访问虚函数表,并定位到第一个虚函数(`func1`)的条目(通常索引0对应第一个虚函数)。 3. 调用该地址处的函数(即`Derived::func1`)。 ### 动态绑定与静态绑定的对比 | 特性 | 静态绑定 | 动态绑定 | |--------------|--------------------------|-----------------------------| | **决定时机** | 编译时 | 运行时 | | **实现方式** | 直接函数调用 | 通过虚函数表间接调用 | | **效率** | 高效(无额外开销) | 有额外开销(两次指针解引用) | | **适用场景** | 非虚函数、普通成员函数 | 虚函数通过基类指针/引用调用 | ### 动态绑定的条件 1. 基类中声明虚函数(使用`virtual`关键字)。 2. 派生类重写(override)该虚函数(函数签名必须相同)。 3. 通过基类指针或基类引用调用虚函数。 ### 性能开销 动态绑定需要两次内存访问: 1. 访问对象的`vptr`以获取虚函数表地址。 2. 访问虚函数表中相应位置的函数指针。 然后才能调用函数。这比静态绑定的直接调用多了一些指令,因此效率稍低。但在大多数情况下,这种开销是可以接受的。 ### 示例代码分析 ```cpp Base* basePtr = new Derived(); basePtr->func1(); // 动态绑定:调用Derived::func1 ``` - 在编译时,编译器不知道`basePtr`实际指向的类型(可能是`Base`,也可能是`Derived`),因此无法静态绑定。 - 编译器会生成代码,在运行时通过虚函数表查找正确的函数地址。 ### 总结 C++的动态绑定通过虚函数表和虚函数表指针实现,具体步骤包括: 1. 每个包含虚函数的类都有一个虚函数表,存储该类虚函数的地址。 2. 每个对象有一个指向其类的虚函数表的指针(vptr)。 3. 通过基类指针或引用调用虚函数时,通过vptr找到虚函数表,再通过表中的函数指针调用正确的函数。 这种机制使得C++能够在运行时根据对象的实际类型调用正确的函数,从而实现多态[^1][^3]。 §§相关问题§§ 1. 虚函数表在多重继承中如何工作? 2. 构造函数中调用虚函数为何无法实现多态? 3. 虚函数表指针`vptr`在对象构造/析构过程中的变化规律? 4. 如何通过调试工具查看虚函数表的内容? 5. 动态绑定与静态绑定的性能差异具体有多大?</think>### C++动态绑定实现原理详解 动态绑定(Dynamic Binding)是C++多态的核心机制,其实现基于**虚函数表(vtable)** 和**虚函数表指针(vptr)** 的协作。与静态绑定在编译时确定函数调用不同,动态绑定在运行时根据对象的实际类型确定调用的函数[^1][^3]。以下是详细实现原理: --- #### 一、核心组件 1. **虚函数表(vtable)** - 每个包含虚函数的类都有一个**只读的虚函数表** - 本质是函数指针数组,存储该类所有虚函数的入口地址 - 派生类继承基类的虚函数表: ```cpp class Base { virtual void func() {} // 虚函数 }; class Derived : public Base { void func() override {} // 重写虚函数 }; ``` ``` Base的vtable: Derived的vtable: [0] &Base::func [0] &Derived::func // 重写后更新 ``` 2. **虚函数表指针(vptr)** - 每个对象隐含一个`vptr`(通常位于对象内存首部) - 指向当前对象所属类的虚函数表 - 大小通常为8字节(64位系统) --- #### 二、动态绑定过程 当通过基类指针/引用调用虚函数时: ```cpp Base* obj = new Derived(); obj->func(); // 动态绑定 ``` 1. **定位vptr** 通过对象首地址获取`vptr`(编译器自动插入指令): ``` mov rax, [obj] // 获取vptr地址 ``` 2. **访问虚函数表** 通过`vptr`找到虚函数表: ``` mov rbx, [rax] // 获取虚函数表基址 ``` 3. **计算函数偏移** 按固定偏移定位目标函数(如`func`在vtable索引0): ``` mov rdx, [rbx + 0*8] // 获取&Derived::func地址 ``` 4. **执行函数调用** ```assembly call rdx // 调用实际函数 ``` > **关键特点**:函数地址在运行时通过查表确定,而非编译时固定[^1][^3]。 --- #### 三、内存布局示例 ```cpp class Animal { public: virtual void speak() {} // 虚函数 int age; // 成员变量 }; class Cat : public Animal { public: void speak() override {} // 重写虚函数 int color; // 新增成员 }; ``` 对象内存布局: ``` Cat对象内存布局: +----------------+ +-------------------+ | vptr (8字节) |---->| &Cat::speak | // 虚函数表 +----------------+ +-------------------+ | Animal::age | | ...其他虚函数地址... +----------------+ +-------------------+ | Cat::color | +----------------+ ``` --- #### 四、动态绑定条件 1. **必要前提**: - 基类声明虚函数(`virtual`关键字) - 派生类重写(override)虚函数 - 通过基类指针或引用调用 2. **失效场景**: ```cpp Cat cat; cat.speak(); // 静态绑定(直接调用Cat::speak) ``` --- #### 五、性能分析 | 绑定类型 | 决定时机 | 开销 | 指令示例 | |------------|----------|---------------|-----------------------| | 静态绑定 | 编译时 | 无额外开销 | `call 0x4000` (直接) | | **动态绑定** | 运行时 | **2次指针解引用** | `call [vptr+offset]` | > 动态绑定开销 ≈ 静态绑定 + 两次内存访问(现代CPU约1-3时钟周期)[^3] --- ### 总结 C++动态绑定通过**虚函数表+虚函数指针**实现: 1. 编译器为每个类生成虚函数表 2. 对象构造时初始化`vptr`指向正确的vtable 3. 虚函数调用转为`vptr->vtable[index]`查表操作 4. 函数地址在运行时动态解析 这种机制在保持高效的同时,实现了面向对象的核心特性——运行时多态[^1][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值