用Qemu模拟ARM(1)

前面已经安装并配置了编译链和qemu,现在可以用qemu来模拟arm平台了。

1. Hello, Qemu!

输入下面的代码:

#include<stdio.h>
int main()
{
    printf("Hello, Qemu!\n");
    return 0;
}

编译并运行:

	$ arm-none-linux-gnueabi-gcc -o hello hello.c -static
	$ qemu-arm ./hello
	$ file hello
	hello: ELF 32-bit LSB  executable, ARM, EABI5 version 1 (SYSV), \
	 statically linked, for GNU/Linux 2.6.16, not stripped

不加-static变量的话,运行时则需要使用-L选项链接到相应的运行库

	$ qemu-arm -L /home/Trusty/CodeSourcery/\
	Sourcery_CodeBench_Lite_for_ARM_GNU_Linux/\
	arm-none-linux-gnueabi/libc/  ./hello_1 
	Hello, Qemu!
	$ file hello_1
	hello_1: ELF 32-bit LSB  executable, ARM, EABI5 version 1 (SYSV),\
	 dynamically linked (uses shared libs), for GNU/Linux 2.6.16, not stripped

动态编译和静态编译生成的文件大小差别:

	$ ls -l -h
	total 656K
	-rwxr-xr-x 1 Trusty root 640K Jul  7 18:46 hello
	-rwxr-xr-x 1 Trusty root 6.6K Jul  7 18:48 hello_1

###小插曲1:

系统里安装了两套编译链arm-none-eabi-和arm-none-linux-eabi-,很容易让人混淆,可参考编译链的命名规则:

arch(架构)-vendor(厂商名)-(os(操作系统名)-)abi(Application Binary Interface,应用程序二进制接口)

举例说明:

  • x86_64-w64-mingw32 = x86_64 “arch"字段 (=AMD64), w64 (=mingw-w64 是"vendor"字段), mingw32 (=GCC所见的win32 API)
  • i686-unknown-linux-gnu = 32位 GNU/linux编译链
  • arm-none-linux-gnueabi = ARM 架构, 无vendor字段, linux 系统, gnueabi ABI.
  • arm-none-eabi = ARM架构, 无厂商, eabi ABI(embedded abi)

两种编译链的主要区别在于库的差别,前者没有后者的库多,后者主要用于在有操作系统的时候编译APP用的。前者不包括标准输入输出库在内的很多C标准库,适合于做面向硬件的类似单片机那样的开发。因而如果采用arm-none-eabi-gcc来编译hello.c会出现链接错误。

###小插曲2:

qemu-arm和qemu-system-arm的区别:

  • qemu-arm是用户模式的模拟器(更精确的表述应该是系统调用模拟器),而qemu-system-arm则是系统模拟器,它可以模拟出整个机器并运行操作系统
  • qemu-arm仅可用来运行二进制文件,因此你可以交叉编译完例如hello world之类的程序然后交给qemu-arm来运行,简单而高效。而qemu-system-arm则需要你把hello world程序下载到客户机操作系统能访问到的硬盘里才能运行。

2. 使用qemu-system-arm运行Linux内核

从www.kernel.org下载最新内核,而后解压

	$ tar xJf linux-3.10.tar.xz
	$ cd linux-3.10
	$ make ARCH=arm versatile_defconfig
	$ make menuconfig ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-

上面的命令指定内核架构为arm,交叉编译链为arm-none-linux-gnueabi, 需要在make menuconfig弹出的窗口中选择到 “Kernel Features”, 激活“Use the ARM EABI to compile the kernel”, 如果不激活这个选项的话,内核将无法加载接下来要制作的initramfs。

如果需要在u-boot上加载内核,就要编译为uImage的格式,uImage通过mkimage程序来压缩的,ArchLinux的yaourt仓库里可以找到这个包:

	$ yaourt -S mkimage

安装好mkimage后,开始编译内核,因为CPU有4核,所以开启了-j8选项以加速编译:

	$ make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- all -j8 uImage 

接下来我们可以在qemu-system-arm中测试我们的内核了

	$ qemu-system-arm -M versatilepb -m 128M -kernel ./arch/arm/boot/uImage

在弹出的窗口中可以内核运行到了kernel panic状态,这是因为内核无法加载root镜像的缘故,我们将制作一个最简单的hello world的文件系统,告知kernel运行之。

#include <stdio.h>
 
void main() {
	  printf("Hello World!\n");
	  while(1);
}

编译并制作启动镜像:

	$ arm-none-linux-gnueabi-gcc -o init init.c -static
	$ echo init |cpio -o --format=newc > initramfs
	1280 blocks
	$ file initramfs 
	initramfs: ASCII cpio archive (SVR4 with no CRC)

接下来我们回到编译目录下执行:

	$ qemu-system-arm -M versatilepb -kernel ./arch/arm/boot/uImage  -initrd
	../initramfs -serial stdio -append "console=tty1"

这时候可以看到,kernel运行并在Qemu自带的终端里打印出"Hello World!"。

如果我们改变console变量为ttyAMA0, 将在启动qemu-system-arm的本终端上打印出qemu的输出。

用Qemu模拟ARM(2)

1. 关于Bootloader:

(引导程序)位于电脑或其他计算机应用上,是指引导操作系统启动的程序。引导程序启动方式和程序视应用机型种类而不同。例如在普通的个人电脑上,引导程序通常分为两部分:第一阶段引导程序位于主引导记录(MBR),用以引导位于某个分区上的第二阶段引导程序,如NTLDR、GNU GRUB等。

嵌入式系统中常见的Bootloader主要有以下几种:

  • Das U-Boot 是一个主要用于嵌入式系统的开机载入程序,可以支持多种不同的计算机系统结构,包括PPC、ARM、AVR32、MIPS、x86、68k、Nios与MicroBlaze。
  • vivi是由mizi公司设计为ARM处理器系列设计的一个bootloader.
  • Redboot (Red Hat Embedded Debug and Bootstrap)是Red Hat公司开发的一个独立运行在嵌入式系统上的BootLoader程序,是目前比较流行的一个功能、可移植性好的BootLoader。

2. 关于“裸机编程(Bare-Metal)”:

微控制器开发人员很熟悉这个概念, Bare-Metal是指的你的程序和处理器之间没有任何东西—-你写的程序将直接运行在处理器上, 换言之,开发人员是在直接操控硬件。在裸机编程的场景中,需要由开发人员检查并排除任何一个可以导致系统崩溃的风险。

“Bare-Metal"要求开发人员了解关于硬件的细节,所以接下来我们将对编译链和qemu本身进行分析。

3. 下载qemu源码包并查询相关硬件信息:

ArchLinux采用ABS(Arch Build System)来管理源码包,下面的步骤将qemu源码包下载到本地,更详细的关于ABS的操作可以在ArchLinux的Wiki中找到

	$ pacman -S abs
	$ pacman -Ss qemu
	extra/qemu 1.4.2-2 [installed]
	$ abs extra/qemu 
	$ cp -r /var/abs/extra/qemu/ ~/abs 
	$ cd ~/abs && makepkg -s --asroot -o

得到versatilepb开发板的CPU型号, 可以看到"arm926"是我们要的结果。

	$ grep "arm" src/qemu-1.4.2/hw/versatilepb.c 
	#include "arm-misc.h"
	static struct arm_boot_info versatile_binfo;
	        args->cpu_model = "arm926";
	    cpu = cpu_arm_init(args->cpu_model);
	    cpu_pic = arm_pic_init_cpu(cpu);
	    arm_load_kernel(cpu, &versatile_binfo);

得到versatilepb开发板的串口寄存器硬件信息:

	$ grep "UART*" src/qemu-1.4.2/hw/versatilepb.c 
	    /*  0x10009000 UART3.  */
	    /*  0x101f1000 UART0.  */
	    /*  0x101f2000 UART1.  */
	    /*  0x101f3000 UART2.  */

所以说开源是王道嘛,很快就查到了每一个需要了解的细节。UART0在内存中map到的地址是0x101f1000, 我们直接往这个地址写数据,就可以在终端上看到数据输出了。

4. 查看编译链支持的平台:

	$ cat ~/CodeSourcery/Sourcery_CodeBench_Lite_for_ARM_EABI/share/doc/arm-arm-none-eabi/info/gcc.info | grep arm926
	     `arm926ej-s', `arm940t', `arm9tdmi', `arm10tdmi', `arm1020t',

arm926ej-s是被支持的,因此我们可以用这套编译链来生成需要的裸机调试代码。

5. 启动应用程序init.c的编写:

首先创建应用程序init.c:

volatile unsigned char * const UART0_PTR = (unsigned char *)0x0101f1000;
void display(const char *string){
    while(*string != '\0'){
        *UART0_PTR = *string;
        string++;
    }
}
 
int my_init(){
    display("Hello Open World\n");
}

init.c中,我们首先声明一个volatile变UART0_PTR,volatile关键字用于告知编译器此变量是用于直接访问内存映像设备的,即串口0内存地址

display()函数则是用于将字符串中的字符按顺序输出到串口0, 直到遇到字符串结尾。

my_init()调用了display(), 接下来我们将把它作为C入口函数.

预编译init.c:

	$ arm-none-eabi-gcc -c -mcpu=arm926ej-s init.c -o init.o

6. 启动代码start.s编写:

.global _Start
_Start:
LDR sp, = sp_top
BL my_init
B .

处理器加电后,将跳转到指定的内存地址,从此地址开始读入并执行代码。

_Start被声明为全局函数,_Start的实现中,首先将栈地址指向sp_top, LDR(load), sp是栈地址寄存器(stack pointer),

BL则是跳转指令,跳转到my_init函数,事实上你可以跳转到任何一个你想跳转的函数,临时写一个their_init()跳转过去也行。Debug时常更改这里以调试不同的子系统功能。

“B.“可以理解为汇编里的while(1)或for(;;)循环,处理器空转,什么也不做。如果不调用它,系统就会崩溃。所谓嵌入式编程的一个基本理念就是,代码无限循环。

预编译汇编文件start.s: $ arm-none-eabi-as -mcpu=arm926ej-s startup.s -o startup.o

7. 接下来我们需要用一个可以被编译器识别的链接脚本链接两文件, linker.ld:

	ENTRY(_Start)
	SECTIONS
	{
	. = 0x10000;
	startup : { startup.o(.text)}
	.data : {*(.data)}
	.bss : {*(.bss)}
	. = . + 0x500;
	sp_top = .;
	}

ENTRY(_Start)用于告知链接器程序的入口点(entry point)是_Start(start.s中定义). Qemu模拟器如果加上-kernel选项时,将自动从0x10000开始执行,所以我们必须将代码放到这个地址。所以第四行我们指定”. = 0x10000”. SECTIONS就是用于定义程序的不同部分的。

startup.o组成了代码的text部分,然后是data部分和bss部分,最后一步则定义了栈指针(sp, stack pointer)地址. 栈通常是向下增长的,所以最好给它一个比较安全的地址, . = .+0x500就是用于避免栈被改写的。sp_top用于存储栈顶地址。

有关程序结构:

  • BSS段: 在采用段式内存管理的架构中,BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。.bss section的空间结构类似于stack, 主要用于存储静态变量、未显式初始化、在变量使用前由运行时初始化为零。
  • 数据段(data segment): 通常是指用来存放程序中已初始化且不为0的全局变量的一块内存区域。数据段属于静态内存分配。
  • 代码段(code segment/text segment): 通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读,某些架构也允许代码段为可写,即允许程序自修改。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

编译:

	$ arm-none-eabi-ld -T linker.ld init.o startup.o -o output.elf
	$ file output.elf 
	output.elf: ELF 32-bit LSB  executable, ARM, EABI5 version 1 (SYSV),statically linked, not stripped
	$  arm-none-eabi-objcopy -O binary output.elf output.bin
	$ file output.bin 
	output.bin: data

8. 使用qemu-system-arm运行output.bin:

	$ qemu-system-arm --help | grep nographic 
	-nographic      disable graphical output and redirect serial I/Os to console.
	$ qemu-system-arm -M versatilepb -nographic -kernel output.bin
	Hello Open World

9. Play more tricks: 改动init.c里的串口输出地址为串口1:

	volatile unsigned char * const UART0_PTR = (unsigned char *)0x0101f2000;
		// 0x101f1000  --> 0x101f2000

按照步骤3~7里重新编译,并运行以查看结果:

	# 没有反应!
	$ qemu-system-arm -M versatilepb -nographic -kernel output.bin
	# 终端有输出字符。
	$ qemu-system-arm -M versatilepb -kernel output.bin -serial vc:800x600 -serial stdio
	Hello Open World

同样你也可以把字符输出到第三个串口,只不过前两个-serial的重定向需要指定到别的设备而已。

用Qemu模拟ARM(3)

1. 下载并交叉编译u-boot。

新版本的u-boot我加载后总有问题,2009.11版则可以顺利通过编译和测试。

	$ wget ftp://ftp.denx.de/pub/u-boot/u-boot-2009.11.tar.bz2
	$ tar xjvf u-boot-2009.11.tar.bz2 
	$ cd u-boot-2009.11
	$ make versatilepb_config arch=ARM CROSS_COMPILE=arm-none-eabi-
	$ make all arch=ARM CROSS_COMPILE=arm-none-eabi- 

编译完成后会在目录下生成u-boot.bin和u-boot文件。

2. 运行u-boot.bin:

	$ qemu-system-arm -M versatilepb -kernel u-boot.bin -nographic

如果采用-nographic来运行qemu-system-arm,终端将无法再响应任何系统输入譬如Ctrl+c/ctrl+d_,要终止qemu-system-arm就只能查到进程号再kill。所以我一般不带-nographic选项,启动后ctrl+alt+2去看serial0输出,保留在终端窗口直接ctrl+c杀死qemu-sytem-arm进程的权力。

3. 用u-boot引导镜像文件:

改动上一篇文章里用于构建启动镜像的linker.ld文件,因为u-boot.bin文件大小的缘故,我们需要把启动镜像的起始地址整体上移.

	$ ls -l -h u-boot.bin 
	-rwxr-xr-x 1 Trusty root 85K Jul  8 15:57 u-boot.bin

linker.ld文件里, 0x100000,这个大小相比于85K显然已经足够。

	ENTRY(_Start)
	SECTIONS
	{
	. = 0x100000;
	startup : { startup.o(.text)}
	.data : {*(.data)}
	.bss : {*(.bss)}
	. = . + 0x500;
	sp_top = .;
	}

按上一章的编译方法生成output.bin,不再重述。

使用mkimage工具创建u-boot可识别的image文件:

	$ mkimage -A arm -C none -O linux -T kernel -d output.bin -a 0x00100000 -e 0x00100000 output.uimg
	Image Name:   
	Created:      Mon Jul  8 16:04:11 2013
	Image Type:   ARM Linux Kernel Image (uncompressed)
	Data Size:    152 Bytes = 0.15 kB = 0.00 MB
	Load Address: 00100000
	Entry Point:  00100000
	
	$ file *.uimg
	output.uimg: u-boot legacy uImage, , Linux/ARM, OS Kernel Image (Not \
	compressed), 152 bytes, Mon Jul  8 16:04:11 2013, Load Address: 0x00100000,\
	Entry Point: 0x00100000, Header CRC: 0x3C62F575, Data CRC: 0x69CE9647

将u-boot.bin和output.uimg打包为一个文件:

	$ cat u-boot.bin output.uimg >flash.bin

下面这条命令用于计算output.img在使用u-boot加载完flash.bin后在内存中的地址,-kernel选项告诉qemu从0x100000开始加载镜像,即65536。 65536+u-boot.bin后的大小,即output.img在内存中的地址。printf则是用16进制的格式打印出来,以便加载.

	$ printf "0x%X" $(expr $(stat -c%s u-boot.bin) + 65536)
	0x2525C

启动qemu-system-arm并运行自定义镜像:

	$ qemu-system-arm -M versatilepb -nographic -kernel flash.bin
	# iminfo 0x2525c
	
	## Checking Image at 0002525c ...
	   Legacy image found
	   Image Name:   
	   Image Type:   ARM Linux Kernel Image (uncompressed)
	   Data Size:    152 Bytes =  0.1 kB
	   Load Address: 00100000
	   Entry Point:  00100000
	   Verifying Checksum ... OK

	VersatilePB # bootm 0x2525c
	## Booting kernel from Legacy Image at 0002525c ...
	   Image Name:   
	   Image Type:   ARM Linux Kernel Image (uncompressed)
	   Data Size:    152 Bytes =  0.1 kB
	   Load Address: 00100000
	   Entry Point:  00100000
	   Loading Kernel Image ... OK
	OK
	
	Starting kernel ...
	
	Hello Open World

u-boot可以支持的选项还有很多,包括使用NFS/TFTP启动等等,留待以后慢慢研究。

ArchLinux DHCP配置问题

安装完ArchLinux后,发现网卡无法从路由器通过dhcp得到地址,ArchLinux的dhcp客户端是dhcpcd,默认配置文件。路由器型号是TP-link WR340G v5, 2010年入手的。

手动调用dhcpcd时候发现LOG里有NAK消息爆出。

翻了下Arch的论坛,这个问题是由于dhcpcd的参数配置引发的,某些dhcpcd向路由器请求的参数无法得到而导致,个人觉得大约是WR340G版本够老,无法提供这些个参数。

##解决方案一:

编辑/etc/dhcpcd.conf, 注释掉classless_static_routes 和 interface_mtu即可:

	# option classless_static_routes
	
	# Respect the network MTU.
	# option interface_mtu

而后我们可以用systemd在每次启动的时候自动调用dhcpcd绑定地址:

	$ systemctl enable dhcpcd@enp0s25
	$ systemctl start dhcpcd@enp0s25

##解决方案二:

安装dhclient:

	$ pacman -S dhclient
	$ dhclient enp0s25

这种方法需要每次手动输入,不过我们可以使用netctl包来自动管理网络接口信息:

	$ cp /etc/netctl/examples/ethernet-dhcp /etc/netctl/ethernet-dhcp

由netctl.profile查到指定dhcp客户端的字段,而后在/etc/netctl/ethernet-dhcp文件中添加:

	DHCPClient=dhclient
	# !!! 别忘了修改dhcp侦听的设备地址:
	# Interface=eth0
	Interface=enp0s25

把ethernet-dhcp作为netctl的默认启动配置文件:

	$ netctl enable ethernet-dhcp

立即开启netctl:

	$ netctl start ethernet-dhcp

查看netctl服务运行情况,我的网络是桥接的,和依据上面步骤配出来的字段会有所不同

	$ systemctl list-units -t service | grep netctl
	netctl@bridge.service        loaded active exited  Example Bridge connection

如果切换了网络环境,例如如果在待机唤醒时处于另一网络中,则需要用下列命令重新配置网络:

	$ netctl restart ethernet-dhcp

两种方法各有千秋,前者比较灵活,但是遇到复杂网络配置的时候可能会很棘手,譬如多网卡/桥接等模型时容易把人弄晕。后者配置选项很多,但一劳永逸。

Qemu快速上手

1. 安装Qemu

ArchLinux的仓库里包含有qemu已编译好的包:

	$ pacman -Ss qemu
	extra/qemu 1.4.2-2 [installed]
	    A generic and open source processor emulator which achieves a good \
	emulation speed by using dynamic translation.
	$ pacman -S qemu

会根据默认配置安装好几乎所有平台支持的Qemu.

或者你可以手动下载源码包进行编译:官方下载地址在http://wiki.qemu.org/Download

安装完qemu后运行qemu-system-i386, 如果弹出窗口,则说明qemu安装正确。

2. 创建新虚拟机磁盘镜像

创建虚拟机的第一步是创建一个新的磁盘镜像,qemu提供了对多种磁盘镜像格式的支持,如raw、qcow2、qed、vdi等,qemu-img的帮助里介绍qcow2是最多才多艺(Versatile)的格式,支持压缩、加密等功能,还能最大程度节省磁盘空间。我们选择它来做磁盘镜像(Virtualbox可以直接读取qcow2格式的虚拟机镜像)。

	$ qemu-img create -f qcow2 ubuntu.qcow2 16G
	Formatting 'ubuntu.qcow2', fmt=qcow2 size=17179869184 encryption=off \
	cluster_size=65536 lazy_refcounts=off 

3. 启动并安装虚拟机:

	$ qemu-system-i386 -hda ubuntu.qcow2 -boot d -cdrom \
	./ubuntu-13.04-desktop-i386.iso -m 1024 -enable-kvm

-boot d代表优先从光驱启动,对应的选项有a, b (软驱 1 和 2), c (硬盘优先), d (光盘优先), n-p(支持Etherboot的网卡1-4).

安装ubuntu的步骤很直观,这里就不用篇幅描述了。安装完毕后,使用下列命令启动安装好的虚拟机镜像

	$ qemu-system-i386 -hda ubuntu.qcow2 -m 1024 -enable-kvm

4. 配置虚拟机网络:

4.1 使用桥接网络

这种方法需要改动系统配置,使用bridge-utils来创建虚拟网卡和实际网卡之间的桥接。

	$ pacman -Ss bridge-utils
	core/bridge-utils 1.5-2
	    Utilities for configuring the Linux ethernet bridge
	$ pacman -S bridge-utils

创建桥接设备(br0)

	$ modprobe bridge
	$ brctl addbr br0

出现add bridge failed: Package not installed时的解决方法:

	$ brctl addbr br0
	add bridge failed: Package not installed
	# 查看kernel中关于bridge配置选项
	$ zcat /proc/config.gz | grep CONFIG_BRIDGE=
	CONFIG_BRIDGE=m
	# 查看是否存在ko文件
	$ ls /usr/lib/modules/`uname -r`/kernel/net/bridge
	bridge.ko.gz  netfilter
	# 加载bridge内核模块
	$ modprobe bridge
	# 检查内核模块是否被加载
	$ lsmod | grep ^bridge
	bridge                 93187  0 

若modprobe bridge后lsmod看不到bridge模块,有可能是因为更新完系统安装到新的kernel version后没有重启,ArchLinux是一个非常激进的发行版,内核往往几天一升级,习惯休眠或长期不关机的用户可能会碰到这个问题。

检查bridge网络

	$ brctl show br0
	bridge name	bridge id		STP enabled	interfaces
	br0		8000.000000000000	no

关于STP: Spanning Tree Protocol (STP) is a Layer 2 protocol that runs on bridges and switches. The specification for STP is IEEE 802.1D. The main purpose of STP is to ensure that you do not create loops when you have redundant paths in your network. Loops are deadly to a network. 生成树协议运行于bridge或交换机上,主要目的为确保在存在冗余路径时网络中不会生成回路,回路会造成网络的死循环。

用下列命令查看网口信息, 或者ifconfig -a直接查看也可:

	$ ifconfig -s | awk '{print $1}' | grep -v "Iface\|lo"
	enp0s25

将enp0s25加入到br0

	$ /sbin/ifconfig enp0s25 down
	$ /sbin/ifconfig enp0s25 0.0.0.0 promisc up
	$ brctl addif br0 enp0s25
	$ dhcpcd br0

查看bridge端口信息,发现enp0s25已经被加入了br0:

	$ brctl show br0
	bridge name	bridge id		STP enabled	interfaces
	br0		8000.c8cbb8b48913	no		enp0s25

有关bridge网络的dhcp相关配置,可以参考ArchLinux网络配置问题

创建qemu启动脚本:

	$ vim /etc/qemu-ifup
	#!/bin/sh
	  
	echo "Executing /etc/qemu-ifup"
	echo "Bringing up $1 for bridged mode..."
	sudo /usr/bin/ip link set $1 up promisc on
	echo "Adding $1 to br0..."
	sudo /usr/bin/brctl addif br0 $1
	sleep 2

更改脚本权限:

	$ chmod 750 /etc/qemu-ifup 
	$ chown -R root /etc/qemu-ifup 
	$ chgrp kvm /etc/qemu-ifup 

创建qemu结束脚本

	$ vim /etc/qemu-ifdown
	#!/bin/sh
	 
	echo "Executing /etc/qemu-ifdown"
	sudo /usr/bin/ip link set $1 down
	sudo /usr/bin/brctl delif br0 $1
	sudo /usr/bin/ip link delete dev $1

更改脚本权限:

	$ chmod 750 /etc/qemu-ifdown 
	$ chown -R root /etc/qemu-ifdown 
	$ chgrp kvm /etc/qemu-ifdown 

添加当前用户到kvm用户组并在visudo中开放对应命令的权限:

	$ usermod -a -G kvm Trusty
	$ visudo
	Cmnd_Alias      QEMU=/usr/bin/ip,/usr/bin/modprobe,/usr/bin/brctl
	%kvm     ALL=NOPASSWD: QEMU

建立run-qemu文件,并添加到系统路径中.

	$ vim /bin/run-qemu
	#!/bin/bash
	USERID=`whoami`
	precreationg=$(/usr/bin/ip tuntap list | /usr/bin/cut -d: -f1 | /usr/bin/sort)
	sudo /usr/bin/ip tuntap add user $USERID mode tap
	postcreation=$(/usr/bin/ip tuntap list | /usr/bin/cut -d: -f1 | /usr/bin/sort)
	IFACE=$(comm -13 <(echo "$precreationg") <(echo "$postcreation"))
	
	# This line creates a random mac address. The downside is the dhcp server will
	assign a different ip each time
	printf -v macaddr "52:54:%02x:%02x:%02x:%02x" $(( $RANDOM & 0xff)) $(( $RANDOM &
	0xff )) $(( $RANDOM & 0xff)) $(( $RANDOM & 0xff ))
	# Instead, uncomment and edit this line to set an static mac address. The
	benefit is that the dhcp server will assign the same ip.
	# macaddr='52:54:be:36:42:a9'
	  
	qemu-system-i386 -enable-kvm -net nic,macaddr=$macaddr -net tap,ifname="$IFACE"
	$*
	  
	sudo ip link set dev $IFACE down &> /dev/null
	sudo ip tuntap del $IFACE mode tap &> /dev/null 
	$ sudo chmod a+w /bin/run-qemu

使用下列命令来运行qemu

	$ /bin/run-qemu -hda ./ubuntu.qcow2 -m 1024 -vga std

运行多个run-qemu实例后的网络信息如下:

	$ ifconfig -s
	Iface      MTU    RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg
	br0       1500    70676      0      0 0         64334      0      0      0 BMRU
	enp0s25   1500   103814      0      0 0         75661      0      0      0 BMPRU
	lo       65536      983      0      0 0           983      0      0      0 LRU
	tap0      1500        3      0      0 0            11      0      0      0 BMPRU
	tap1      1500        3      0      0 0            11      0      0      0 BMPRU

4.2 使用VDE(Virtual Distrubuted Ethernet)创建Qemu网络,这种方法无需改变系统配置。

有关VDE的配置可以参考前面写过的在CentOS上安装基于Qemu的虚拟机一文。

值得注意的是, vdeswitch和slirpvde都可以在ArchLinux的软件库中找到:

	$ pacman -Ss vde
	extra/vde2 2.3.2-4 [installed]
	    Virtual Distributed Ethernet for emulators like qemu

使用VDE创建的网络对外是不可见的,它使用由slirpvde分配的dhcp地址,通常以10.0.0开头,VDE配置的网络也可以在VirtualBox中看到,VirtualBox中关于NAT的网络配置就是基于VDE的。