Instantiated Object of ClassOpenThread 的代码中,各个功能模块都是以类的形式存在的。比如我们之前分析过的 class Instance,class Mac 等。这里我们和结构体做个类比。结构体的定义只是规定了结构体的形式,它并没有形成一个结构体的实体,也就是并没有实际分配内存。只有声明了结构体变量之后,才产生了真正的结构体实体。类的实例化类似于声明结构体变量,是产生类实体的动作。OpenThread 中这些类都是在哪里实例化的呢?下面我们将进一步分析。
在下图的 class Instance 定义中,我们可以看到它的 private 成员定义了很多模块类的实体。比如类 Mac::Mac 的实例就是 mMac。实例化 class Instance ,就可以实例化了各个类。那么下一个问题 class Instance 自己是在哪里实例化的呢?
#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE Dns::ServiceDiscovery::Server mDnssdServer;#endif#if OPENTHREAD_CONFIG_DNS_DSO_ENABLE Dns::Client mSntpClient;#endif#if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE Sntp::Client mSntpClient;#endif#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE BackboneRouter:

ocal mBackboneRouterLocal;#endifMeshCop::ActiveDatasetManager mMActiveDataset;MeshCop:

endingDatasetManager mMpendingDataset;MeshCop::ExtendedPanIdManager mExtendedPanIdManager;MeshCop::NetworkNameManager mNetworkNameManager;Ip6::Filter mIp6Filter;KeyManager mMKeyManager;Lowpan:

owpan mLowpan;Mac::Mac mMac;MeshForwarder mMeshForwarder;Mle::MleRouter mMleRouter;Mle:

iscoveryScanner mDiscoveryScanner;AddressResolver mAddressResolver;
在寻找 class Instance 在哪里实例化之前,我们首先要搞清楚一个问题:如何将 class Instance 实例化?class Instance 的实例化是通过静态函数 InitSingle 来实现的。这里面涉及到下面几个 C++ 的基础知识:
- static function 静态函数。 类中的静态成员。静态成员函数不属于任何对象,可以直接通过类名调用。静态成员函数通常用于执行全局性质的操作,比如单例模式 Singleton 等。class Instance 中的函数 InitSingle 在加了 static 后可以在 class Instance 实例化前直接调用。
- Singleton 单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问。class Instance 显然需要这个特性。在 class Instance 实现单例模式需要以下三步:
- 单例模式的类只提供私有的构造函数。class Instance 中的构造函数 Instance(void) 是 private 属性。
- 类定义中含有一个该类的静态私有对象。modules\lib\openthread\src\core\instance\instance.cpp 中定义的数组 gInstanceRaw 实现了这个功能。
- 类提供了一个静态的共有的函数用于创建或获取它本身的静态私有对象。class Instance 中的函数 InitSingle 实现了这个功能。并且 InitSingle 中通过 VerifyOrExit(!instance->mIsInitialized) 和 instance->AfterInit() 确保这个函数在重复调用时,只能生效一次。
- placement new 在用户指定的内存位置上(这个内存是已经预先分配好的)构建新的对象,因此这个构建过程不需要额外分配内存,只需要调用对象的构造函数在该内存位置上构造对象即可。具体的实现方式是 Object * p = new (address) ClassConstruct(...)。从下图中我们看到 gInstanceRaw 是预先定义的数组。new(&gInstanceRaw) Instance() 就是在 gInstanceRaw 的地址上实例化类 Instance。在实例化的过程中会自动调用私有的构造函数初始化成员变量。
/*** Initializes the single OpenThread instance.** Initializes OpenThread and prepares it for subsequent OpenThread API calls. This * function must be called before any other calls to OpenThread.** @returns A reference to the single OpenThread instance.**/static Instance &InitSingle(void);// Define the raw storage used for OpenThread instance (in single-instance case).OT_DEFINE_ALIGNED_VAR(gInstanceRaw, sizeof(Instance), uint64_t);Instance &Instance::InitSingle(void){ Instance *instance = &Get(); VerifyOrExit(!instance->mIsInitialized); instance = new (&gInstanceRaw) Instance(); instance->AfterInit();exit: return *instance;}
知道了如何实例化单例模式的 class Instance 后,下面我们来分析 CLI 程序是在哪里调用静态成员函数 InitSingle 来实例化 class Instance 的。如下,文件 instance_api.cpp 中的接口函数 otInstanceInitSingle 对 InitSingle 做了一个封装。CLI 这个例程代码中是 调用 otInstanceInitSingle 来完成实例化的。
otInstance *otInstanceInitSingle(void) { return &Instance::InitSingle(); }
下面是函数 otInstanceInitSingle 的调用顺序。net_init 在整个 zephyr 系统初始化的时候调用。
SYS_INIT(net_init, POST_KERNEL, CONFIG_NET_INIT_PRIO)
CLI Command ot ifconfig up我们再分析一条 CLI 指令。我们可以从下图中很容易的看出这条指令是通过 otIp6SetEnabled(GetInstancePtr(), true) 执行的。
template <> otError Interpreter:

rocess<Cmd("ifconfig")>(Arg aArgs[]){ otError error = OT_ERROR_NONE; else if (aArgs[0] == "up") { SuccessOrExit(error = otIp6SetEnabled(GetInstancePtr(), true)); } else if (aArgs[0] == "down") { SuccessOrExit(error = otIp6SetEnabled(GetInstancePtr(), false)); }}
找到如下图函数 otIp6SetEnabled 的定义。我们可以看到里面主要是执行了 instance.Get<ThreadNetif>().Up() 。经过上面对 C++ 基础知识的学习,我们可以很快速的看出来这句调用了类 ThreadNetif 中的 up 函数。
otError otIp6SetEnabled(otInstance *aInstance, bool aEnabled){ Error error = kErrorNone; Instance &instance = AsCoreType(aInstance); if (aEnabled) { instance.Get<ThreadNetif>().Up(); } else { instance.Get<ThreadNetif>().Down(); } return error;}
找到如下图的函数 ThreadNetif::Up 的定义,里面调用了 Get<Mac::Mac>().SetEnabled(true) 这样一层一层的分析下去,"ot ifconfig up" 最终调用了 otPlatRadioEnable。
void ThreadNetif::Up(void){ VerifyOrExit(!mIsUp); // Enable the MAC just in case it was disabled while the Interface was down. Get<Mac::Mac>().SetEnabled(true); Get<MeshForwarder>().Start(); mIsUp = true; SubscribeAllNodesMulticast(); IgnoreError(Get<Mle::MleRouter>().Enable()); IgnoreError(Get<Tmf::Agent>().Start());#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE IgnoreError(Get<Dns::ServiceDiscovery::Server>().Start());#endif Get<Notifier>().Signal(kEventThreadNetifStateChanged);}
UDP data transfer我们用两块 nRF52840DK 来进行 UDP 通讯的演示。先用之前所述的命令启动网络。先启动的网络的一块DK会成为 OpenThread 网络中的 leader 角色。先在 leader 上输入如下左图的的命令。 然后在另一块 DK 上输入如右图的指令。右图的指令首先加入已有的 Thread 网络,然后发送一个 UDP 数据包到 leader。
- ot ipaddr 用来获取 IP address
- ot udp bind :: 1234 用来监听端口 1234
- ot udp send 用来发送 UDP 数据 hello。 这里所用的地址和端口号需要和 leader 一侧的对应。
- 从 leader 上打印出来的信息,我们可以看到 另一块 DK 发过来的 UDP 数据。
> ot ipaddrot ipaddrfdde:ad00:beef:0:0:ff:fe00:fc00fdde:ad00:beef:0:0:ff:fe00:c400fdde::ad00:beef:0:e8a:1dff:5148:d37fe80:0:0:0:840f::8040:5a1b:655eDoneuart:~$ > ot udp openot udp openDoneuart

> ot udp bind :: 1234ot udp bind :: 1234Done5 bytes from fdde:ad00:beef:0:d248:c8e4:f8c9:df33 49156 hellouart:~$uart:~$ > ot panid 0xabcdot panid 0xabcdDoneuart:~$ > ot networkkey 00112233445566778899aabbccddeeffot networkkey 00112233445566778899aabbccddeeffDoneuart:~$ > ot ifconfig upot ifconfig upDoneuart:~$ > ot thread startot thread startDoneuart:~$ > ot stateot staterouterDoneuart:~$ > ot udp send fdde:ad00:beef:0:0:ff:fe00:fc00 1234 helloot udp send fdde:ad00:beef:0:0:ff:fe00:fc00 1234 helloDone
接下来,我们可以运用之前学到的 C++ 知识,通过分析命令 ot udp send 来探索 OpenThread 数据发送流程。左边是通过分析 ot udp send 所得到的关键函数。这些函数可以对应右侧的 TX data flow 图。
