Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1940317
  • 博文数量: 1000
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 7921
  • 用 户 组: 普通用户
  • 注册时间: 2013-08-20 09:23
个人简介

storage R&D guy.

文章分类

全部博文(1000)

文章存档

2019年(5)

2017年(47)

2016年(38)

2015年(539)

2014年(193)

2013年(178)

分类: 服务器与存储

2015-04-22 09:59:58

转自:

 

 

 

 

 

 

 

目录

 

 

 

QEMU 的簡介請見 。

建置 QEMU

 

 預設會編譯成 PIE,這對舊版的 GDB 會有影響。強制開啟 IO thread。使用  編譯 QEMU 會出現以下訊息1)2)3)

In file included from /z/tmp/chenwj/qemu-1.0/user-exec.c:21:/z/tmp/chenwj/qemu-1.0/dyngen-exec.h:64:20: error:  register variables are not supportedregister CPUState *env asm(AREG0); ^1 warning and 1 error generated.

TCI 沒有用到 global  variable。

$ ../qemu-1.0/configure --target-list=i386-bsd-user --enable-tcg-interpreter --cc=clang \ --extra-cflags="-v" --disable-smartcard-nss

 

User Mode

 

目前 QEMU 將 glib2.0 列為必要,。4)

  1. 如果系統沒有安裝 glib,則自行安裝。

    $ apt-get install zlib1g-dev pkg-config libglib2.0-dev$ wget  tar xvf glib-2.28.0.tar.gz$ mkdir build; cd build$ ../glib-2.28.0/configure --prefix=$INSTALL$ make install

     

  2. 下載 QEMU。

    #  git clone git://git.qemu.org/qemu.git

     

  3. 修改 configure。

    # glib support probe#if $pkg_config --modversion gthread-2.0 > /dev/null 2>&1 ; then # 用 pkg-config 查詢 glib_cflags="-pthread -I$INSTALL/include/glib-2.0/ -I$INSTALL/lib/glib-2.0/include/" glib_libs="-L$INSTALL/lib -pthread -lgthread-2.0 -lrt -lglib-2.0" LIBS="$glib_libs $LIBS" libs_qga="$glib_libs $libs_qga"#else# echo "glib-2.0 required to compile QEMU"# exit 1#fi

     

  4. 編譯並安裝 QEMU。

    # config.log 可以用來檢查設定 QEMU 時哪裡出錯$ mkdir build; cd build$ ../qemu/configure --prefix=$INSTALL --target-list=i386-linux-user --enable-debug$ make; make install

     

  5. 如果 glib 是自行安裝。

    $ export LD_LIBRARY_PATH=$INSTALL/lib/# -m32 會編譯 32-bit 執行檔$ gcc -m32 hello.c -o hello$ qemu-i386 hello

     

 

SPARC

 

關於 SPARC V8 (32-bit)、V8PLUS (64-bit with 32-bit ABI) 和 V9 (64-bit) 架構的差異請見 。QEMU 對 v9 支援最完整 5)。64-bit with 32-bit ABI 和在 x86_64 上運行 x86 binary 不一樣。64-bit with 32-bit ABI 代表程式可以存取 64-bit 硬體資源,但其內存空間仍只侷限在 32-bit; x86 binary 運行在 x86_64 上不能存取 64-bit 硬體資源 6)

$ uname -aLinux sparc 2.6.37-rc5-git #1 SMP Tue Dec 21 17:03:53 CST 2010 sparc64 sun4v UltraSparc T2 (Niagara2) GNU/Linux$  /bin/ls/bin/ls: ELF 32-bit MSB executable, SPARC32PLUS, V8+ Required, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.9, stripped# 雖然機器是 64-bit,但是裝的是 32-bit OS# --sparc_cpu=v9 改用 sparc64-linux-gcc$ configure --prefix=$INSTALL --target-list=i386-linux-user --sparc_cpu=v8plus

 

PowerPC

 

因為 PS3 記憶體不足,可能導致 GCC 編譯 QEMU 失敗 7)8)。改以 debug 模式編譯。

  1. 註解 assert。

    --- qemu-0.13.0/tcg/tcg.c.orig 2010-10-16 04:56:09.000000000 +0800+++ qemu-0.13.0/tcg/tcg.c 2011-02-24 09:46:28.796899001 +0800@@ -1031,7 +1031,7 @@ def = &tcg_op_defs[op]; #if defined(CONFIG_DEBUG_TCG) - assert(!def->used);+ //assert(!def->used); def->used = 1; #endif nb_args = def->nb_iargs + def->nb_oargs;

     

  2. 以 debug 模式編譯,或是參考 9)

    $ configure --prefix=$INSTALL --target-list=i386-linux-user --enable-debug$ make install

     

  3. 拷貝 x86 函式庫供 powerpc 上的 qemu-i386 使用。函式庫必須放在以 lib 為名的目錄底下。

    $ mkdir $HOME/tools/lib$ cp /lib32/ld-linux.so.2 $HOME/tools/lib$ cp /lib32/libc.so.6 $HOME/tools/lib$ export LD_LIBRARY_PATH=$HOME/tools/lib$ qemu-i386 -L $HOME/tools hello

     

 

ARM

 

  1. 安裝交叉工具鏈。使用預編譯好的工具鏈。可以到  下載。或者使用  建立工具鏈。

    1. 下載並安裝 crosstool-ng。

      # 預設安裝在 $HOME/x-tools 下。# 注意運行 QEMU 平台的內核,這裡要選較其為舊的內核建立工具鏈。# 請記得將 crosstool.config 更名為 .config。$ wget  tar xvf crosstool-ng-1.9.2.tar.bz2$ cd crosstool-ng-1.9.2;$ ./configure --prefix=$INSTALL$ make install

       

    2. 建置交叉工具鏈。

      $ mkdir toolchain-build$ cp $INSTALL/lib/ct-ng-1.9.2/samples/arm-unknown-linux-gnueabi

      或是將 QEMU 終端導至標準輸出。

      $ qemu -hda linux-0.2.img -vnc 0.0.0.0:1 -monitor stdio

       

      Snapshot

       

      1. 先確定裝置格式支援 snapshot。raw 不支援 snapshot。。

        (qemu) info blockide0-hd0: removable=0 io-status=ok file=linux-0.2.img ro=0 drv=raw encrypted=0ide1-cd0: removable=1 locked=0 tray-open=0 io-status=ok [not inserted]floppy0: removable=1 locked=0 tray-open=0 [not inserted]sd0: removable=1 locked=0 tray-open=0 [not inserted](qemu) savevmDevice 'ide0-hd0' is writable but does not support snapshots.

         

      2. 用 qemu-img 轉換硬盤映像成 qcow2 格式。

        $ qemu-img convert -O qcow2 linux-0.2.img linux-0.2.qcow2

         

      3. 在 monitor 在下 savevm 把 RAM、device state 和 disk 存到當前使用的硬盤映像13)

        (qemu) savevm(qemu) loadvm(qemu) info snapshots

         

       

      Network

       

      QEMU 可以透過虛擬網卡連至 vlan,vlan 之間可以透過 slirp,socket,tap 或是 vde 連接。在 Documentation/Networking 提到的 virtual network device 即為虛擬網卡,network backend 負責將虛擬網卡送出的資料放到真實網路上,可以是 slirp,socket,tap 或是 vde。 裡面的 usermode network stack 即為 slirp,slirp 為預設網路後端,見下圖。QEMU 內部會啟動一個 DHCP 伺服器,宿主作業系統不可見。

      1. 存取宿主機的資料。

        # 在 host 執行底下命令。$ cd path/to/shared/files && python -m SimpleHTTPServer# 在 guest 執行底下命令。$ wget 

         

      2. 存取客戶機的資料。

        # 將 host TCP 連線 127.0.0.1:8000 導到 guest 內部 DHCP 預設 IP 埠 8000。$ qemu-system-x86_64 -m 1024M -hda ubuntu_x86_64.qcow2 \ -net user,hostfwd=tcp:127.0.0.1:8000-:8000 \ -vnc 0.0.0.0:1$ wget localhost:8000/README

         

      $ qemu-system-i386 -kernel bzImage-x86 -hda disk-x86.raw -append "root=/dev/sda" \ -net nic -net tap,ifname=tap0,script=no -vnc :3 -monitor stdio

       

      Tracing

       

      請見 docs/tracing.txt。

      $ wget  tar xvf qemu-0.14.0.tar.gz$ vim qemu-0.14.0/trace-events # qemu-malloc.cqemu_malloc(size_t size, void *ptr) "size %zu ptr %p"qemu_realloc(void *ptr, size_t size, void *newptr) "ptr %p size %zu newptr %p"qemu_free(void *ptr) "ptr %p" $ make install build; cd build;$ ../qemu-0.14.0/configure --prefix=/path/to/install --target-list=i386-linux-user --enable-trace-backend=stderr$ ./path/to/install/qemu-i386 hello

       

      Cross Compile

       

      1. 需要 zlib 的 ARM binary。

        $ wget  tar xvf zlib-1.2.5.tar.gz# export PATH=/PATH/TO/CROSSTOOL:$PATH$ CC=arm-none-linux-gnueabi-gcc ./configure \ --prefix=$INSTALL

         

      2. 執行底下腳本。

        #!/bin/bash SOURCE_DIR=../user-modeZLIB_PREBUILT=$HOME/x-toolsTARGET_LIST=arm-linux-userCROSS_PREFIX=arm-none-linux-gnueabi-CPU=armv7lCFLAGS=-I$ZLIB_PREBUILT/includeLDFLAGS=-L$ZLIB_PREBUILT/libPREFIX=`pwd`OPT_FLAGS="--disable-sdl"DEBUG=--disable-stripSTATIC=--static $SOURCE_DIR/configure --target-list=$TARGET_LIST \ --cpu=$CPU \ --extra-ldflags=$LDFLAGS \ --extra-cflags=$CFLAGS \ --cross-prefix=$CROSS_PREFIX \ --prefix=$PREFIX

         

       

      Debug & Testing

       

      $ git clone git://git.qemu.org/qemu-test.git$ cd qemu-test$ git submodule update --init$ make

      舊版 GDB 無法處理 PIE。

      $ ../qemu-1.0/configure --prefix=$INSTALL --target-list=i386-softmmu --enable-debug --disable-pie
      (gdb) handle SIG38 noprint pass# 定位感興趣的 guest pc 以便查找其在 code cache 中的位址。(gdb) b gen_intermediate_code_internal if tb->pc == 0x801a3bc# 此時已生成 host binary,查找 qemu.log。(gdb) b tb_find_fast

      QEMU 內部會註冊 SIGNAL 供自己使用,例如 IO thread 執行完畢之後會發送 SIGNAL 給 vcpu thread。

      1. 在 qemu_tcg_init_cpu_signals (cpus.c) 註冊 QEMU 內部使用的 SIGNAL。

        static void qemu_tcg_init_cpu_signals(void){ sigset_t set; struct sigaction sigact; memset(&sigact, 0, sizeof(sigact)); sigact.sa_handler = cpu_signal; sigaction(SIG_IPI, &sigact, NULL);  sigemptyset(&set); sigaddset(&set, SIG_IPI); pthread_sigmask(SIG_UNBLOCK, &set, NULL);}

         

      2. cpu_signal 會將執行從 code cache 拉出來,回到 QEMU 處理 singal。

        static void cpu_signal(int sig){ if (cpu_single_env) { cpu_exit(cpu_single_env); } exit_request = 1;}

         

       

      Internal

       

      QEMU 的簡介請見 。

      QEMU 中的 target 有兩種意義,

      1. 描述被模擬的硬體

      2. 對 TCG 而言,target 描述產生何種宿主硬體代碼

      QEMU 0.9 版以前使用 dyngen,對於 dyngen 的描述可以參考以下文件。QEMU 0.10 以後改採 TCG。可以從下載  源碼。

      請先閱讀 Documentation/GettingStartedDevelopers

      • QEMU does not have a high level design description document - only the source code tells the full story 8-)

      • HACKING 、CODING_STYLE 和 tcg/README。

      • QEMU 使用宏展開。編譯時加上 –extra-cflags="-save-temps" 可以得到宏展開之後的檔案 *.i。

      • 有些註解是 QEMU 0.9 以前的殘留。

      •  保留早期 QEMU 的 log。

      簡單的 patch 請送到 ,請見 。

       

      源碼目錄概觀

       

      • target-ARCH/

        • 定義被模擬硬體,反匯編

      • OS-user/

        • 作業系統相關

      • tcg/

        • 定義如何生成宿主平台指令

       

      Memory

       

      QEMU 1.0 將有變動。請見 HACKING、docs/memory.txt 或 。

      請見 memory.[ch]。

      • target_phys_addr_t (targphys.h) 代表客戶機物理地址空間。如果客戶機是 x86 開啟 PAE 的話,target_phys_addr_t 為 64 bit。

      • target_ulong 代表客戶機暫存器大小和虛擬地址空間。如果客戶機是 x86 開啟 PAE 的話,target_ulong 為 32 bit。

      • ram_addr_t (cpu-common.h) 代表宿主機虛擬地址空間。如果宿主機是 x86 的話,ram_addr_t 為 32 bit。

      • tb_page_addr_t (exec-all.h) 在 system mode 中被 typedef 成 ram_addr_t; 在 process mode 中被 typedef 成 abi_ulong,abi_ulong 又被 typedef 成 target_ulong。

      以宿主機作業系統的角度來看,QEMU 就是一般的使用者進程。QEMU 會在自己的虛擬位址空間分配內存給客戶機作業系統。以客戶機作業系統的角度來看,該塊內存即是客戶機作業系統的物理內存。該物理內存分成一般使用的內存和內存映射 IO。透過 cpu_register_physical_memory_offset (exec.c) 註冊。hw lp = l1_map + ((index >> V_L1_SHIFT) & (V_L1_SIZE - 1));  for (i = V_L1_SHIFT / L2_BITS - 1; i > 0; i--) { }}

       

    3. QEMU 基本上是以 page 為單位將該 page 所屬 TB 清掉。

      • stl_mmu (softmmu_template.h) → io_writel (softmmu_template.h) → notdirty_mem_writel (exec.c) → notdirty_mem_writel → tb_invalidate_phys_page_fast (exec.c) → tb_invalidate_phys_page_range (exec.c) → tb_phys_invalidate (exec.c) 會將屬於某虛擬頁面/客戶機物理頁面的 TB 清掉。

        void tb_invalidate_phys_page_range(tb_page_addr_t start, tb_page_addr_t end, ...){ p = page_find(start >> TARGET_PAGE_BITS);  tb = p->first_tb; while (tb != NULL) { }}

         

      • tb_invalidate_phys_page (exec.c) → tb_phys_invalidate (exec.c)。tb_invalidate_phys_page 僅在 process mode 有定義,用來處理 SMC。

        #if !defined(CONFIG_SOFTMMU)static void tb_invalidate_phys_page(tb_page_addr_t addr, unsigned long pc, void *puc){ addr &= TARGET_PAGE_MASK; p = page_find(addr >> TARGET_PAGE_BITS); // 取得該 page 的第一個 tb。 // tb 末兩位如果是 01 (1),代表 tb 對應的 guest bianry 跨 page。 tb = p->first_tb;  while (tb != NULL) { n = (long)tb & 3; // 取得 block chaing 的方向 tb = (TranslationBlock *)((long)tb & ~3); // 去掉末兩位的編碼,還原回真正的 tb tb_phys_invalidate(tb, addr); tb = tb->page_next[n]; // 取得 tb 所屬 page (或下一個 page) 的下一個 tb } p->first_tb = NULL; }

         

      • 最終會呼叫到 tb_phys_invalidate。

        void tb_phys_invalidate(TranslationBlock *tb, tb_page_addr_t page_addr){ // 將該 tb 從 tb_phys_hash 中移除 phys_pc = tb->page_addr[0] + (tb->pc & ~TARGET_PAGE_MASK); // virtual addr 中 page offset 的部分和 physical addr 一樣 h = tb_phys_hash_func(phys_pc); tb_remove(&tb_phys_hash[h], tb, offsetof(TranslationBlock, phys_hash_next));  // 將 tb 從相應的 PageDesc 中移除 if (tb->page_addr[0] != page_addr) { p = page_find(tb->page_addr[0] >> TARGET_PAGE_BITS); tb_page_remove(&p->first_tb, tb); invalidate_page_bitmap(p); } if (tb->page_addr[1] != -1 && tb->page_addr[1] != page_addr) { p = page_find(tb->page_addr[1] >> TARGET_PAGE_BITS); tb_page_remove(&p->first_tb, tb); invalidate_page_bitmap(p); }  tb_invalidated_flag = 1;  // 將 tb 從 tb_jmp_cache 移除 h = tb_jmp_cache_hash_func(tb->pc); // 因為每一個 env 都有一份自己的 tb_jmp_cache,全部清除。 for(env = first_cpu; env != NULL; env = env->next_cpu) { if (env->tb_jmp_cache[h] == tb) env->tb_jmp_cache[h] = NULL; }  // 處理 tb1 (tb -> tb1) tb_jmp_remove(tb, 0); tb_jmp_remove(tb, 1);  // 處理 tb1 (tb1 -> tb) tb1 = tb->jmp_first; for(;;) { n1 = (long)tb1 & 3; if (n1 == 2) // tb1 末兩位如果為 10 (2),代表 tb1 沒有跳至其它 tb break; tb1 = (TranslationBlock *)((long)tb1 & ~3); // 還原回原本的 tb1 tb2 = tb1->jmp_next[n1]; // 處理 tb2 (tb1 -> tb2) tb_reset_jump(tb1, n1); // 將 tb1 至其它的 tb 的 block chaining 打斷 (code cache) tb1->jmp_next[n1] = NULL; tb1 = tb2; } tb->jmp_first = (TranslationBlock *)((long)tb | 2); // 將 jmp_first 再次指向自己}

         

        • tb_jmp_remove 將該 tb 移出 circular lists?。

          static inline void tb_jmp_remove(TranslationBlock *tb, int n){ ptb = &tb->jmp_next[n]; // n (0 或 1) 指示 tb 下一個 block chaining 的方向 tb1 = *ptb; // 處理 tb1 (tb -> tb1) if (tb1) { for(;;) { tb1 = *ptb; n1 = (long)tb1 & 3; // 取出 tb1 末兩位 tb1 = (TranslationBlock *)((long)tb1 & ~3); 還原回原本的 tb1 if (n1 == n && tb1 == tb) // 代表 tb 沒有跳至其它 tb break; if (n1 == 2) { ptb = &tb1->jmp_first; // 代表沒有其它 tb 跳至 tb1 } else { ptb = &tb1->jmp_next[n1]; // 處理 tb2 (tb1 -> tb2) } } *ptb = tb->jmp_next[n];  tb->jmp_next[n] = NULL; }}

           

        • cpu_exec (cpu-exec.c) 會用到 tb_invalidated_flag。

          if (tb_invalidated_flag) { next_tb = 0; tb_invalidated_flag = 0;}

           

       

      MemoryRegion

       

      請見 memory.[ch] 和 doc/memory.txt。

      • target_phys_addr_t (targphys.h) 代表客戶機物理地址空間。如果客戶機是 x86 開啟 PAE 的話,target_phys_addr_t 為 64 bit。

      • target_ulong 代表客戶機暫存器大小和虛擬地址空間。如果客戶機是 x86 開啟 PAE 的話,target_ulong 為 32 bit。

      • ram_addr_t (cpu-common.h) 代表宿主機虛擬地址空間。如果宿主機是 x86 的話,ram_addr_t 為 32 bit。

      • tb_page_addr_t (exec-all.h) 在 system mode 中被 typedef 成 ram_addr_t; 在 process mode 中被 typedef 成 abi_ulong,abi_ulong 又被 typedef 成 target_ulong。

      • MemoryRegion (memory.h)。

        struct MemoryRegion { const MemoryRegionOps *ops; void *opaque; MemoryRegion *parent; Int128 size; target_phys_addr_t addr; void (*destructor)(MemoryRegion *mr); ram_addr_t ram_addr; bool subpage; bool terminates; bool readable; bool ram; bool readonly; bool enabled; bool rom_device; bool warning_printed; MemoryRegion *alias; target_phys_addr_t alias_offset; unsigned priority; bool may_overlap; QTAILQ_HEAD(subregions, MemoryRegion) subregions; QTAILQ_ENTRY(MemoryRegion) subregions_link; QTAILQ_HEAD(coalesced_ranges, CoalescedMemoryRange) coalesced; const char *name; uint8_t dirty_log_mask; unsigned ioeventfd_nb; MemoryRegionIoeventfd *ioeventfds;};

         

       

      System Mode

       

      Before QEMU 1.0

       

      以 QEMU 1.0 版以前,qemu (i386-softmmu) 為例,主要流程如下:

      main (vl.c) → init_clocks (qemu-timer.c) → module_call_init(MODULE_INIT_MACHINE) (module.c) → cpu_exec_init_all (初始 dynamic translator) (exec.c) → module_call_init(MODULE_INIT_DEVICE) (module.c) → machine→init (初始 machine) (vl.c) → main_loop (vl.c)

      • main_loop (vl.c) → qemu_main_loop_start (cpus.c) → cpu_exec_all (cpus.c) → main_loop_wait (vl.c)

        • cpu_exec_all (cpus.c) → qemu_clock_enable (qemu-timer.c) → qemu_alarm_pending (qemu-timer.c) → any_cpu_has_work (cpus.c)

        • cpu_exec_all (cpus.c) → qemu_cpu_exec (cpus.c) → cpu_x86_exec (cpu-exec.c) → tb_find_fast (cpu-exec.c) → tb_find_slow (cpu-exec.c)

          • tb_find_slow (cpu-exec.c) → get_page_addr_code (exec-all.h)

          • tb_find_slow (cpu-exec.c) → tb_gen_code (exec.c) → cpu_gen_code (translate-all.c) → gen_intermediate_code (target-i386/translate.c) → tcg_gen_code (tcg/tcg.c) → tcg_gen_code_common (tcg/tcg.c)

      • main_loop_wait (vl.c) 處理事件。

      check exception -> check interrupt (setjmp) -> tb_find_fast -> tb_exec -> check exception (check interrupt)main_loop_wait -> select (alarm)

      QEMU 會設置定時器 (qemu_signal_init),定時發出 SINGALARM 將 QEMU 從 code cache 拉出,去檢查 exception 或 interrupt。

      1. 進入點為 main.c (vl.c)。初始化環境。

        int main(int argc, char **argv, char **envp){ // QEMU 內部維護三個 clock,分別為: rt_clock,vm_clock 和 host_clock。 // 之後會根據命令行參數將 rtc_clock 設為前述三者之一。 init_clocks();  // module_call_init -> pc_machine_init -> qemu_register_machine // 會有預設 QEMUMachine,之後處理命令行參數時可被替換。 module_call_init(MODULE_INIT_MACHINE);    // 初始 QEMU 會用到的鎖以及使用的 signal number if (qemu_init_main_loop()) { fprintf(stderr, "qemu_init_main_loop failed\n"); exit(1); }  // alarm_timers 數組存放各種 timer 相對應的啟動/終止函式指針,以及其它資料。 // init_timer_alarm 依序呼叫 alarm_timers 數組中各個 timer 的啟動函式。 // dynticks_start_timer 會註冊 SIGALRM 相對應的信號句柄。 if (init_timer_alarm() < 0) { fprintf(stderr, "could not initialize alarm timer\n"); exit(1); } cpu_exec_init_all(tb_size * 1024 * 1024);  // drive_init_func 最後會呼叫到 paio_init 註冊 SIGUSR2 的信號句柄。 if (qemu_opts_foreach(&qemu_drive_opts, drive_init_func, &machine->use_scsi, 1) != 0) exit(1);  // 初始化設備 module_call_init(MODULE_INIT_DEVICE);  // 建立 QEMUMachine (hw/pc_piix.c) 並呼叫 machine->init (pc_init_pci) 初始化。 machine->init(ram_size, boot_devices, kernel_filename, kernel_cmdline, initrd_filename, cpu_model);    main_loop(); // 主要執行迴圈 quit_timers(); net_cleanup();  return 0;}

         

        • dynticks_start_timer 所註冊的 SIGALRM 的信號句柄是 host_alarm_handler。當宿主機作業系統發出 SIGALRM 時,host_alarm_handler 視情況會呼叫 qemu_notify_event。qemu_notify_event 用 cpu_exit 將 QEMU 從當前 code cache 中拉出來檢查 IO。關於 clock 請見 [Qemu-devel] Question on kvm_clock working ...

        • cpu_exec_init_all 的代碼如下:

          void cpu_exec_init_all(unsigned long tb_size){ cpu_gen_init(); code_gen_alloc(tb_size); code_gen_ptr = code_gen_buffer; page_init();#if !defined(CONFIG_USER_ONLY) io_mem_init(); // 註冊 MMIO 回掉函式#endif#if !defined(CONFIG_USER_ONLY) || !defined(CONFIG_USE_GUEST_BASE) tcg_prologue_init(&tcg_ctx);#endif}

           

        • pc_init_pci (hw/pc_piix.c) 呼叫 pc_init1 (hw/pc_piix.c) 進行 PC 機器的初始化。

          static void pc_init1(ram_addr_t ram_size, ...){ // 呼叫 pc_new_cpu (hw/pc.c) -> cpu_init/cpu_x86_init (target-i386/helper.c) 初始化 CPU。 pc_cpus_init(cpu_model); // 配置客戶機內存,載入 BIOS。 // 這部分在 QEMU 1.0 會用 memory API 改寫。 // http://lists.gnu.org/archive/html/qemu-devel/2011-07/msg02716.html pc_memory_init(ram_size, kernel_filename, kernel_cmdline, initrd_filename, &below_4g_mem_size, &above_4g_mem_size);  // 呼叫 qemu_allocate_irqs (hw/irq.c) 設置中斷處理常式。 cpu_irq = pc_allocate_cpu_irq();  pc_vga_init(pci_enabled? pci_bus: NULL);  pc_basic_device_init(isa_irq, &floppy_controller, &rtc_state); pc_vga_init(pci_enabled? pci_bus: NULL);  pc_basic_device_init(isa_irq, &floppy_controller, &rtc_state);}

           

          • void pc_memory_init(ram_addr_t ram_size, ...){ // 透過 qemu_ram_alloc 跟 QEMU 申請內存空間。QEMU 以 RAMBlock 為單位分配內存,並以 RAMList 管理所有 RAMBlock。 // QEMU 依命令行參數的不同,會從檔案或是跟宿主機作業系統申請 (posix_memalign) 配置空間。 // 回傳的是 RAMBlock 在 RAMList 的偏移量。 ram_addr = qemu_ram_alloc(NULL, "pc.ram", below_4g_mem_size + above_4g_mem_size); // 所有類型的 RAM (一般內存、內存映射 IO) 皆要透過 cpu_register_physical_memory 跟 QEMU 註冊。 // 將該資訊記錄在 PhysPageDesc。 cpu_register_physical_memory(0, 0xa0000, ram_addr); cpu_register_physical_memory(0x100000, below_4g_mem_size - 0x100000, ram_addr + 0x100000);}

             

      2. main_loop (vl.c) 是主要的執行迴圈。

        static void main_loop(void){ // 若是沒有開啟 IO 執行緒的話,無作用。 qemu_main_loop_start();  // 主要執行的無窮迴圈。 for (;;) { do { bool nonblocking = false; #ifndef CONFIG_IOTHREAD nonblocking = cpu_exec_all(); // 翻譯並執行客戶端代碼#endif main_loop_wait(nonblocking); // 處理 IO } while (vm_can_run()); // 如果此虛擬機沒有收到關機或是重開機等諸如此類的請求,則繼續執行。  } bdrv_close_all(); // 關閉所有設備 pause_all_vcpus(); // 暫無作用}

         

      3. 翻譯並執行客戶端代碼是由 cpu_exec_all (cpus.c) 負責。

        bool cpu_exec_all(void){ // 依序檢視虛擬處理器 for (; next_cpu != NULL && !exit_request; next_cpu = next_cpu->next_cpu) { CPUState *env = next_cpu; qemu_clock_enable(vm_clock, (env->singlestep_enabled & SSTEP_NOTIMER) == 0);  if (qemu_alarm_pending()) break; if (cpu_can_run(env)) { // qemu_cpu_exec 以 process mode 的路徑執行。 // cpu_x86_exec (cpu-exec.c) → tb_find_fast (cpu-exec.c) → tb_find_slow (cpu-exec.c) // cpu_exec 執行完後會返回 exception_index 狀態,狀態定義在 cpu-defs.h。 if (qemu_cpu_exec(env) == EXCP_DEBUG) { break; } } else if (env->stop) { break; } } exit_request = 0; return any_cpu_has_work();}

         

        • qemu_cpu_exec 基本上只額外多做計數。

      4. 處理 IO 是由 main_loop_wait (vl.c) 負責。

        void main_loop_wait(int nonblocking){ nfds = -1; FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&xfds); QLIST_FOREACH(ioh, &io_handlers, next) { // 將欲處理的設備加入上述的 file set }  // 根據 nonblocking 與否計算 select 等待時間 tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000;  // 將設備以 file descriptor 來處理 qemu_mutex_unlock_iothread(); // 用 select 由設備描述符中選擇一個能立即處理的設備 // select 參數代表的意義分別是: 欲處理的設備個數,要處理的輸入設備的檔案描述詞的集合,要處理的輸出設備的檔案描述詞的集合, // 有突發狀態發生的設備的檔案描述詞的集合和要求 select 等待的時間。 ret = select(nfds + 1, &rfds, &wfds, &xfds, &tv); qemu_mutex_lock_iothread(); if (ret > 0) { IOHandlerRecord *pioh; QLIST_FOREACH_SAFE(ioh, &io_handlers, next, pioh) { } }  qemu_run_all_timers(); qemu_bh_poll();}

         

        • QEMU 1.0 之前,預設編譯為 non-iothread。不論 guest OS 是否為 SMP,只有一個 QEMU thread 負責執行 guest code 和 IO 處理。如果開啟 IO thread,每一個 guest CPU 有一個 QEMU thread 對映,加上一個處理 IO 的 thread。因為 TCG 為 non thread safe,以上兩種模式同時都只有一個 thread 在執行。

       

      After QEMU 1.0

       

      QEMU 1.0 開啟 IO thread,無法關閉。仍舊以 qemu-system-i386 為例:

      模擬虛擬 CPU 和虛擬外設分為不同的執行緒。開機時至少會看到兩個執行緒,主執行緒處理 IO,另一個則是模擬虛擬 CPU 的執行緒。模擬客戶機 CPU 的流程如下:

      1. cpu_init/cpu_x86_init (target-i386/helper.c) 在初始化虛擬 CPU 時,會呼叫 qemu_init_vcpu

        CPUX86State *cpu_x86_init(const char *cpu_model){ CPUX86State *env; static int inited; env = g_malloc0(sizeof(CPUX86State)); cpu_exec_init(env); env->cpu_model_str = cpu_model;  if (tcg_enabled() && !inited) { inited = 1; optimize_flags_init();#ifndef CONFIG_USER_ONLY prev_debug_excp_handler = cpu_set_debug_excp_handler(breakpoint_handler);#endif } if (cpu_x86_register(env, cpu_model) < 0) { cpu_x86_close(env); return NULL; } env->cpuid_apic_id = env->cpu_index; mce_init(env);  qemu_init_vcpu(env);  return env;}

         

      2. qemu_init_vcpu (cpus.c)

        void qemu_init_vcpu(void *_env){ CPUState *env = _env;  env->nr_cores = smp_cores; env->nr_threads = smp_threads; env->stopped = 1; if (kvm_enabled()) { qemu_kvm_start_vcpu(env); } else { qemu_tcg_init_vcpu(env); }}

         

      3. qemu_tcg_init_vcpu (cpus.c)

        static void qemu_tcg_init_vcpu(void *_env){ CPUState *env = _env;  if (!tcg_cpu_thread) { env->thread = g_malloc0(sizeof(QemuThread)); env->halt_cond = g_malloc0(sizeof(QemuCond)); qemu_cond_init(env->halt_cond); tcg_halt_cond = env->halt_cond; qemu_thread_create(env->thread, qemu_tcg_cpu_thread_fn, env, QEMU_THREAD_JOINABLE);#ifdef _WIN32 env->hThread = qemu_thread_get_handle(env->thread);#endif while (env->created == 0) { qemu_cond_wait(&qemu_cpu_cond, &qemu_global_mutex); } tcg_cpu_thread = env->thread; } else { env->thread = tcg_cpu_thread; env->halt_cond = tcg_halt_cond; }}

         

      4. qemu_tcg_cpu_thread_fn (cpus.c)

        static void *qemu_tcg_cpu_thread_fn(void *arg){ CPUState *env = arg; qemu_tcg_init_cpu_signals(); qemu_thread_get_self(env->thread); qemu_mutex_lock(&qemu_global_mutex); for (env = first_cpu; env != NULL; env = env->next_cpu) { env->thread_id = qemu_get_thread_id(); env->created = 1; } qemu_cond_signal(&qemu_cpu_cond);  while (first_cpu->stopped) { qemu_cond_wait(tcg_halt_cond, &qemu_global_mutex); }  while (1) { tcg_exec_all(); if (use_icount && qemu_clock_deadline(vm_clock) <= 0) { qemu_notify_event(); } qemu_tcg_wait_io_event(); }  return NULL;}

         

      5. tcg_exec_all (cpus.c) 執行所有的虛擬 CPU。

        static void tcg_exec_all(void){ int r;  qemu_clock_warp(vm_clock);  if (next_cpu == NULL) { next_cpu = first_cpu; } for (; next_cpu != NULL && !exit_request; next_cpu = next_cpu->next_cpu) { CPUState *env = next_cpu;  qemu_clock_enable(vm_clock, (env->singlestep_enabled & SSTEP_NOTIMER) == 0);  if (cpu_can_run(env)) { r = tcg_cpu_exec(env); if (r == EXCP_DEBUG) { cpu_handle_guest_debug(env); break; } } else if (env->stop || env->stopped) { break; } } exit_request = 0;}

         

        • qemu_tcg_cpu_thread_fn (cpus.c) → tcg_exec_all (cpus.c) → tcg_cpu_exec (cpus.c) → cpu_x86_exec (cpu-exec.c)

      目前 QEMU 本身即為 IO thread 執行 main_loop_wait,當遇到 block IO 時,會 fork 出 posix-aio-compat.c worker thread 去處理。

      1. main (vl.c)

        cpu_exec_init_all();  if (snapshot) qemu_opts_foreach(qemu_find_opts("drive"), drive_enable_snapshot, NULL, 0); if (qemu_opts_foreach(qemu_find_opts("drive"), drive_init_func, &machine->use_scsi, 1) != 0) exit(1);  qemu_init_cpu_loop(); // qemu_init_main_loop 呼叫 main_loop_init (main-loop.c) if (qemu_init_main_loop()) { fprintf(stderr, "qemu_init_main_loop failed\n"); exit(1); }

         

        • qemu_init_cpu_loop (cpus.c)

          void qemu_init_cpu_loop(void){ qemu_init_sigbus(); qemu_cond_init(&qemu_cpu_cond); qemu_cond_init(&qemu_pause_cond); qemu_cond_init(&qemu_work_cond); qemu_cond_init(&qemu_io_proceeded_cond); qemu_mutex_init(&qemu_global_mutex); qemu_thread_get_self(&io_thread);}

           

        • main_loop_init (main-loop.c)

          int main_loop_init(void){ int ret;  qemu_mutex_lock_iothread(); ret = qemu_signal_init(); if (ret) { return ret; }  ret = qemu_event_init(); if (ret) { return ret; }  return 0;}

           

      2. main_loop (vl.c) 是主要的執行迴圈,IO thread。

        static void main_loop(void){ bool nonblocking; int last_io = 0;  do { nonblocking = !kvm_enabled() && last_io > 0;  last_io = main_loop_wait(nonblocking);  } while (!main_loop_should_exit());}

         

      3. main_loop_wait (main-loop.c)

        int main_loop_wait(int nonblocking){ fd_set rfds, wfds, xfds; int ret, nfds; struct timeval tv; int timeout;  if (nonblocking) { timeout = 0; } else { timeout = qemu_calculate_timeout(); qemu_bh_update_timeout(&timeout); } os_host_main_loop_wait(&timeout);  tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000;  nfds = -1; FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&xfds); #ifdef CONFIG_SLIRP slirp_select_fill(&nfds, &rfds, &wfds, &xfds);#endif qemu_iohandler_fill(&nfds, &rfds, &wfds, &xfds); glib_select_fill(&nfds, &rfds, &wfds, &xfds, &tv);  if (timeout > 0) { qemu_mutex_unlock_iothread(); }  ret = select(nfds + 1, &rfds, &wfds, &xfds, &tv);  if (timeout > 0) { qemu_mutex_lock_iothread(); } glib_select_poll(&rfds, &wfds, &xfds, (ret < 0)); qemu_iohandler_poll(&rfds, &wfds, &xfds, ret);#ifdef CONFIG_SLIRP slirp_select_poll(&rfds, &wfds, &xfds, (ret < 0));#endif  qemu_run_all_timers();  qemu_bh_poll();  return ret;}

         

      main (vl.c) → qemu_opts_foreach (qemu-option.c) → qemu_aio_wait (aio.c) → qemu_bh_poll (async.c) → spawn_thread_bh_fn (posix-aio-compat.c) → do_spawn_thread (posix-aio-compat.c)

      aio_thread (posix-aio-compat.c) → cond_timedwait (posix-aio-compat.c)

      底下腳本可以觀察 QEMU 本身。

      $ vi command.gdbset breakpoint pending onfile qemuhandle SIGUSR2 noprint nostopbreak main_looprun linux-0.2.img -vnc 0.0.0.0:1$ gdb -x command.gdb
        • $BUILD/pc-bios/bios.bin

       

      Reboot

       

      虛擬機重啟 (reboot) 的時候,會重置 virtual cpu 的 reset vector,這樣 virtual cpu 才會跳至開機預設的位址執行。請在 cpu_reset 下斷點,並 reboot 虛擬機 14)

      (gdb) bt#0 cpu_reset (env=0x1251290) at /nfs_home/chenwj/work/svn/qemu-1.0/target-i386/helper.c:37#1 0x0000000000638753 in pc_cpu_reset (opaque=0x1251290) at /nfs_home/chenwj/work/svn/qemu-1.0/hw/pc.c:928#2 0x00000000004fe916 in qemu_system_reset (report=true) at /nfs_home/chenwj/work/svn/qemu-1.0/vl.c:1381#3 0x00000000004feb71 in main_loop_should_exit () at /nfs_home/chenwj/work/svn/qemu-1.0/vl.c:1452#4 0x00000000004fec48 in main_loop () at /nfs_home/chenwj/work/svn/qemu-1.0/vl.c:1485#5 0x0000000000503864 in main (argc=4, argv=0x7fffffffe218, envp=0x7fffffffe240) at /nfs_home/chenwj/work/svn/qemu-1.0/vl.c:3485(gdb)
      1. 當有 reboot (reset) 的需要時,會呼叫 qemu_system_reset_request (vl.c) 拉起 reset_requested。

        void qemu_system_reset_request(void){ if (no_reboot) { shutdown_requested = 1; } else { reset_requested = 1; } cpu_stop_current(); qemu_notify_event();}

         

        • 以 i386 為例,大約有以下幾處會呼叫 qemu_system_reset_request。前兩者都是當出現  的時候重啟系統,後者是拉起 port 92。

          1. target-i386/op_helper.c

          2. target-i386/helper.c

          3. hw/pc.c

      2. 在 main_loop (vl.c) 中會呼叫 main_loop_should_exit 判斷是否需要跳離主迴圈。

        static void main_loop(void){ bool nonblocking; int last_io = 0;  do { nonblocking = !kvm_enabled() && last_io > 0;  last_io = main_loop_wait(nonblocking);  } while (!main_loop_should_exit());}

         

      3. main_loop_should_exit

        static bool main_loop_should_exit(void){ RunState r; if (qemu_debug_requested()) { vm_stop(RUN_STATE_DEBUG); } if (qemu_shutdown_requested()) { qemu_kill_report(); monitor_protocol_event(QEVENT_SHUTDOWN, NULL); if (no_shutdown) { vm_stop(RUN_STATE_SHUTDOWN); } else { return true; } } if (qemu_reset_requested()) { // 返回 reset_requested pause_all_vcpus(); cpu_synchronize_all_states(); qemu_system_reset(VMRESET_REPORT); // 重啟系統 resume_all_vcpus(); if (runstate_check(RUN_STATE_INTERNAL_ERROR) || runstate_check(RUN_STATE_SHUTDOWN)) { runstate_set(RUN_STATE_PAUSED); } } if (qemu_powerdown_requested()) { monitor_protocol_event(QEVENT_POWERDOWN, NULL); qemu_irq_raise(qemu_system_powerdown); } if (qemu_vmstop_requested(&r)) { vm_stop(r); } return false;}

         

      4.  

        void qemu_system_reset(bool report){ QEMUResetEntry *re, *nre;  // 從 reset_handlers 抓出 device 重啟。之前就會用 qemu_register_reset 註冊各個裝置的 reset 回掉函式。 QTAILQ_FOREACH_SAFE(re, &reset_handlers, entry, nre) { re->func(re->opaque); } if (report) { monitor_protocol_event(QEVENT_RESET, NULL); } cpu_synchronize_all_post_reset();}

         

      5. pc_cpu_reset 呼叫 cpu_reset (target-i386/helper.c)。

        static void pc_cpu_reset(void *opaque){ CPUState *env = opaque;  cpu_reset(env); env->halted = !cpu_is_bsp(env);}

         

      6. cpu_reset (target-i386/helper.c) 開機或是重啟時會將 CPU 狀態重置。

        void cpu_reset(CPUX86State *env){ int i;  if (qemu_loglevel_mask(CPU_LOG_RESET)) { qemu_log("CPU Reset (CPU %d)\n", env->cpu_index); log_cpu_state(env, X86_DUMP_FPU | X86_DUMP_CCOP); }  memset(env, 0, offsetof(CPUX86State, breakpoints));  tlb_flush(env, 1);  env->old_exception = -1;   #ifdef CONFIG_SOFTMMU env->hflags |= HF_SOFTMMU_MASK;#endif env->hflags2 |= HF2_GIF_MASK;  cpu_x86_update_cr0(env, 0x60000010); env->a20_mask = ~0x0; env->smbase = 0x30000;  env->idt.limit = 0xffff; env->gdt.limit = 0xffff; env->ldt.limit = 0xffff; env->ldt.flags = DESC_P_MASK | (2 << DESC_TYPE_SHIFT); env->tr.limit = 0xffff; env->tr.flags = DESC_P_MASK | (11 << DESC_TYPE_SHIFT);  cpu_x86_load_seg_cache(env, R_CS, 0xf000, 0xffff0000, 0xffff, DESC_P_MASK | DESC_S_MASK | DESC_CS_MASK | DESC_R_MASK | DESC_A_MASK); cpu_x86_load_seg_cache(env, R_DS, 0, 0, 0xffff, DESC_P_MASK | DESC_S_MASK | DESC_W_MASK | DESC_A_MASK); cpu_x86_load_seg_cache(env, R_ES, 0, 0, 0xffff, DESC_P_MASK | DESC_S_MASK | DESC_W_MASK | DESC_A_MASK); cpu_x86_load_seg_cache(env, R_SS, 0, 0, 0xffff, DESC_P_MASK | DESC_S_MASK | DESC_W_MASK | DESC_A_MASK); cpu_x86_load_seg_cache(env, R_FS, 0, 0, 0xffff, DESC_P_MASK | DESC_S_MASK | DESC_W_MASK | DESC_A_MASK); cpu_x86_load_seg_cache(env, R_GS, 0, 0, 0xffff, DESC_P_MASK | DESC_S_MASK | DESC_W_MASK | DESC_A_MASK);  env->eip = 0xfff0; env->regs[R_EDX] = env->cpuid_version;  env->eflags = 0x2;  for(i = 0;i < 8; i++) env->fptags[i] = 1; env->fpuc = 0x37f;  env->mxcsr = 0x1f80;  env->pat = 0x0007040600070406ULL; env->msr_ia32_misc_enable = MSR_IA32_MISC_ENABLE_DEFAULT;  memset(env->dr, 0, sizeof(env->dr)); env->dr[6] = DR6_FIXED_1; env->dr[7] = DR7_FIXED_1; cpu_breakpoint_remove_all(env, BP_CPU); cpu_watchpoint_remove_all(env, BP_CPU);}

         

       

阅读(3436) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~