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. eclipse彻底去除validation(彻底解决编辑js文件的卡顿问题)

    Eclipse中默认的JS编辑器非常慢,尤其在拷贝粘贴代码时,CPU总是占用很高甚至到100%,也就导致了编辑起来很卡. 这是因为Eclipse中带的Validate功能导致的,这个鸡肋的功能简直让人 ...

  2. oracle 关联更新

    不多说了,我们来做实验吧. 创建如下表数据 select * from t1 ; select * from t2; 现需求:参照T2表,修改T1表,修改条件为两表的fname列内容一致. 方式1,u ...

  3. Spring Cloud Gateway入坑记

    Spring Cloud Gateway入坑记 前提 最近在做老系统的重构,重构完成后新系统中需要引入一个网关服务,作为新系统和老系统接口的适配和代理.之前,很多网关应用使用的是Spring-Clou ...

  4. 使用maven快速入门

    Maven 基础知识 官网: 传送门 Maven 项目结构 $ MavenProject |-- pom.xml |-- src | |-- main | | `-- java | | `-- res ...

  5. C# 校验并转换 16 进制字符串到字节数组

    问题 最近在进行硬件上位机开发的时候,经常会遇到将 16 进制字符串转换为 byte[] 的情况,除了这种需求以外,还需要判定一个字符串是否是有效的 16 进制数据. 解决 字符串转 byte[] 的 ...

  6. maven使用问题总结

    maven dependencies 报红叉的问题: 第一种:检查bulid path 里面maven dependencies 是否丢失包 miss jar. 解决方法1:https://blog. ...

  7. 在Dynamics CRM中自定义一个通用的查看编辑注释页面

    关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复162或者20151016可方便获取本文,同时可以在第一时间得到我发布的最新的博文信息,follow me! 注释在CRM中的显示是比较特别, ...

  8. linux远程桌面连接 VNC Server

    更新源 # sudo apt-get update 安装vnc4server # sudo apt-get install vnc4server 修改vnc远程连接密码 # vncpasswd 编辑v ...

  9. swift个人总结

    最近iOS10 已经开始正式使用,研究使用了swift3.0,将一些总结记录于此,以便以后查阅.持续更新中. swift中一般将一些功能相近的方法写在同一个延展中,便于代码的规范,找起来也方便.区别o ...

  10. 3.JavaCC 语法描述文件的格式解析

      JavaCC的语法描述文件格式如下所示: options { JavaCC的选项 } PARSER_BEGIN(解析器类名) package 包名; import 库名; public class ...