Qemu

来自百问网嵌入式Linux wiki
Weidongshan讨论 | 贡献2019年11月25日 (一) 15:16的版本

目录

QEMU使用手册

QEMU简介

QEMU的英文单词是:Quick Emulator,它是一个小巧的模拟器。

还有很多模拟器,比如VMWare、Virtual Box等。但是VMWare、Virtual Box只能模拟x86、AMD64/Intel64等PC系统;

而QEMU可以模拟更多硬件:ARM、MIPS、PPC、x86、AMD64/Intel64。
QEMU用途广泛,比如Xen、Android模拟器等都是基于QEMU的。
在嵌入式领域,很多人使用QEMU来深研Linux,比如研究文件系统、优化等等。
QEMU有两种模式:

用户模式(User Mode)

简单地说,一个使用arm-xxx-gcc编译出来的程序,是给ARM板子使用的,它无法在PC机上运行,只能放到ARM板子上去运行。
借助qemu,可以在PC机上运行ARM程序。比如:
$ gcc -o hello hello.c -static
$ ./hello // 这个hello程序是使用gcc给PC机编译的,可以直接运行
Hello, world!
$ arm-linux-gnueabihf-gcc -o hello hello.c -static // 它是给ARM板子编译的
$ ./hello // 所以无法在PC上运行
bash: ./hello: cannot execute binary file: Exec format error
$ ./qemu-arm ./hello // 我们可以用QEMU在PC上运行它
Hello, world!
在PC上使用qemu运行单个ARM程序时,这就是使用QEMU的用户模式。
它会把ARM指令翻译为PC的指令去运行。
注意,你可能无法做上述实验,因为:
  1. 你没有安装ARM交叉编译工具链
  2. 你没有安装QEMU


你根据下章《QEMU快速使用》安装QEMU后,就可以进行上述实验了。


系统模式(System Mode)

很多时候我们并不满足于在PC上运行单个ARM程序,我们想模拟出整个ARM单板:在这个模拟出来的虚拟ARM单板上,运行Linux系统,在其中运行各种APP。
这时候需要使用QEMU的系统模式。
我们就是使用QEMU的系统模式来模拟IMX6ULL开发板,具体的使用请看下章。


我们做的改进

QEMU可以模拟x86,也可以模拟各种ARM板子,还可以模拟各种外设。
百问网对QEMU做了很多改进,支持更多硬件,支持更多GUI现实,支持更方便的调试。

*

100ask-qemu特点
  1. 模拟网卡
  2. 模拟LCD显示功能
  3. 模拟led灯、按键
  4. 模拟at24cxx i2c存储芯片,直接可以通过用户态操作看到效果
  5. 增加逻辑分析仪显示功能
  6. 后续会逐渐增加更多的模拟硬件模块


6.1 温湿度传感器
6.2 红外
6.3 超声波模块
6.4 ADC,DAC模块
6.5 I2C接口的传感器
6.6 SPI接口的OLED
6.7 你想加啥,跟我们说


QEMU快速使用

使用apt-get当然也可以安装QEMU,但是它版本太低,也不支持IMX6ULL。
新版本QEMU已经支持IMX6ULL,我们在此基础上添加了更多功能,也修改了一些BUG。
所以,请参考本文安装我们定制的QEMU。

准备工作

必须的:
  1. 一台可以上网的windows电脑
  2. 一个可以正常使用Ubuntu虚拟机VMWare系统,也要能上网


注意:很多人碰到VMWare中Ubuntu联网问题问题,可以参考此链接:


可选的(注意,如果想要有更快的效果,在ubuntu下可以安装KVM):
安装kvm加速qemu运行,在终端下执行如下命令:
$ sudo apt-get update
$ sudo apt-get install qemu qemu-kvm libvirt-bin bridge-utils virt-manager

获取镜像

我们只提供ubuntu 16.04和ubuntu 18.04系统的QEMU镜象文件。

① ubuntu-18.04开发环境下qemu imx6ul系统镜像下载地址:


② ubuntu-16.04开发环境下qemu imx6ul系统镜像下载地址:


下载完成后上传到Ubuntu虚拟机中,执行如下命令进行解压缩操作。

① ubuntu-16.04解压操作步骤

$ tar -xvf weidongshan-ubuntu-16.04_imx6ul_qemu_system-release.tgz
然后进入ubuntu-16.04_imx6ul_qemu_system目录执行后文介绍的命令。


② ubuntu-18.04解压操作步骤

$ tar -xvf weidongshan-ubuntu-18.04_imx6ul_qemu_system-release.tgz
然后进入ubuntu-18.04_imx6ul_qemu_system目录执行后文介绍的命令。


运行QEMU系统

假设你已经按照上文下载、解压好了QEMU镜像文件,你需要进入QEMU的目录,执行下列命令。

首次运行需要安装SDL环境以及解压文件系统镜像:

使用脚本自动解压安装:
$ ./install_sdl.sh // 提示输入用户密码,等待安装完成

运行带GUI的imx6ul模拟器

① 模拟百问网imx6ul-qemu开发板
$ ./qemu-imx6ul-gui.sh // 启动后,登录名是root,无需密码
② 模拟野火imx6ul-pro开发板
$ ./qemu-imx6ul-gui.sh fire // 启动后,登录名是root,无需密码
③ 模拟正点原子imx6ul-alpha开发板
$ ./qemu-imx6ul-gui.sh atk // 启动后,登录名是root,无需密码

运行不带GUI的imx6ul模拟器

$ ./qemu-imx6ul-nogui.sh // 启动后,登录名是root,无需密码

参数讲解

可以打开脚本文件qemu-imx6ul-gui.sh,它就是运行qemu-system-arm程序。其中用到了很多参数:

-M mcimx6ul-evk 指定需要模拟的单板型号。

-m 512M 指定板子的内存大小。

-kernel zImage 指定使用的内核镜像文件。

-dtb 100ask_imx6ul_qemu.dtb 指定使用的设备树文件。

-display sdl 指定使用那种图形显示输出。

-serial mon:stdio 指定串口信息输出。

-drive file=rootfs.img,format=raw,id=mysdcard 名为mysdcard的drive,源为rootfs.img

-device sd-card,drive=mysdcard 添加一个sd-card设备,内容来自名为mysdcard的drive

-append “console=ttymxc0,115200 rootfstype=ext4 root=/dev/mmcblk1 rw rootwait init=/sbin/init loglevel=8” 指定内核的命令行参数

-nic user 指定网卡为user mode

有了内核zImage、设备树、文件系统(rootfs.img),这就是一个完整的Linux系统。
注意:QEMU中没有实现bootloader,以后我们会完全模拟SD卡,在SD卡上面放置u-boot、内核、设备树、文件系统。

百问网imx6ull-qemu开发板资料下载

百问网imx6ul-qemu开发板,并不是真实的开发板,我们可以在它上面添加任意硬件,当然也会提供原理图。
这款虚拟开发板的资料可以从网盘中下载,打开以下链接:
找到:找到“韦东山升级版全系列视频开发板BSP包”,打开对应的网盘链接后,可以看到一个目录“100ask_imx6ull-qemu”


QEMU操作示例

先执行以下命令启动QEMU,它模拟百问网imx6ul-qemu开发板:
$ ./qemu-imx6ul-gui.sh // 启动后,登录名是root,无需密码
它会弹出一个开发板的界面,并且运行该开发板的Linux系统,你可以在终端中操作该开发板:
100ask imx6ull qemu 001.png
注意:当你的鼠标点击QEMU的GUI界面时,鼠标将无法移出这个GUI界面。这时可以通过快捷键“Ctrl+Alt+g”把鼠标从GUI界面中退出来。

操作设备管理器

我们的计划是模拟更多的外设,为形象地操作这些外设,每个外设都会有一个GUI界面。可能会有多达几十、上百个外设,如果一下子显示那么多GUI界面,会很乱。
所以我们实现了一个“设备管理”,如下所示。可以点击它上面的某个按钮,显示或隐藏某个外设的GUI界面。
100ask imx6ull qemu 002.png


操作LCD

我们模拟的IMX6ULL板子,它的Linux系统中已经带有LCD的测试命令,可以执行以下命令测试:
[root@qemu_imx6ul:~]# fb-test

[root@qemu_imx6ul:~]# myfb-test /dev/fb0
效果如下:
100ask imx6ull qemu 003.png


操作LED

我们模拟的IMX6ULL板子,它的Linux系统中已经带有LED驱动和测试命令,可以执行以下命令测试:
[root@qemu_imx6ul:~]# cd led_driver_qemu/
[root@qemu_imx6ul:~/led_driver_qemu]# insmod 100ask_led.ko 
[root@qemu_imx6ul:~/led_driver_qemu]# ./ledtest /dev/100ask_led0 off
[root@qemu_imx6ul:~/led_driver_qemu]# ./ledtest /dev/100ask_led0 on
效果如下:
100ask imx6ull qemu 004.png

使用按键来控制LED

我们模拟的IMX6ULL板子,它的Linux系统中已经带有LED驱动、按键驱动和测试命令。
首先在“QEMU 设备管理器”中打开按键的界面,然后执行以下命令测试:
[root@qemu_imx6ul:~]# cd led_driver_qemu/
[root@qemu_imx6ul:~/led_driver_qemu]# insmod 100ask_led.ko 
[root@qemu_imx6ul:~/led_driver_qemu]# cd ../button_driver_qemu/
[root@qemu_imx6ul:~/button_driver_qemu]# insmod button_drv.ko 
[root@qemu_imx6ul:~/button_driver_qemu]# insmod board_100ask_qemu_imx6u
[root@qemu_imx6ul:~/button_driver_qemu]# ./button_led_test
效果如下图(只实现了左边的2个按键,右边的2个按键留待以后实现其他目的):
100ask imx6ull qemu 005.png


读写I2C EEPROM AT24C02

我们模拟的IMX6ULL板子,它已经实现了对I2C设备AT24C02的模拟。
首先在“QEMU 设备管理器”中打开at24c02的界面,然后执行以下命令测试:
// 0x50是AT24C02的I2C设备地址
[root@qemu_imx6ul:~]# i2c_usr_test /dev/i2c-0 0x50 r 0 // 读地址0
data: , 0, 0x00 
[root@qemu_imx6ul:~]# i2c_usr_test /dev/i2c-0 0x50 w 1 0x58 // 写地址1,写入0x58
效果如下图:
100ask imx6ull qemu 006.png


使用QEMU进行嵌入式Linux开发

完整的嵌入式Linux系统包含:bootloader、Linux内核、设备树、根文件系统。
QEMU可以略过bootloader而直接启动内核,这给开发带来了便利。
但是以后我们会修改QEMU,让它支持从SD卡上启动bootloader,让bootloader来启动内核。
在本章中,我们介绍如何编译并替换内核、编译并替换设备树,修改根文件系统。

获取源码

我们的源码都是存放在GIT中,所以需要确保你的Ubuntu可以上网。如果不能上网,请参考以下链接:

设置git邮箱账号和用户名

在Ubuntu中执行如下命令:
book@100ask:~$ git config --global user.email "you@example.com"
book@100ask:~$ git config --global user.name "Your Name"
初次使用GIT时,需要配置邮箱帐号和用户名,可以随意指定。

下载源码

考虑到代码仓库过多,特使用repo工具管理代码。
先用git clone下载repo工具,再用repo工具下载源码:
book@100ask:~$ git clone https://git.dev.tencent.com/codebug8/repo.git
book@100ask:~$ mkdir -p 100ask_imx6ull-qemu && cd 100ask_imx6ull-qemu
book@100ask:~/100ask_imx6ull-qemu$ ../repo/repo init -u https://dev.tencent.com/u/weidongshan/p/manifests/git -b linux-sdk -m imx6ull/100ask-imx6ull_qemu_release_v1.0.xml --no-repo-verify
book@100ask:~/100ask_imx6ull-qemu$ ../repo/repo sync -j4
上面使用的repo管理的是国内coding仓库,从国内仓库下载会快很多。


如果一切正常,你在/home/book目录下创建了一个100ask_imx6ull-qemu目录,里有如下内容:
100ask imx6ull qemu 007.png


设置工具链

交叉编译工具链主要是用于在ubuntu主机上编译可以在其它平台上运行的系统,比如在PC上为ARM板子编译程序。
设置交叉编译工具主要是设置PATH, ARCH和CROSS_COMPILE三个环境变量,下面介绍具体设置方法。

永久生效

如需永久修改,请修改用户配置文件。在Ubuntu系统下,修改如下:
book@100ask:~$ vi ~/.bashrc
在行尾添加或修改:
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-qemu/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin

临时生效

如果你有很多单板,为了不冲突,你不能使用“永久生效”的办法。比如你有32位的ARM板,也有64位的ARM板,在使用前者时需要设置ARCH=arm,在使用后者时需要设置ARCH=arm64。
这种情况下,可以使用“export”命令设置环境变量,这种设置方法只对当前终端有效:
book@100ask:~$ export PATH=$PATH:/home/book/100ask_imx6ull-qemu/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
book@100ask:~$ export ARCH=arm
book@100ask:~$ export CROSS_COMPILE=arm-linux-gnueabihf-

手动指定

执行make命令时,可以手工指定ARCH架构、CROSS_COMPILE等变量:
book@100ask:~$ export PATH=$PATH:/home/book/100ask_imx6ull-qemu/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
book@100ask:~$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
不过这种方法效率太低,不建议使用。


编译内核及设备树

前面我们下载了源码,设置好工具链后,即可编译:
book@100ask:~/100ask_imx6ull-qemu$ cd linux-4.9.88
book@100ask:~/100ask_imx6ull-qemu/linux-4.9.88$ make mrproper
book@100ask:~/100ask_imx6ull-qemu/linux-4.9.88$ make 100ask_imx6ul_qemu_defconfig
book@100ask:~/100ask_imx6ull-qemu/linux-4.9.88$ make zImage -jN //编译zImage 内核镜像,其中N参数可以根据CPU个数,来加速编译系统。 
book@100ask:~/100ask_imx6ull-qemu/linux-4.9.88$ make dtbs //编译设备树文件
编译成功后,可以得到如下文件:
arch/arm/boot/zImage // 内核
arch/arm/boot/dts/100ask_imx6ul_qemu.dtb // 设备树
如果你修改过内核,或是修改过设备树文件,那么可以用上面2个文件去替换QEMU中的zImage和100ask_imx6ul_qemu.dtb。
QEMU中的zImage和100ask_imx6ul_qemu.dtb在哪?你安装我们提供的QEMU时,可以得到这样的脚本:qemu-imx6ul-gui.sh。打开它就可以知道这2个文件在哪里了。
一般位于imx6ul-system-image目录下:
100ask imx6ull qemu 008.png


修改文件系统

安装好我们提供的QEMU后,你可以得到一个imx6ul-system-image目录,里面有名为rootfs.img的文件,它就是根文件系统:
100ask imx6ull qemu 009.png
你可以在Ubuntu下直接修改rootfs.img,不过要先挂载,执行以下命令:
100ask imx6ull qemu 010.png
主要命令就是:
sudo mount -o loop rootfs.img /mnt
你就可以在/mnt目录下对其中的文件进行操作了,也可以把Ubuntu中的文件复制进去。
注意:修改完毕后,要执行以下命令:
sudo umount /mnt

启动模拟器后使用NFS

安装好我们提供的QEMU后,可以执行如下命令启动开发板:
$ ./qemu-imx6ul-gui.sh // 启动后,登录名是root,无需密码
等待进入系统后,就可以使用网络命令挂载Ubuntu的NFS目录了。

在Ubuntu上安装、配置NFS服务

如果你使用的是我们提供的Ubuntu,那么已经安装好了NFS服务。
如果你的Ubuntu未安装NFS服务,那么在确保Ubuntu可以上网的前提下,执行以下命令:
sudo apt-get install nfs-kernel-server
然后,还得修改/etc/exports,添加类似以下的内容,下面的例子里允许开发板通过NFS访问Ubuntu的/home/book、/work两个目录:
/home/book *(rw,nohide,insecure,no_subtree_check,async,no_root_squash)
/work *(rw,nohide,insecure,no_subtree_check,async,no_root_squash)
最后,重启NFS服务,在Ubuntu上执行以下命令:
sudo /etc/init.d/nfs-kernel-server restart
可以在Ubuntu上通过NFS挂载自己,验证一下NFS可用:
sudo mount -t nfs -o nolock,vers=3 127.0.0.1:/home/book /mnt
ls /mnt

开发板获取IP地址

QEMU运行时,Ubuntu是Host即宿主机,QEMU给它分配的IP是10.0.2.2。
QEMU模拟的imx6ull板子是Guest即客户机,它会自动获取IP,也可以自己设置。
Guest可以通过10.0.2.2访问Host,Host不能访问Guest。
Guest中可以使用ifconfig命令查看IP,如果没有IP,可以如下设置:
[root@qemu_imx6ul:~]# ifconfig eth0 10.0.2.15

挂载主机nfs目录

QEMU模拟的imx6ull开发板,可以去访问10.0.2.2,比如使用NFS挂载:
[root@qemu_imx6ul:~]# mount -t nfs -o nolock,vers=3 10.0.2.2:/home/book/nfs_rootfs /mnt
如果一切正常,在开发板上就可以通过/mnt目录访问Ubuntu的/home/book/nfs_rootfs目录了。

更新QEMU

我们使用QEMU来模拟IMX6ULL开发板,目的是学习嵌入式Linux系统。

随着我们课程的陆续发布,我们也会给QEMU添加更多的功能。

比如后面要讲到触摸屏时,就会修改QEMU让它支持触摸屏。
所以你需要不断地更新QEMU,有3个方法。

下载最新的release包

从下面2个地址就可以下载到最新的release包,解压开后即可使用:

① ubuntu-18.04开发环境下qemu imx6ul系统镜像下载地址:


② ubuntu-16.04开发环境下qemu imx6ul系统镜像下载地址:


注意:如果你之前对内核、设备树或是文件系统进行过修改,想继续使用这些修改过的文件的话,需要用它们来覆盖release包中解压出来的对应文件。

下载最新的qemu-system-arm可执行程序及配置文件

如果你不想下载整个release包,可以只替换其中的某些文件。
可以打开下面的某个网址(根据你的ubuntu版本号选择):

https://dev.tencent.com/u/weidongshan/p/ubuntu-16.04_imx6ul_qemu_system/git

https://dev.tencent.com/u/weidongshan/p/ubuntu-18.04_imx6ul_qemu_system/git

以ubuntu 18.04为例,进入下图所示的bin、etc目录:
100ask imx6ull qemu 011.png
对于bin目录,只需要下载其中的qemu-sysmte-arm;对于etc目录,里面所有的bmp文件都要下载。
你之前在Ubuntu中曾经安排过我们提供的QEMU,你用下载的文件覆盖QEMU中的对应文件即可。

下载最新的qemu源码并编译

请参考后续章节,这属于对QEMU的开发了。对于嵌入式Linux初学者,没必要研究QEMU,使用我们提供的可执行程序即可。

QEMU开发手册

重要的事情说三遍:
对于嵌入式Linux初学者,对于QEMU无感者,对于急于找工作的人,
不需要深入研究QEMU,不需要看本开发手册,看前面的使用手册就可以了,会用就行。
不需要深入研究QEMU,不需要看本开发手册,看前面的使用手册就可以了,会用就行。
不需要深入研究QEMU,不需要看本开发手册,看前面的使用手册就可以了,会用就行。


我们使用QEMU来模拟IMX6ULL开发板,就像去制作一个开发板一样。作为初学者,你并不需要去设计开发板,会用开发板就可以了。
如果你想深入研究QEMU,想承接我们发布的QEMU外包项目,那么请阅读本手册。


QEMU框架简单,功能却很强大。
理解了它的框架之后,添加新的外设并不困难。当然这需要你对外设的原理有清楚的认识。
我们开发QEMU,要做的事情主要有3部分:
  1. 添加外设


比如添加一个LED,那么在QEMU中得有对应的源码,这些源码要监测LED对应的寄存器的值:当APP写寄存器时,这些源码要把那些值记录下来。


  1. 添加外设的GUI


QEMU的界面很简陋,它默认只实现了LCD的GUI显示。你想点亮LED,在QEMU上是看不出效果的。
所以我们需要给外设添加GUI界面。
比如上面举的LED例子,APP写寄存器时,除了把寄存器的值记录下来之后,我们还要在GUI界面显示一个LED,并且把它点亮或熄灭。


  1. 在虚拟开发板上开发测试程序


这个测试程序可以是裸机,也可以是Linux驱动+应用程序。
我们在QEMU模拟出来的板子上运行这些测试程序时,跟真实板子应该没有差别。


上述列的第1、2部分,需要对QEMU有所了解,请看本文的“开发手册”。
上述列的第3部分,跟QEMU无关,跟在真实板子上写的程序是一样的。


QEMU下载与编译

源码下载

官方的QEMU对IMX6ULL的支持还太弱,没有更形象化的GUI,也没有更多的外设。我们对它进行了大量的改进。
修改后的源码位于这2个GIT中:


实际上,如果你之前根据《使用手册:3. 使用QEMU进行嵌入式Linux开发》里的说明使用repo下载过源码,那么在下图中的qemu目录就是源码了:
100ask imx6ull qemu 012.png


你当然可以自行下载。在Ubuntu下,执行如下命令即可下载:
$ git clone [https://gitee.com/weidongshan/qemu.git https://gitee.com/weidongshan/qemu.git]

$ git clone [https://github.com/100askTeam/qemu.git https://github.com/100askTeam/qemu.git]
我们会不断更新QEMU,你下载过QEMU之后,可以进入qemu目录执行下述命令更新代码:
$ git pull origin


配置、编译、安装

进入qemu目录,执行如下配置命令:
./configure --prefix=$PWD/ --target-list="arm-softmmu arm-linux-user" --enable-debug --enable-sdl --enable-kvm --enable-tools --disable-curl
编译、安装命令如下:
$ make
$ make install
如果一切正常,会在qemu源码目录下生成bin子目录,里面存放有各种可执行程序。



配置、编译过程中有可能出错,一般就是缺少库文件。
如果你的ubuntu能上网,那么使用apt-get命令即可安装这些库。


错误示例,提示信息如下:
ERROR: pixman >= 0.21.8 not present.
Please install the pixman devel package.
解决方法:

① 确定库的名称:

执行如下命令:
$ apt-cache search pixman
libpixman-1-0 - pixel-manipulation library for X and cairo
libpixman-1-dev - pixel-manipulation library for X and cairo (development files)
根据输出信息,需要安装开发包(dev表示开发包): libpixman-1-dev。


② 安装开发包:

$ sudo apt-get install libpixman-1-dev
可能你的ubuntu中已经安装了某些开发包,下面列出一些必须的包:
$ sudo apt-get install pkg-config
$ sudo apt-get install libsdl2-dev
$ sudo apt-get install libpixman-1-dev
每次出错后,根据提示信息安装开发包,然后重新配置、编译、安装。
如果一切正常,在当前目录下会生成bin子目录, 里面有生成的QEMU程序:
qemu-system-arm


使用新的qemu-system-arm

将上面编译出来的bin/qemu-system-arm 可执行文件复制到如下目录:
ubuntu-18.04_imx6ul_qemu_system/qemu-system-arm/bin

ubuntu-16.04_imx6ul_qemu_system/qemu-system-arm/bin
我们也可能添加了更多的GUI显示,这些GUI所用图片位于源码目录的etc子目录下,这些图片也需要复制到如下目录去:
ubuntu-18.04_imx6ul_qemu_system/qemu-system-arm/etc

ubuntu-16.04_imx6ul_qemu_system/qemu-system-arm/etc
然后就可以执行 qemu-imx6ul-gui.sh 或 qemu-imx6ul-nogui.sh来使用您编译出来的QEMU了。


QEMU源码目录

我们只罗列出涉及的少许文件,一般来说一个.c文件会有一个.h文件,它们的目录类似。
比如hw/gpio/imx_gpio.c对应的头文件为include/hw/gpio/imx_gpio.h
100ask imx6ull qemu 013.png


QEMU的设备创建过程

重要结构体TypeInfo

一个板子上有很多硬件:IMX6ULL、LED、按键、LCD、触摸屏、网卡等等。
IMX6ULL这类芯片被称为SoC(System on Chip),它里面也有很多部件,比如CPU、GPIO、SD控制器、中断控制器等等。
这些硬件,或是部件,各有不同。怎么描述它们?
每一个都使用一个TypeInfo结构体来描述。


比如对于CPU,有这样的结构体(hw/cpu/a15mpcore.c):
100ask imx6ull qemu 014.png


对于LCD,有这样的结构体(hw/display/100ask_qemu_fb.c):
100ask imx6ull qemu 015.png


对于GPIO,也有这样的结构体(hw/gpio/imx_gpio.c):
100ask imx6ull qemu 016.png


由此可见,在QEMU中,每一个硬件都由一个TypeInfo来描述。这些结构体都会被注册进程序里,在一个链表中保存着,备用注意,是备用,它们并不一定会用到。


怎么注册这些TypeInfo结构体呢?不需要我们去调用注册函数,以GPIO为例,在hw/gpio/imx_gpio.c中有如下代码:
100ask imx6ull qemu 017.png


关键点在于type_init,这个宏在include/qemu/module.h中定义:
100ask imx6ull qemu 018.png
对于属性为“constructor”函数,它在main函数之前被调用。
对于上述的type_init(imx_gpio_register_types),它的宏展开如下:
static void __attribute__((constructor)) do_qemu_init_imx_gpio_register_types (void) \
{ \
register_module_init(imx_gpio_register_types, MODULE_INIT_QOM); \
}
这就得到了一个属性为“constructor”的函数do_qemu_init_imx_gpio_register_types,这个函数将在main函数之前被调用,它调用了register_module_init。
register_module_init中构造了一个ModuleEntry结构体,并把它添加进某个链表里,这个链表是init_type_list[MODULE_INIT_QOM]:
100ask imx6ull qemu 019.png
这样就得到了一个链表:
100ask imx6ull qemu 020.png

生成TypeImpl

从名字上讲,TypeImpl就是TypeInfo的实现,就是使用TypeInfo的信息构造一个TypeImpl结构体。
在init_type_list[MODULE_INIT_QOM]链表中,有一系列的ModuleEntry结构体,每个ModuleEntry结构体中有一个init函数,它指向某个xxx_register_types,比如:
a15mp_register_types
ask100fb_register_types
这些ModuleEntry结构体中的init函数何时被调用?
vl.c:
main
    module_call_init(MODULE_INIT_QOM);
        QTAILQ_FOREACH(e, l, node) {
            e->init();
        }
这些xxx_register_types执行后,又得到了什么?

① 分配一个TypeImpl结构体,使用TypeInfo来设置它:

100ask imx6ull qemu 021.png

② 把TypeImpl放入链表:type_table

于是,在程序中就有了这样的链表:
100ask imx6ull qemu 022.png


使用TypeImpl:实例化

在程序的type_table链表中,有很多TypeImpl结构体,比如CPU、GPIO、LED、LCD对应的TypeImpl结构体。
但是这并不表示QEMU模拟的板子上有这些硬件,必竟它们只是“TypeImpl”,表示“类型”,需要在“实例化”之后,才表示板子上有了这些硬件。


以CPU为例,代码为hw/cpu/a15mpcore.c,里面声明了一个A15MPPrivState结构体,还定义了一个TypeInfo结构体:
100ask imx6ull qemu 023.png


A15MPPrivState和TypeInfo、TypeImpl之间有什么关系?
TypeImpl的信息基本来自TypeInfo,
所以问题转为:A15MPPrivState和TypeInfo之间有什么关系?
A15MPPrivState用来表示一个CPU,你要在板子上添加一个CPU,必须分配、设置一个A15MPPrivState结构体。


板子上的主芯片可能是单核CPU的,也可能是多核CPU的。
假设有2个CPU,那么应该有对应的2个A15MPPrivState结构体。这2个CPU是类似的,同属于某类:用TypeImpl来描述。
所以,可以得到下面的图:
100ask imx6ull qemu 024.png


谁来分配、设置A15MPPrivState结构体?

① 分配:

猜测是根据TypeInfo中的instrance_size来malloc出A15MPPrivState结构体。

② 设置:

猜测是调用TypeInfo中的instrance_init函数来设置刚malloc出A15MPPrivState结构体。


谁来malloc、谁来调用TypeInfo中的instrance_init函数?
有2种方法:

① qdev_create/qdev_init_nofail

② sysbus_init_child_obj/object_property_set_bool


实例化方法1:qdev_create/qdev_init_nofail

这2个函数是成对出现的,以hw/display/100ask_qemu_fb.c为例:
100ask imx6ull qemu 025.png


这2个函数的作用如下图所示:
100ask imx6ull qemu 026.png

qdev_create分析

① 第2个参数是name,会被用来找到对应的TypeInfo结构体

② 分配instance_size大小的内存,即分配ASK100FbState结构体,这用来表示LCD

③ 调用TypeInfo结构体中的class_init函数

class_init,顾名思义,这个设备属于什么类别?先初始化一下它的类别。
这些class_init函数都很类似,都是设置dc->realize函数,比如:
100ask imx6ull qemu 027.png

④ 调用TypeInfo结构体中的instance_init函数


qdev_init_nofail分析

qdev_init_nofail做的事情很简单:
100ask imx6ull qemu 028.png
只是把设备的realized属性设置为true,表示可以对它进行realize(变为现实)了。这会导致dc->realize函数被调用,即设备的类里的realize函数被调用。


总结:怎么创建设备

怎么定义一个设备?如下图:
100ask imx6ull qemu 029.png

① 先定义一个TypeInfo结构体

里面有name,表示它的类型名。
有class_init,这是“类别的初始化函数”,该类下可能有多个设备。class_init函数中通常给该类设置一个realize函数。
有instance_size,每一个设备都用一个结构体来表示,比如LCD用ASK100FbState来描述。Instance_size表示这个结构体的大小。
有instance_init,这是“实例的初始化函数”,它会被用来初始化设备结构体,比如初始化ASK100FbState结构体。


② 注册这个TypeInfo结构体:

定义一个ask100fb_register_types函数,里面会注册TypeInfo结构体。

③ 使用type_init,把ask100fb_register_types函数放入链表中

④ 调用qdev_create创建设备,这会传入type name

⑤ 调用qdev_init_nofail设置设备的状态为realized,这会导致类别的realize函数被调用。


简单地说,一个设备被创建时,这些函数被依次调用:

① TypeInfo中的class_init:它会设置dc->realize = 某个函数

② TypeInfo中instance_size大小的对象被malloc

③ TypeInfo中的instance_init函数被调用,它被用来初始化步骤②中malloc出来的结构体

④ dc->realize被调用




实例化方法2:object_initialize_child/object_property_set_bool

参考hw/arm/fsl-imx6ul.c,里面大量的成对代码,比如:

100ask imx6ull qemu 030.png


上述函数的内部调用过程,跟qdev_create/qdev_init_nofail是类似的:

100ask imx6ull qemu 031.png

object_initialize_child分析

该函数的第5个参数是type,表示type name,它会被用来找到对应的TypeImpl。
找到后,会分配instance_size大小的结构体;
然后调用TypeImpl中的class_init函数,这一般是设置dc->realize。
最后调用TypeImpl中的instance_init函数。

object_property_set_bool分析

比如:
100ask imx6ull qemu 032.png
只是把设备的realized属性设置为true,表示可以对它进行realize(变为现实)了。这会导致dc->realize函数被调用,即设备的类里的realize函数被调用。


总结:怎么创建设备

怎么定义一个设备?如下图所示:
100ask imx6ull qemu 033.png

① 先定义一个TypeInfo结构体

里面有name,表示它的类型名。
有class_init,这是“类别的初始化函数”,该类下可能有多个设备。class_init函数中通常给该类设置一个realize函数。
有instance_size,每一个设备都用一个结构体来表示,比如CPU用A15MPPrivState来描述。Instance_size表示这个结构体的大小。
有instance_init,这是“实例的初始化函数”,它会被用来初始化设备结构体,比如初始化A15MPPrivState结构体。


② 注册这个TypeInfo结构体:

定义一个a15mp_register_types函数,里面会注册TypeInfo结构体。

③ 使用type_init,把a15mp_register_types函数放入链表中

④ 调用object_initialize_child创建设备,这会传入type name

⑤ 调用object_property_set_bool设置设备的状态为realized,这会导致类别的realize函数被调用。


怎么创建设备

其实在2.4.3、2.5.3已经整理出来了,在这里只是再次总结一下。


设置TypeInfo结构体

比如LCD:
100ask imx6ull qemu 034.png
要注意到,里面有一个ASK100FbState结构体,这个结构体由我们自己设置。但是它的格式有一定要求:
100ask imx6ull qemu 035.png

注册TypeInfo结构体

比如:
100ask imx6ull qemu 036.png

使用TypeInfo创建设备/设置设备的realized属性为true

这有2种方法,前面介绍过:

qdev_create/qdev_init_nofail

object_initialize_child/object_property_set_bool

3. QEMU的设备模拟

3.1 QEMU模拟外设的原理

QEMU主要是实现了CPU核的模拟,可以读写某个地址。
QEMU的模拟外设的原理很简单:硬件即内存
要在QEMU上模拟某个外设,思路就是:

① CPU读某个地址时,QEMU模拟外设的行为,把数据返回给CPU

② CPU写某个地址时,QEMU获得数据,用来模拟外设的行为。

即:要模拟外设备,我们只需要针对外设的地址提供对应的读写函数即可。


以GPIO为例:
100ask imx6ull qemu 037.png


以LCD控制器为例,它主要有2大功能:

① 写LCD控制器,根据外接的LCD设置参数,比如分辨率、BPP、各种时序

② 从FrameBuffer中不断获得数据发给LCD,在LCD上显示出来。


站在Linux LCD驱动的角度,上述2大功能可以分为:

① 写LCD控制器的相关寄存器

② 分配FrameBuffer,写FrameBuffer


QEMU中要模拟LCD控制器,需要:

① 记录驱动程序写寄存器的值,解析出分辨率等信息

② 记录FrameBuffer的地址,并持续不断地从中得到图像数据并显示出来


框图如下:
100ask imx6ull qemu 038.png


简单地说,

① 设置LCD控制器时:

在QEMU中可以给LCD控制器的访问地址A1~A2提供读写回调函数,比如:

qemu_a1a2_read

qemu_a1a2_write

当Linux LCD驱动程序写LCD控制器的寄存器时,就会导致qemu中的qemu_a1a2_write函数被调用,在函数中分析、记录这些值,得到分辨率等信息。


② 写FrameBuffer时:

在QEMU中针对FrameBuffer提供一个刷新函数。
当LCD驱动写FrameBuffer时,QEMU会使用这些数据更新GUI窗口的图像。


3.2 给某段地址提供读写函数

怎么描述某段地址:基地址、大小;还得给这段地址提供读写函数。
这段地址设置好后,需要添加进system_memory去。
有2种方法。


3.2.1 memory_region_init_io/memory_region_add_subregion

比如:
100ask imx6ull qemu 039.png
上图中memory_region_init_io被用来初始化一块内存s->iomem,指定了它的读写函数、大小。
然后给s->iomem指定了基地址,并添加进system_memory中。
以后,客户机上的程序读写这块地址时,就会导致对应的读写函数被调用。


3.2.2 memory_region_init_io/sysbus_init_mmio/sysbus_mmio_map

以hw/net/smc91c111.c为例:
100ask imx6ull qemu 040.png


4. 模拟LED

4.1 主要函数

客户机上的程序要操作LED时,会先设置GPIO为output,然后把值写入某个数据寄存器。
所以我们只需要针对数据寄存器提供对应的write函数即可
QEMU中已经实现IMX6ULL的GPIO模拟,代码在hw\gpio\imx_gpio.c中。
对应的write函数如下:
100ask imx6ull qemu 041.png


4.2 添加一段内存

客户机的程序要设置GPIO5_IO03为输出引脚时,需要访问这个寄存器:

IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3

它的基地址是0x2290014,QEMU中并没有添加这个地址,客户机访问它时就会发生硬件错误。


所以在hw/arm/fsl-imx6ul.c中使用如下代码添加了这块空间:
100ask imx6ull qemu 042.png


5. 模拟LCD

我们添加的文件是:hw/display/100ask_qemu_fb.c,新加一个文件时要把它编进QEMU中,需要修改同目录的Makefile.objs,比如修改hw/display/Makefile.objs,添加一行:

common-obj-$(CONFIG_FSL_IMX6UL) += 100ask_qemu_fb.o


5.1 在Linux上编写LCD驱动程序

既然操作的不是真实的LCD控制器,那么LCD驱动程序可以极大精简。

① 对于LCD控制器,只需要操作4个寄存器:

分别用来保存:framebuffer的物理地址、宽度、高度、BPP。
你需要记住这些寄存器的物理地址(可以自己指定地址是什么)。

② 对于FrameBuffer:

驱动程序分配得到FrameBuffer后,要把它的物理地址写到上述第1个寄存器里。


部分代码如下,其他的时钟使能、GPIO设置等等都不再需要:
100ask imx6ull qemu 043.png

5.2 在QEMU中创建LCD控制器的设备

100ask imx6ull qemu 044.png


5.3 重要函数分析

5.3.1 给LCD控制器的寄存器提供回调函数

100ask imx6ull qemu 045.png
100ask imx6ull qemu 046.png

5.3.2 给FrameBuffer提供更新函数

100ask imx6ull qemu 047.png


5.3.3 更新函数

100ask imx6ull qemu 048.png


6. QEMU的输出:GUI系统

QEMU的GUI系统,支持SDL、GTK等。SDL使用比较简单,我们就使用SDL来显示GUI。
SDL的显示原理跟LCD FrameBuffer是一样的,可以认为每一个SDL窗口都有一个显存。你可以在显存中修改每一个象素的颜色。
本文以hw/display/100ask_qemu_fb.c为例进行讲解。

6.1 创建GUI Console

创建GUI Console的函数有2个:graphic_console_init、graphic_console_init_hidden。
后者是我们添加的,它创建的GUI Console默认是隐藏的,要显示的话需要在“Device Manager”中点击对应的按钮。


LCD代码如下,主要是graphic_console_init函数:
100ask imx6ull qemu 049.png


调用graphic_console_init时,要传入一个GraphicHwOps结构体,如下:
100ask imx6ull qemu 050.png


6.2 GUI更新函数

ask100fb_update函数被GUI系统周期性地调用:
100ask imx6ull qemu 051.png


效果如下:
100ask imx6ull qemu 052.png


6.3 GUI函数详解

6.3.1 解析BMP文件得到RGB数据

100ask imx6ull qemu 053.png


6.3.2 显示RGB数据

100ask imx6ull qemu 054.png


6.3.3 显示LCD数据

100ask imx6ull qemu 055.png


7. QEMU的输入:鼠标事件

点击buttons界面时,对应的按钮会被按下或松开:
100ask imx6ull qemu 056.png
这是怎么实现的?
我们需要给这个GUI界面添加鼠标处理函数,代码在ui/button_ui.c中。这是新加的文件,要把它编进QEMU中需要修改同目录下的Makefile.objs,比如修改ui/Makefile.objs,添加一行:

common-obj-$(CONFIG_FSL_IMX6UL) += button_ui.o


7.1 分配、设置、注册QemuInputHandler

100ask imx6ull qemu 057.png


7.2 QemuInputHandler处理函数分析

100ask imx6ull qemu 058.png


7.3 输入事件处理流程

输入事件的处理源头是sdl2_2d_refresh函数:
100ask imx6ull qemu 059.png

sdl2_2d_refresh

sdl2_poll_events(scon);
SDL_PollEvent(ev)
handle_keydown(ev);
handle_keyup(ev);
handle_textinput(ev);
handle_mousemotion(ev);
handle_mousebutton(ev); // SDL_MOUSEBUTTONDOWN,UP
sdl_send_mouse_event(scon, 0, 0, bev->x, bev->y, buttonstate);


7.4 QEMU捕获的输入事件,可以作为ABS也可以作为REL事件上传

作为哪类事件上传,取决于qemu_input_is_absolute()的返回值:
100ask imx6ull qemu 060.png


qemu_input_is_absolute函数只会判断第1个GUI Console中的QemuInputHandler。我们的第1个GUI Console是“Device Manager”,它的代码是hw/display/device_manager.c。
对应的QemuInputHandler的mask必须设置为INPUT_EVENT_MASK_ABS:
100ask imx6ull qemu 061.png



8. 使用中断

在QEMU中模拟一个外设时,这个外设要使用中断的话,并不复杂。
先看看硬件框架:
100ask imx6ull qemu 062.png


8.1 外设发出哪一个中断?

GIC从多个中断源获得中断信号,它会发信号给CPU,这样CPU才会处理中断。
作为外设备,它要发出哪一个中断呢?
在include/hw/arm/fsl-imx6ul.h中列出了各个中断源,如下:
100ask imx6ull qemu 063.png


你需要查看这些中断源,确定你的外设要发出哪一个中断。
你的外设,要跟中断控制器建立联系,即确定使用哪一个中断。


8.8.1 调用sysbus_init_irq初始化qemu_irq

在QEMU中,用qemu_irq结构体来描述中断,需要初始化qemu_irq结构体。
以GPIO为例,代码在hw/gpio/imx_gpio.c中:
100ask imx6ull qemu 064.png


sysbus_init_irq函数很有意思:

① 它会给设备dev添加一个属性prop,prop的名字是“sysbus-irq[0]”或“sysbus-irq[1]”

② 会给这个prop添加一个link,link到&s->irq[0]&s->irq[1]

8.8.2 调用sysbus_connect_irq连接中断

在hw/arm/fsl-imx6ul.c中,给GPIO模块和GIC建立联系:
100ask imx6ull qemu 065.png


sysbus_connect_irq也很神奇,它是通过名字“sysbus-irq[0]”或“sysbus-irq[1]”找到设备的属性。
然后根据属性的link找到之前传入的&s->irq[0]或&s->irq[1],最后设置它。
设置为什么呢?下面的函数会返回一个qemu_irq,第2个参数用来指定是哪一个中断:

qdev_get_gpio_in(DEVICE(&s->a7mpcore), FSL_IMX6UL_I2Cn_IRQ[i])


8.2 中断触发

发出中断:

qemu_set_irq(irq, 1);

清除中断:

qemu_set_irq(irq, 0);


上面的2个调用必须成双出现,如果多次发出中断而没有清除中断,那后面发出的中断是无效的。
这里说的成双出现,并不是说它们必须前后出现。流程是这样的:

① 用户按下按键后,使用“qemu_set_irq(irq, 1)”发出中断

② 客户机的程序处理完中断后,它会写ISR寄存器来清除中断:

对ISR寄存器的写操作会导致QEMU中对应的write函数被调用,在里面执行“qemu_set_irq(irq, 0)”。


对于hw/gpio/imx_gpio.c,发出中断的函数流程为:

notify_imx_gpio_change

imx_gpio_set(s, pin, level);
imx_gpio_update_int(s);

qemu_set_irq(s->irq[0], 1);



清除中断的流程为:

imx_gpio_write

case ISR_ADDR:
s->isr &= ~value;
imx_gpio_set_all_int_lines(s);
qemu_set_irq(s->irq[0], 0);


9. 模拟按键

我们模拟的按键涉及GUI、鼠标输入、中断,值得仔细研究。
GUI和鼠标的处理,都在这个文件里:ui/button_ui.c


当用户在GUI中点击某个按键时,它对应哪个引脚?
这由这个文件决定:hw/gpio/100ask_imx6ull_buttons.c


上述文件确定是哪一个引脚之后,要通知hw/gpio/imx_gpio.c来处理。


9.1 按键GUI

100ask imx6ull qemu 066.png


9.2 点击按键

100ask imx6ull qemu 067.png


9.3 更新按键图片的同时

100ask imx6ull qemu 068.png

9.4 单板的代码怎么处理按键

对应的代码在hw/gpio/100ask_imx6ull_buttons.c中:
100ask imx6ull qemu 069.png

9.5 IMX6ULL的代码怎么处理输入引脚

对应的代码在hw/gpio/imx_gpio.c中:
100ask imx6ull qemu 070.png


10. QEMU启动过程

10.1 各个模块的注册

各个模块都定义了一个TypeInfo结构体,比如LCD:
100ask imx6ull qemu 071.png


并且都使用type_init定义了一个属性为constructor的函数(include/qemu/module.h):

#define type_init(function) module_init(function, MODULE_INIT_QOM)

#define module_init(function, type) \

static void __attribute__((constructor)) do_qemu_init_ ## function(void) \

{ \

register_dso_module_init(function, type); \

}

100ask imx6ull qemu 072.png


这些属性为constructor的函数会在main函数之前被调用,这样就得到了一个链表:
100ask imx6ull qemu 073.png


10.2 各个模块的初始化

main函数位于vl.c中,它有如下调用:

main

module_call_init(MODULE_INIT_QOM);
QTAILQ_FOREACH(e, l, node) {
e->init();
}


这些xxx_register_types执行后,又得到了什么?

① 分配一个TypeImpl结构体,使用TypeInfo来设置它:

100ask imx6ull qemu 074.png

② 把TypeImpl放入链表:type_table

于是,在程序中就有了这样的链表:
100ask imx6ull qemu 075.png


程序中有一系列的TypeImpl,但是并不表示会用到它们。要用某个TypeImpl,需要创建对应的设备。



10.3 选择machine

启动qemu时,会传入参数“-M mcimx6ul-evk”,对应的文件为:hw/arm/mcimx6ul-evk.c
100ask imx6ull qemu 076.png


10.4 选择CPU

100ask imx6ull qemu 077.png


CPU在中hw/arm/fsl-imx6ul.c定义:
100ask imx6ull qemu 078.png


10.5 CPU的初始化

hw/arm/mcimx6ul-evk.c mcimx6ul_evk_init:
100ask imx6ull qemu 079.png


这会导致hw/arm/fsl-imx6ul.c中fsl_imx6ul_init函数被调用:


在fsl_imx6ul_init函数中,会去创建名为a7mpcore的设备,它的类型是TYPE_A15MPCORE_PRIV:
100ask imx6ull qemu 081.png
100ask imx6ull qemu 082.png


a7mpcore设备对应的realize函数被调用时,里面会实例化GIC等。

10.6 在system memory中添加内存

分配system memory,代码为hw/arm/mcimx6ul-evk.c中的mcimx6ul_evk_init函数:


其中的ram_size来自QEMU运行时的参数“-m 512M”;内存基地址为0x80000000。
从这里可以知道,system memory中既含有可读可写的内存,也含有各种模块的寄存器。



























11. 调试

11.1 要调试,运行gdb程序之前,必须先进入源码目录

无论是使用gdb调试PC程序,还是使用arm-xxx-gdb调试ARM程序,运行这些gdb程序前,必须进入要调试的源码的目录。
然后再启动gdb或arm-xxx-gdb。


11.2 调试QEMU本身

先进入QEMU源码目录,执行如下命令。
这个命令中涉及qemu-system-arm、zImage、设备树、文件系统这4个文件的路径,你要改成你的路径。

gdb --args /home/book/mywork/qemu/qemu_git/qemu/bin/qemu-system-arm -M \

mcimx6ul-evk -m 512M -kernel /home/book/nfs_rootfs/zImage \

-dtb /home/book/nfs_rootfs/mynet.dtb -serial stdio \

-drive file=/home/book/mywork/imx6ull_test_image/buildroot_rootfs.img,format=raw,id=mysdcard -device sd-card,drive=mysdcard \

-append "console=ttymxc0,115200 console=tty1 rootfstype=ext4 root=/dev/mmcblk1 rw rootwait init=/sbin/init loglevel=8" \

-nic user


关键点在于:gdb --args ,它后面就可以跟一连串的命令及参数了。
这种方法有一个缺点,就是客户机上的Linux系统启动后,你无法再在终端中输入gdb命令。可以使用下一种方法来调试。


11.3 调试之前就运行的QEMU

先进入QEMU源码目录。

① 首先确定qemu-system-arm进程的PID

执行如下命令:

ps -A | grep qemu


假设得到PID为9527


② 在QEMU源码目录下执行如下命令:

sudo gdb -p 9527


注意:一定要加上sudo


11.4 调试客户机上的Linux内核

11.4.1 首先运行qemu-system-arm

执行QEMU的命令时,在最后加上参数“-s -S”。
-s是“-gdb tcp::1234”的缩写,表示QEMU将会在本机端口1234上开启gdbserver。
-S表示不启动CPU,当你使用arm-xxx-gdb连接上QEMU后,必须执行c命令才可以运行内核。


我用的命令如下所示。
这个命令中涉及qemu-system-arm、zImage、设备树、文件系统这4个文件的路径,你要改成你的路径。

gdb --args /home/book/mywork/qemu/qemu_git/qemu/bin/qemu-system-arm -M \

mcimx6ul-evk -m 512M -kernel /home/book/nfs_rootfs/zImage \

-dtb /home/book/nfs_rootfs/mynet.dtb -serial stdio \

-drive file=/home/book/mywork/imx6ull_test_image/buildroot_rootfs.img,format=raw,id=mysdcard -device sd-card,drive=mysdcard \

-append "console=ttymxc0,115200 console=tty1 rootfstype=ext4 root=/dev/mmcblk1 rw rootwait init=/sbin/init loglevel=8" \

-nic user -s -S


14.4.2 进入内核源码目录,运行arm-xxx-gdb vmlinux