px4源码学习六--uORB模块研究

来源:互联网 发布:中建八局四公司知乎 编辑:程序博客网 时间:2024/05/18 20:36

UORB模块研读

uORB函数解析:

uORB模块(Micro Object Request Broker,微对象请求代理器)

uORB是Pixhawk系统中关键的一个模块,肩负了数据传输任务。所有传感器,数据传输任务,GPS,PPM信号从芯片获取后通过uORB进行传输,到各个模块计算处理。(可以理解为数据中心仓库)uORB是跨进程的IPC通信模块。实际多个进程打开同一设备文件,通过此文件节点进行数据交互和共享。进程间通过命名(总线)交换消息成为topic一个topic包含一种消息类型/数据类型。每个进程可以订阅/发布topic,一个进程可以订阅多个主题,但一条总线始终只能有一条消息。

公告topic

extern int orb_advertise(const struct orb_metadata *meta,const void *data)meta:uORB元对象,可以看topic的ID,通过ORB_ID赋值。data:指向一个被初始化,发布者要发布的数据存储变量指针。

发布更新

extern int orb_publish(const struct orb_metadata *meta, int handle, const void *data)

handle:orb_advertise函数返回的句柄

data:指向发布数据的制作

订阅topic

要求的满足条件:

  • 调用ORB_DEFINE()或ORB_DEFINE_OPTIONAL()宏(在订阅头文件中包含他们)
  • 发布到主题的数据结构定义

extern int orb_subscribe(const struct orb_metadata *meta)

取消订阅

extern int orb_unsubscribe(int handle)

拷贝数据

订阅者不能引用从ORB中存储数据或其他订阅共享的数据,只能拷贝到临界缓冲区

extern int orb_copy(const struct orb_metadata *meta, int handle, void *buffer)

检查更新

extern int orb_check(int handle, bool *updataed)

发布时间戳

extern int orb_stat(int handle,uint64_t *time)

Note:

  1. 必须要先orb_advertise()+orb_subscribe()后,才能使用orb_copy()
  2. 所有主题在Firmware/Build_px4fmu-v2_default/Src_Modules/uORB/Topics
  3. 也可以到Firmware/Msg下找到

uORB原理解析:
uorb实现原理

实现方式:

  1. 首先模块入口是uORBMain.cpp,这里会创建唯一的MasterNode实例,负责内部的设备节点的控制
  2. MasterNode简洁:继承自CDev,是一个字符设备,linux中设备都是文件,所以会在对应路径下生成文件,主要对Cdev的ioctl函数进行多态实现,类内部维护一个Nodemap的数据结构(后面有详细简绍),所有的DeviceNode都会加入其中管理。
  3. 当外部调用uORB的方法时候,实际间接调用了uORB::Manger类的方法,也可以理解做Manger是外部函数的管理入口,MasterNode是内部节点的管理器。
  4. 外部函数调用uORB时候都会通过uORB::Manger::get_instance()这个函数获取Manger的实例指针。这个函数是静态函数,根据判断命名空间中_Instance指针是否为空,确保全nameespace中只有这么一个Manger实例。
  5. 实际节点,DeviceNode,继承自CDev,每一个字符设备都会生成对应的文件。这个类重载了open,close,read,wirte,ioctl,还有其他一些函数。
  6. 当外部调用orb_advertise时,Manger会做如下的事情:
    1. 以发布者的身份调用node_open函数,最后返回对应文件的文件描述符fd。先确认一下是否这个主题对应实例的序号已经被发布过,如果是,会重新发布一下,否则(对应文件不存在,也就是这个主题对应ID的DeviceNode不存在),会调用node_advertise函数进行DeviceNode实例的创建。
    2. (调用node_open函数)
    3. 通过fd调用ioctl函数,返回DeiceNode对象的指针。
    4. 发布初始化的数据
  7. 当外部调用orb_publish时
    1. 根据传入的DeviceNode的指针,调用这个类的静态的发布数据的方法。就是将数据写入DeviceNode对象的属性中。
  8. 当外部调用orb_Subsctibe时,
    1. 使用Manger类的node_open函数,返回对应主题对应实例的文件描述符,其他信息都可以通过调用被DeviceNode多态实现过的ioctl函数获得。
    2. 取消订阅就将文件描述符关闭就可以了。

uORB

这个文件中所有的方法的实现方式都是调用ORB::Manger的方法,只是一个扩展接口的包装文件

因为这个没有在任何命名空间或者类中,这里将具体的ORB::Manger操作封装,使得其他类可以调用

orb_advertise(*meta, *data) orb_advert_t

参数列表含义:主题名称,初始数据

作为发布者做广播,如果正常,返回一个可以用来发布这个主题的句柄

orb_advertise(*meta, *data, *instance, priority) orb_advert_t

参数列表含义:主题名称,初始数据,实例ID的指针,实例优先级

一个主题下可以有多个实例,每个实例根据handle区分,最多5个

orb_pubshlist_auto(*meta, *handle, *data, *instance, priority) int

参数列表含义:同上

根据handle是否有效发布数据

orb_publish(*meta, handle, *data) int

参数列表含义:主题名称,主题的实例句柄,发布的数据

数据发布是原子操作,任何等待更新的订阅者都会被通知。其他没有等待的可以使用orb_check/orb_stat函数检查更新

orb_subscribe(*meta) int

参数列表含义: 主题名称

返回文件描述符,可以使用poll函数等待主题更新,就像使用topic_read/orb_check/orb_stat一样

即使这个主题没有发布订阅也可能成功,这种情况下,poll,复制它的值什么的操作都会失败,直到这个主题被发布未知。如果系统不知道这样一个主题,那么会订阅失败

orb_subscribe_muti(*meta, instance) int

参数列表含义: 主题名称, 实例ID

orb_unsubscribe(handle) int

参数列表含义:实例句柄

orb_copy(*meta, handle, *buffer) int

参数列表含义:主题名称,实例句柄,缓冲区

这是唯一个可以重置内部主题是否更新标志的函数。一旦使用poll或者orb_check返回可用更新,必须使用这个方法更新参数

orb_check(handle, *updated) int

参数列表含义:实例的句柄,是否被更新的标志

检查在上次orb_copy后这个主题是否被重新发布过

更新以每个句柄为基础进行跟踪; 这个调用将一直返回true直到使用相同的句柄调用orb_copy。由于stat和copy之间的竞争窗口可能导致错过更新,所以此接口应优先于调用orb_stat。

orb_stat(handle, *time) int

返回上一次更新主题的时间

orb_exists(*meta, instance) int

检查主题是否存在

orb_group_count(*meta) int

返回主题的实例数量

orb_priority(handle, *priority) int

返回句柄的优先级

orb_set_interval(handle, interval) int

设置最小的更新间隙

uORBCommon

ORBSet

  • insert(*node_node) void
  • find(*node_name) bool
  • erase(*node_name) bool
  • unlinkNext(Node*) void

实现方式与ORBMap一毛一样,唯一的区别就是这个类维护的队列中的node节点是只有节点名字,没有节点设备。

ORBMap

  • insert(*node_name, *node) void
  • find(*node_name) bool
  • *get(*node) uORB::DeviceNode
  • unlinkNext(*a) void

类的实现流程:(太简单了吧,过分)

类中维护一个队列,使用对象top,end俩个指针标识队列,当有新的数据结点,加入到end后面,查找、获取是从头到尾顺序查找队列,使用strcmp()比较node_name

uORBDevices_nuttx

namespace uORB{class DeviceNode;  //具体的节点class DeviceMaster; //节点管理}

DeviceNode

继承device::CDev(一切字符设备的基类)

DeviceNode(*meta, *name, *path, priority)

open(struct file *filp) int virtual

设备节点打开:算是虚拟的设备文件虽然继承与CDev类

根据文件信息,读取权限:(filp记录了这次打开文件的信息,比如权限,PID等数据)

如果文件写入权限,认为是发布者,设备节点属性发布者设为当前PID,使用CDev::open打开(但实际这个类也没干什么,可以忽略这一句),如果是读取权限,认为是订阅者,申请SubscriberData给filp->f_priv,记录这个订阅对象fd所私有的数据,并且告诉对象属性又一个订阅者订阅了你

close(*filp) int virtual

设备节点关闭:

如果是发布者调用的关闭,将属性发布者置为0如果是订阅者调用,获取filp中的SubscriberData,从调用列表中删除,移除内部调用者

read(*filp, *buffer, buflen) ssize_t virtual

将类中数据_data读入到buffer中

write(*filp, *buffer, buflen) ssize_t virtual

将buffer的数据读入到类的_data属性中

ioctl(*filp, cmd, arg) int virtual

根据cmd的值调整arg的类型,输出对应数据属性

publish(*meta, handle, *data) ssize_t static

这是静态函数,所有实例公用一个

根据传入的句柄(即DeviceNode的指针),将data的数据写入类的对象属性_data中

//以下3个函数应该是设计远程调用IChannel接口的,这里不做讨论,因为没看懂,也没有示例使用

process_add_subscription(rateInHz) int16_t

process_remove_subscription() int16_t

process_received_message(length, *data) int16_t

将接受的数据写入_data属性中

add_internal_subscriber() void

remove_internal_subscriber() void

is_published() bool

//protected:

poll_state(*filp) pollevent_t virtual

判断主题是否推送给订阅者,需要推送返回POLLIN,否则0

poll_notify_one(struct pollfd *fds,pollevent_t events )

对于fds来说,如果有更新,调用Cdev::poll_notify_one()函数进行更新

appears_updated(SubscriberData *sd) bool

根据sd的信息,判断是否有更新(发布者调用这个函数)

update_deferred() void

监控拉的需求

private:    struct SubscriberData {        unsigned  generation; /**< last generation the subscriber has seen */        unsigned  update_interval; /**< if nonzero minimum interval between updates */        struct hrt_call update_call;  /**< deferred wakeup call if update_period is nonzero */        void    *poll_priv; /**< saved copy of fds->f_priv while poll is active */        bool    update_reported; /**< true if we have reported the update via poll/check */        int   priority; /**< priority of publisher */    };    const struct orb_metadata *_meta; /**< object metadata information */    uint8_t     *_data;   /**< allocated object buffer */    hrt_abstime   _last_update; /**< time the object was last updated */    volatile unsigned   _generation;  /**< object generation count */    pid_t     _publisher; /**< if nonzero, current publisher */    const int   _priority;  /**< priority of topic */    bool _published;  /**< has ever data been published */

DeviceMaster

继承device::CDev,管理设备节点的类

DeviceMaster(Flavor f)

*GetDeviceNode(*node_name) uORB::DeviceNode

ioctl(*filp, cmd,arg ) int virtual

private:    Flavor      _flavor;  //是PUBSUB还是OBJ    static ORBMap _node_map; //所有的DeviceNode设备都在其中

uORBDevices_posix

nuttx是在px4在nuttx操作系统下运行的,这个应该是裸机运行的。

uORBManager

这个类管理着每一个UORB主题和节点,也实现了UORb的API

继承自IChannelRxHandler类,类中函数大多都在前面见过,我只大概描述一下实现,如果有必要的话

get_instance() Manager* static

获取生成的uORBManager实例,静态的,单个进程中只生成一个实例

orb_advertise(*meta,*data) orb_advert_t

orb_advertise_multi(*meta, *data, *instance, priority) orb_advert_t

根据主题和实例编号获取文件描述符,根据文件描述符获取当前发布者的指针并返回发布者的指针

orb_publish(*meta, handle, *data) int

发布数据,返回成功与否

实现:

调用DevicNode的publish方法,DeviceNode::publish是静态方法,调用handle->write()函数(write会使属性_generation++,标记数据版本的),将数据写入文件中

orb_subscribe(*meta) int

orb_subscribe_multi(*meta, instance) int

订阅的原理就是打开以只读打开文件,返回fd

(一个文件可以被打开多次,返回的fd都不一样,fd对应这file *filp这个指针(也就是你在DeivceNode类中参数filp一样,这个指针的结构体参数比较全),所以可以打开一次,有多个备份的filp)

orb_unsubscribe(handle) int

orb_copy(*meta, handle, buffer) int

从handle中读取相应长度的数据到buffer中

orb_check(handle, *updated) int

使用ioctl方法(ioctrl方法有DeviceNode类实现)

这个检查的办法是对比filp中维护的SubscriberData数据和对象中属性generation是否一致,虽然filp有多个,但是文件(或者说DeviceNode对象只有一个),所以可以进行判断。(更新版本是在写入时候generation++)

orb_stat(handle, *time) int

使用ioctl方法

orb_exists(*meta, instance) int

主题的对应实例是否有

根据参数生成路径,然后试图打开对应文件

orb_priority(handle, *priority) int

使用ioctl方法

orb_set_interval(handle, interval) int

使用ioctl方法

set_uorb_communicator(*comm_channel) void

get_uorb_communicator() IChannel*

is_remote_subscriber_present(*messageName) bool

private:

node_advertise(*meta, *instance, priority) int

通过调用控制设备MasterNode的ioctl函数(被重载过),让控制文件去创建对应的DeviceNode对象(有了对象,对应路径下就有了文件)

node_open(Flavor, *meta, *data, advertiser, *instance, priority) int

根据订阅发布的属性打开对应的设备文件,返回文件描述符

这个函数让你屏蔽是否存在这样的设备文件,根据你的需求去解决。

如果你是订阅者,存在,直接返回。不存在,那就创建一个,方法同下。

如果你是发布者,存在,关闭fd。重新打开一次,(这时node_advertise调用将自动进入下一个空闲条目)不存在,调用node_advertise发布创建一个DeviceNode对象(有了对象自然会有文件)

实现:判断输入的逻辑合法性根据订阅者还是发布者,选择对应读取权限打开文件如果是发布者&&打开成功,说明这个文件存在关闭文件重新调用node_advertise发布再打开文件返回文件描述符fd

process_add_subscription(*messageName, msgRate) int16_t

处理订阅的回调函数,Manger类实现的

实现逻辑:

将主体加入订阅主题的集合中根据对应信息获取路径,再从设备管理器DeviceMaster获取对应的设备节点具体设备节点处理订阅频率

process_remove_subscription(*messageName) int16_t

处理移除订阅的回调函数,Manger类实现

实现逻辑:

将主题移除主题集合获取对应的设备节点具体设备节点处理移除事件

process_received_message(*message, length, *data) int16_t

处理接受信息的回调函数,Manger类实现

实现逻辑:

获取对应的设备节点具体的设备节点处理接受的信息    static Manager *_Instance; //全局唯一的管理器    // the communicator channel instance.    uORBCommunicator::IChannel *_comm_channel;    ORBSet _remote_subscriber_topics;

uORBUtils

工具类Utils中只有2个同名的静态函数

int node_mkpath(char* buf, Flavor f, const struct orb_metadata *meta, int *instance)int node_mkpath(char* buf, Flavor f, const char* orbMsgName)

参数含义:

buf:输出路径的字符串f:  标志,是发布订阅还是作为参数(PUBSUB/PARAM)meta: 通过meta->o_name = orbMsgNameinstance: 如果是多个实例,对应相应的,否则默认为0生成路径为/(obj?param)/orbMsgName/instance ,应该是节点设备的文件路径

uORBCommunicator

namespace uORBCommunicator{ //俩个类都为纯虚类,可以认为java中的接口类class IChannel; // 可以理解为这个是客户端class IChannelRxHandler; //这个是服务器端}

IChannel

启用远程订阅的接口,接口的实现类需要管理通信信道,快速RPC/TCP/IP

add_subscription(*messageName, msgRateInHz) int16_t

参数列表含义:主题名称(全局唯一),最大消息更新速率

通知有兴趣的远程实体订阅消息的接口

remove_subscription(*messageName) int16_t

通知远程实体移除订阅的接口

register_handler(IChannelRxHandler *handler) int16_t

注册消息句柄

send_message(*messageName, length, *data) int16_t

通过通讯链接发送数据

IChannelRxHandler

通过通信链路回调的类

process_add_subscription(*messageName, msgRateInHz) int16_t

处理从远程接受添加的接口函数

process_remove_subscription(*messageName) int16_t

处理远程移除订阅的接口

process_received_message(*messageName, length, *data) int16_t

处理远程发送数据的类

原创粉丝点击