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是所有字符设备的基类,它隐藏了注册并创建设备的具体细节,包括字符设备中最重要的那个结构体。我们在实际使用的时候直接从该类继承就可以了。
那么关于进程间通讯我们就先讲到这里。其实了解了上面这些,那些细节稍微理理也就清楚了。