Skip to content

Instantly share code, notes, and snippets.

@liuyanghejerry
Created May 7, 2014 12:50
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save liuyanghejerry/a309ef54580004879b40 to your computer and use it in GitHub Desktop.
Save liuyanghejerry/a309ef54580004879b40 to your computer and use it in GitHub Desktop.
在小米路由上运行Node.js

相信很多人和我一样,拿到小米路由玩了几天,感觉官方的步伐不够快不够激进。既然如此,何不自己动手丰衣足食呢?

背景知识

首先你得知道Linux的一些基本理论,比如什么是bash啊,cdls这些命令怎么用等等。

其次你还得知道一些编程相关的东西,因为文章专注于Node.js的编译,所以Node.js和C++的一些基本知识是要有的。

热身准备

搞开发真的不适合Windows,说多少遍了。我这里用的是Centos 6,其他发行版应该也没啥问题。

小米路由需要root权限和SSH,没有的话在论坛上先找找搞到这俩的方案吧。另外我这里用的是小米路由的普通版本,mini版本没搞过不知道,但是因为需要硬盘来存放一些文件,没有的话估计会很麻烦。

交叉编译器

做过嵌入式开发的人肯定清楚什么是交叉编译器,一般来说,你想在一个平台上编译另一类平台所跑的软件的编译器,就是交叉编译器了。要让Node.js跑在小米路由上,就需要使用交叉编译器来在我们自己的计算机上,编译出小米路由能跑的程序。有人可能好奇,为什么不直接在小米路由上装一个编译器,直接在路由上编译呢?答案是,编译这样的编译器本身就很可能需要一个交叉编译器,另外即使有,它的编译速度也会非常非常慢。之前看到树莓派上运行GCC的编译测试,时间和PC的差距在数十倍至百倍之间,想想看吧。

对openwrt不是很了解,其实拿到小米路由之前只是听过,但没真正用过,不清楚网上有没有openwrt ARM的交叉编译器。我的第一个想法是,自己编译一套。然后为此忙活了好久,结果交叉编译器的编译真是按下葫芦起来瓢,不是这儿有问题就是那儿出毛病。经常是GCC编译出来了,但是libstdc++编译不出来。

最后为了节省时间,还是去弄了一套别人做好的,这里直接给大家网址了,省得像我一样折腾:Sourcery CodeBench Lite。Sourcery CodeBench Lite是Sourcery CodeBench的一个免费版本,关系上和VS以及VS Express之间的关系差不多,主要区别就是不带IDE,只含交叉编译器,这个对我们来说就已经非常足够了。我选用的是IA32 GNU/Linux TAR这个版本,比较灵活。

跑一个Hello, World

拿到交叉编译器的第一件事是测试它所生成的程序能不能在目标平台上运行。

假设Sourcery CodeBench Lite解压在了$HOME/opt

export PATH=$HOME/opt/arm-2013.11/bin:$HOME/opt/arm-2013.11/arm-none-linux-gnueabi/bin/:$PATH

这样我们运行gcc时,运行的应该是交叉编译器而不是系统自带的。

运行gcc -v

# gcc -v
Using built-in specs.
COLLECT_GCC=gcc
Target: arm-none-linux-gnueabi
Configured with: /scratch/jroelofs/builds/fallrelease/src/gcc-4.8-2013.11/configure --build=i686-pc-linux-gnu --host=i686-pc-linux-gnu --target=arm-none-linux-gnueabi --enable-threads --disable-libmudflap --disable-libssp --disable-libstdcxx-pch --enable-extra-sgxxlite-multilibs --with-arch=armv5te --with-gnu-as --with-gnu-ld --with-specs='%{save-temps: -fverbose-asm} %{funwind-tables|fno-unwind-tables|mabi=*|ffreestanding|nostdlib:;:-funwind-tables} -D__CS_SOURCERYGXX_MAJ__=2013 -D__CS_SOURCERYGXX_MIN__=11 -D__CS_SOURCERYGXX_REV__=33' --enable-languages=c,c++ --enable-shared --enable-lto --enable-symvers=gnu --enable-__cxa_atexit --with-pkgversion='Sourcery CodeBench Lite 2013.11-33' --with-bugurl=https://sourcery.mentor.com/GNUToolchain/ --disable-nls --prefix=/opt/codesourcery --with-sysroot=/opt/codesourcery/arm-none-linux-gnueabi/libc --with-build-sysroot=/scratch/jroelofs/builds/fallrelease/install/opt/codesourcery/arm-none-linux-gnueabi/libc --with-gmp=/scratch/jroelofs/builds/fallrelease/obj/pkg-2013.11-33-arm-none-linux-gnueabi/arm-2013.11-33-arm-none-linux-gnueabi.extras/host-libs-i686-pc-linux-gnu/usr --with-mpfr=/scratch/jroelofs/builds/fallrelease/obj/pkg-2013.11-33-arm-none-linux-gnueabi/arm-2013.11-33-arm-none-linux-gnueabi.extras/host-libs-i686-pc-linux-gnu/usr --with-mpc=/scratch/jroelofs/builds/fallrelease/obj/pkg-2013.11-33-arm-none-linux-gnueabi/arm-2013.11-33-arm-none-linux-gnueabi.extras/host-libs-i686-pc-linux-gnu/usr --with-isl=/scratch/jroelofs/builds/fallrelease/obj/pkg-2013.11-33-arm-none-linux-gnueabi/arm-2013.11-33-arm-none-linux-gnueabi.extras/host-libs-i686-pc-linux-gnu/usr --with-cloog=/scratch/jroelofs/builds/fallrelease/obj/pkg-2013.11-33-arm-none-linux-gnueabi/arm-2013.11-33-arm-none-linux-gnueabi.extras/host-libs-i686-pc-linux-gnu/usr --disable-libgomp --disable-libitm --disable-libssp --enable-poison-system-directories --with-build-time-tools=/scratch/jroelofs/builds/fallrelease/install/opt/codesourcery/arm-none-linux-gnueabi/bin --with-build-time-tools=/scratch/jroelofs/builds/fallrelease/install/opt/codesourcery/arm-none-linux-gnueabi/bin SED=sed
Thread model: posix
gcc version 4.8.1 (Sourcery CodeBench Lite 2013.11-33)

如果看到这样的输出,就说明OK了。注意Target: arm-none-linux-gnueabi,这是我们的目标平台。

交叉编译器的名字和平台、接口、系统都有关系,arm-none-linux-gnueabi表示的就是ARM平台针对Linux系统,接口为gnueabi的编译器。none是提供商的名称,这个部分是什么对实际编译没有什么影响,除非厂商做了专门的改动。

现在写个hello world,看看编译出来的程序能不能在路由器上运行,测试demo:

#include <iostream>

int main() 
{
	std::cout<<"hello"<<std::endl;
	return 0;
}

编译:g++ -static -o test test.cpp

注意这个地方有个参数-static,这个提示编译器我们需要一个静态链接的程序,否则在路由器上会有动态库依赖问题。

编译如果正常,会得到一个叫test的程序。现在我们可以SSH连接到路由上运行它了。

不过路由器的文件系统大部分是只读的,root文件夹都是不可写的怎么办呢?没关系,重新挂载root文件夹到路由自带的硬盘里就行了:

cd /userdisk/data && mkdir diy && mkdir diy/root && cd diy
mount ./root /root

好了,现在root是可写的了,上传程序进去执行:

root@XiaoQiang:/userdisk/data/diy# ./test
hello

太棒了,交叉编译器能用!

编译Node.js

Node.js的最新稳定版是v0.10.28,下载回来源码,解压。为了让gyp能够推断出我们要编译的平台,设置几个变量:

export AR=arm-none-linux-gnueabi-ar
export CC=arm-none-linux-gnueabi-gcc
export CXX=arm-none-linux-gnueabi-g++
export LINK=arm-none-linux-gnueabi-g++

注意LINK也是g++。

同时,为了确保编译出来的node是静态链接的,再设置另外几个变量:

export CFLAGS=-static
export CXXFLAGS=-static
export LDFLAGS=-static

现在可以配置、编译了:

./configure --without-snapshot --prefix=/someplace/node
make && make install

经过一系列编译,如果没有什么报错的话,最终会在/someplace/node/bin下得到node和npm。

# file /someplace/node/bin/node
/someplace/node/bin/node: ELF 32-bit LSB executable, ARM, version 1 (SYSV), statically linked, for GNU/Linux 2.6.16, not stripped

没错,ARM平台,静态链接的node。

有了自己编译出来的node,已经可以运行node脚本了,比如经典的HTTP server:

var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(1337, '0.0.0.0');
console.log('Server running at http://0.0.0.0:1337/');

现在访问网关:http://192.168.31.1:1337,就会有经典的Hello World了。

@patrickyyang
Copy link

按照你写的没成功,最新的4.2.1咋编译?

!/bin/sh -e

export STAGING_DIR=/home/patyang/workspace/toolchain-arm_cortex-a9_gcc-4.8-linaro_musl-1.1.11_eabi

Tools

export CSTOOLS=${STAGING_DIR}
export CSTOOLS_INC=${CSTOOLS}/include
export CSTOOLS_LIB=${CSTOOLS}/lib
export ARM_TARGET_LIB=$CSTOOLS_LIB
export TARGET_ARCH="-march=armv6j"
export BIN_PREFIX="arm-openwrt-linux-"

Define the cross compilators on your system

export AR="${BIN_PREFIX}ar"
export CC="${BIN_PREFIX}gcc"
export CXX="${BIN_PREFIX}g++"
export LINK="${BIN_PREFIX}g++"
export CPP="${BIN_PREFIX}gcc -E"
export LD="${BIN_PREFIX}ld"
export AS="${BIN_PREFIX}as"
export CCLD="${BIN_PREFIX}gcc ${TARGET_ARCH} ${TARGET_TUNE}"
export NM="${BIN_PREFIX}nm"
export STRIP="${BIN_PREFIX}strip"
export OBJCOPY="${BIN_PREFIX}objcopy"
export RANLIB="${BIN_PREFIX}ranlib"
export F77="${BIN_PREFIX}g77 ${TARGET_ARCH} ${TARGET_TUNE}"

unset LIBC

Define flags

export CXXFLAGS="-march=armv6j"
export LDFLAGS="-L${CSTOOLS_LIB} -Wl,-rpath-link,${CSTOOLS_LIB} -Wl,-O1 -Wl,--hash-style=gnu"
export CFLAGS="-static -nostdlib -isystem${CSTOOLS_INC} -fexpensive-optimizations -frename-registers -fomit-frame-pointer -O2"
export CPPFLAGS="-isystem${CSTOOLS_INC}"
export CCFLAGS="-march=armv6j"
export PATH="${CSTOOLS}/bin:$PATH"

./configure --without-snapshot --dest-cpu=arm --dest-os=linux --without-npm

bash --norc

编译之后,执行,有这个错误。

node/node: symbol '__ctype_tolower': can't resolve symbol
node/node: symbol '__ctype_b': can't resolve symbol

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment