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\n");
                        /* 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\n");
                        return -ENOMEM;
                }
                if (OK != g_dev->init()) {
                        fprintf(stderr, "[uorb] driver init failed\n");
                        delete g_dev;
                        g_dev = nullptr;
                        return -EIO;
                }
                printf("[uorb] ready\n");
                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'\n");
        return -EINVAL;
}

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

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

posted @ 2019-05-17 09:44  eastgeneral  阅读(302)  评论(0编辑  收藏  举报