ps3 装linux qemu,最全的剖析QEMU原理的文章1

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。

進入點為 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);}

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(); // 暫無作用}

翻譯並執行客戶端代碼是由 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 基本上只額外多做計數。

處理 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 的流程如下:

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;}

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); }}

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; }}

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;}

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 去處理。

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;}

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());}

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)

當有 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。

target-i386/op_helper.c

target-i386/helper.c

hw/pc.c

在 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());}

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;}

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();}

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);}

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);}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值