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行执行结束:


注意点

  1. 不要联网安装,这种安装方式无法修改源码!要选择源码安装方式。
  2. 每次修改完qemu源码,都需要进行如上步骤,重新编译qemu。
  3. 安装x86的qemu,qemu设备建模用的是x86。
  4. 下载的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编译成功。


制作根文件系统

  1. 制作根文件系统的根目录
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

根文件系统的根目录如下:


  1. 编写如下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脚本的名字
  1. 将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

添加设备、设备驱动

  1. 把编译后的qemu再重新拷贝一份,命名为qemu1,在qemu1中添加设备、设备驱动
  2. 每次修改qemu新增设备后,还要记得修改更改/qemu-5.1.0/hw/timer/makefile文件后,才可重新编译qemu。
  3. 每次修改linux源码,加入新驱动文件后,记得修改dirvers/makefile + drivers/Kconfig后,再重新编译linux内核,生成新的ko文件。
  4. 每次将ko文件拷贝进initramfs/x86_64_busybox目录后,记得重新生成cpio文件。

添加自定义设备

  1. 在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)
  1. 在hw/timer目录下的Makefile.objs文件中添加如下一行代码:
obj-$(CONFIG_PCI) += pci-sample-timer.o
  1. 重新编译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
  1. 修改完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

注意:

  1. 将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;
}
  1. 修改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);
    }
}*/

添加自定义设备驱动

  1. 在/linux-5.12.9/drivers目录下新建一个设备驱动目录hello,其中包含其中包含hello.cMakefileKconfig三个文件

(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
  1. 修改linux-5.12.9/drivers/ Makefile,新增如下一行:
obj-$(CONFIG_HELLO) += hello/
  1. 修改linux-5.12.9/drivers/Kconfig,新增如下一行:
source "drivers/hello/Kconfig"
  1. 重新编译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目录下,生成了如下文件,表示被编译成功。


  1. 将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
  1. 重新启动设备,并操作设备
~/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