轉載:http://huchh.com/2015/06/22/qemu-%E5%AF%B9%E8%99%9A%E6%9C%BA%E7%9A%84%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80%E7%A9%BA%E9%97%B4%E7%AE%A1%E7%90%86/
前言
cpu有兩個地址空間:io 地址空間和內存地址空間。io地址空間是給設備用的,平時說設備占有哪些端口,指的就是io地址空間里的地址。內存地址空間相對比較復雜,這個地址空間被DRAM,設備和Flash rom等使用,最終呈現給cpu的是一個線性地址空間。
附:平時編程說的物理地址指的是內存地址空間的地址,不要誤認為這個地址一定是物理內存,譬如3G以上的物理地址很可能對應的是某個PCI設備。
什么是線性地址空間,鑒于不同的地方對這個名詞有不同的解釋,先在文章的開頭申明一下,本文說的線性地址空間指的是從cpu的角度看到的一段連續的可以訪問的地址空間,其中包括了真正的物理內存RAM,PCI地址空間,還有一些設備的ROM占據的地址空間,這些地址空間互相重疊最后呈現給cpu的是一個統一的線性的地址空間。
附上兩張圖:
??
?
這兩圖截自兩篇系列文章:?System Address Map Initialization in x86/x64 Architecture Part 1: PCI-Based Systems?System Address Map Initialization in x86/x64 Architecture Part 2: PCI Express-Based Systems?這兩篇文章詳細解釋了pci和pcie設備在系統地址里的映射,對于理解線性地址空間和pci設備有很好的幫助,強烈建議仔細閱讀。
qemu維護地址空間
qemu負責模擬虛機的外設,因此虛機的線性地址空間主要由qemu進行管理,也就是確定線性地址空間中哪段地址屬于哪個設備或者DRAM或者其他的什么。通過qemu的monitor可以查看運行中的虛機的地址空間,如果用libvirt啟動的話,可以這樣查看:?
virsh qemu-monitor-command –hmpinfo mtree?
注: qemu源碼里有一篇文檔介紹了qemu的虛機內存管理 Docs/memory.txt?
address space 和 memory region
在qemu里有幾個重要的數據結構來維護虛機的線性地址空間: AddressSpace, MemoryRegion, FlatView, MemoryListener等。
在memory_map_init 中可以看到對兩個最重要的address space的初始化: address_space_memory 和 address_space_io
| static?void?memory_map_init(void) |
| ????system_memory = g_malloc(sizeof(*system_memory)); |
| ????memory_region_init(system_memory, NULL,?"system", UINT64_MAX); |
| ????//每個address space 都有個root memory region |
| ????address_space_init(&address_space_memory, system_memory,?"memory"); |
| ????system_io = g_malloc(sizeof(*system_io)); |
| ????memory_region_init_io(system_io, NULL, &unassigned_io_ops, NULL,?"io", |
| ??????????????????????????65536); |
| ????address_space_init(&address_space_io, system_io,?"I/O"); |
| ????memory_listener_register(&core_memory_listener, &address_space_memory); |
address_space_memory其實就是虛機的線性地址空間(設備的mmio分布在這個地址空間),address_space_io是虛機的io地址空間(設備的io port就分布在這個地址空間里)。
不管是DRAM還是設備的資源都要通過memory region添加到address space里。
DRAM的memory region
DRAM的memory_region初始化在pc_memory_init里可以看到:
| FWCfgState *pc_memory_init(MachineState *machine, |
| ???????????????????????????MemoryRegion *system_memory, |
| ???????????????????????????ram_addr_t below_4g_mem_size, |
| ???????????????????????????ram_addr_t above_4g_mem_size, |
| ???????????????????????????MemoryRegion *rom_memory, |
| ???????????????????????????MemoryRegion **ram_memory, |
| ???????????????????????????PcGuestInfo *guest_info) |
| ????ram = g_malloc(sizeof(*ram)); |
| ????memory_region_allocate_system_memory(ram, NULL,?"pc.ram", |
| ?????????????????????????????????????????machine->ram_size); |
| ????ram_below_4g = g_malloc(sizeof(*ram_below_4g)); |
| ????memory_region_init_alias(ram_below_4g, NULL,?"ram-below-4g", ram, |
| ?????????????????????????????0, below_4g_mem_size); |
| ????//ram-below-4g到4G之間的地址主要是留給PCI設備的mmio地址使用 |
| ????memory_region_add_subregion(system_memory, 0, ram_below_4g); |
| ????e820_add_entry(0, below_4g_mem_size, E820_RAM); |
| ????if?(above_4g_mem_size > 0) { |
| ????????ram_above_4g = g_malloc(sizeof(*ram_above_4g)); |
| ????????memory_region_init_alias(ram_above_4g, NULL,?"ram-above-4g", ram, |
| ?????????????????????????????????below_4g_mem_size, above_4g_mem_size); |
| ????????memory_region_add_subregion(system_memory, 0x100000000ULL, |
| ????????????????????????????????????ram_above_4g); |
| ????????e820_add_entry(0x100000000ULL, above_4g_mem_size, E820_RAM); |
legacy devices的地址一般是固定的,在設備初始化的時候就可以通過memory_region_add_subregion加入到地址空間的確切位置。?
pci設備的memory region
PCI設備的資源在地址空間中的偏移是動態不確定的,一般PCI設備需要的memory region對應的就是bar,一開始初始化memory region,然后用pci_register_bar注冊bar。那么到底在什么地方將bar對應的memory region添加到address space里呢?
看一下pci_update_mappings函數:
| static?void?pci_update_mappings(PCIDevice *d) |
| ????for(i = 0; i < PCI_NUM_REGIONS; i++) { r = &d->io_regions[i]; |
| ????????new_addr = pci_bar_address(d, i, r->type, r->size); |
| ????????/* This bar isn't changed */ |
| ????????if?(new_addr == r->addr) |
| ????????/* now do the real mapping */ |
| ????????if?(r->addr != PCI_BAR_UNMAPPED) { |
| ????????????trace_pci_update_mappings_del(d, pci_bus_num(d->bus), |
| ??????????????????????????????????????????PCI_FUNC(d->devfn), |
| ??????????????????????????????????????????PCI_SLOT(d->devfn), |
| ??????????????????????????????????????????i, r->addr, r->size); |
| ????????????memory_region_del_subregion(r->address_space, r->memory); |
| ????????r->addr = new_addr; |
| ????????if?(r->addr != PCI_BAR_UNMAPPED) { |
| ????????????trace_pci_update_mappings_add(d, pci_bus_num(d->bus), |
| ??????????????????????????????????????????PCI_FUNC(d->devfn), |
| ??????????????????????????????????????????PCI_SLOT(d->devfn), |
| ??????????????????????????????????????????i, r->addr, r->size); |
| ????????????/*r->address_space的賦值在pci_register_bar里完成*/ |
| ????????????memory_region_add_subregion_overlap(r->address_space, |
| ????????????????????????????????????????????????r->addr, r->memory, 1); |
| void?pci_register_bar(PCIDevice *pci_dev,?int?region_num, |
| ??????????????????????uint8_t type, MemoryRegion *memory) |
| ????pci_dev->io_regions[region_num].address_space |
| ????????= type & PCI_BASE_ADDRESS_SPACE_IO |
| ????????? pci_dev->bus->address_space_io |
| ????????: pci_dev->bus->address_space_mem; |
pci bus 的address_space_io和address_space_mem又是在哪里定義的?
| ????MemoryRegion *system_io = get_system_io(); |
| ????????pci_memory = g_new(MemoryRegion, 1); |
| ????????memory_region_init(pci_memory, NULL,?"pci", UINT64_MAX); |
| ????????rom_memory = pci_memory; |
| ????????pci_bus = i440fx_init(&i440fx_state, &piix3_devfn, &isa_bus, gsi, |
| ??????????????????????????????system_memory, system_io, machine->ram_size, |
| ??????????????????????????????below_4g_mem_size, |
| ??????????????????????????????above_4g_mem_size, |
| ??????????????????????????????pci_memory, ram_memory); |
| PCIBus *i440fx_init(PCII440FXState **pi440fx_state, |
| ????????????????????int?*piix3_devfn, |
| ????????????????????ISABus **isa_bus, qemu_irq *pic, |
| ????????????????????MemoryRegion *address_space_mem, |
| ????????????????????MemoryRegion *address_space_io, |
| ????????????????????ram_addr_t ram_size, |
| ????????????????????ram_addr_t below_4g_mem_size, |
| ????????????????????ram_addr_t above_4g_mem_size, |
| ????????????????????MemoryRegion *pci_address_space, |
| ????????????????????MemoryRegion *ram_memory) |
| ????b = pci_bus_new(dev, NULL, pci_address_space, |
| ????????????????????address_space_io, 0, TYPE_PCI_BUS); |
| ????/* setup pci memory mapping */ |
| ????pc_pci_as_mapping_init(OBJECT(f), f->system_memory, |
| ???????????????????????????f->pci_address_space); |
| PCIBus *pci_bus_new(DeviceState *parent,?const?char?*name, |
| ????????????????????MemoryRegion *address_space_mem, |
| ????????????????????MemoryRegion *address_space_io, |
| ????????????????????uint8_t devfn_min,?const?char?*typename) |
| ????pci_bus_init(bus, parent, name, address_space_mem, |
| ?????????????????address_space_io, devfn_min); |
| static?void?pci_bus_init(PCIBus *bus, DeviceState *parent, |
| ?????????????????????????const?char?*name, |
| ?????????????????????????MemoryRegion *address_space_mem, |
| ?????????????????????????MemoryRegion *address_space_io, |
| ?????????????????????????uint8_t devfn_min) |
| ????bus->address_space_mem = address_space_mem; |
| ????bus->address_space_io = address_space_io; |
| void?pc_pci_as_mapping_init(Object *owner, MemoryRegion *system_memory, |
| ????????????????????????????MemoryRegion *pci_address_space) |
| ????/* Set to lower priority than RAM */ |
| ????memory_region_add_subregion_overlap(system_memory, 0x0, |
| ????????????????????????????????????????pci_address_space, -1); |
從上面的代碼片段可以看出pci bus的address_space_io就是address_space_io的root memory region,而address_space_mem是新建的一個屬于pci設備的總的memory region,在pc_pci_as_mapping_init里將pci_address_space以-1的優先級加入到system_memory里,將pci設備的地址空間和線性地址空間進行統一。
而每個pci設備在pci_update_mappings里將他們的bar作為sub memory region加入到其附屬的pci總線的address_space_io或者address_space_mem里,其實就是添加到統一的io地址空間或者內存地址空間(線性地址空間)。?
回顧一下pci_update_mappings,它是在pci_default_write_config里被調用的,而大部分pci設備寫config space的時候都會調用到pci_default_write_config,也就是說虛機的fireware或者OS確定了bar的基地址后,更新config space,然后bar就會正式添加到io地址空間或者線性地址空間,在此之前,qemu里的pci設備只是定義了bar,相當于準備好了硬件,但是還不能在地址空間里看到pci設備的bar。
內部細節
有關地址空間分布的api內部有一些細節挺繞的,當初也花了一些時間來理解,這里記錄一些認為比較關鍵的函數點,權充日后按圖索驥之用,并不會詳細地展開每個函數。
?鎖的存在
memory_region_add_subregion這樣的函數會更新memory region內部的數據結構,可以從代碼上看明顯沒有鎖的存在,難道這個函數確保不會被并發訪問嗎? 當然不是,在主線程和vcpu線程都可能會更新設備的memory region,因此這類函數一定存在并發使用的可能。那么同步措施到底在哪里做的呢?
關鍵在qemu_mutex_lock_iothread這個函數,從下面的代碼可以看到這個函數其實就是鎖住了一把全局鎖。
| void?qemu_mutex_lock_iothread(void) |
| ????atomic_inc(&iothread_requesting_mutex); |
| ????if?(!tcg_enabled() || !first_cpu || !first_cpu->thread) { |
| ????????qemu_mutex_lock(&qemu_global_mutex); |
| ????????atomic_dec(&iothread_requesting_mutex); |
| ????????if?(qemu_mutex_trylock(&qemu_global_mutex)) { |
| ????????????qemu_cpu_kick_thread(first_cpu); |
| ????????????qemu_mutex_lock(&qemu_global_mutex); |
| ????????atomic_dec(&iothread_requesting_mutex); |
| ????????qemu_cond_broadcast(&qemu_io_proceeded_cond); |
這個函數在vcpu線程里使用:
| int?kvm_cpu_exec(CPUState *) |
| ????qemu_mutex_unlock_iothread(); |
| ????run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0); |
| ????qemu_mutex_lock_iothread(); |
可以看到整個線程除了進入kvm沒有加鎖,其他時候都會加鎖。也就是說vcpu線程里處理io事件的時候是會持有這把鎖的。
再看看這把鎖在qemu里的應用:
在os_host_main_loop_wait里有這把鎖的存在:
| static?int?os_host_main_loop_wait(int64_t timeout) |
| ????????spin_counter = 0; |
| ????????qemu_mutex_unlock_iothread(); |
| ????ret = qemu_poll_ns((GPollFD *)gpollfds->data, gpollfds->len, timeout); |
| ????????qemu_mutex_lock_iothread(); |
可以看出,除了poll的時候釋放了鎖,其他時候會占有鎖。而os_host_main_loop_wait這個函數是主線程里循環等待事件的函數節點,
| ????????last_io = main_loop_wait(nonblocking); |
| ????}?while?(!main_loop_should_exit()); |
| ????ret = os_host_main_loop_wait(timeout_ns); |
| ????qemu_iohandler_poll(gpollfds, ret); |
| ????qemu_clock_run_all_timers(); |
所以主線程里每次處理io事件的時候也會獲取這把鎖,這時候就可以解釋memory region的更新函數里為什么沒有看見鎖了,因此實際上用的是這一把全局鎖。
memory_region_transaction_begin和memory_region_transaction_commit
在每個更新memory region的函數里都能看到這兩個函數對,這兩個函數對干什么呢?
| void?memory_region_transaction_begin(void) |
| ????qemu_flush_coalesced_mmio_buffer(); |
| ????++memory_region_transaction_depth; |
| void?memory_region_transaction_commit(void) |
| ????--memory_region_transaction_depth; |
| ????if?(!memory_region_transaction_depth) { |
函數對的關鍵其實是memory_region_transaction_depth的計數,也就是說這兩個函數對允許遞歸調用,在一個函數對內部可以再調用多個函數對,只要函數數量是配對的,那么只有等到最外層memory_region_transaction_commit才會開始地址空間的更新。為什么需要這樣做呢,這是因為每次更新地址空間的花銷是比較大的,如果把多個memory region的更新操作放在一起執行,那么最終只會產生一次地址空間的更新,這是很劃算的。
在ich9.c里找到了這樣的一個例子:
| void?ich9_pm_iospace_update(ICH9LPCPMRegs *pm, uint32_t pm_io_base) |
| ????ICH9_DEBUG("to 0x%x\n", pm_io_base); |
| ????assert((pm_io_base & ICH9_PMIO_MASK) == 0); |
| ????pm->pm_io_base = pm_io_base; |
| ????memory_region_transaction_begin(); |
| ????memory_region_set_enabled(&pm->io, pm->pm_io_base != 0); |
| ????memory_region_set_address(&pm->io, pm->pm_io_base); |
| ????memory_region_transaction_commit(); |
memory_listener
地址空間里有個比較重要的數據結構是memory listner,這個數據結構里可以存放一些回調函數,顧名思義,回調函數被調用的時機就是地址空間發生變動的時候。譬如在memory_region_transaction_commit里可以看到對begin和commit的調用,而在address_space_update_topology_pass里可以看到對region_add,region_del,region_nop的調用。
| ????void?(*begin)(MemoryListener *listener); |
| ????void?(*commit)(MemoryListener *listener); |
| ????void?(*region_add)(MemoryListener *listener, MemoryRegionSection *section); |
| ????void?(*region_del)(MemoryListener *listener, MemoryRegionSection *section); |
| ????void?(*region_nop)(MemoryListener *listener, MemoryRegionSection *section); |
| ????void?(*log_start)(MemoryListener *listener, MemoryRegionSection *section); |
| ????void?(*log_stop)(MemoryListener *listener, MemoryRegionSection *section); |
| ????void?(*log_sync)(MemoryListener *listener, MemoryRegionSection *section); |
| ????void?(*log_global_start)(MemoryListener *listener); |
| ????void?(*log_global_stop)(MemoryListener *listener); |
| ????void?(*eventfd_add)(MemoryListener *listener, MemoryRegionSection *section, |
| ????????????????????????bool?match_data, uint64_t data, EventNotifier *e); |
| ????void?(*eventfd_del)(MemoryListener *listener, MemoryRegionSection *section, |
| ????????????????????????bool?match_data, uint64_t data, EventNotifier *e); |
| ????void?(*coalesced_mmio_add)(MemoryListener *listener, MemoryRegionSection *section, |
| ???????????????????????????????hwaddr addr, hwaddr len); |
| ????void?(*coalesced_mmio_del)(MemoryListener *listener, MemoryRegionSection *section, |
| ???????????????????????????????hwaddr addr, hwaddr len); |
| ????/* Lower = earlier (during add), later (during del) */ |
| ????AddressSpace *address_space_filter; |
| ????QTAILQ_ENTRY(MemoryListener) link; |
比較重要的memory_listner有kvm_memory_listener,kvm_io_listener,dispatch_listener。kvm相關的兩個listner比較明顯,用意就是在qemu的地址空間發生變動的時候通過回調函數通知到kvm。
dispatch_listener的初始化在address_space_init_dispatch,它在每個地址空間里都存在,用意是在地址空間發生變動的時候,通過內部的數據結構記錄這種變化,以此得知地址空間里每一段地址應該屬于哪個memory region,這樣當虛機有io操作需要在qemu里完成的時候,也就是vcpu線程從kvm返回需要處理io或者mmio的時候都需要通過對應的地址空間的dispatch_listner找到io操作的目標。具體可以看address_space_rw里的address_space_translate函數。
?
轉載于:https://www.cnblogs.com/wuchanming/p/4732604.html
總結
以上是生活随笔為你收集整理的qemu 对虚机的地址空间管理的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。