1. qemu-user 是什么

本来, 对于 QEmu, 我只知道它是一个模拟器, 可以像 VirtualBox/VMWare 那样跑一个操作系统, 只不过 QEmu 可以在 AMD64 上面跑针对 PowerPC, ARM 的操作系统, 当然, CPU 指令是解释执行的, 相对来说比较慢.

但是前几天折腾 CentOS/Fedora 上面的rpm构建工具mock时才发现, 原来 QEmu 还有一种运行方式, 那就是跟wine的运行方式相同: 直接运行程序文件.在这种模式下, 这个针对 PowerPC或者ARM编译的程序, 就比较像一个本地程序, 它跟本机的Linux内核打交道, 进行系统调用, 访问本地文件(其实是通过qemu进行)和本地设备.

在 QEmu 的术语中, 前面那种运行整个操作系统的方式, 称为"full system emulation", 在 Ubuntu/CentoS 由软件包 qemu-system-xxx (比如qemu-system-ppc, qemu-system-aarch64, qemu-system-arm)提供功能;后面这种运行单个程序文件的方式, 称为"user mode emulation", 由软件包qemu-user或者qemu-user-static提供功能(注意没有细分为qemu-user-ppc, qemu-user-arm, 不过这也许只是因为这些模拟器文件都不大, 就揉到了一个包里面.至于qemu-userqemu-user-static的区别, 现在只需要知道后者是静态链接版本, 至于在什么场景下需要用到哪一种, 以后再来说).

1.1. 举个例子

这里举个例子说明一下应用场景:在树莓派 2 (CPU是armv7) 上面跑针对 i386 编译的linux程序.

我在命令行上工作是, 喜欢用一个叫做 fzf 的小程序 (这类程序我以前介绍过: 命令行上的narrowing(随着输入逐步减少备选项)工具 - 巴蛮子 - 博客园 ), 但它的早期有个问题: 我日常用的比较多的Linux是在树莓派上的Raspbian 8, 但fzf自己提供的预编译版本在Linux上只有amd64386两个版本, 没有针对arm的.这个工具又是用Go语言写的, 我对这个语言不熟, 也不想去折腾安装工具链在树莓派上自行编译.于是就可以试试这条路: 跑i386的版本

$ sudo apt install qemu-user

$ wget -c 'https://github.com/junegunn/fzf-bin/releases/download/0.16.3/fzf-0.16.3-linux_386.tgz'

$ tar xvf fzf-0.16.3-linux_386.tgz

$ file fzf-0.16-3-linux_386
fzf-0.16.3-linux_386: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped $ qemu-i386 fzf-0.16.3-linux_386 -h
usage: fzf [options] Search
-x, --extended Extended-search mode
(enabled by default; +x or --no-extended to disable)
[...] $ history | qemu-i386 fzf-0.16.3-linux_386

上面倒数第三个命令是检查程序文件fzf-0.16.3-linux_386的类型, 从结果看它的确是针对386的ELF文件, 并且是静态链接的;倒数 第二个命令 qemu-i386 fzf-0.16.3-linux_386 -h是试着运行一下, 程序成功地跑起来, 打印除了帮助信息.最后一个命令history | qemu-i386 fzf-0.16.3-linux_386是真正在使用fzf这个程序的功能.

2. 用binfmt_misc机制来让启动运行更方便

上面虽然把这个程序运行起来了, 但命令行上需要将qemu-i386放在前面, 也就是说实际启动的qemu-i386这个程序, 它再把fzf跑起来.这样并不太方便, 尤其fzf这个程序一般都不是直接使用, 而是通过fzf-tmux, fkill等封装脚本来使用, 脚本里面准备好备选数据后再调用fzf程序文件来让用户挑选, 我们要一一修改这些脚本就太麻烦了.

perl/python脚本就不需要这样, 只要第一行是#!/usr/bin/perl或者#!/usr/bin/env python就可以了.我们能借用这个方法吗?fzf-0.16.3-linux_386是个二进制的可执行程序, 我们没办法去修改所谓的"第一行";

对了, 有没有注意到, 安装wine之后, 命令行上直接输入notepad.exe也是可以直接启动"记事本"程序的, 并不一定需要wine notepad.exe才能启动, 这是怎么实现的呢?

这就需要一种叫做binfmt_misc的机制.

binfmt_misc是Linux内核说提供的一种扩展机制, 使得更多类型的文件得以成为"可执行"文件.Linux内核本身支持ELF、a.out、脚本(也就是上面所说的第一行#!指定解释器的方式)这集中"可执行文件".但它还提供了一个称为binfmt_misc的内核模块, 通过这个模块可以动态注册一些"可执行文件格式",注册之后我们就可以直接"执行"这个程序文件了.

其实上面用apt install qemu-user-static安装这个包时, 它的postinstall脚本已经在binfmt_misc中注册了相应的配置, 我们可以通过下面的方式检查一下:

$ lsmod | grep binfmt
binfmt_misc 6306 1 $ ls /proc/sys/fs/binfmt_misc/
python2.7 qemu-cris qemu-mips qemu-ppc64abi32 qemu-sh4eb qemu-x86_64
python3.4 qemu-i386 qemu-mipsel qemu-ppc64le qemu-sparc register
qemu-alpha qemu-m68k qemu-ppc qemu-s390x qemu-sparc32plus status
qemu-armeb qemu-microblaze qemu-ppc64 qemu-sh4 qemu-sparc64 $ cat /proc/sys/fs/binfmt_misc/qemu-i386
enabled
interpreter /usr/bin/qemu-i386-static
flags: OC
offset 0
magic 7f454c4601010100000000000000000002000300
mask fffffffffffefefffffffffffffffffffeffffff $ xxd fzf-0.16.3-linux_386 | head -2
0000000: 7f45 4c46 0101 0100 0000 0000 0000 0000 .ELF............
0000010: 0200 0300 0100 0000 d090 0908 3400 0000 ............4... $ ./fzf-0.16.3-linux_386 -h
usage: fzf [options] Search
-x, --extended Extended-search mode
(enabled by default; +x or --no-extended to disable)
[....]

解释一下上面几条命令:

  1. lsmod | grep binfmt: 这是检查内核模块binfmt_misc是否已经加载, 有内容输出说明已经加载了.如果没有加载, 则可以用modprobe binfmt_misc来加载它(在当前的很多Linux发行版中, 一般可以通过sudo systemctl restart systemd-binfmt来启动/重启它, 修改了注册配置也可以通过这条命令来重新加载)
  2. ls /proc/sys/fs/binfmt_misc/: 这是检查内核中目前注册了哪些格式(registerstatus这两个除外)
  3. cat /proc/sys/fs/binfmt_misc/qemu-i386: 这是在检查我们所关心的与qemu-i386相关的配置, 从输出中可以看到, 对于以7f454c4601010100000000000000000002000300开头的文件, 可以调用/usr/bin/qemu-i386-static来执行(各字段的详细解释可以参见binfmt_misc - Wikipedia
  4. xxd fzf-0.16.3-linux_386 | head -2: 这是检查一下我们所想运行的程序文件的开头几个字节是怎样的, 从输出可以看出, 它与上面所注册的信息是匹配的
  5. ./fzf-0.16.3-linux_386 -h: 这是直接运行了这个i386程序, 可以看到它能够正确打印出帮助信息

关于binfmt_misc的一些相关链接:

3. 补充说明:现实并没有那么简单/美好

虽然在上面我们成功运行了fzf-0.16.3-linux_386, 但如果你多实验几个程序, 就会发现失败几率是比较高的.因为大多数程序都会环境有很多依赖, 比如动态库依赖、数据文件/配置文件、子进程调用、CPU扩展指令集、环境变量、设备文件等等, 它们的缺失或者错误都可能导致程序无法正常运行.很少有只需要单个程序文件就能跑起来的(上面运行的fzf-0.16.3-linux_386是个静态链接版本, Go语言写的工具一般都是静态链接的).

对于动态库依赖、数据文件/配置文件这类文件系统层面的问题, 虽然表面上可以想办法把文件补齐, 比如Debian/Ubuntu考虑了多架构并存, 但其它Linux发行版并没有考虑这个问题(有的考虑了x86_64与x86并存), 混合安装也会给问题定位带来诸多困难.所以在实际使用中, qemu-user大都是通过chroot在一个独立的文件系统中运行的.关于qemu与chroot配合的话题下次再展开吧

Debian/Ubuntu的动态库都安装在.../lib/<target>-<vendor>-<abi>目录下, 比如同样一个动态库libncurses.so.5.9, 通过libncurses5:armhf包提供的动态库安装在/lib/arm-linux-gnueabihf/libncurses.so.5.9,通过libncurses5:i386包提供的动态库安装在/lib/i386-linux-gnu/libncurses.so.5.9

(通过dpkg --add-architecture armhf && apt-get update && apt-get install libc6:armhf这种方式可以并行安装多种架构的包)

qemu-user有一个-L path选项, 可以用来变更动态库查找路径(/set the elf interpreter prefix to 'path'/): 将程序所需要的动态库都放置到 /home/bamanzi/i386-libs/lib 目录下, 然后用qemu-user -L /home/bamanzi/i386-libs ./prog来启动程序, 就会优先到/home/bamanzi/i386-libs/lib查找prog所需要的动态库, 而不是主机里面/etc/ld.so.conf里面设定的路径(那些路径里存放的都是针对主机的动态库, 在我这个例子里面, 就是针对

用 qemu-user 在arm linux机器上运行amd64/x86程序的更多相关文章

  1. 假设在一个 32 位 little endian 的机器上运行下面的程序,结果是多少?

    假设在一个 32 位 little endian 的机器上运行下面的程序,结果是多少? #include <stdio.h> int main(){ , b = , c = ; print ...

  2. 如何在linux主机上运行/调试 arm/mips架构的binary

    如何在linux主机上运行/调试 arm/mips架构的binary 原文链接M4x@10.0.0.55 本文中用于展示的binary分别来自Jarvis OJ上pwn的add,typo两道题 写这篇 ...

  3. 利用ganymed-ssh2远程执行其它Linux机器上的shell命令

    实际应用中,有时候需要从web管理界面上,远程去启动其它linux主机上的程序,利用ssh协议可以方便的满足这一需求.事实上hadoop架构中,从nn上启动dn时,就是利用了免密码ssh登录.gany ...

  4. 在Linux机器上安装MySQL

    在Linux机器上安装MySQL,仔细认真些就没有问题. CentOS 7下MySQL 5.7安装.配置与应用_数据库技术_Linux公社-Linux系统门户网站 搞不定的话,直接删掉这个MySQL, ...

  5. 如何将项目部署到远程的Linux机器上的tomcat上

    下面来练习一下如何将本地的一个项目部署到远程的Linux机器上去. 第一步:将要部署到Linux机器上的项目打成一个war包 war包有一个好处tomcat可以自动解压 第二步:将打好的war上传到L ...

  6. linux机器上部署多台Tomcat

    在Linux机器上部署多台Tomcat, 我部署的是Tomcat8,只需要一步,即避免端口号冲突. 在解压后的tomcat目录下,修改conf下server.xml. 修改shutdown端口: &l ...

  7. 如何将.Net Core应用程序部署在Linux操作系统上运行

    .Net Core简介 跨平台: 可以在 Windows.macOS 和 Linux 操作系统上运行. 跨体系结构保持一致: 在多个体系结构(包括 x64.x86 和 ARM)上以相同的行为运行代码. ...

  8. 在没装VS2010的机器上运行VS2010开发的C++程序

    在VS2010下写了一个win32控制台应用程序,编译ok.exe,需要依赖osg相关动态库 第一次编译的是Debug版本的,直接将ok.exe和osg相关dll文件拷贝到没有安装VS2010机器上运 ...

  9. 一台机器上运行多个ActiveMq

    由于业务需要一台机器上运行多个ActiveMq,这里主要说一下有什么地方不重复: 1.brokerName名称不能重复 2.端口号不能重复uri = tcp://localhost:50509 3.k ...

随机推荐

  1. 图解servlet

    You can see the following illustration to better understand the lifecycle of the Servlet. When the r ...

  2. Redis for OPS 06:Redis Cluster 集群

    写在前面的话 前面的主从,HA 都只是解决我们数据安全性方面的问题,并没有解决我们业务瓶颈的问题.当业务并发到达一定瓶颈的时候,我们需要对服务进行横向扩展,而不是纵向扩展.这就需要引入另外一个东西,R ...

  3. vscode 通过ftp发布vue到azure服务器

    参考资料:vs code配置ftp连接远程服务器实现代码文自动上传 1.在vscode应用商店中搜索拓展sftp插件,然后进行安装.2.安装完成后重启窗口,按快捷键Ctrl+shift+p,输入sft ...

  4. go-gui-控件和信号

    go-gui-控件和信号 控件 控件简介 控件是对数据和方法的封装.控件有自己的属性和方法.属性是指控件的特征.方法是指控件的一些简单而可见的功能.如按钮就是一个控件,这个按钮是方形的,里面有张图片, ...

  5. Python笔记:设计模式之工厂模式

    工厂模式:“工厂”即表示一个负责创建其他类型的对象的类,通常情况下,一个工厂的对象会有一个或多个方法与之关联,这些方法用于创建不同类型的对象,工厂对象会根据客户端给方法传递的不同的参数或者客户端调用不 ...

  6. 完整版的CAD技巧!3天轻松玩转CAD,零基础也能学会

    最近有很多小伙伴反应,CAD图纸学起来有点小困难,也许你还没能掌握技巧,CAD大神带你3天轻松玩转CAD,零基础也能快速学会. 一.看懂图纸是关键 CAD制图首先得让自己知道要绘制什么,如果心中对图纸 ...

  7. SpringMVC学习笔记一(请求流程和配置,启动项目)

    springmvc请求流程: 1.用户发送请求至前端控制器DispatcherServlet 2.DispatcherServlet收到请求调用HandlerMapping处理器映射器. 3.处理器映 ...

  8. JS基础语法---分支语句之:三元表达式

    获取两个数字中的最大值 用if-else语句        var num1 = 10;        var num2 = 100;        if (num1 > num2) {     ...

  9. Android 蓝牙开发(1)

    普通蓝牙设备官方文档 Android 平台包含蓝牙网络堆栈支持,凭借此支持,设备能以无线方式与其他蓝牙设备交换数据.应用框架提供了通过 Android Bluetooth API 访问蓝牙功能的途径. ...

  10. 9.智能快递柜SDK(串口型锁板)

    1.智能快递柜(开篇) 2.智能快递柜(终端篇) 3.智能快递柜(通信篇-HTTP) 4.智能快递柜(通信篇-SOCKET) 5.智能快递柜(通信篇-Server程序) 6.智能快递柜(平台篇) 7. ...