让Ruby On Rails走进嵌入式开发

lmxbitihero 2009-02-04
      在嵌入式开发领域C和Linux是一对黄金搭档,几乎占据国内的绝大部分市场,成为嵌入式开发的主流模式。当前,对于一些设备做配置的界面,很多都是通过网页来进行,比如adsl modem。可是要用C来写Web服务和网页,那工作量将是巨大的。不知道各位是怎么来实现的呢?本人不是嵌入式开发的专家,在这方面了解的不是很多。但是我前段时间尝试把Ruby On Rails平台移植到龙芯的嵌入式开发板上,将我在Windows平台开发的Web应用一行不改,顺利移植到了板子上,这期间经历了不少挫折,虽然最终也不算是一个完美的胜利,但我有必要把这个经验说出来,让大家提提建议,也希望大家帮我把这个工作做到完美。



开发环境介绍:

编译平台:Red Hat Enterprise 4, mipsel-linux交叉编译器(龙芯开发板公司帮助配置好,重庆)

运行环境:龙芯开发板,uboot,剪裁型linux(体积2M),内存64M,CPU主频300多M,存储空间1G采用外接设备

Ruby版本:Ruby 1.8.7, Rails 1.2.2, sqlite3.6.8, Mongrel1.1.5

Rails应用介绍:我们公司从事安防领域,做视频编解码设备。我做的这个系统是一个管理系统,进行各项信息的管理配置,通过socket与多重第三方设备或者客户端软件通讯。需要增删改查的信息并不是太多,但是socket通讯的分量比较重。



整个工作持续了近3周,中间经历了不少挫折,可谓工作量巨大。之前我曾用ruby1.8.5和ruby1.8.6去编译,可最终发现问题较多(主要在GC垃圾回收上),最后采用ruby1.8.7编译后发现运行更加完美。所以这里介绍ruby1.8.7编译的方式。



1:编译方式

自然跟一般的嵌入式编译是一样的,找到Ruby1.8.7的源码,进入源码目录./configure --host=mipsel-linux, 然后make。编译出miniruby后下面的代码无法编译了。没关系,miniruby就是我们需要的。



2:底层库依赖问题

Ruby主程序运行依赖c,dl, encrypt库,为了避免裁剪型linux底层库版本不一致问题,必须将Ruby进行静态编译,也就是修改makefile文件,在gcc后加一个 -static选项。



3:Ruby扩展库问题

众所周知,Ruby有许多的用C实现的扩展库,最典型的比如socket.so。扩展库被Ruby动态加载后提供功能。那么在嵌入式上,Ruby主程序和扩展库怎么组织?最初我的想法是Ruby主程序做静态编译,各个扩展库也做静态编译,象一般的Ruby平台一样处理Ruby与扩展库的关系。但是后来发现,扩展库编译成so库,无论如何也不能做静态链接。也就是说我要把socket.so库编译成一个动态链接库,怎么样也无法摆脱对libc的依赖。而开发板上的libc无法提供red hat上libc的全功能,导致socket.so无法加载。

无奈之下,只好自己动手修改Ruby代码了。找到扩展库的源码,把.c和.h文件都复制到ruby源码根目录,修改MakeFile,将需要编译的.c文件加入进去。然后修改inits.c文件,这里面的功能会在Ruby初始化的时候执行。按如下方式修改:

/**********************************************************************

  inits.c -

  $Author: knu $
  $Date: 2008-04-09 20:13:04 +0900 (Wed, 09 Apr 2008) $
  created at: Tue Dec 28 16:01:58 JST 1993

  Copyright (C) 1993-2003 Yukihiro Matsumoto

**********************************************************************/

#include "ruby.h"

void Init_Array _((void));
void Init_Bignum _((void));
void Init_Binding _((void));
void Init_Comparable _((void));
void Init_Dir _((void));
void Init_Enumerable _((void));
void Init_Enumerator _((void));
void Init_Exception _((void));
void Init_syserr _((void));
void Init_eval _((void));
void Init_load _((void));
void Init_Proc _((void));
void Init_Thread _((void));
void Init_File _((void));
void Init_GC _((void));
void Init_Hash _((void));
void Init_IO _((void));
void Init_Math _((void));
void Init_marshal _((void));
void Init_Numeric _((void));
void Init_Object _((void));
void Init_pack _((void));
void Init_Precision _((void));
void Init_sym _((void));
void Init_process _((void));
void Init_Random _((void));
void Init_Range _((void));
void Init_Regexp _((void));
void Init_signal _((void));
void Init_String _((void));
void Init_Struct _((void));
void Init_Time _((void));
void Init_var_tables _((void));
void Init_version _((void));
//以下是我添加的
void Init_socket _((void));
void Init_sha2 _((void));
void Init_sha1 _((void));
void Init_digest _((void));
void Init_thread _((void));
void Init_stringio _((void));
void Init_syck _((void));
void Init_zlib _((void));
void Init_md5 _((void));
void Init_sqlite3_api _((void));
void Init_bigdecimal _((void));
void Init_strscan _((void));
void Init_fcntl _((void));
void Init_syslog _((void));
void Init_nkf _((void));
void Init_iconv _((void));
void Init_etc _((void));
void Init_http11 _((void));

void
rb_call_inits()
{
    Init_sym();
    Init_var_tables();
    Init_Object();
    Init_Comparable();
    Init_Enumerable();
    Init_Precision();
    Init_eval();
    Init_String();
    Init_Exception();
    Init_Thread();
    Init_Numeric();
    Init_Bignum();
    Init_syserr();
    Init_Array();
    Init_Hash();
    Init_Struct();
    Init_Regexp();
    Init_pack();
    Init_Range();
    Init_IO();
    Init_Dir();
    Init_Time();
    Init_Random();
    Init_signal();
    Init_process();
    Init_load();
    Init_Proc();
    Init_Binding();
    Init_Math();
    Init_GC();
    Init_Enumerator();
    Init_marshal();
    Init_version();
    Init_socket();
    Init_digest();
Init_sha2();
Init_sha1();
Init_thread();
Init_stringio();
Init_syck();
Init_zlib();
Init_md5();
Init_sqlite3_api();
Init_bigdecimal();
Init_strscan();
Init_syslog();
Init_fcntl();
Init_nkf();
Init_iconv();
Init_etc();
Init_http11();
}



这样编译后将各个扩展库都内置到Ruby体内了,所以你写"sock = UDPSocket.new"的时候不必调用require 'socket',但是为了保证与现有代码的无缝连接,我们必须在ruby的lib目录下生成一些空文件,比如socket.rb,这样调用require 'socket'才不至于出现异常。



4:数据库问题

在嵌入式上数据库除了sqlite似乎没有其他选择了。在red hat上编译sqlite3.6.8,注意编译选项要指定单线程,因为开发板上对多线程支持可能不好。编译完成后将sqlite.h文件、编译出的lib文件复制到Ruby目录下,从rubyforge上下载sqlite-ruby-2.2.3,这是sqlite和rails结合的一个驱动文件,按照3的方法编译。ok,数据库搞定了。在red hat上调用rake db:migrate,先初始化好数据库。然后复制到板子上



5:Gem问题

按照上述方法将,miniruby编译出来以后,改名为ruby,把ruby需要的lib目录复制到板子里,配置好环境变量,ruby就可以运行了。为了运行rails还得先安装Gem,从RubyForge上下载Gem安装包,非常顺利的就可以安装上。调用"gem

list",如果不出错,就说明Gem安装成功了。安装Gem后可能出现各种小问题,不要着急,需要自己看情况手动修改代码。Gem运行成功后就可以安装Rails了,"gem install rails -v=1.2.2",ok。



5:Web服务的问题

期初我打算用Ruby自身做Web服务,本身Rails就自带这个功能。但是运行后发现非常不稳定,Ruby容易崩溃,后来选用Mongrel,发现稳定性还不错。但Mongrel不能使用Gem编译,而是从RubyForge手动下载,将ext目录下的http11.c和http11_parser.c复制到Ruby源码目录中,编译到Ruby体内。



当上面的工作都顺利完成后,把Rails应用通过NFS复制到板子里。在系统中配置一下Ruby的环境变量,激动人心的时刻来到了,"mongrel_rails start"应用起来了。通过ps观察一下,耗用35M作用的内存。打开IE访问网址,网页弹出来了,输入用户名密码,看到了熟悉的界面。内存时涨时销,垃圾回收起作用了。





工作做到这个程度,按理说就算是成功了,可是事情偏偏没有那么顺利。调用"mongrel_rails start"的时候经常会发生Ruby崩溃,导致Rails应用起不来。Rails起来后频繁访问网页,一段时间以后Ruby也会崩溃。而且这种崩溃似乎没有什么规律性,也没有什么提示。在板子上如何调试程序,这都超出了我的能力。



希望大家看到这篇文章的时候,能多提意见,如何能够调试Ruby,捕捉错误所在。解决了这个问题,Ruby On Rails算是真正的移植到嵌入式了。
JCheung 2009-07-10
恩 不错 主要是移植Ruby Rails Sqlite3 Mongrel到龙芯上
Global site tag (gtag.js) - Google Analytics