树莓派论坛

 找回密码
 立即注册

初涉Raspberry Pi的VCHI(转)

树老大 发表于 2013-5-21 14:55:31 | 显示全部楼层 |阅读模式

最近想给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的代码如下:

  1. #include <node.h>
  2. #include <v8.h>

  3. #include <string>

  4. #include <interface/vchi/vchi.h>
  5. #include <interface/vmcs_host/vchost.h>
  6. #include <interface/vmcs_host/vc_gencmd_defs.h>
  7. #include <interface/vcos/vcos_types.h>
  8. #include <vcinclude/common.h>


  9. #define GENCMD_MAX_LENGTH 1024

  10. using namespace v8;
  11. using namespace std;

  12. static VCHI_INSTANCE_T vchi_instance;
  13. static VCHI_CONNECTION_T vchi_connection;
  14. static VCHI_SERVICE_HANDLE_T vchi_service_handle;


  15. static string getString(Local<Value> arg) {
  16.   Local<String> value = arg->ToString();
  17.   int len = value->Length();
  18.   char buf[len + 1];
  19.   memset(buf, 0, len + 1);
  20.   value->WriteAscii(buf);
  21.   string str(buf);
  22.   return str;
  23. }


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

  26.   if (args.Length() < 1) {
  27.     ThrowException(Exception::Error(String::New("must specify command")));
  28.     return scope.Close(Undefined());
  29.   }
  30.   if (!args[0]->IsString()) {
  31.     ThrowException(Exception::Error(String::New("command must be a string")));
  32.     return scope.Close(Undefined());
  33.   }
  34.   string cmd = getString(args[0]);

  35.   if (vchi_initialise(&vchi_instance) != 0) {
  36.     ThrowException(Exception::Error(String::New("VCHI initialization failed")));
  37.     return scope.Close(Undefined());
  38.   }
  39.   if (vchi_connect(NULL, 0,  vchi_instance) != 0) {
  40.     ThrowException(Exception::Error(String::New("VCHI connection failed")));
  41.     return scope.Close(Undefined());
  42.   }

  43.   SERVICE_CREATION_T gencmd_parameters;
  44.   gencmd_parameters.version = VCHI_VERSION(VC_GENCMD_VER);
  45.   gencmd_parameters.service_id = MAKE_FOURCC("GCMD");
  46.   gencmd_parameters.connection = &vchi_connection;
  47.   gencmd_parameters.rx_fifo_size = 0;
  48.   gencmd_parameters.tx_fifo_size = 0;
  49.   gencmd_parameters.callback = NULL;
  50.   gencmd_parameters.callback_param = NULL;
  51.   gencmd_parameters.want_unaligned_bulk_rx = VC_FALSE;
  52.   gencmd_parameters.want_unaligned_bulk_tx = VC_FALSE;
  53.   gencmd_parameters.want_crc = VC_FALSE;
  54.   if (vchi_service_open(vchi_instance, &gencmd_parameters, &vchi_service_handle) != 0) {
  55.     ThrowException(Exception::Error(String::New("VCHI service open failed")));
  56.     return scope.Close(Undefined());
  57.   }

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

  63.   char buffer[GENCMD_MAX_LENGTH];
  64.   uint32_t actual_buffer_size;
  65.   while (vchi_msg_dequeue(vchi_service_handle, buffer, sizeof (buffer), &actual_buffer_size, VCHI_FLAGS_NONE) != 0);

  66.   int ret_code = VC_VTOH32(*(int *)buffer);
  67.   if (ret_code < 0) {
  68.     string err_msg = "fail to receive message from VideoCore ";
  69.     err_msg += "(Error code: ";
  70.     err_msg += ret_code;
  71.     err_msg += ")";

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

  76.   actual_buffer_size -= sizeof (int);
  77.   char response[actual_buffer_size + 1];
  78.   memset(response, 0, actual_buffer_size + 1);
  79.   memcpy(response, buffer + sizeof (int), actual_buffer_size);
  80.   while (vchi_service_close(vchi_service_handle) != 0);
  81.   return scope.Close(String::New(response));
  82. }

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

  86. NODE_MODULE(gcmd, init);
复制代码
由于addon中调用了VCHI,所以编译时需要包含VCHI的头文件路径及链接时需要用于的动态链接库文件/opt/vc/lib/libbcm_host.so,node-gyp的构建配置文件binding.gyp如下:
  1. {
  2.   "targets": [
  3.     {
  4.       "target_name": "gcmd",
  5.       "sources": [ "src/gcmd.cc" ],
  6.       "include_dirs": [
  7.         "/opt/vc/include",
  8.         "/opt/vc/include/interface/vcos/pthreads",
  9.         "/opt/vc/include/interface/vmcs_host/linux"
  10.       ],
  11.       "libraries": [
  12.         "/opt/vc/lib/libbcm_host.so"
  13.       ],
  14.       "cflags": [
  15.         "-std=c++11"
  16.       ]
  17.     }
  18.   ]  
  19. }
复制代码

经过测试,没发现什么问题。在node.js层面上还将从VCHI获取到的硬件状态数据加工了一下,即把字符串转换成数值,并用js类封装好再返回给调用者。

最后还是没有罢休,google了一下关于v8的HandleScope的内容,找到了一篇分析v8与node.js整合机制的文章。从文章的内容了解到v8的工作方式,推断VCHI的回调函数中使用HandleScope出现问题的原因可能是在于HandleScope的分配出了问题,至于是什么问题就没有进行深入调试,日后有时间再继续深究一下。



本文转自:http://gutspot.com/
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版 | Archiver | 树莓派论坛 ( 粤ICP备15075382号-1 )