qemu安装
1.编译qemu
编译过程
wget https://download.qemu.org/qemu-5.1.0.tar.xz
tar xvJf qemu-5.1.0.tar.xz
mkdir build && cd build
~/workspace/build$../qemu-5.1.0/configure --enable-kvm --target-list=x86_64-softmmu,x86_64-linux-user,arm-softmmu,arm-linux-user,mips-softmmu
make -j4
sudo make install
第4行执行结束:
第5行执行结束:
注意点
- 不要联网安装,这种安装方式无法修改源码!要选择源码安装方式。
- 每次修改完qemu源码,都需要进行如上步骤,重新编译qemu。
- 安装x86的qemu,qemu设备建模用的是x86。
- 下载的qemu版本严格为5.1.0
2.制作根文件系统
安装busybox
//此处下载1.32.0版本
wget http://www.busybox.net/downloads/busybox-1.32.0.tar.bz2
tar xjf busybox-1.32.0.tar.bz2
cd busybox-1.32.0
make menuconfig
make -j4
make install
- 第5行,出现错误:
解决:64位操作系统,缺少libncurses5-dev库文件,安装即可解决。
sudo apt-get install libncurses5-dev
安装libncurses5-dev出现错误:
解决:执行sudo apt-get --fix-broken install来修复这些包。
- 第5行修改为静态链接:
- 第7行,执行之后,在busybox-1.32.0目录下生成了_install目录,则busybox编译成功。
制作根文件系统
- 制作根文件系统的根目录
cd busybox-1.32.0
mkdir -pv initramfs/x86_64_busybox #根文件系统的根目录
cd initramfs/x86_64_busybox/
mkdir -pv {bin,sbin,etc,proc,sys,usr/{bin,sbin}}
cp -av ../../_install/* . #把_install目录下的内容复制到根文件系统根目录下
mkdir dev
cd dev
sudo mknod -m 666 tty1 c 4 1
sudo mknod -m 666 tty2 c 4 2
sudo mknod -m 666 tty3 c 4 3
sudo mknod -m 666 tty4 c 4 4
sudo mknod -m 666 console c 5 1
sudo mknod -m 666 null c 1 3
根文件系统的根目录如下:
- 编写如下shell脚本作为init
编写一个名为init的文件,文件的内容如下代码;init文件放在根文件系统的根目录下,即busybox1.32.0/initramfs/x86_64_busybox目录下。
#!/bin/sh #声明此脚本有sh解释器执行,否则使用系统默认的解释器
mount -t proc none /proc
mount -t sysfs none /sys
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
exec /bin/sh
chmod u+x init #加上权限控制,init是shell脚本的名字
- 将x86_64_busybox目录下的内容打包归档为cpio文件,供linux内核在做initramfs启动时执行。
initramfs/x86_64_busybox$find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs-busybox-x86_64.cpio.gz
3.编译linux内核
wget https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.12.9.tar.xz
tar -xvf linux-5.12.9.tar.xz
cd linux-5.12.9
make ARCH=x86_64 x86_64_defconfig
make ARCH=x86_64 menuconfig
make -j4
make modules
sudo make modules_install
- 第5行,做如下修改:
- 第6行,出现如下错误
解决:
sudo apt install libelf-dev
sudo apt install libssl-dev
- 第8行,如下图表示linux内核编译成功
4.启动qemu
启动qemu
~/liuxinxin/qemu/build/x86_64-softmmu/qemu-system-x86_64
-smp 2
-m 1024M
-kernel ~/liuxinxin/qemu/linux-5.12.9/arch/x86_64/boot/bzImage
-nographic
-append "root=/dev/ram0 rw rootfstype=ext4 console=ttyS0 init=/linuxrc"
-initrd ~/liuxinxin/qemu/busybox-1.32.0/initramfs/initramfs-busybox-x86_64.cpio.gz
启动成功:
启动qemu的内置设备
启动qemu的内置设备,如can设备,设备名为kvaser_pci
~/liuxinxin/qemu/build/x86_64-softmmu/qemu-system-x86_64
-smp 2
-m 1024M
-kernel ~/liuxinxin/qemu/linux-5.12.9/arch/x86_64/boot/bzImage
-nographic
-append "root=/dev/ram0 rw rootfstype=ext4 console=ttyS0 init=/linuxrc"
-initrd ~/liuxinxin/qemu/busybox-1.32.0/initramfs/initramfs-busybox-x86_64.cpio.gz
-object can-bus,id=canbus0 -device kvaser_pci,canbus=canbus0
添加设备、设备驱动
- 把编译后的qemu再重新拷贝一份,命名为qemu1,在qemu1中添加设备、设备驱动
- 每次修改qemu新增设备后,还要记得修改更改/qemu-5.1.0/hw/timer/makefile文件后,才可重新编译qemu。
- 每次修改linux源码,加入新驱动文件后,记得修改dirvers/makefile + drivers/Kconfig后,再重新编译linux内核,生成新的ko文件。
- 每次将ko文件拷贝进initramfs/x86_64_busybox目录后,记得重新生成cpio文件。
添加自定义设备
- 在qemu1/qemu-5.1.0/hw/timer下添加一个文件pci-sample-timer.c,文件内容如下:
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/timer.h"
#include "exec/address-spaces.h"
#include "qemu/error-report.h"
#include "hw/hw.h"
#include "hw/pci/pci.h"
#include "sysemu/sysemu.h"
//启动设备时 -device pci-sample-timer
#define TYPE_PCI_SAMPLE_TIMER "pci-sample-timer"
typedef struct PCIPstDevState {
PCIDevice parent_obj;
MemoryRegion mmio;
QEMUTimer *irq_timer;
int timer_enabled;
} PCIPstDevState;
#define PCI_PST_DEV(obj) \
OBJECT_CHECK(PCIPstDevState, (obj), TYPE_PCI_SAMPLE_TIMER)
static void pst_irq_timer(void *opaque)
{
PCIPstDevState *d = opaque;
PCIDevice *pci_dev = PCI_DEVICE(d);
pci_irq_assert(pci_dev);
if (d->timer_enabled == 1)
timer_mod(d->irq_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)+NANOSECONDS_PER_SECOND);
}
static uint64_t
pci_pstdev_mmio_read(void *opaque, hwaddr addr, unsigned size)
{
PCIPstDevState *d = opaque;
printf("read pst timer: %d\n", d->timer_enabled);
return d->timer_enabled;
}
static void
pci_pstdev_mmio_write(void *opaque, hwaddr addr, uint64_t val,
unsigned size)
{
PCIPstDevState *d = opaque;
PCIDevice *pci_dev = PCI_DEVICE(d);
switch(addr) {
case 0:
printf("write pst timer: %d-->%ld\n", d->timer_enabled, val);
d->timer_enabled = val;
if (d->timer_enabled == 1) {
if (d->irq_timer == NULL)
d->irq_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, pst_irq_timer, d);
timer_mod(d->irq_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)+NANOSECONDS_PER_SECOND);
}
break;
case 0x8:
pci_irq_deassert(pci_dev);
break;
default:
printf("write pst timer error: 0x%lx\n", addr);
}
return;
}
static const MemoryRegionOps pci_pstdev_mmio_ops = {
.read = pci_pstdev_mmio_read,
.write = pci_pstdev_mmio_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = {
.min_access_size = 1,
.max_access_size = 1,
},
};
static int pci_pstdev_init(PCIDevice *pci_dev)
{
PCIPstDevState *d = PCI_PST_DEV(pci_dev);
uint8_t *pci_conf;
pci_conf = pci_dev->config;
pci_conf[PCI_INTERRUPT_PIN] = 1;
memory_region_init_io(&d->mmio, OBJECT(d), &pci_pstdev_mmio_ops, d,
"pci-pstdev-mmio", 128);
pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);
d->timer_enabled = 0;
printf("load pst timer\n");
return 0;
}
static void
pci_pstdev_uninit(PCIDevice *pci_dev)
{
PCIPstDevState *d = PCI_PST_DEV(pci_dev);
timer_del(d->irq_timer);
timer_free(d->irq_timer);
printf("unload pst timer\n");
}
static void qdev_pci_pstdev_reset(DeviceState *dev)
{
printf("reset pst timer\n");
}
static void pci_pstdev_class_init(ObjectClass *kclass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(kclass);
PCIDeviceClass *k = PCI_DEVICE_CLASS(kclass);
//k->init = pci_pstdev_init;
k->realize =pci_pstdev_realize;
k->exit = pci_pstdev_uninit;
k->vendor_id = 0x1234;
k->device_id = 0x0086;
k->revision = 0x00;
k->class_id = PCI_CLASS_OTHERS;
dc->desc = "PCI-based Sample Timer";
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
dc->reset = qdev_pci_pstdev_reset;
}
static const TypeInfo pci_pst_info = {
.name = TYPE_PCI_SAMPLE_TIMER,
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(PCIPstDevState),
.class_init = pci_pstdev_class_init,
};
static void pci_sample_timer_register_types(void)
{
type_register_static(&pci_pst_info);
}
type_init(pci_sample_timer_register_types)
- 在hw/timer目录下的Makefile.objs文件中添加如下一行代码:
obj-$(CONFIG_PCI) += pci-sample-timer.o
- 重新编译qemu
cd build
build$../qemu-5.1.0/configure --enable-kvm --target-list=x86_64-softmmu,x86_64-linux-user,arm-softmmu,arm-linux-user,mips-softmmu
make -j4
sudo make install
- 修改完qemu源码后,可以启动设备,但还不能操作设备,因为未添加设备驱动
~/liuxinxin/qemu1/build/x86_64-softmmu/qemu-system-x86_64
-smp 2
-m 1024M
-kernel ~/liuxinxin/qemu1/linux-5.12.9/arch/x86_64/boot/bzImage
-nographic
-append "root=/dev/ram0 rw rootfstype=ext4 console=ttyS0 init=/linuxrc"
-initrd ~/liuxinxin/qemu1/busybox-1.32.0/initramfs/initramfs-busybox-x86_64.cpio.gz
-device pci-sample-timer
注意:
- 将pci-sample-timer.c文件中这部分代码修改。
static void pci_pstdev_class_init(ObjectClass *kclass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(kclass);
PCIDeviceClass *k = PCI_DEVICE_CLASS(kclass);
//k->init = pci_pstdev_init;
k->realize =pci_pstdev_realize;
k->exit = pci_pstdev_uninit;
k->vendor_id = 0x1234;
k->device_id = 0x0086;
k->revision = 0x00;
k->class_id = PCI_CLASS_OTHERS;
dc->desc = "PCI-based Sample Timer";
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
dc->reset = qdev_pci_pstdev_reset;
}
- 修改qemu-5.1.0/hw/pci目录下的pci.c文件
static const TypeInfo pci_device_type_info = {
.name = TYPE_PCI_DEVICE,
.parent = TYPE_DEVICE,
.instance_size = sizeof(PCIDevice),
.abstract = true,
.class_size = sizeof(PCIDeviceClass),
.class_init = pci_device_class_init,
//.class_base_init = pci_device_class_base_init,
};
/*
static void pci_device_class_base_init(ObjectClass *klass, void *data)
{
if (!object_class_is_abstract(klass)) {
ObjectClass *conventional =
object_class_dynamic_cast(klass, INTERFACE_CONVENTIONAL_PCI_DEVICE);
ObjectClass *pcie =
object_class_dynamic_cast(klass, INTERFACE_PCIE_DEVICE);
assert(conventional || pcie);
}
}*/
添加自定义设备驱动
- 在/linux-5.12.9/drivers目录下新建一个设备驱动目录hello,其中包含其中包含hello.c、Makefile、Kconfig三个文件
(1)hello.c是设备驱动文件,用于操作设备,代码如下。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
struct pci_bc {
void __iomem *mmio;
int irq;
unsigned int count;
unsigned long mmio_base;
unsigned long mmio_flags;
unsigned long mmio_length;
};
static struct pci_bc *pst_data;
static const struct pci_device_id pcidevtbl[] = {
{ 0x1234, 0x0086, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
{ }
};
irqreturn_t pci_sample_timer_handler(int irq, void *dev_id)
{
pst_data->count++;
iowrite8(1, pst_data->mmio+8);
return IRQ_HANDLED;
}
static int
pci_bc_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
int timer_enabled;
int err;
pst_data = (struct pci_bc *)
kmalloc(sizeof(struct pci_bc), GFP_KERNEL);
err = pci_enable_device(pdev);
if (err) {
printk(KERN_ALERT "worning: cannot enable pci-based bc device!\n");
}
pst_data->mmio_base = pci_resource_start(pdev, 0);
pst_data->mmio_flags = pci_resource_flags(pdev, 0);
pst_data->mmio_length = pci_resource_len(pdev, 0);
pst_data->mmio = pci_iomap(pdev, 0, 0);
if (!pst_data->mmio) {
printk(KERN_ALERT "now bc failed to iomap!\n");
return -ENODEV;
}
pst_data->irq = pdev->irq;
printk(KERN_ALERT "now bc interrupt link enabled at irq %d\n", pst_data->irq);
err = request_irq(pst_data->irq, pci_sample_timer_handler, 0, "bc", pdev);
timer_enabled = ioread8(pst_data->mmio);
if (timer_enabled == 0)
printk(KERN_ALERT "bc is disabled initially\n");
else
printk(KERN_ALERT "bc is enabled initially\n");
pst_data->count = 0;
iowrite8(1, pst_data->mmio);
return 0;
}
static void pci_bc_remove(struct pci_dev *pdev)
{
free_irq(pdev->irq, pdev);
iounmap(pst_data->mmio);
pci_release_region(pdev, 0);
pci_disable_device(pdev);
kfree(pst_data);
printk(KERN_ALERT "PST: remove PCI Sample Timer\n");
}
static struct pci_driver pci_bc_driver = {
.name = "bc", /* 璁惧妯″潡鍚嶇О */
.id_table = pcidevtbl, /* 鑳藉椹卞姩鐨勮澶囧垪琛?*/
.probe = pci_bc_probe, //4.
.remove = pci_bc_remove, /* 鍗歌浇璁惧妯″潡 */
};
//2.
static int __init pci_bc_init(void)
{
int rc;
//3.
rc = pci_register_driver(&pci_bc_driver);
if (rc) {
printk(KERN_ALERT "now failed register driver of bc device !r\n");
return rc;
}
printk(KERN_ALERT "now successfully register driver of bc device !\n");
return 0;
}
static void __exit pci_bc_exit(void)
{
pci_unregister_driver(&pci_bc_driver);
printk(KERN_ALERT "now start exit the module !\n");
}
//1.
module_init(pci_bc_init);
module_exit(pci_bc_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("PCI-based bc Device");
MODULE_AUTHOR("gss");
(2)Makefile文件内容如下。
#obj-m +=hello.o
obj-$(CONFIG_HELLO) += hello.o #在编译linux内核时,如果选择M,即CONFIG_HELLO=M,则obj+=hello.o,hello.o会被编译
#generate the path
CURRENT_PATH:=$(shell pwd)
#the absolute path
LINUX_KERNEL_PATH:=~/liuxinxin/qemu1/linux-5.12.9 #linux内核的位置
#complie object
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
(3)Kconfig文件的内容如下
menu "BC TEST Driver " #在编译linux内核时,进行menuconfig时作为设备驱动名
comment "BC TEST Driver Config"
config HELLO #对应于/drivers/hello/Makefile中的CONFIG_HELLO
tristate "bc module test"
default m
help
This is the bc test driver
endmenu
- 修改linux-5.12.9/drivers/ Makefile,新增如下一行:
obj-$(CONFIG_HELLO) += hello/
- 修改linux-5.12.9/drivers/Kconfig,新增如下一行:
source "drivers/hello/Kconfig"
- 重新编译linux
cd linux-5.12.9
make ARCH=x86_64 x86_64_defconfig
make ARCH=x86_64 menuconfig
make -j4 #正在执行这一行
make modules
make modules_install
- 第3行
选中BC TEST Driver,选择为M,表示编译成内核模块,生成相应ko文件,则证明编译成功。
- 第6行:最后,在drivers/hello目录下,生成了如下文件,表示被编译成功。
- 将hello.ko文件复制到根文件系统的根目录(qemu1/busybox-1.32.0/initramfs/x86_64_busybox)下,并重新打包成.cpio.gz。
soc@VM-0-3-ubuntu:~/liuxinxin/qemu1/linux-5.12.9/drivers/hello$ sudo cp hello.ko ~/liuxinxin/qemu1/busybox-1.32.0/initramfs/x86_64_busybox/
soc@VM-0-3-ubuntu:~/liuxinxin/qemu1/busybox-1.32.0/initramfs/x86_64_busybox$
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs-busybox-x86_64.cpio.gz
- 重新启动设备,并操作设备
~/liuxinxin/qemu1/build/x86_64-softmmu/qemu-system-x86_64
-smp 2
-m 1024M
-kernel ~/liuxinxin/qemu1/linux-5.12.9/arch/x86_64/boot/bzImage
-nographic
-append "root=/dev/ram0 rw rootfstype=ext4 console=ttyS0 init=/linuxrc"
-initrd ~/liuxinxin/qemu1/busybox-1.32.0/initramfs/initramfs-busybox-x86_64.cpio.gz
-device pci-sample-timer
chmod a+x hello.ko #添加可执行的权限
insmod hello.ko
1/busybox-1.32.0/initramfs/x86_64_busybox/
soc@VM-0-3-ubuntu:~/liuxinxin/qemu1/busybox-1.32.0/initramfs/x86_64_busybox$
find . -print0 | cpio --null -ov --format=newc | gzip -9 > …/initramfs-busybox-x86_64.cpio.gz
6. 重新启动设备,并操作设备
```bash
~/liuxinxin/qemu1/build/x86_64-softmmu/qemu-system-x86_64
-smp 2
-m 1024M
-kernel ~/liuxinxin/qemu1/linux-5.12.9/arch/x86_64/boot/bzImage
-nographic
-append "root=/dev/ram0 rw rootfstype=ext4 console=ttyS0 init=/linuxrc"
-initrd ~/liuxinxin/qemu1/busybox-1.32.0/initramfs/initramfs-busybox-x86_64.cpio.gz
-device pci-sample-timer
chmod a+x hello.ko #添加可执行的权限
insmod hello.ko