树老大 发表于 2013-5-21 14:55:31

初涉Raspberry Pi的VCHI(转)

最近想给RPi写个监控应用,打算用node.js写成Web端,方便在笔记本上用浏览器来查看。监控的内容不仅仅是RPi上Linux的运行状况,还要有RPi硬件的状态信息,如果CPU温度、时钟频率、电压等。RPi的固件中提供了一个程序来获取这些硬件的状态数据——/opt/vc/bin/vcgencmd,此命令的详细用法见这个说明页面。如果要在node.js程序里获取这些数据,可以通过child_process模块的spawn或exec方法来开启一个新的进程,并运行vcgencmd,进而读取其标准输出流的内容。这样,每次需要一个数据都要新建一个进程,感觉资源耗费比较大。毕竟单核的ARM,只有700MHz的处理速度,还是要省着用。况且监控程序更不应该消耗如此大的计算资源。于是想通过node.js的addon方式来获取硬件的状态信息。幸好,vcgencmd是在RPi开源的VideoCore接口上编写的,raspberrypi的github上提供了源码。通过查看源码,去掉其中使用的互斥锁及事件的API后,发现vcgencmd其实是调用VideoCore的VCHI。该接口其实是通过消息队列实现的。客户程序给特定的消息队列发送命令,即运行vcgencmd时使用的参数,之后再从队列中读取消息,便所需要的硬件状态数据了。从开放的源码中不能获知具体的实现方式,不过可以推测,发送消息和接收消息应该是两个不同的队列。VideoCore的服务程序应该是从消息队列A中读取命令,经过处理后,再放到消息队列B让客户程序读取的。既然看不到具体的实现细节就算了,只要有接口就可以开始编码了。突然很惊喜地在vchi.h文件中发现VCHI的API中有设置回调函数的选项,即SERVICE_CREATION_T结构体的callback属性,便有了以异步方式调用VCHI的想法,配合node.js的事件驱动模式,感觉运行效率会比创建新进程运行vcgencmd要好很多。但这毕竟是理论上的想法,实际上可否实现还得等代码写出来运行后才知道,因为VCHI与v8的接口之间能否相互相调用还是一个问题。VCHI回调函数中的reason参数的值代表是了调用此回调函数的事件,可用的事件列表及相应的说明被定义在vchi-common.h文件的VCHI_CALLBACK_REASON_T枚举类型中。其中的VCHI_CALLBACK_MSG_AVAILABLE是消息可用事件,即可以通过vchi_msg_dequeue函数来读取命令运行结果的消息体了。在只用VCHI编写的测试程序中,发现回调函数中获取消息体是可行的,而且也是真正的异步处理。但不知道为什么把它写入Node,js的addon代码后,运行到回调函数时会Segment fault。也许真的像我之前所预料的,v8和VCHI之间还是不太兼容对方,而且特别是VCHI函数中用了一句HandleScope scope;所带来的效果是无法预料的,因为它的作用是在当前栈中分配很多本地v8的类型控制器。经过调试,基本能够确定程序是运行到这句时出现Segment fault的。可能是个人喜欢追求完美的性格问题,总是喜欢做这些不太合适的cross over,而且常常在这些边界位置上纠结。这次也不例外,出现问题后很想找到原因。但才发现无从下手,因为没有VCHI的实现代码,而且对v8的运行机制也不熟悉。最后只好放弃了!除了创建进程外,还剩下的选择还有
[*]同步调用vchi_msg_queue和vchi_msg_dequeue
[*]使用libuv的work queue在新线程中调用vchi_msg_queue和vchi_msg_dequeue实现异步调用
权衡了一下,VCHI应该是比较接近硬件底层的接口,与系统调用应该差不多了,而且可能是运行在内核态,所以运行的效率应该很快。虽然同步调用vchi_msg_dequeue需要一直循环到接收到消息为止,但阻塞的时间应该不长。如果在新线程中实现异步调用,一来创建线程会消耗资源外,异步回调的程序写起来是比较麻烦。对于这种只是获取一个很少量的数据的调用而言感觉没有必要。所以选择了同步调用的方式。其实也要想过直接使用vcgencmd所使用的接口,即vc_vchi_gencmd.h文件中提供的接口,但查看了其实现文件vc_vchi_gencmd.c,发现其中用了互斥锁和IPC事件信号的API,而且提供的接口可用于创建多个VCHI连接的。虽然不知道过中的缘由,但感觉自己写的监控程序中这些东西都是不需要的。(其实是追求完美而钻牛角尖的老毛病又犯了。)不管怎么样,跟着感觉走吧,发现问题再改也不迟,反正也不是什么重要的东西,经得起折腾。node.js调用VCHI的实现的addon的代码如下:#include <node.h>
#include <v8.h>

#include <string>

#include <interface/vchi/vchi.h>
#include <interface/vmcs_host/vchost.h>
#include <interface/vmcs_host/vc_gencmd_defs.h>
#include <interface/vcos/vcos_types.h>
#include <vcinclude/common.h>


#define GENCMD_MAX_LENGTH 1024

using namespace v8;
using namespace std;

static VCHI_INSTANCE_T vchi_instance;
static VCHI_CONNECTION_T vchi_connection;
static VCHI_SERVICE_HANDLE_T vchi_service_handle;


static string getString(Local<Value> arg) {
Local<String> value = arg->ToString();
int len = value->Length();
char buf;
memset(buf, 0, len + 1);
value->WriteAscii(buf);
string str(buf);
return str;
}


Handle<Value> Exec(const Arguments& args) {
HandleScope scope;

if (args.Length() < 1) {
    ThrowException(Exception::Error(String::New("must specify command")));
    return scope.Close(Undefined());
}
if (!args->IsString()) {
    ThrowException(Exception::Error(String::New("command must be a string")));
    return scope.Close(Undefined());
}
string cmd = getString(args);

if (vchi_initialise(&vchi_instance) != 0) {
    ThrowException(Exception::Error(String::New("VCHI initialization failed")));
    return scope.Close(Undefined());
}
if (vchi_connect(NULL, 0,vchi_instance) != 0) {
    ThrowException(Exception::Error(String::New("VCHI connection failed")));
    return scope.Close(Undefined());
}

SERVICE_CREATION_T gencmd_parameters;
gencmd_parameters.version = VCHI_VERSION(VC_GENCMD_VER);
gencmd_parameters.service_id = MAKE_FOURCC("GCMD");
gencmd_parameters.connection = &vchi_connection;
gencmd_parameters.rx_fifo_size = 0;
gencmd_parameters.tx_fifo_size = 0;
gencmd_parameters.callback = NULL;
gencmd_parameters.callback_param = NULL;
gencmd_parameters.want_unaligned_bulk_rx = VC_FALSE;
gencmd_parameters.want_unaligned_bulk_tx = VC_FALSE;
gencmd_parameters.want_crc = VC_FALSE;
if (vchi_service_open(vchi_instance, &gencmd_parameters, &vchi_service_handle) != 0) {
    ThrowException(Exception::Error(String::New("VCHI service open failed")));
    return scope.Close(Undefined());
}

if (vchi_msg_queue(vchi_service_handle, cmd.c_str(), cmd.length() + 1, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL) != 0) {
    ThrowException(Exception::Error(String::New("fail to send message accross VCHI service")));
    vchi_service_release(vchi_service_handle);
    return scope.Close(Undefined());
}

char buffer;
uint32_t actual_buffer_size;
while (vchi_msg_dequeue(vchi_service_handle, buffer, sizeof (buffer), &actual_buffer_size, VCHI_FLAGS_NONE) != 0);

int ret_code = VC_VTOH32(*(int *)buffer);
if (ret_code < 0) {
    string err_msg = "fail to receive message from VideoCore ";
    err_msg += "(Error code: ";
    err_msg += ret_code;
    err_msg += ")";

    ThrowException(Exception::Error(String::New(err_msg.c_str())));
    while (vchi_service_close(vchi_service_handle) != 0);
    return scope.Close(Undefined());
}

actual_buffer_size -= sizeof (int);
char response;
memset(response, 0, actual_buffer_size + 1);
memcpy(response, buffer + sizeof (int), actual_buffer_size);
while (vchi_service_close(vchi_service_handle) != 0);
return scope.Close(String::New(response));
}

void init(Handle<Object> target) {
target->Set(String::NewSymbol("exec"), FunctionTemplate::New(Exec)->GetFunction());
}

NODE_MODULE(gcmd, init);由于addon中调用了VCHI,所以编译时需要包含VCHI的头文件路径及链接时需要用于的动态链接库文件/opt/vc/lib/libbcm_host.so,node-gyp的构建配置文件binding.gyp如下:{
"targets": [
    {
      "target_name": "gcmd",
      "sources": [ "src/gcmd.cc" ],
      "include_dirs": [
      "/opt/vc/include",
      "/opt/vc/include/interface/vcos/pthreads",
      "/opt/vc/include/interface/vmcs_host/linux"
      ],
      "libraries": [
      "/opt/vc/lib/libbcm_host.so"
      ],
      "cflags": [
      "-std=c++11"
      ]
    }
]
}经过测试,没发现什么问题。在node.js层面上还将从VCHI获取到的硬件状态数据加工了一下,即把字符串转换成数值,并用js类封装好再返回给调用者。最后还是没有罢休,google了一下关于v8的HandleScope的内容,找到了一篇分析v8与node.js整合机制的文章。从文章的内容了解到v8的工作方式,推断VCHI的回调函数中使用HandleScope出现问题的原因可能是在于HandleScope的分配出了问题,至于是什么问题就没有进行深入调试,日后有时间再继续深究一下。

本文转自:http://gutspot.com/
页: [1]
查看完整版本: 初涉Raspberry Pi的VCHI(转)