zoukankan      html  css  js  c++  java
  • Pixhawk源码快速阅读 02_进程间通信

    Pixhawk源码快速阅读 02_进程间通信

                                                                                  -------- 转载请注明出处 
                                                                                 -------- 更多笔记请访问我的博客:merafour.blog.163.com 

                                                                                 -------- 2015-01-26.冷月追风

                                                                                 -------- email:merafour@163.com 

    1.ORB_DEFINE

        前一篇笔记中我们看到 RC数据是在 _timer_tick函数中通过 ORB获取的,其中用到了两条 ORB语句: "orb_check(_rc_sub, &rc_updated)"和 "orb_copy(ORB_ID(input_rc), _rc_sub, &_rcin)",源码如下:

    int orb_check(int handle, bool *updated)
    {
     return ioctl(handle, ORBIOCUPDATED, (unsigned long)(uintptr_t)updated);
    }
    int orb_copy(const struct orb_metadata *meta, int handle, void *buffer)
    {
     int ret;
     ret = read(handle, buffer, meta->o_size);
     if (ret < 0)
      return ERROR;
     if (ret != (int)meta->o_size) {
      errno = EIO;
      return ERROR;
     }
     return OK;
    }

    所以主要是两个系统调用: ioctl和 read。那么很明显 "_rc_sub"必须是文件描述符。该文件描述符是在 "PX4RCInput::init"中创建的,源码如下:

    void PX4RCInput::init(void* unused)
    {
            _perf_rcin = perf_alloc(PC_ELAPSED, "APM_rcin");
            _rc_sub = orb_subscribe(ORB_ID(input_rc));
            if (_rc_sub == -1) {
                    hal.scheduler->panic("Unable to subscribe to input_rc");
            }
            clear_overrides();
            pthread_mutex_init(&rcin_mutex, NULL);
    }

    这其中有一个很重要的东西是 "ORB_ID(input_rc)",这是一个宏,定义为:"#define ORB_ID(_name)  &__orb_##_name",第一眼看我们会觉得很奇怪,为什么是一个地址?宏展开之后实际上就变成了"&__orb_input_rc",我们不禁要问有没有谁去定义它啊?如果我傻傻地到源码中去找 "__orb_input_rc"那铁定是找不到的,因为人家是通过 ORB来定义的。我们可以在 "PX4Firmware/src/modules/uORB/uORB.h"文件中找到相关信息。

    /**
     * Object metadata.
     */
    struct orb_metadata {
            const char *o_name;             /**< unique object name */
            const size_t o_size;            /**< object size */
    };
    typedef const struct orb_metadata *orb_id_t;
    /**
     * Generates a pointer to the uORB metadata structure for
     * a given topic.
     *
     * The topic must have been declared previously in scope
     * with ORB_DECLARE().
     *
     * @param _name         The name of the topic.
     */
    #define ORB_ID(_name)           &__orb_##_name
    /**
     * Define (instantiate) the uORB metadata for a topic.
     *
     * The uORB metadata is used to help ensure that updates and
     * copies are accessing the right data.
     *
     * Note that there must be no more than one instance of this macro
     * for each topic.
     *
     * @param _name         The name of the topic.
     * @param _struct       The structure the topic provides.
     */
    #define ORB_DEFINE(_name, _struct)                      
            const struct orb_metadata __orb_##_name = {     
                    #_name,                                 
                    sizeof(_struct)                         
            }; struct hack

    所以专门有一个 "ORB_DEFINE"。这样我们就可以通过更为有效的方式匹配信息:

    bitcraze@bitcraze-vm:~/apm$ grep -nr ORB_DEFINE ./ |grep input_rc
    ./PX4Firmware/src/modules/uORB/objects_common.cpp:67:ORB_DEFINE(input_rc, struct rc_input_values);
    bitcraze@bitcraze-vm:~/apm$
    #include <drivers/drv_rc_input.h>
    ORB_DEFINE(input_rc, struct rc_input_values);
    struct rc_input_values {
            uint64_t                timestamp_publication;
            uint64_t                timestamp_last_signal;
            uint32_t                channel_count;
            int32_t                 rssi;
            bool                    rc_failsafe;
            bool                    rc_lost;
            uint16_t                rc_lost_frame_count;
            uint16_t                rc_total_frame_count;
            uint16_t                rc_ppm_frame_length;
            enum RC_INPUT_SOURCE    input_source;
            rc_input_t              values[RC_INPUT_MAX_CHANNELS];
    };

    所以前面我们看到取数据是通过 values这个成员,因为该数据定义就是用来存放 RC数据的。至于其他的成员我们这里没有涉及到,就不讨论了。

    2.orb_subscribe

        那么现在的问题是 "orb_subscribe"返回的是一个文件描述符吗?通常我们获得一个文件描述符都是通过 open函数,那这里应该是封装了 open函数在里边。

    int orb_subscribe(const struct orb_metadata *meta)
    {
            return node_open(PUBSUB, meta, nullptr, false);
    }
    /**
     * Common implementation for orb_advertise and orb_subscribe.
     *
     * Handles creation of the object and the initial publication for
     * advertisers.
     */
    int node_open(Flavor f, const struct orb_metadata *meta, const void *data, booladvertiser)
    {
            char path[orb_maxpath];
            int fd, ret;
            /*
             * If meta is null, the object was not defined, i.e. it is not
             * known to the system.  We can't advertise/subscribe such a thing.
             */
            if (nullptr == meta) {
                    errno = ENOENT;
                    return ERROR;
            }
            /*
             * Advertiser must publish an initial value.
             */
            if (advertiser && (data == nullptr)) {
                    errno = EINVAL;
                    return ERROR;
            }
            /*
             * Generate the path to the node and try to open it.
             */
            ret = node_mkpath(path, f, meta);
            if (ret != OK) {
                    errno = -ret;
                    return ERROR;
            }
            /* open the path as either the advertiser or the subscriber */
            fd = open(path, (advertiser) ? O_WRONLY : O_RDONLY);
            /* we may need to advertise the node... */
            if (fd < 0) {
                    /* try to create the node */
                    ret = node_advertise(meta);
                    /* on success, try the open again */
                    if (ret == OK)
                            fd = open(path, (advertiser) ? O_WRONLY : O_RDONLY);
            }
            if (fd < 0) {
                    errno = EIO;
                    return ERROR;
            }
            /* everything has been OK, we can return the handle now */
            return fd;
    }

    里边确实封装了 open函数。理解这段代码关键是两个函数: node_mkpath和 node_advertise。前者显然是用来获取路径的,而后者根据注释是用来创建节点的。而节点这个说法我觉得是比较宽泛的。

    int node_mkpath(char *buf, Flavor f, const struct orb_metadata *meta)
    {
            unsigned len;
            len = snprintf(buf, orb_maxpath, "/%s/%s",
                           (f == PUBSUB) ? "obj" : "param",
                           meta->o_name);
            if (len >= orb_maxpath)
                    return -ENAMETOOLONG;
            return OK;
    }
    int node_advertise(const struct orb_metadata *meta)
    {
            int fd = -1;
            int ret = ERROR;
            /* open the control device */
            fd = open(TOPIC_MASTER_DEVICE_PATH, 0);
            if (fd < 0)
                    goto out;
            /* advertise the object */
            ret = ioctl(fd, ORBIOCADVERTISE, (unsigned long)(uintptr_t)meta);
            /* it's OK if it already exists */
            if ((OK != ret) && (EEXIST == errno))
                    ret = OK;
    out:
            if (fd >= 0)
                    close(fd);
            return ret;
    }

    以我们的 "input_rc"为例,可能得到两个路径: "/obj/input_rc"和 "/param/input_rc"。但如果你通过终端连接上去查看你就会知道我们这里用的是前者 obj的路径。其实我觉得我们根本就没有必要太关心具体用的是哪个路径,因为都是 ORB在处理。而后面的 open跟 ioctl就是重中之重了,因为它创建了我们后面要打开的设备。可能我们会觉得很奇怪,这里明明是操作一个设备,怎么突然又变成了创建一个设备了呢?其中的秘密就是 "TOPIC_MASTER_DEVICE_PATH"这个设备。
        实际上 ORB的核心就是 "TOPIC_MASTER_DEVICE_PATH"这个设备。为什么这么说呢?因为它是其他 ORB的始祖。

    bitcraze@bitcraze-vm:~/apm$ grep -nr TOPIC_MASTER_DEVICE_PATH ./
    ./PX4Firmware/src/drivers/drv_orb_dev.h:53:#defineTOPIC_MASTER_DEVICE_PATH     "/obj/_obj_"
    ./PX4Firmware/src/modules/uORB/uORB.cpp:540:         (f == PUBSUB) ? TOPIC_MASTER_DEVICE_PATH : PARAM_MASTER_DEVICE_PATH),
    ./PX4Firmware/src/modules/uORB/uORB.cpp:846:    fd = open(TOPIC_MASTER_DEVICE_PATH, 0);
    bitcraze@bitcraze-vm:~/apm$
    class ORBDevMaster : public device::CDev
    {
    public:
            ORBDevMaster(Flavor f);
            ~ORBDevMaster();
            virtual int             ioctl(struct file *filp, int cmd, unsigned long arg);
    private:
            Flavor                  _flavor;
    };
    ORBDevMaster::ORBDevMaster(Flavor f) :
            CDev((f == PUBSUB) ? "obj_master" : "param_master",
                 (f == PUBSUB) ? TOPIC_MASTER_DEVICE_PATH : PARAM_MASTER_DEVICE_PATH),
            _flavor(f)
    {
            // enable debug() calls
            _debug_enabled = true;
    }

    最重要的其实还是这个 ioctl接口。说它重要是因为它又是 "TOPIC_MASTER_DEVICE_PATH"这个设备的核心。在 node_advertise函数中我们调用 ioctl创建了一个新的设备文件。

    int ORBDevMaster::ioctl(struct file *filp, int cmd, unsigned long arg)
    {
            int ret;
            switch (cmd) {
            case ORBIOCADVERTISE: {
                            const struct orb_metadata *meta = (const struct orb_metadata *)arg;
                            const char *objname;
                            char nodepath[orb_maxpath];
                            ORBDevNode *node;
                            /* construct a path to the node - this also checks the node name */
                            ret = node_mkpath(nodepath, _flavor, meta);
                            if (ret != OK)
                                    return ret;
                            /* driver wants a permanent copy of the node name, so make one here */
                            objname = strdup(meta->o_name);
                            if (objname == nullptr)
                                    return -ENOMEM;
                            /* construct the new node */
                            node = new ORBDevNode(meta, objname, nodepath);
                            /* initialise the node - this may fail if e.g. a node with this name already exists */
                            if (node != nullptr)
                                    ret = node->init();
                            /* if we didn't get a device, that's bad */
                            if (node == nullptr)
                                    return -ENOMEM;
                            /* if init failed, discard the node and its name */
                            if (ret != OK) {
                                    delete node;
                                    free((void *)objname);
                            }
                            return ret;
                    }
            default:
                    /* give it to the superclass */
                    return CDev::ioctl(filp, cmd, arg);
            }
    }

    从源码中我们看到,ioctl中最关键的是 ORBDevNode 这个类,它最终创建了我们需要的设备文件。从网上的一些资料来看,这个类提供了数据分发的功能。这样如果我们需要分发数据,我们就可以创建这样一个设备,然后写入数据。

    /**
     * Per-object device instance.
     */
    class ORBDevNode : public device::CDev
    {
    public:
            ORBDevNode(const struct orb_metadata *meta, const char *name, constchar *path);
            ~ORBDevNode();
            virtual int             open(struct file *filp);
            virtual int             close(struct file *filp);
            virtual ssize_t         read(struct file *filp, char *buffer, size_t buflen);
            virtual ssize_t         write(struct file *filp, const char *buffer, size_t buflen);
            virtual int             ioctl(struct file *filp, int cmd, unsigned long arg);
            static ssize_t          publish(const orb_metadata *meta, orb_advert_t handle, const void *data);
    ORBDevNode::ORBDevNode(const struct orb_metadata *meta, const char *name, const char *path) :
            CDev(name, path),
            _meta(meta),
            _data(nullptr),
            _last_update(0),
            _generation(0),
            _publisher(0)
    {
            // enable debug() calls
            _debug_enabled = true;
    }

    基本上都是标准接口,用来读写数据。而我们调用的 init接口则是从 CDev中继承来的,用来创建设备文件。如果你想了解数据分发的细节可以去阅读这几个接口函数。但我们还是继续往下走。

    3. uorb
        现在我们的问题 "TOPIC_MASTER_DEVICE_PATH"这个设备从何而来?肯定是有人去创建他的。那么又是怎么创建的呢?
        如果我们去看启动脚本我们就会看到这样一段:

    if uorb start
    then
        echo "uorb started OK"
    else
        sh /etc/init.d/rc.error
    fi

    也就是说存在 uorb这样一个应用程序。对应的入口函数就是 "uorb_main"。

    int uorb_main(int argc, char *argv[])
    {
            /*
             * Start/load the driver.
             *
             * XXX it would be nice to have a wrapper for this...
             */
            if (!strcmp(argv[1], "start")) {
                    if (g_dev != nullptr) {
                            fprintf(stderr, "[uorb] already loaded ");
                            /* user wanted to start uorb, its already running, no error */
                            return 0;
                    }
                    /* create the driver */
                    g_dev = new ORBDevMaster(PUBSUB);
                    if (g_dev == nullptr) {
                            fprintf(stderr, "[uorb] driver alloc failed ");
                            return -ENOMEM;
                    }
                    if (OK != g_dev->init()) {
                            fprintf(stderr, "[uorb] driver init failed ");
                            delete g_dev;
                            g_dev = nullptr;
                            return -EIO;
                    }
                    printf("[uorb] ready ");
                    return OK;
            }
            /*
             * Test the driver/device.
             */
            if (!strcmp(argv[1], "test"))
                    return test();
            /*
             * Print driver information.
             */
            if (!strcmp(argv[1], "status"))
                    return info();
            fprintf(stderr, "unrecognised command, try 'start', 'test' or 'status' ");
            return -EINVAL;
    }

    在这里我们看到创建了 ORBDevMaster类的对象。然后通过调用其 init函数就会去创建设备文件。创建设备文件的具体细节是由 CDev来实现的。应该来说 CDev是所有字符设备的基类,它隐藏了注册并创建设备的具体细节,包括字符设备中最重要的那个结构体。我们在实际使用的时候直接从该类继承就可以了。

        那么关于进程间通讯我们就先讲到这里。其实了解了上面这些,那些细节稍微理理也就清楚了。

  • 相关阅读:
    Visual Studio 进行单元测试时如何附加被测试文件的方法总结
    PowerDesigner实体模型CDM中关于建立Entity之间关系的备忘
    【转帖】C# 与 C++ 数据类型对照
    【转帖】解决继承窗体或用户控件时“visual继承当前被禁用,因为基类引用设备特定的组件或包含 p/invoke”问题
    【Winform窗体控件开发】之五 实现类型转换器TypeConverterAttribute
    SQL 使用CONVERT函数 格式化日期
    【转帖】const 与 readonly 的区别
    【转帖】C#与C Windows API数据类型对应关系
    【.Net Compact Framework开发】 使用 Visual Studio 对移动项目进行Unit Testing的方法总结
    【部署】Visual Studio 2008 打包部署.Net Framework 2.0 应用程序提示需要安装.Net Framework 3.5的解决方法
  • 原文地址:https://www.cnblogs.com/eastgeneral/p/10879655.html
Copyright © 2011-2022 走看看