精华内容
下载资源
问答
  • qemu启动测试器两款

    2018-09-11 19:05:02
    qemu制作的两款启动测试器,可以测试u盘的pe,光盘镜像。软硬盘镜像等启动操作系统
  • qemu启动Busybox和内核

    千次阅读 2017-02-23 13:30:08
    尝试用qemu启动busybox以及内核。方便学习内核的代码和内部的流程。 主机x86_64 linux-3.13.1.tar.gz qemu-2.8.0.tar.xz busybox-1.25.0.tar.bz2 1编译qemu cd qemu-2.8.0 ./configure make -j4 sudo make ...

    尝试用qemu启动busybox以及内核。方便学习内核的代码和内部的流程。

    主机x86_64



    linux-3.13.1.tar.gz

    qemu-2.8.0.tar.xz

    busybox-1.25.0.tar.bz2


    1编译qemu

    cd qemu-2.8.0

    ./configure

    make -j4

    sudo make install


    2 编译linuxkernel

    make defconfig

    make

    得到bzimage


    3编译busybox

    make menuconfig

    选择静态编译

    Busyboxsetting->build options->Build Busybox as a static binary

    保存

    make -j4


    4 构建根文件系统

    dd if=/dev/zero of=rootfs.img BS=1M count=10

    mkdir tempmount

    mkfs.ext4 -F -L linuxroot rootfs.img

    sudo mount -o loop rootfs.img tempmount

    进入Busybox的编译目录

    sudo make install CONFIG_PREFIX=/path/to/tempmount

    sudo umount tempmount



    5启动整个busybox

    qemu-system-x86_64  -kernel bzImage -hda rootfs.img -append "root=/dev/sda  rootfstype=ext4 init=/bin/ash"



    为了能够在文件或者主机终端中看到qemu的输出。

    1 qemu执行命令中需要添加 

    -serial stdio 或者-serial file:/home/a.txt  将qemu串口输出到 本地终端或者文件。

    2内核命令行中需要添加

    console=tty console=ttyAMA0 console=ttyS0


    qemu-system-x86_64  -kernel bzImage -hda rootfs.img  -serial stdio -append "root=/dev/sda console=tty console=ttyAMA0 console=ttyS0  rootfstype=ext4 init=/bin/ash"




    展开全文
  • QEMU启动过程

    千次阅读 2017-03-27 16:47:08
    虚拟机启动过程 第一步,获取到kvm句柄 kvmfd = open("/dev/kvm", O_RDWR); 第二步,创建虚拟机,获取到虚拟机句柄。 vmfd = ioctl(kvmfd, KVM_CREATE_VM, 0); 第三步,为虚拟机映射内存,还有其他的PCI,信号处理...

    虚拟机启动过程

    第一步,获取到kvm句柄
    kvmfd = open("/dev/kvm", O_RDWR);
    第二步,创建虚拟机,获取到虚拟机句柄。
    vmfd = ioctl(kvmfd, KVM_CREATE_VM, 0);
    第三步,为虚拟机映射内存,还有其他的PCI,信号处理的初始化。
    ioctl(kvmfd, KVM_SET_USER_MEMORY_REGION, &mem);
    第四步,将虚拟机镜像映射到内存,相当于物理机的boot过程,把镜像映射到内存。
    第五步,创建vCPU,并为vCPU分配内存空间。
    ioctl(kvmfd, KVM_CREATE_VCPU, vcpuid);
    vcpu->kvm_run_mmap_size = ioctl(kvm->dev_fd, KVM_GET_VCPU_MMAP_SIZE, 0);
    第五步,创建vCPU个数的线程并运行虚拟机。
    ioctl(kvm->vcpus->vcpu_fd, KVM_RUN, 0);
    第六步,线程进入循环,并捕获虚拟机退出原因,做相应的处理。
    这里的退出并不一定是虚拟机关机,虚拟机如果遇到IO操作,访问硬件设备,缺页中断等都会退出执行,退出执行可以理解为将CPU执行上下文返回到QEMU
    open("/dev/kvm")
    ioctl(KVM_CREATE_VM)
    ioctl(KVM_CREATE_VCPU)
    for (;;) {
         ioctl(KVM_RUN)
         switch (exit_reason) {
         case KVM_EXIT_IO:  /* ... */
         case KVM_EXIT_HLT: /* ... */
         }
    }

    关于KVM_CREATE_VM参数的描述,创建的VM是没有cpu和内存的,需要QEMU进程利用mmap系统调用映射一块内存给VM的描述符,其实也就是给VM创建内存的过程。

    KVM ioctl接口文档

    先来一个KVM API开胃菜

    下面是一个KVM的简单demo,其目的在于加载 code 并使用KVM运行起来.
    这是一个at&t的8086汇编,.code16表示他是一个16位的,当然直接运行是运行不起来的,为了让他运行起来,我们可以用KVM提供的API,将这个程序看做一个最简单的操作系统,让其运行起来。
    这个汇编的作用是输出al寄存器的值到0x3f8端口。对于x86架构来说,通过IN/OUT指令访问。PC架构一共有65536个8bit的I/O端口,组成64KI/O地址空间,编号从0~0xFFFF。连续两个8bit的端口可以组成一个16bit的端口,连续4个组成一个32bit的端口。I/O地址空间和CPU的物理地址空间是两个不同的概念,例如I/O地址空间为64K,一个32bit的CPU物理地址空间是4G。
    最终程序理想的输出应该是,al,bl的值后面KVM初始化的时候有赋值。
    4\n (并不直接输出\n,而是换了一行),hlt 指令表示虚拟机退出

    .globl _start
        .code16
    _start:
        mov $0x3f8, %dx
        add %bl, %al
        add $'0', %al
        out %al, (%dx)
        mov $'\n', %al
        out %al, (%dx)
        hlt

    我们编译一下这个汇编,得到一个 Bin.bin 的二进制文件

    as -32 bin.S -o bin.o
    ld -m elf_i386 --oformat binary -N -e _start -Ttext 0x10000 -o Bin.bin bin.o

    查看一下二进制格式

    ➜  demo1 hexdump -C bin.bin
    00000000  ba f8 03 00 d8 04 30 ee  b0 0a ee f4              |......0.....|
    0000000c
    对应了下面的code数组,这样直接加载字节码就不需要再从文件加载了
        const uint8_t code[] = {
            0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */
            0x00, 0xd8,       /* add %bl, %al */
            0x04, '0',        /* add $'0', %al */
            0xee,             /* out %al, (%dx) */
            0xb0, '\n',       /* mov $'\n', %al */
            0xee,             /* out %al, (%dx) */
            0xf4,             /* hlt */
        };
    #include <err.h>
    #include <fcntl.h>
    #include <linux/kvm.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <sys/mman.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    
    int main(void)
    {
        int kvm, vmfd, vcpufd, ret;
        const uint8_t code[] = {
            0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */
            0x00, 0xd8,       /* add %bl, %al */
            0x04, '0',        /* add $'0', %al */
            0xee,             /* out %al, (%dx) */
            0xb0, '\n',       /* mov $'\n', %al */
            0xee,             /* out %al, (%dx) */
            0xf4,             /* hlt */
        };
        uint8_t *mem;
        struct kvm_sregs sregs;
        size_t mmap_size;
        struct kvm_run *run;
        
        // 获取 kvm 句柄
        kvm = open("/dev/kvm", O_RDWR | O_CLOEXEC);
        if (kvm == -1)
            err(1, "/dev/kvm");
    
        // 确保是正确的 API 版本
        ret = ioctl(kvm, KVM_GET_API_VERSION, NULL);
        if (ret == -1)
            err(1, "KVM_GET_API_VERSION");
        if (ret != 12)
            errx(1, "KVM_GET_API_VERSION %d, expected 12", ret);
        
        // 创建一虚拟机
        vmfd = ioctl(kvm, KVM_CREATE_VM, (unsigned long)0);
        if (vmfd == -1)
            err(1, "KVM_CREATE_VM");
        
        // 为这个虚拟机申请内存,并将代码(镜像)加载到虚拟机内存中
        mem = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
        if (!mem)
            err(1, "allocating guest memory");
        memcpy(mem, code, sizeof(code));
    
        // 为什么从 0x1000 开始呢,因为页表空间的前4K是留给页表目录
        struct kvm_userspace_memory_region region = {
            .slot = 0,
            .guest_phys_addr = 0x1000,
            .memory_size = 0x1000,
            .userspace_addr = (uint64_t)mem,
        };
        // 设置 KVM 的内存区域
        ret = ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &region);
        if (ret == -1)
            err(1, "KVM_SET_USER_MEMORY_REGION");
        
        // 创建虚拟CPU
        vcpufd = ioctl(vmfd, KVM_CREATE_VCPU, (unsigned long)0);
        if (vcpufd == -1)
            err(1, "KVM_CREATE_VCPU");
    
        // 获取 KVM 运行时结构的大小
        ret = ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, NULL);
        if (ret == -1)
            err(1, "KVM_GET_VCPU_MMAP_SIZE");
        mmap_size = ret;
        if (mmap_size < sizeof(*run))
            errx(1, "KVM_GET_VCPU_MMAP_SIZE unexpectedly small");
        // 将 kvm run 与 vcpu 做关联,这样能够获取到kvm的运行时信息
        run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpufd, 0);
        if (!run)
            err(1, "mmap vcpu");
    
        // 获取特殊寄存器
        ret = ioctl(vcpufd, KVM_GET_SREGS, &sregs);
        if (ret == -1)
            err(1, "KVM_GET_SREGS");
        // 设置代码段为从地址0处开始,我们的代码被加载到了0x0000的起始位置
        sregs.cs.base = 0;
        sregs.cs.selector = 0;
        // KVM_SET_SREGS 设置特殊寄存器
        ret = ioctl(vcpufd, KVM_SET_SREGS, &sregs);
        if (ret == -1)
            err(1, "KVM_SET_SREGS");
    
        
        // 设置代码的入口地址,相当于32位main函数的地址,这里16位汇编都是由0x1000处开始。
        // 如果是正式的镜像,那么rip的值应该是类似引导扇区加载进来的指令
        struct kvm_regs regs = {
            .rip = 0x1000,
            .rax = 2,    // 设置 ax 寄存器初始值为 2
            .rbx = 2,    // 同理
            .rflags = 0x2,   // 初始化flags寄存器,x86架构下需要设置,否则会粗错
        };
        ret = ioctl(vcpufd, KVM_SET_REGS, &regs);
        if (ret == -1)
            err(1, "KVM_SET_REGS");
    
        // 开始运行虚拟机,如果是qemu-kvm,会用一个线程来执行这个vCPU,并加载指令
        while (1) {
            // 开始运行虚拟机
            ret = ioctl(vcpufd, KVM_RUN, NULL);
            if (ret == -1)
                err(1, "KVM_RUN");
            // 获取虚拟机退出原因
            switch (run->exit_reason) {
            case KVM_EXIT_HLT:
                puts("KVM_EXIT_HLT");
                return 0;
            // 汇编调用了 out 指令,vmx 模式下不允许执行这个操作,所以
            // 将操作权切换到了宿主机,切换的时候会将上下文保存到VMCS寄存器
            // 后面CPU虚拟化会讲到这部分
            // 因为虚拟机的内存宿主机能够直接读取到,所以直接在宿主机上获取到
            // 虚拟机的输出(out指令),这也是后面PCI设备虚拟化的一个基础,DMA模式的PCI设备
            case KVM_EXIT_IO:
                if (run->io.direction == KVM_EXIT_IO_OUT && run->io.size == 1 && run->io.port == 0x3f8 && run->io.count == 1)
                    putchar(*(((char *)run) + run->io.data_offset));
                else
                    errx(1, "unhandled KVM_EXIT_IO");
                break;
            case KVM_EXIT_FAIL_ENTRY:
                errx(1, "KVM_EXIT_FAIL_ENTRY: hardware_entry_failure_reason = 0x%llx",
                     (unsigned long long)run->fail_entry.hardware_entry_failure_reason);
            case KVM_EXIT_INTERNAL_ERROR:
                errx(1, "KVM_EXIT_INTERNAL_ERROR: suberror = 0x%x", run->internal.suberror);
            default:
                errx(1, "exit_reason = 0x%x", run->exit_reason);
            }
        }
    }

    编译并运行这个demo

    gcc -g demo.c -o demo
    ➜  demo1 ./demo
    4
    KVM_EXIT_HLT

    另外一个简单的QEMU emulator demo

    IBM的徐同学有做过介绍,在此基础上我再详细介绍一下qemu-kvm的启动过程。

    .globl _start
        .code16
    _start:
        xorw %ax, %ax   # 将 ax 寄存器清零
    
    loop1:
        out %ax, $0x10  # 像 0x10 的端口输出 ax 的内容,at&t汇编的操作数和Intel的相反。
        inc %ax         # ax 值加一
        jmp loop1       # 继续循环

    这个汇编的作用就是一直不停的向0x10端口输出一字节的值。

    从main函数开始说起

    int main(int argc, char **argv) {
        int ret = 0;
        // 初始化kvm结构体
        struct kvm *kvm = kvm_init();
    
        if (kvm == NULL) {
            fprintf(stderr, "kvm init fauilt\n");
            return -1;
        }
        
        // 创建VM,并分配内存空间
        if (kvm_create_vm(kvm, RAM_SIZE) < 0) {
            fprintf(stderr, "create vm fault\n");
            return -1;
        }
        
        // 加载镜像
        load_binary(kvm);
    
        // only support one vcpu now
        kvm->vcpu_number = 1;
        // 创建执行现场
        kvm->vcpus = kvm_init_vcpu(kvm, 0, kvm_cpu_thread);
        
        // 启动虚拟机
        kvm_run_vm(kvm);
    
        kvm_clean_vm(kvm);
        kvm_clean_vcpu(kvm->vcpus);
        kvm_clean(kvm);
    }

    第一步,调用kvm_init() 初始化了 kvm 结构体。先来看看怎么定义一个简单的kvm。

    struct kvm {
       int dev_fd;              // /dev/kvm 的句柄
       int vm_fd;               // GUEST 的句柄
       __u64 ram_size;          // GUEST 的内存大小
       __u64 ram_start;         // GUEST 的内存起始地址,
                                // 这个地址是qemu emulator通过mmap映射的地址
       
       int kvm_version;         
       struct kvm_userspace_memory_region mem; // slot 内存结构,由用户空间填充、
                                               // 允许对guest的地址做分段。将多个slot组成线性地址
    
       struct vcpu *vcpus;      // vcpu 数组
       int vcpu_number;         // vcpu 个数
    };
    

    初始化 kvm 结构体。

    struct kvm *kvm_init(void) {
        struct kvm *kvm = malloc(sizeof(struct kvm));
        kvm->dev_fd = open(KVM_DEVICE, O_RDWR);  // 打开 /dev/kvm 获取 kvm 句柄
    
        if (kvm->dev_fd < 0) {
            perror("open kvm device fault: ");
            return NULL;
        }
    
        kvm->kvm_version = ioctl(kvm->dev_fd, KVM_GET_API_VERSION, 0);  // 获取 kvm API 版本
    
        return kvm;
    }

    第二步+第三步,创建虚拟机,获取到虚拟机句柄,并为其分配内存。

    int kvm_create_vm(struct kvm *kvm, int ram_size) {
        int ret = 0;
        // 调用 KVM_CREATE_KVM 接口获取 vm 句柄
        kvm->vm_fd = ioctl(kvm->dev_fd, KVM_CREATE_VM, 0);
    
        if (kvm->vm_fd < 0) {
            perror("can not create vm");
            return -1;
        }
    
        // 为 kvm 分配内存。通过系统调用.
        kvm->ram_size = ram_size;
        kvm->ram_start =  (__u64)mmap(NULL, kvm->ram_size, 
                    PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, 
                    -1, 0);
    
        if ((void *)kvm->ram_start == MAP_FAILED) {
            perror("can not mmap ram");
            return -1;
        }
        
        // kvm->mem 结构需要初始化后传递给 KVM_SET_USER_MEMORY_REGION 接口
        // 只有一个内存槽
        kvm->mem.slot = 0;
        // guest 物理内存起始地址
        kvm->mem.guest_phys_addr = 0;
        // 虚拟机内存大小
        kvm->mem.memory_size = kvm->ram_size;
        // 虚拟机内存在host上的用户空间地址,这里就是绑定内存给guest
        kvm->mem.userspace_addr = kvm->ram_start;
        
        // 调用 KVM_SET_USER_MEMORY_REGION 为虚拟机分配内存。
        ret = ioctl(kvm->vm_fd, KVM_SET_USER_MEMORY_REGION, &(kvm->mem));
    
        if (ret < 0) {
            perror("can not set user memory region");
            return ret;
        }
        return ret;
    }

    接下来就是load_binary把二进制文件load到虚拟机的内存中来,在第一个demo中我们是直接把字节码放到了内存中,这里模拟镜像加载步骤,把二进制文件加载到内存中。

    void load_binary(struct kvm *kvm) {
        int fd = open(BINARY_FILE, O_RDONLY);  // 打开这个二进制文件(镜像)
    
        if (fd < 0) {
            fprintf(stderr, "can not open binary file\n");
            exit(1);
        }
    
        int ret = 0;
        char *p = (char *)kvm->ram_start;
    
        while(1) {
            ret = read(fd, p, 4096);           // 将镜像内容加载到虚拟机的内存中
            if (ret <= 0) {
                break;
            }
            printf("read size: %d", ret);
            p += ret;
        }
    }

    加载完镜像后,需要初始化vCPU,以便能够运行镜像内容

    struct vcpu {
        int vcpu_id;                 // vCPU id,vCPU
        int vcpu_fd;                 // vCPU 句柄
        pthread_t vcpu_thread;       // vCPU 线程句柄
        struct kvm_run *kvm_run;     // KVM 运行时结构,也可以看做是上下文
        int kvm_run_mmap_size;       // 运行时结构大小
        struct kvm_regs regs;        // vCPU的寄存器
        struct kvm_sregs sregs;      // vCPU的特殊寄存器
        void *(*vcpu_thread_func)(void *);  // 线程执行函数
    };
    
    struct vcpu *kvm_init_vcpu(struct kvm *kvm, int vcpu_id, void *(*fn)(void *)) {
        // 申请vcpu结构
        struct vcpu *vcpu = malloc(sizeof(struct vcpu));
        // 只有一个 vCPU,所以这里只初始化一个
        vcpu->vcpu_id = 0;
        // 调用 KVM_CREATE_VCPU 获取 vCPU 句柄,并关联到kvm->vm_fd(由KVM_CREATE_VM返回)
        vcpu->vcpu_fd = ioctl(kvm->vm_fd, KVM_CREATE_VCPU, vcpu->vcpu_id);
    
        if (vcpu->vcpu_fd < 0) {
            perror("can not create vcpu");
            return NULL;
        }
        
        // 获取KVM运行时结构大小
        vcpu->kvm_run_mmap_size = ioctl(kvm->dev_fd, KVM_GET_VCPU_MMAP_SIZE, 0);
    
        if (vcpu->kvm_run_mmap_size < 0) {
            perror("can not get vcpu mmsize");
            return NULL;
        }
    
        printf("%d\n", vcpu->kvm_run_mmap_size);
        // 将 vcpu_fd 的内存映射给 vcpu->kvm_run结构。相当于一个关联操作
        // 以便能够在虚拟机退出的时候获取到vCPU的返回值等信息
        vcpu->kvm_run = mmap(NULL, vcpu->kvm_run_mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpu->vcpu_fd, 0);
    
        if (vcpu->kvm_run == MAP_FAILED) {
            perror("can not mmap kvm_run");
            return NULL;
        }
        
        // 设置线程执行函数
        vcpu->vcpu_thread_func = fn;
        return vcpu;
    }

    最后一步,以上工作就绪后,启动虚拟机。

    void kvm_run_vm(struct kvm *kvm) {
        int i = 0;
    
        for (i = 0; i < kvm->vcpu_number; i++) {
            // 启动线程执行 vcpu_thread_func 并将 kvm 结构作为参数传递给线程
            if (pthread_create(&(kvm->vcpus->vcpu_thread), (const pthread_attr_t *)NULL, kvm->vcpus[i].vcpu_thread_func, kvm) != 0) {
                perror("can not create kvm thread");
                exit(1);
            }
        }
    
        pthread_join(kvm->vcpus->vcpu_thread, NULL);
    }

    启动虚拟机其实就是创建线程,并执行相应的线程回调函数。
    线程回调函数在kvm_init_vcpu的时候传入

    void *kvm_cpu_thread(void *data) {
        // 获取参数
        struct kvm *kvm = (struct kvm *)data;
        int ret = 0;
        // 设置KVM的参数
        kvm_reset_vcpu(kvm->vcpus);
    
        while (1) {
            printf("KVM start run\n");
            // 启动虚拟机,此时的虚拟机已经有内存和CPU了,可以运行起来了。
            ret = ioctl(kvm->vcpus->vcpu_fd, KVM_RUN, 0);
        
            if (ret < 0) {
                fprintf(stderr, "KVM_RUN failed\n");
                exit(1);
            }
            
            // 前文 kvm_init_vcpu 函数中,将 kvm_run 关联了 vCPU 结构的内存
            // 所以这里虚拟机退出的时候,可以获取到 exit_reason,虚拟机退出原因
            switch (kvm->vcpus->kvm_run->exit_reason) {
            case KVM_EXIT_UNKNOWN:
                printf("KVM_EXIT_UNKNOWN\n");
                break;
            case KVM_EXIT_DEBUG:
                printf("KVM_EXIT_DEBUG\n");
                break;
            // 虚拟机执行了IO操作,虚拟机模式下的CPU会暂停虚拟机并
            // 把执行权交给emulator
            case KVM_EXIT_IO:
                printf("KVM_EXIT_IO\n");
                printf("out port: %d, data: %d\n", 
                    kvm->vcpus->kvm_run->io.port,  
                    *(int *)((char *)(kvm->vcpus->kvm_run) + kvm->vcpus->kvm_run->io.data_offset)
                    );
                sleep(1);
                break;
            // 虚拟机执行了memory map IO操作
            case KVM_EXIT_MMIO:
                printf("KVM_EXIT_MMIO\n");
                break;
            case KVM_EXIT_INTR:
                printf("KVM_EXIT_INTR\n");
                break;
            case KVM_EXIT_SHUTDOWN:
                printf("KVM_EXIT_SHUTDOWN\n");
                goto exit_kvm;
                break;
            default:
                printf("KVM PANIC\n");
                goto exit_kvm;
            }
        }
    
    exit_kvm:
        return 0;
    }
    
    void kvm_reset_vcpu (struct vcpu *vcpu) {
        if (ioctl(vcpu->vcpu_fd, KVM_GET_SREGS, &(vcpu->sregs)) < 0) {
            perror("can not get sregs\n");
            exit(1);
        }
        // #define CODE_START 0x1000
        /* sregs 结构体
            x86
            struct kvm_sregs {
                struct kvm_segment cs, ds, es, fs, gs, ss;
                struct kvm_segment tr, ldt;
                struct kvm_dtable gdt, idt;
                __u64 cr0, cr2, cr3, cr4, cr8;
                __u64 efer;
                __u64 apic_base;
                __u64 interrupt_bitmap[(KVM_NR_INTERRUPTS + 63) / 64];
            };
        */
        // cs 为code start寄存器,存放了程序的起始地址
        vcpu->sregs.cs.selector = CODE_START;
        vcpu->sregs.cs.base = CODE_START * 16;
        // ss 为堆栈寄存器,存放了堆栈的起始位置
        vcpu->sregs.ss.selector = CODE_START;
        vcpu->sregs.ss.base = CODE_START * 16;
        // ds 为数据段寄存器,存放了数据开始地址
        vcpu->sregs.ds.selector = CODE_START;
        vcpu->sregs.ds.base = CODE_START *16;
        // es 为附加段寄存器
        vcpu->sregs.es.selector = CODE_START;
        vcpu->sregs.es.base = CODE_START * 16;
        // fs, gs 同样为段寄存器
        vcpu->sregs.fs.selector = CODE_START;
        vcpu->sregs.fs.base = CODE_START * 16;
        vcpu->sregs.gs.selector = CODE_START;
        
        // 为vCPU设置以上寄存器的值
        if (ioctl(vcpu->vcpu_fd, KVM_SET_SREGS, &vcpu->sregs) < 0) {
            perror("can not set sregs");
            exit(1);
        }
        
        // 设置寄存器标志位
        vcpu->regs.rflags = 0x0000000000000002ULL;
        // rip 表示了程序的起始指针,地址为 0x0000000
        // 在加载镜像的时候,我们直接将binary读取到了虚拟机的内存起始位
        // 所以虚拟机开始的时候会直接运行binary
        vcpu->regs.rip = 0;
        // rsp 为堆栈顶
        vcpu->regs.rsp = 0xffffffff;
        // rbp 为堆栈底部
        vcpu->regs.rbp= 0;
    
        if (ioctl(vcpu->vcpu_fd, KVM_SET_REGS, &(vcpu->regs)) < 0) {
            perror("KVM SET REGS\n");
            exit(1);
        }
    }

    运行一下结果,可以看到当虚拟机执行了指令 out %ax, $0x10 的时候,会引起虚拟机的退出,这是CPU虚拟化里面将要介绍的特殊机制。
    宿主机获取到虚拟机退出的原因后,获取相应的输出。这里的步骤就类似于IO虚拟化,直接读取IO模块的内存,并输出结果。

    ➜  kvmsample git:(master) ✗ ./kvmsample
    read size: 712288
    KVM start run
    KVM_EXIT_IO
    out port: 16, data: 0
    KVM start run
    KVM_EXIT_IO
    out port: 16, data: 1
    KVM start run
    KVM_EXIT_IO
    out port: 16, data: 2
    KVM start run
    KVM_EXIT_IO
    out port: 16, data: 3
    KVM start run
    KVM_EXIT_IO
    out port: 16, data: 4
    ...
    

    总结

    虚拟机的启动过程基本上可以这么总结:
    创建kvm句柄->创建vm->分配内存->加载镜像到内存->启动线程执行KVM_RUN。从这个虚拟机的demo可以看出,虚拟机的内存是由宿主机通过mmap调用映射给虚拟机的,而vCPU是宿主机的一个线程,这个线程通过设置相应的vCPU的寄存器指定了虚拟机的程序加载地址后,开始运行虚拟机的指令,当虚拟机执行了IO操作后,CPU捕获到中断并把执行权又交回给宿主机。

    当然真实的qemu-kvm比这个复杂的多,包括设置很多IO设备的MMIO,设置信号处理等。

    下一篇将介绍CPU虚拟化相关知识。

    文章属原创,转载请注明出处 联系作者: Email:zhangbolinux@sina.com QQ:513364476
    转载地址:http://www.cnblogs.com/Bozh/p/5753379.html
    展开全文
  • qemuconf - 具有配置文件支持的简单 qemu 启动器 概要 qemuconf [ -q exec ]配置文件[-- qemu-options ...] qemuconf [ -v ] 描述 qemuconf使用从配置文件编译的参数执行 qemu。 选项 -q exec设置默认二进制文件。 ...
  • qemu 启动虚拟机 sheepdog

    千次阅读 2015-06-17 16:28:43
    sheepdog是一个专门为qemu设计的虚拟机分布式文件系统,采用完全对称的结构,没有元数据服务的中心节点,因此免除了单点故障的隐患.本文基于最新的0.7.0版本测试,介绍sheepdog的安装配置和使用. sheepdog的后端集群...

     

    前言

    sheepdog是一个专门为qemu设计的虚拟机分布式文件系统,采用完全对称的结构,没有元数据服务的中心节点,因此免除了单点故障的隐患.本文基于最新的0.7.0版本测试,介绍sheepdog的安装配置和使用.

    sheepdog的后端集群管理

    sheepdog的后端集群管理用于管理节点间的成员关系和消息通讯.目前可使用四种模式,分别是local driver(用于单机测试),corosync (默认), zookeeper and Accord.

    分为两种模型:

    – 全对称 (依赖 Corosync ,运行于 Sheepdog 的地址空间)

    ● 缺点:规模小 [<100]

    ● 优点:无需配置

    – 单独的控制集群 (依赖 Accord* 或者 Zookeeper ,运行于独立地址空间)

    ● 缺点:需要配置控制集群

    ● 优点:规模大 [>1000]

    本测试使用的就是默认的Corosync模式.

    安装前准备:

    先做一些系统设置

      
    echo " * soft nofile 65533 " >> / etc / security / limits . conf echo " * hard nofile 65533 " >> / etc / security / limits . conf yum install -y make automake autoconf gcc nss-devel wget git glib2

    安装epel源

      
    rpm -ivh http: // dl . fedoraproject . org / pub / epel / 6 / x86_64 / epel-release- 6 - 8 . noarch . rpm

    安装sheepdog

    1).安装corosync

    centos6自带的版本过旧,我们需要安装新版

      
    yum remove corosync corosynclib corosynclib-devel -y git clone git: // github . com / corosync / corosync . git cd corosync git checkout -b flatiron origin / flatiron ./ autogen . sh ./ configure --enable-nss make install

    2).安装sheepdog

      
    cd / home2 / install git clone git: // github . com / collie / sheepdog . git cd sheepdog ./ autogen . sh ./ configure make install

    配置corosync和sheepdog

      
    cd / etc / corosync cp corosync . conf . example corosync . conf

    编辑corosync.conf修改bindnetaddr成你的网段如192.168.1.0

      
    # Please read the corosync . conf 5 manual page compatibility: whitetank totem { version: 2 secauth: off threads : 0 # Note , fail_recv_const is only needed if you're # having problems with corosync crashing under # heavy sheepdog traffic . This crash is due to # delayed / resent / misordered multicast packets . # fail_recv_const: 5000 interface { ringnumber: 0 bindnetaddr: 192.168 . 1.0 mcastaddr: 226.94 . 1.1 mcastport: 5405 } } logging { fileline: off to_stderr: no to_logfile: yes to_syslog: yes # the pathname of the log file logfile: / var / log / cluster / corosync . log debug : off timestamp: on logger_subsys { subsys: AMF debug : off } } amf { mode : disabled }

    启动服务

      
    / etc / init . d / corosync start # 或通过命令启动 (/ var / lib / sheepdog是sheepdog使用的数据目录 , 你可以更改到其他 ) # sheep / var / lib / sheepdog

    sheepdog要求/var/lib/sheepdog目录所在的分区支持xattr

      
    mount -o remount , user_xattr /

    以上操作需要在每一台sheepdog节点执行

    格式化sheepdog集群并设置3个副本,只需要在其中一台节点执行

      
    collie cluster format --copies = 3

    查看节点状态

      
    collie node list

    至此sheepdog集群已经配置完毕

    使用qemu-img创建sheepdog磁盘镜像

    由于centos6自带的qemu1.2版本并不支持sheepdog,这里要编译新版的qemu.

    安装依赖的包

     
       
    yum install zlib-devel glib2-devel -y

    编译qemu

     
       
    wget http: // wiki . qemu-project . org / download / qemu- 1.5 . 2 . tar . bz2 tar jxvf qemu- 1.5 . 2 . tar . bz2 cd qemu- 1.5 . 2 ./ configure make ; make install

    创建磁盘

      
    / usr / local / bin / qemu-img create -f qcow2 sheepdog :MyFirstDisk 10G

    如果sheepdog运行在其他机器上,需要指定IP和端口

      
    / usr / local / bin / qemu-img create -f qcow2 sheepdog: 192.168 . 1.100 : 7000 :MyFirstDisk 10G

    列出sheepdog里的镜像

      
    collie vdi list

    将现有的镜像转换到sheepdog里

      
    qemu-img convert ./ mysystem . raw sheepdog :mysystem

    启动虚拟机

      
    qemu-system-x86_64 --enable-kvm -m 1024 -drive file = sheepdog :MyFirstDisk , cache = writeback -vnc : 15 -cdrom / data / CentOS- 6.4 -i386-minimal . iso

    其他用法

    1)创建快照

      
    qemu-img snapshot -c name sheepdog :MyFirstDisk

    快照创建后查看镜像列表

      
    collie vdi list name id size used shared creation time object id -------------------------------------------------------------------- Bob 0 2.0 GB 1.6 GB 0.0 MB 2010 - 03 - 23 16 : 16 80000 MyFirstDisk 0 256 GB 0.0 MB 0.0 MB 2010 - 03 - 23 16 : 21 c0000 s MyFirstDisk 1 256 GB 0.0 MB 0.0 MB 2010 - 03 - 23 16 : 16 40000

    用快照启动虚拟机

      
    qemu-system-x86_64 sheepdog :MyFirstDisk : 1

    2)磁盘克隆

      
    qemu-img create -b sheepdog :MyFirstDisk sheepdog :Disk2

    3)关闭sheepdog集群

      
    collie cluster shutdown

    此命令会关闭所有节点的进程

    4)使用fuse挂载sheepdog

    首先建立一个名为my_volume的卷

      
    echo my_volume > / var / lib / sheepdog / vdi / mount

    格式化为ext4并挂载

      
    mkfs . ext4 / var / lib / sheepdog / volume / my_volume mount -o loop / var / lib / sheepdog / volume / my_volume / mnt

    当连接的sheepdog机器宕机我们可以在线切换到另一台sheepdog

      
    echo ip :port > / var / lib / sheepdog / config / sheep_info

     

    展开全文
  • qemu启动文件重打包

    2021-07-22 11:43:40
    根据inittab文件的内容console::askfirst:-/bin/sh, 在启动脚本etc/init.d/rcS执行完后将在终端启动一个shell。Shell启动过程中会根据文件/etc/profile配置登陆环境。 所以需要添加profile文件,内容如下: USER="`...

    一、重打包

    1. 找到要解压的ramdisk.img文件。

      通过file命令可以查看ramdisk.img文件的类型:

           file ramdisk.img 
           ramdisk.img: gzip compressed data, from Unix
    
    由此,可知其文件类型为gzip类型的。解压的步骤如下:
    
    1》.先将ramdisk.img文件重命名为ramdisk.img.gzip文件
    2》.使用gunzip ramdisk.img.gz对其进行解压得到ramdisk.img文件。
    3》.使用cpio提取ramdisk.img文件中的内容
    
       新建一个temp目录:
    

    mkdir temp
      cd temp
      cpio -i -F …/ramdisk.img
    解压以后可以在temp目录下看到ramdisk.img中的内容。

    2. 修改相应要修改的文件后,打包成ramdisk.img文件的步骤如下:
    
      cd /temp
      find . |cpio -ov -H newc |gzip > ../ramdisk.img
      cd ..
      ls
     可以看到生成的ramdisk.img文件。
    

    编译busybox 生成initram

    二、静态文件系统

    构建根文件系统

    1 编译busybox

    cd $BUSY_BOX_PATH
    make menuconfig
    make install

    2 创建系统所需的目录。并给启动程序创建一个软连接

    cd _install
    mkdir -p proc sys dev etc etc/init.d lib tmp
    ln -sf linuxrc init

    3 创建配置文件

    cat > etc/init.d/rcS <<EOF
    #!/bin/sh
    mount -t proc none /proc
    mount -t sysfs none /sys
    /sbin/mdev -s
    ifconfig lo up
    EOF
    chmod +x etc/init.d/rcS

    cat > etc/inittab <<EOF
    #/etc/inittab
    ::sysinit:/etc/init.d/rcS
    ::askfirst:-/bin/sh
    ::ctrlaltdel:/sbin/reboot
    ::shutdown:/bin/umount -a -r
    EOF

    三、动态文件系统

    1.创建所需文件夹(根据FHS标准)
    mkdir dev etc home lib media mnt opt proc sys tmp var root

    1. 拷贝lib/下编译文件的系统库
      lib64目录下面有一库必要放在lib64下,否则init执行不起来。
      3.添加配置文件(即/etc下的文件)

    这里对照我们之前的分析步骤

    a.首先是inittab文件,内容如下:

    #/etc/inittab
    ::sysinit:/etc/init.d/rcS
    console::askfirst:-/bin/sh
    ::once:/usr/sbin/telnetd -l /bin/login
    ::ctrlaltdel:/sbin/reboot
    ::shutdown:/bin/umount -a -r
    
    b.添加init.d/rcS文件,内容如下:
    #!/bin/sh
    
    PATH=/sbin:/bin:/usr/sbin:/usr/bin
    export PATH
    
    #
    #Trap CTRL-C &c only in this shell so we can interrupt subprocesses.
    #
    
    mount -a
    mkdir -p /dev/pts
    mount -t devpts devpts /dev/pts
    echo /sbin/mdev > /proc/sys/kernel/hotplug
    mdev -s
    mkdir -p /var/lock
    
    hwclock -s
    feed_wdg &
    
    ifconfig lo 127.0.0.1
    ifconfig eth0 192.168.2.99
    
    /bin/hostname -F /etc/HOSTNAME
    

    c.添加HOSTNAME文件,内容就是代表主机名,可以随意定,如:
    Blue

    d.添加fstab文件:

    #device         mount-point     type    options         dump    fsck order
    proc            /proc           proc    defaults                0       0
    tmpfs           /tmp            tmpfs   defaults                0       0
    sysfs           /sys            sysfs   defaults                0       0
    tmpfs           /dev            tmpfs   defaults                0       0
    var             /dev            tmpfs   defaults                0       0
    ramfs           /dev            ramfs   defaults                0       0
    

    e.根据inittab文件的内容console::askfirst:-/bin/sh,
    在启动脚本etc/init.d/rcS执行完后将在终端启动一个shell。Shell启动过程中会根据文件/etc/profile配置登陆环境。
    所以需要添加profile文件,内容如下:

    USER="`id -un`"
    LOGNAME=$USER
    PS1='[\u@\h \W]# '      # 这个显示的是命令行下的主机名和用户名格式,如:[root@Blue etc]#
    PATH=$PATH
    HOSTNAME=`/bin/hostname`
    export USER LOGNAME PS1 PATH
    

    f.如果要配置正确的用户,必须具有两个文件:group和passwd

    group内容如下:
    root:x:0:root
    
    passwd内容如下:
    root::0:0:root:/:/bin/sh
    

    4.在/dev/下创建设备节点:
    mknod console c 5 1
    mknod null c 1 3

    5 使用cpio生成根文件系统
    find . | cpio -o --format=newc > $CUR_DIR/initramfs

    展开全文
  • 在aarch64主机中使用qemu启动虚机

    千次阅读 2016-11-25 20:35:19
    在aarch64主机CentOS7.2中使用qemu启动ubuntu16.04系统
  • qemu启动vm后,如何从host ssh连接vm? qemu启动参数 启动命令: ${cmd_qemu_system} --enable-kvm \ -machine type=q35,accel=kvm,kernel-irqchip=on -cpu host -smp 4 -m 16384 \ -serial mon:stdio -nographic...
  • 使用qemu启动iphone内核

    千次阅读 2019-06-28 01:22:55
    cd 到 iemu_prep_test3这个目录使用编译好的qemu(在~/Desktop/macos/xnu-qemu-arm64/aarch64-softmmu/qemu-system-aarch64目录) 开始启动xun内核的iphone /Users/test/Desktop/macos/xnu-qemu-arm64/aarch64-...
  • 常见编译后会出现类似如下的信息: AS arch/arm/boot/compressed/piggy.gzip.o LD arch/arm/boot/compressed/vmlinux OBJCOPY arch/arm/boot/zImage Kernel: arch/arm/boot/zImage is ready 如何用QEMU启动内核 ...
  • 1. 准备工作 ...但是想要动态分析服务器运行状态时,需要运行镜像的服务器,这时采用qemu-kvm技术可以达到目的。 环境 物理机win10 VM14pro虚拟机里面Ubuntu18 Img镜像 与Ubuntu18 连接 检查...
  • 在上一篇文章中在aarch64主机中使用qemu启动虚机,介绍了如何使用用户网络模式来启动虚拟机,但是这种模式有很多缺点,例如不能和其他虚机通信,不能使用ping测试通信等。所以在这篇中讲述如何使用tap模式来启动虚机...
  • qemu-install/bin/qemu-system-x86_64 -nographic -hda disk.img -kernel linux-$KVERSION/arch/x86_64/boot/bzImage -initrd my-initramfs.cpio -append "console=ttyS0" -s -S 使用gdb连接内核: 现在就...
  •  通过调用qemu-system-arm启动qemu的时候,最终会去加载从bios_qemu_tz_arm目录中编译出来的bios.bin文件,bios.bin镜像是由linux kernel image, OP-TEE os image, rootfs image以及bios_qemu_tz_arm目录中的其他.o...
  • qemu网络启动模式配置

    千次阅读 2014-05-14 16:15:52
     ...Qemu的tap初始化脚本缺省是/etc/qemu-ifup,内容如下: #!/bin/sh /sbin/ifconfig $1 192.168.0.11 如果不存在tun内核模块,可以自己编译。TUN模块的内核配置项位于: Device
  • 一,qemu 源码编译及安装: 1,首先当然是下载解压源码 里面有一个文件configure要有可执行权限 2,然后启动容器 sudo apt-get install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev libsdl1.2-dev...
  • Qemu模拟ARM(1) 前面已经安装并配置了编译链和qemu,现在可以用qemu来模拟arm平台了。 1. Hello, Qemu! 输入下面的代码: hello.c - hello.c #include int main() { printf("Hello, Qemu!\n");...
  • (四)QEMU仿真启动zynq7

    千次阅读 2018-08-22 17:02:11
    1、source ./oe-init-build-...2、MACHINE=qemu-zynq7 bitbake core-image-minimal 3、runqemu qemu-zynq7 runqemu - INFO - Assuming MACHINE = qemu-zynq7 runqemu - INFO - Running MACHINE=qemu-zynq7 bi...
  • 一:安装QEMU虚拟机 QEMU 官网下载安装包https://www.qemu.org/ 本文安装qemu-4.1.1 安装步骤如下: 1):安装库 sudo apt-get install zlib1g-dev sudo apt-get install libglib2.0-0 sudo apt-get install ...
  • qemu-kvm 命令行方式启动虚拟机

    万次阅读 2019-04-10 09:56:39
    对于KVM虚拟机,一般启动我们会用 virsh create ×××.xml 方式启动,其实底层还是调用了qemu-kvm命令行去执行,只不过virsh在启动前做了一些准备工作。有时候,我们需要用qemu-kvm方式去启动虚拟机,比如本人想做...
  • 将run_qemu.sh脚本符号链接到PATH某个位置例如ln -s ~/git/run_qemu/run_qemu.sh ~/bin/run_qemu.sh 注意:在任何符号链接解析之后,此仓库中的支持文件必须与脚本位于相同位置。 仅复制脚本本身将不起作用。 重击...
  • QEMU 的 UEFI BIOS 建造 准备 ...cd edk2 bios32.bin OvmfPkg/build.sh -a IA32 -n 4 cp Build/OvmfX64/DEBUG_GCC4?... qemu-system-x86_64 -bios /path/to/bios32.bin ....<other>.... 64 位系统中的 64 位 UEFI q
  • QEMU

    千次阅读 2015-05-30 17:58:42
    QENU 一个传奇 QEMU是一套由Fabrice Bellard所编写的模拟处理器的自由软件。它与Bochs,PearPC近似,但其具有某些后两者所不具备的特性,如高速度及跨平台的...QEMU启动那些为不同中央处理器编译的Linux程序。而Wine
  • qemu

    千次阅读 2013-12-21 18:59:53
    QEMU常见使用 使用vnc # qemu-kvm -m 512 -hda xp.img -net nic,macaddr=00:00:11:33:02:02 \ -net tap,ifname=tap2,script=/etc/init.d/qemu-ifup \ -localtime -vnc 172.16.70.30:2 --daemonize 上面让kvm...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,631
精华内容 4,252
关键字:

qemu启动bin