Emane流程概述

背景

现在要继续做emane相关的开发,之前做tdma相关内容写了篇 Emane模型调研 ,现在想梳理下整个仿真器的仿真流程,记录下。

debug Emane

为了更好的理解和开发 emane 需要使用gdb调试该程序,使用 vscode 在 core 容器节点执行调试。

由于 core 节点为 root 用户,管理员执行vscode需要如下命令,将root配置放于 /home/lk233/emane(自行修改目录) 才能打开vscode管理员模式

同时,宿主主机执行 xhost + 否则容器内vscode无法通过下x11连接外部显示器,显示vscode图像化界面。

#!/bin/bash
code  --user-data-dir="/home/lk233/.vscode/root_config" --no-sandbox /home/lk233/emane

可以将上述脚本命名为 vscode_root 添加执行权限放入系统变量目录 /usr/local/bin 中,方便在core节点中直接执行 vscode_root 打开 root 用户下的 vsocde。

不过gdb启动emane进程调试会出现问题,他需要重启emane进程,core的 link_monitor 监听线程会报错。其次重启后需要发送所有节点的位置事件(location event)更新下重启后的节点位置表(可以界面拖动节点触发emane事件),否则节点通信时发现对端节点位置不存在则无法发送 OTA(Over The Air) 包,导致应用层反馈无线网卡无法和其他节点通信。

目前用的一直是 attach 模式,不中断原有的emane进程。 而且用attach的好处在于不用在容器内启动vscode启动调试。

命名空间具有层级关系,容器内的进程id会有一个映射到宿主主机上。通过容器的进程直接在宿主机上 attach 那个emane进程就能调试了,如果提示需要权限输入y即可。

测试发现有些数据在容器外调试看不到,还是老老实实在容器内调吧

调试配置可以通过vscode菜单栏的 运行 项目的添加配置,生成debug配置文件,修改后其调试文件 /.vscode/launch.json 的配置如下所示:

{
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) 附加",
            "type": "cppdbg",
            "request": "attach",
            "program": "/usr/bin/emane",
            "processId": "${command:pickProcess}",
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true,
                    "sudo": true,
                }
            ]
        }
    ]
}

20221003_1

看代码如果高亮有问题,可能是Vscode语法指向安装在系统usr的头文件去了,Vscode Ctrl Shift p 快捷键后填入 C/C++:编辑配置(UI) 里的包含路径改为 ${workspaceFolder}/include 即可。

调试时变量显示 optimized out 状态,解决这个问题需要关闭编译C++项目时的优化选项,执行configure命令时进行下述设置 ./configure CFLAGS='-g -O0' CXXFLAGS='-g -O0 -Wall -Wextra' 之后重新编译。(emane的configure 有debug参数 等价执行 ./configure --with-debug )如果只是临时改一小部分,直接修改makefile里的对应参数改优化等级。

Emane 执行流程

每一个容器节点都可以部署一个Emane进程,并在容器节点内配置其Emane数据传输网卡(每容器唯一), 监听数据网卡(一个网卡对应一个自组网模型)来将仿真内拓扑节点孪生映射为自组网内节点。

以网络模型为例,应用层、传输层、网络层的模型都是linux内核栈原生的数据结构,所有指向监听数据网卡 的数据都会被Emane监听并转为Emane内进程无线数据并在Emane专用数据传输网卡上与其他节点的Emane 进程通信。Emane就是实现该监听数据无线网卡的链路层、物理层的模拟仿真。对于用户来说,这个监听数据 网卡就是无线模型网卡,所有应用开发可以不用考虑无线的细节。

当然Emane也有其事件调度机制,可以基于Emane提供的事件api接口, 在容器节点或Emane多播通道内以gRPC的方式向Emane发送控制事件消息组播包。 原理是Emane进程会开启监听服务器,收集网内Emane事件并更新当前模型。 常见的事件有位置、路径损耗、发射功率等。 无论是物理层、还是链路层都有相应的事件处理函数。

emane的可执行文件都在 emane/src/applications 生成,其中emane文件夹自然是 最后使用的emane二进制文件。

大部分都抽象的函数 对象 都存在 EMANE 的 Application 命名空间下。

使用 DECLARE_APPLICATION 宏将其main函数隐藏 即为调用app的main函数并传参调用 (其他例如 emaneeventd 等app也是如此实现)

抽象进程

以 emane 为例,src/libemane/main.cc Main类中有成员函数main函数,在宏定义预编译后其即为整个进程的主函数,其抽象的流程就在这里实现,大致为

  1. 创建和EMANE logger并设置日志级别 EMANE::Application::Logger logger;
  2. 执行 tye catch 捕获异常。
  3. 读取cmd配置参数 并 解析 doConfigure(sConfigurationXML);
  4. 执行由派生类实现具体细节的纯虚函数 doStart
  5. 堵塞互斥锁等待信号捕获流程终止 mutex.lock();
  6. 一切完成后 执行 结束、销毁工作 doStop(); doDestroy();

20221003_1

模块分层介绍

emane 调用的是 Emulator 对象。Emulator 由 Main 公有 继承。

其 Emulator 模板类的 Builder Director Manager 分别传入 NEMBuilder NEMDirector NEMManager 来对 emane 无线仿真器中的最小单位 NEM 定义其实现。

Builder 定义其间所有 layer 的构建初始化过程,Director 实现了XML的解析,由 Builder 和 读取到的配置xml进行实例化创建Director 对象,每一个仿真器都有一个唯一的ID(uuid),根据该 Director 的id 创建一个Manager对象 进行整个仿真流程的启动 停止等流程的管理。

结构


看到这里是不是很熟悉,这里回顾下mac层的基础api清单:

  • initialize
    • 注册插件配置项和一个可选的配置验证器
    • 注册无线模型的统计信息和统计表
    • 注册无线模型需要的emane事件
  • configure
    • 处理所有已加载的配置
  • start
    • 仿真从该方法开始,启动无线模型仿真内定时器等。
  • postStart
    • NEM层堆栈中的所有组件现在都处于“启动”状态。跟踪或配置相邻层的组件。
  • stop
    • 与 start 相反,执行停止行为
  • destroy
    • 与 initialize 相反,执行清理销毁操作。

执行过程中就是运行这些流程:


经过之前的分析,可以知道 emane 在物理层之下 封装为多播的OTA 流量,其上是 网卡Adapter 的网络层数据包。OTA流量分为emane事件的流量和普通无线数据的流量,仿真就是 对 phy mac 对其实现特定的算法 入出队等实现无线传输的特性。所以这里我只对 无线网络相关的模块进行分析,至于其他 统计数据 特定模型的优先级 emane事件等 就暂不讨论。

数据处理流程

一个运行了emane的容器节点就是platform,其中有可能搭载多个NEM。NEM代表具体的网络模型,会映射到容器内的Adapter网卡。

在 start 过程中 src/libemane/nemqueuedlayer.cc 会开启processWorkQueue线程 使用IO复用epoll_wait监听 来处理platform中所有nem的数据 事件的触发并进行处理。

NEM的抽象结构如下所示:

 *   NEMNetworkAdapter (connectLayers UpstreamTransport * param)
 *   ... --
 *   MAC   |
 *   ...   | - stack addLayer, bottom of stack pushed first.
 *   ...   |     where, ... can be 0 or more shims
 *   PHY   |
 *   ... --
 *   NEMOTAAdapter     (connectLayers DownstreamTransport * param)

nem 根据 读取的配置文件 一层一层构建 layer stack 来 对仿真的数据进行流程的定义。

在 emane 中 无论是 NEMNetworkAdapter 的链路数据 还是 NEMOTAAdapter的OTA数据都是通过 UpstreamTransport DownstreamTransport 来传输的,这些数据队列 会按照 现实世界的数据一般 一层一层 封装、解析。

从 NEMNetworkAdapter 接收的数据 就是 UpstreamTransport, 需要 sendDownstreamPacket,在processDown中处理。 代表主机发送数据通过emane来封装为OTA数据,然后它的下一层就会执行processDown。从 NEMOTAAdapter 接收的数据 就是 DownstreamTransport, 需要 sendUpstreamPacket,在processUp中处理。 代表主机接收到 OTA 流量 需要解封装到 Adapter 中构建为 网络层数据包,然后它的上一层就会执行processUp。

底层序列化的二进制流在平台的网络接口 通过多播通道(over the air multicast channel)传输。经过emane的封装解封来实现现实无线数据的处理流程,也将无线仿真模块化降低与容器平台的耦合。这就是仿真器物理层在异构无线电模型中考虑信号传播、天线效应和干扰源的方式,都在emane进程中处理。

  • NEMNetworkAdapter

    该 Adapter 在配置xml文件中可设定为 raw vitual。即虚拟一个三层TUN设备用于传输数据否则使用原生网卡设备,默认用vitual 即可。tun的arp二层操作由emane来实现,mac地址映射到nem id来维护一个arp表。这就是他不使用Tap设备的原因,mac层的操作由emane进程处理而非内核协议栈。(arp部分src/transports/common/ethernettransport.cc 167行)

    其监听读写的具体工作内容为 src/transports/virtual/virtualtransport.cc 开启一个线程不断读取该设备内数据。

    thread_ = std::thread{&VirtualTransport::readDevice,this};

  • Mac layer

    大部分模型都是采用 物理层计算好的信噪比 配合实验验证的例如带宽1M的加性高斯白噪声下的误码率等参数计算得到的包完成度曲线(Packet Completion Rate)来进程丢包操作。

    Mac层协议较灵活,根据实际需求开发。简言之需要在仿真器实现一套对下游的数据包解析mac头 对上游数据封装mac头 等一系列操作。详情:Emane模型调研

  • PHY layer

    emane 默认使用 src/libemane/frameworkphy.cc 实现的通用phy头。关于物理层数据,简化了许多细节,可以自定义的添加物理层的帧头数据,发送端帧头包含 txpower 时延 带宽等参数。

    除此之外通用的物理层模型还包含角度、位置、天线等参数增加仿真颗粒度,例如:相位角俯仰角等参数以及天线增益设定xml来模拟天线,也支持MIMO来进行天线的增益。位置事件包含了当前经纬高 俯仰角 方位角参数,类似的这些事件可以规定一个代理进行场景的动态模拟。

    关于传播的衰落模型 自带 2-ray nakagami freespace 模型,也可基于事件来触发更新不同节点间对立的pathloss,接收功率、接收灵敏度公式如下所示,当接收功率小于灵敏度时将会丢弃该ota报文。计算公式如下所示:

    • rxPower = txPower + txAntennaGain + rxAntennaGain – pathloss

    • rxSensitivity = −174 + noiseFigure + 10log(bandWidth)

  • NEMOTAAdapter

    通过主/辅 控制网 在网桥内发送多播组根据端口不同发送 事件 和 普通数据,以模拟自组网数据。确定是同组数据后接收传输给phy层。具体也是不断读取控制网数据 src/libemane/otamanager.cc

    thread_ = std::thread{&EMANE::OTAManager::processOTAMessage,this};

  • Other layer

    src/libemane/nembuilder.cc 中会读取配置文件 确定当前Nem中初始化有哪些 layer 并执行 /src/libemane/nemlayerstack.cc 的addLayer。更深层的数据传输的细节就要在 PHY MAC 等 layer 的 processUpstreamPacket 等函数中查看。

    之后 同样是调用 nemlayerstack 的 connectLayers 将不同层间的上下游关系通过 setUpstreamTransport setDownstreamTransport 确定,通过 sendDownstreamPacket sendUpstreamPacket 将这层处理后的数据 发送到上层 的process函数 构建无线数据的流通。

    具体实现可以看看 NEMQueuedLayer 联合继承 FileDescriptorServiceProvider 自 NEMLayer。NEMLayer 联合继承类 Component, UpstreamTransport, DownstreamTransport, PlatformServiceUser, Buildable, RunningStateMutable。所以使用send函数会找到 NEMQueuedLayer 的 transport 所对应 上下游的 process函数去做处理。

在细看 phy 层代码之前,我以为 emane会将 location 俯仰角之类的参数放到 phy的 head中,后面才发现每个emane维护了所有的节点的这些参数(具体看 frameworkphy.cc 1429行),在发射这个数据的时候就已经完成衰落的计算了。

其他细节

待补充