qemu-kvm (QEMU2.0.0)代码分析

 

物理计算机由处理器,存储器,输入输出设备三大部件组成,虚拟机实际也是一台计算机,必然包括这三大组件,所以虚拟技术包括处理机虚拟化,存储器虚拟化,输入与输出设备虚拟化,这些虚拟化是通过软件虚拟还是硬件虚拟呢?
接下来看看具体虚拟化软件qemu-kvm是如何处理的?qemu是采用纯软件模拟的技术,kvm暂且认为采用硬件模拟技术, 可见qemu-kvm的虚拟机是
硬件辅助虚拟机,其中处理机虚拟化和存储器虚拟化采用硬件模拟技术,输入与设备采用软件模拟的。

虚拟机组成
实际上面所说计算机组成比较笼统,实际处理器,存储器,输入与设备种类繁多,
  
总线桥:  i440FX pci桥,PIIX3 ISA

    -   i440FX host PCI bridge and PIIX3 PCI to ISA bridge

  显卡: 5446 PCI VGA card or dummy VGA card (含Brochs VESA 扩展功能)

    -   Cirrus CLGD 5446 PCI VGA card or dummy VGA card with Bochs VESA extensions (hardware level, including all non standard modes).

 
鼠标和键盘: PS2 键盘、鼠标

    -   PS/2 mouse and keyboard

  IDE的硬盘和光驱

       -   2 PCI IDE interfaces with hard disk and CD-ROM support

  软盘
       -   Floppy disk

  PCI ISA 总线

       -   PCI and ISA network adapters

  串口

       -   Serial ports

  声卡 SoundBlaster 1370 AC97

       -   Creative SoundBlaster 16 sound card

       -   ENSONIQ AudioPCI ES1370 sound card
       -   Intel 82801AA AC97 Audio compatible sound card
       -   Intel HD Audio Controller and HDA codec
       -   Adlib(OPL2) - Yamaha YM3812 compatible chip
       -   Gravis Ultrasound GF1 sound card
       -   CS4231A compatible sound card

 UHCI USB总线控制器
       -   PCI UHCI USB controller and a virtual USB hub.

 最大支持 255cpu
       SMP is supported with up to 255 CPUs.

qemu-kvm
参数说明:
 -S -M rhel6.3.0 -enable-kvm  //
启用硬件辅助模拟,模拟的机器类型为rhel6.3.0,虚拟机默认不启动。(后面解释类型为rhel6.3.0大概配置)

//机器配置
-m 1111  //
内存 
-smp 1,sockets=1,cores=1,threads=1 //
处理器
-rtc base=localtime,clock=vm,driftfix=slew //
时钟
-device piix3-usb-uhci,id=usb,bus=
pci.0,addr=0x1.0x2 //usb控制器usb-uhci
-device usb-ehci,id=usb1,bus=
pci.0,addr=0x5 //usb控制器usb-ehci
-device virtio-serial-pci,id=
virtio-serial0,bus=pci.0,addr=0x6 //pci
-drive file=/mnt/local/4d4fdbb5-216a-44a4-afae-ea6e2a80b481,if=none,
id=drive-ide0-0-0
,format=qcow2,cache=writeback //ide硬盘(指明主IDE驱动)
-device
 ide-drive,bus=ide.0,unit=0,drive=drive-ide0-0-0,id=ide0-0-0,bootindex=2 //ide(指明主IDE设备)
-drive if=none,media=cdrom
,id=drive-ide0-1-0,readonly=on,format=raw,cache=writeback //ide光驱(指明从IDE驱动)
-device 
ide-drive,bus=ide.1,unit=0,drive=drive-ide0-1-0,id=ide0-1-0,bootindex=1  //ide(指明从IDE设备) 
-netdev tap,fd=23,
id=hostnet0 -device e1000,netdev=hostnet0
,id=net0,mac=02:00:77:a2:00:0d,bus=pci.0,addr=0x3  //网卡
-chardev pty
,id=charserial0 -device isa-serial,chardev=charserial0,id=serial0 //伪终端
-chardev spicevmc
,id=charchannel0,name=vdagent -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=com.redhat.spice.0 //串口
-device usb-tablet,id=input0,bus=usb.0,port=1  //
触摸盘
-spice port=5902,addr=0.0.0.0,disable-ticketing //
远程显示
-vga qxl -global qxl-vga.vram_size=67108864 -device AC97,id=sound0,bus=
pci.0,addr=0x4  //显卡
-device virtio-balloon-pci,id=balloon0,
bus=pci.0,addr=0x7 -readconfig /etc/qemu/ich9-ehci-uhci.cfg -chardev spicevmc,name=usbredir,id=usbredirchardev1
-device usb-redir,chardev=usbredirchardev1,id=usbredirdev1,bus=ehci.0,debug=3 -chardev spicevmc,name=usbredir,id=usbredirchardev2 //usb
设备重定向
-device usb-redir,chardev=usbredirchardev2,id=usbredirdev2,bus=ehci.0,debug=3 -chardev spicevmc,name=usbredir,id=usbredirchardev3
-device usb-redir,chardev=usbredirchardev3,id=usbredirdev3,bus=ehci.0,debug=3

虚拟机流程

1.
硬件虚拟设备初始化
main-->
configure_accelerator->kvm_init

/*打开KVM管理接口*/
s->fd = qemu_open("/dev/kvm", O_RDWR);

/*创建VM*/
ret = kvm_ioctl(s, KVM_CREATE_VM, type);

/*记录VM的文件描述符*/
s->vmfd = ret;

/*建立ARCH相关*/
ret = kvm_arch_init(s);

/*创建VM的中断设备,在内核实现*/
ret = kvm_irqchip_create(s); -> ret = kvm_vm_ioctl(s, KVM_CREATE_IRQCHIP);

/*保存创建好的虚拟机*/
kvm_state = s;

     /*
     * 注册物理内存和IO内存的各种回调函数,并为虚拟机加入物理内存和IO内存,
     * 内存保存在address_spaces中,在address_space_init中加入,每个节点为AddressSpace
     * 
     * 各个AddressSpace的定义在如下路径完成
     * 物理内存和IO内存
     * main -> cpu_exec_init_all -> memory_map_init -> address_space_init(system_memory) 和 address_space_init(system_io)-> TAILQ_INSERT_TAIL(&address_spaces, as, address_spaces_link);
     *
     * 这里需要注意的是,main中先调用的configure_accelerator后调用的cpu_exec_init_all,也就是说,在上面初始化的时候,address_space中根本没有节点,所以也就没有添加内存
     * 那么什么时候添加的内存呢?是在address_space_init中,加入了memory region的根
     * address_space_init-> memory_region_transaction_commit->address_space_update_topology->address_space_update_topology_pass->MEMORY_LISTENER_UPDATE_REGION(frnew, as, Forward, region_add);
     * 然后每次调用memory_region_transaction_commit都会添加相应的内存到KVM内核
     *
     * 在memory_listener_register就会调用listener_add_address_space加入相关内存到KVM的memslots
     * 添加路径如下:
     * kvm_memory_listener->kvm_region_add->kvm_set_phys_mem->kvm_set_user_memory_region->kvm_vm_ioctl(s, KVM_SET_USER_MEMORY_REGION, &mem);
     */
memory_listener_register(&kvm_memory_listener, &address_space_memory);
memory_listener_register(&kvm_io_listener, &address_space_io);


由上面代码可见,硬件虚拟技术为处理器,内存,中断

2.
启动虚拟机
1>
初始化所有设备
module_call_init(MODULE_INIT_MACHINE);
块驱动,外围设备,机器初始化...
#define block_init(function) module_init(function, MODULE_INIT_BLOCK)
#define qapi_init(function) module_init(function, MODULE_INIT_QAPI)
#define type_init(function) module_init(function, MODULE_INIT_QOM)

#define machine_init(function) module_init(function, MODULE_INIT_MACHINE)

2>
根据-M rhel6.3.0类型启动虚拟机。

machine_init(pc_machine_init); -> qemu_register_machine(&pc_i440fx_machine_v2_0);

pc_i440fx_machine_v2_0 .init = pc_init_pci 

main--> machine->init(ram_size, boot_devices,kernel_filename, kernel_cmdline, initrd_filename, cpu_model);就是调用pc_init_pci

1
)创建vcpu

type_init(x86_cpu_register_types)

x86_cpu_register_types -> type_register_static(&host_x86_cpu_type_info); -> .class_init = x86_cpu_common_class_init -> dc->realize = x86_cpu_realizefn 

x86_cpu_realizefn -> qemu_init_vcpu -> qemu_kvm_start_vcpu -> qemu_kvm_cpu_thread_fn -> kvm_init_vcpu -> ret = kvm_vm_ioctl(s, KVM_CREATE_VCPU, (void *)kvm_arch_vcpu_id(cpu));

初始化vcpu

qemu_kvm_cpu_thread_fn -> kvm_init_vcpu->kvm_arch_init_vcpu

2) 创建内存
    mmap_size = kvm_ioctl(kvm_state, KVM_GET_VCPU_MMAP_SIZE, 0);
   env->kvm_run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, env->kvm_fd,


3)
启动虚拟机,运行系统
qemu_kvm_cpu_thread_fn -> kvm_cpu_exec -> run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0);

3
处理i/o
 
当虚拟机运行过程,有输入输出操作,进行i/o设备模拟。i/o设备分为端口和内存映射两种 
  case KVM_EXIT_IO:
  r = kvm_handle_io(run->io.port,
  case KVM_EXIT_MMIO:
  r = 
cpu_physical_memory_rw
      

qemu-kvm虚拟机线程

1. qemu-kvm线程工作过程:
1
)启动一个子线程,创建初始化vcpu,主线程等待
2
)子线程创建初始化vcpu完毕,子线程等待,并等候通知主线程运行
3
)主线程继续初始化虚拟机工作,初始化完成,通知子线程继续运行
4
)子线程继续启动虚拟机kvm_run,主线程执行select交互处理

qemu_init_vcpu->qemu_thread_create->qemu_cond_wait()//等待cpu->created, 

qemu_kvm_cpu_thread_fn在kvm_init_vcpu后会设定cpu->created = true;创建的主线程返回


2.
一个虚拟机进程包含着几个线程
1
)启动虚拟机
2
ps -eLf | grep qemu-kvm

3.
每个线程作用是什么呢?
gdb -p 28678
(gdb) info thread
  3 Thread 0x7f0972706700 (LWP 28697)  0x00007f0977426257 in ioctl () from /lib64/libc.so.6
  2 Thread 0x7f092b5fe700 (LWP 28698)  0x00007f09790697bb in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
* 1 Thread 0x7f0979674940 (LWP 28678)  0x00007f0977426d03 in select () from /lib64/libc.so.6

(gdb) thread 1
[Switching to thread 1 (Thread 0x7f0979674940 (LWP 28678))]#0  0x00007f0977426d03 in select () from /lib64/libc.so.6
(gdb) bt
#0  0x00007f0977426d03 in select () from /lib64/libc.so.6
#1  0x000000000040c3b0 in main_loop_wait (timeout=1000) at /usr/src/debug/qemu-kvm-0.12.1.2/vl.c:4017
#2  0x000000000042aefa in kvm_main_loop () at /usr/src/debug/qemu-kvm-0.12.1.2/qemu-kvm.c:2225
#3  0x000000000040de85 in 
main_loop (argc=, argv=, envp=)
    at /usr/src/debug/qemu-kvm-0.12.1.2/vl.c:4234
#4  main (argc=, argv=, envp=)
    at /usr/src/debug/qemu-kvm-0.12.1.2/vl.c:6470
这个线程是主线程,这个线程loop循环,循环操作select.实际就是查看有无读写文件描述符,有的话进行读写操作呗。

(gdb) thread 3
[Switching to thread 3 (Thread 0x7f0972706700 (LWP 28697))]#0  0x00007f0977426257 in ioctl () from /lib64/libc.so.6
(gdb) bt
#0  0x00007f0977426257 in ioctl () from /lib64/libc.so.6
#1  0x000000000042c7cf in 
kvm_run 
(env=0x1a6fde0) at /usr/src/debug/qemu-kvm-0.12.1.2/qemu-kvm.c:989
#2  0x000000000042cc59 in kvm_cpu_exec (env=) at /usr/src/debug/qemu-kvm-0.12.1.2/qemu-kvm.c:1730
#3  0x000000000042da9e in kvm_main_loop_cpu (_env=0x1a6fde0) at /usr/src/debug/qemu-kvm-0.12.1.2/qemu-kvm.c:1991
#4  ap_main_loop (_env=0x1a6fde0) at /usr/src/debug/qemu-kvm-0.12.1.2/qemu-kvm.c:2041
#5  0x00007f0979065851 in start_thread () from /lib64/libpthread.so.0
#6  0x00007f097742e11d in clone () from /lib64/libc.so.6
这个子线程,VCPU线程, kvm_run启动和运行虚拟机,

 

(gdb) thread 2
[Switching to thread 2 (Thread 0x7f092b5fe700 (LWP 28698))]#0  0x00007f09790697bb in pthread_cond_timedwait@@GLIBC_2.3.2 ()
   from /lib64/libpthread.so.0
(gdb) bt
#0  0x00007f09790697bb in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1  0x00000000004856a3 in cond_timedwait (unused=) at posix-aio-compat.c:104
#2  aio_thread (unused=) at posix-aio-compat.c:325
#3  0x00007f0979065851 in start_thread () from /lib64/libpthread.so.0
#4  0x00007f097742e11d in clone () from /lib64/libc.so.6
(gdb)
这个子线程,异步进行i/o操作,主要针对磁盘映像操作(block drive

qemu-kvm
虚拟机进程的字符设备处理(char drive)
1.
查看采用哪些字符设备
ps aux | grep qemu-kvm | grep "
-chardev" --color=auto
-chardev 
socket,id=charmonitor,path=/var/lib/libvirt/qemu/ha1.monitor,server,nowait -mon chardev=charmonitor,id=monitor,mode=control //QEMUlibvirt通信使用
-chardev 
pty,id=charserial0 -device isa-serial,chardev=charserial0,id=serial0 //PTY串口
第一个unix套接字,做为虚拟机进程与libvirt(或者进程)通信使用,例如输入命令等
第二个pty伪终端, 模拟串口使用

2.
代码查处字符设备
1
)解析命令行参数,查找所有的 -chardev
for(;;) {
   case QEMU_OPTION_chardev:
   opts = qemu_opts_parse(&qemu_chardev_opts, optarg, 1);
}
2.
初始化相应字符设备驱动
1
)解析
    if (qemu_opts_foreach(qemu_find_opts("chardev"), chardev_init_func, NULL, 1) != 0)
        exit(1);


    chardev_init_func-> qemu_chr_new_from_opts -> qmp_chardev_add 

    chardev_init_func -> qemu_chr_new_from_opts -> chr = cd->open(opts);


2
)初始化
  
register_char_driver("socket", qemu_chr_open_socket);

  register_char_driver_qapi("pty", CHARDEV_BACKEND_KIND_PTY, NULL);
  

qmp_chardev_add {
#ifdef HAVE_CHARDEV_TTY
    case CHARDEV_BACKEND_KIND_PTY:
        chr = qemu_chr_open_pty(id, ret);
        break;
#endif

 }

3)打开字符设备

chr = cd->open(opts);

static CharDriverState *qemu_chr_open_socket(QemuOpts *opts){
   is_listen      = qemu_opt_get_bool(opts, "server", 0);
    is_waitconnect = qemu_opt_get_bool(opts, "wait", 1);
    is_telnet      = qemu_opt_get_bool(opts, "telnet", 0);
    do_nodelay     = !qemu_opt_get_bool(opts, "delay", 1);
    is_unix        = qemu_opt_get(opts, "path") != NULL;
   fd = unix_listen_opts(opts); //
创建unix套接字
   socket_set_nonblock(fd); //
设置描述符为非阻塞
   
chr = qemu_chr_open_socket_fd(fd, do_nodelay, is_listen, is_telnet, is_waitconnect, &local_err);//为主线程select函数,读描述进行设置

 }

4.qemu-kvm
monitor如何使用chardev
  if (qemu_opts_foreach(&qemu_mon_opts, mon_init_func, NULL, 1) != 0)
        exit(1);
  
解析字符设备,为监控使用
static int mon_init_func(QemuOpts *opts, void *opaque)
 chardev = qemu_opt_get(opts, "chardev");
    chr = qemu_chr_find(chardev);
    if (chr == NULL) {
        fprintf(stderr, "chardev \"%s\" not found\n", chardev);
        exit(1);
    }
    monitor_init(chr, flags);

}

5 系统关机,关闭电源等操作事件的通知

void qemu_system_shutdown_request(void)
{
    shutdown_requested = 1;
    qemu_notify_event();
}

void qemu_system_powerdown_request(void)
{
    powerdown_requested = 1;
    qemu_notify_event();
}

当系统关机,关闭电源等操作,以通过事件通知方式,通知主线程。 由于主线程进行select可能短时间阻塞,如果进行关机,关闭电源操作,可以解除select阻塞,进行处理这些操作。

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值