使用交叉混合编译从源代码编译龙芯上的llvm/clang-3.4

来源:互联网 发布:工作站品牌 知乎 编辑:程序博客网 时间:2024/04/30 12:41

目标:

使用交叉混合编译(hybrid cross-compiling)从源代码编译龙芯上的llvm/clang-3.4。之所以是混合交叉编译而不是交叉编译,是因为编译的过程中,大部分代码是由性能较高的主体(Host)平台编译,而少部分代码是由性能较低的目标(Target)平台编译。混合交叉编译采用了类似 COMET: Code Offload by Migrating Execution Transparently (OSDI’12) 的思想。其原理是,在目标平台上配置,之后开始编译时,将所有文件同步到主体平台上,并使用主体平台的交叉编译器编译。当需要执行目标平台命令(例如llvm-config)时,将所有文件同步到目标平台、执行命令,再同步回主体平台,并继续编译。

准备工作:

编译环境:

  • 主体平台

    • CPU:Intel Xeon(R) E5620 * 16
    • OS:Ubuntu 14.04 + Linux 3.13.0
    • 编译器:mipsel-linux-gnu-gcc-4.4/mipsel-linux-gnu-g++-4.4
  • 目标平台

    • CPU:loongson 3.2.0-4-loongson-2f, mipsel
    • OS:Debian 3.2.51
    • 编译器: gcc-4.6.3/g++-4.6.3

预先准备

  1. 搭建编译环境
    1. 下载 llvm-3.4的源代码 以及clang-3.4的源代码
    2. 在主体平台和目标平台上同时创建编译目录并拷贝编译文件
  $ mkdir <common/path/prefix>/mips3/llvm  $ cp *-3.4.src.tar.gz <common/path/prefix>/mips3/llvm  $ cd <common/path/prefix>/mips3/llvm  $ tar -xvf *-3.4.src.tar.gz  $ mv clang-3.4 llvm-3.4/tools/clang  $ rm *-3.4.src.tar.gz 

以上脚本同时在两个平台上运行。其中<common/path/prefix>要求两个平台上的路径名称完全相同,这是因为./configure后的makefile中包含了使用绝对路径的变量,因此如果路径名称不同,会导致编译出错。
3. 创建编译器符号链接
类似的,在两个平台上的编译器名称也需要一致。本文中使用mipsel-common-g++为两者的c++编译器,mipsel-common-gcc为两者的c编译器。在主体平台上:

 $ sudo ln -s /usr/bin/mipsel-common-g++ /usr/bin/mipsel-linux-gnu-g++ $ sudo ln -s /usr/bin/mipsel-common-gcc /usr/bin/mipsel-linux-gnu-gcc

在目标平台上

 $ sudo ln -s /usr/bin/mipsel-common-g++ /usr/bin/g++-4.6 $ sudo ln -s /usr/bin/mipsel-common-gcc /usr/bin/gcc-4.6
  1. 同步编译时间
    由于不同平台的时间和时区可能都不同,而编译时makefile会依赖文件时间来确定生成文件的新旧,因此需要同步两者的系统时间。
    1. 同步时区
  $ sudo dpkg-reconfigure tzdata

之后选择asia->Shanghai,以便两者平台的时区一致。
2. 同步时间
只要使得两个平台的时间相同即可,本文中利用ntp协议使得目标平台的时间和主体平台的时间一致。在目标平台运行以下代码:

  $ sudo sntp -s <host-ip>

或者在安装ntp之后

  $ sudo service ntp stop  $ sudo ntpdate -s time.nist.gov  $ sudo service ntp start
  1. 同步编译文件
    本文使用rsync同步主体平台和目标平台之间的文件。
    1. 在主体平台创建rsync配置文件(因为主体平台是同步服务器)
 address = <host-ip> use chroot = yes read only = no pid file = /var/run/rsyncd.pid log file = /var/log/rsync.log [mips3] uid = <host-user-name> gid = <host-group-name> path = <common/path/prefix>/mips3 hosts allow = <ip-mask>  #例如192.168.0.0/8 auth users = mips3 secrets file = /etc/rsyncd.secrets

其中/etc/rsyncd.secrets中保存用户名为mips3的密码,内容如下

 mips3:<host-password>

此外,为了保证rsyncd可以使用,需要将/etc/rsyncd.secrets的权限设置为600。
2. 启动rsyncd:

 rsyncd --daemon
  1. 创建同步命令rsync-commitrsync-update分别用于将目标平台上的程序提交到主体平台上,以及从主体平台上更新目标平台上的程序。此外,创建rsync.password用户存放密码,以避免每次同步时输入密码。
 $ cat <common/path/prefix>/mips3/rsync-commit #!/bin/bash rsync --password-file=<common/path/prefix>/mips3/rsync.password -azv <common/path/prefix>/mips3/llvm mips3@<host-ip>::mips3 $ cat <common/path/prefix>/mips3/rsync-update #!/bin/bash rsync --password-file=<common/path/prefix>/mips3/rsync.password -azv mips3@<host-ip>::mips3/llvm <common/path/prefix>/mips3 $ cat <common/path/prefix>/mips3/rsync.password <host-password>

编译

配置

在编译前,需要在目标平台上配置编译环境:

$ cd <common/path/prefix>/mips3/llvm/llvm-3.4$ CC=mipsel-common-gcc CXX=mipsel-common-g++ ./configure --enable-bindings=none --prefix=`pwd`/root --enable-targets=mips

开始编译

配置完,即可开始编译:

$ make

在出现开始编译某个文件时,CTRL+C终止编译,因为在目标平台上编译太慢了。此时执行<common/path/prefix>/mips3/rsync-commit,将目标平台上的编译结果同步到主体平台上。之后,在主体平台上

$ make -j<n>

使用主体平台上numa的并行性编译。一旦主体平台上的编译过程中出现错误,立刻切换到目标平台上

$ <common/path/prefix>/mips3/rsync-update$ make... # 开始编译下一个文件CTRL+C$ <common/path/prefix>/mips3/rsync-commit

来回切换50次以内,即可编译完成。

优化编译过程,提高编译效率

由于频繁CRTL+C、切换、同步非常耗时,而且是在不同平台上的切换(不同的物理机器),因此减少切换次数可以大大提高自动化程度。

  • 只配置mips
    clang默认会配置所有的目标平台,而在配置每一种目标平台时,都要调用当前目标平台上的编译结果。这也是需要频繁切换的原因之一。因此在配置时加上--enable-targets=mips,只配置当前的目标平台,减少切换次数。
  • 使用主体平台上的编译结果
    clang在编译可执行文件和动态共享库时,会调用llvm-config获得当前应该链接的静态库。这是需要频繁切换的原因之二。因此,将llvm-config替换成主体平台也可以运行的、具有相同功能的llvm-config可以显著减少切换次数。
    在主体平台上,先使用相同的配置选项配置clang,之后会生成主体平台上的mipsel交叉编译clang。这里我们用的不是交叉编译clang,而是生成的副产品:llvm-config。在将其重命名为llvm-config-amd64,将目标平台上的对应文件命名为llvm-config-mips3。在主体平台上执行以下命令,替换原有的llvm-config,即可使用主体平台上的编译程序。
$ cp <path/to/llvm-config-amd64>/llvm-config-amd64 <common/path/prefix>/mips3/llvm/llvm-3.4/Release+Asserts/bin/llvm-config
  • 删去单元测试和其他测试
    删去clang中和test、unittest相关内容,删去llvm中test目录,这些在编译时都不是必要的,并且会大大增加数据的传输量。

安装

$ cd <common/path/prefix>/mips3/llvm/llvm-3.4$ make install$ <common/path/prefix>/mips3/rsync-commit

即可将编译好的llvm/clang安装到<common/path/prefix>/mips3/llvm/llvm-3.4/root目录下。

编译完成

在目标平台上我们来测试一个helloworld程序:

#include <iostream>using namespace std;int main() {    cout << "Hello World!" << endl;    return 0;}

保存并将其命名为test.cpp。使用新安装好的clang++编译:

$ <common/path/prefix>/mips3/llvm/llvm-3.4/root/bin/clang++ test.cpp --target=mipsel-pc-linux$ ./a.outHello World!

这里需要加上--target=mipsel-pc-linux选项。这是因为目标平台的龙芯属于 mipsel 体系结构,即little endianness。 而系统uname -a的结果却是mips64。因此,llvm的配置文件会误认为当前平台是mipsel64,从而生成无法运行的代码。

0 0
原创粉丝点击