日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

qemu 对虚机的地址空间管理

發布時間:2024/9/5 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 qemu 对虚机的地址空间管理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載: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_memory = ram;
????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)
????????????continue;
?
????????/* 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又是在哪里定義的?

static?void?pc_init1()
{
????...
????MemoryRegion *system_io = get_system_io();
????...
????if?(pci_enabled) {
????????pci_memory = g_new(MemoryRegion, 1);
????????memory_region_init(pci_memory, NULL,?"pci", UINT64_MAX);
????????rom_memory = pci_memory;
????}
????...
????if?(pci_enabled) {
????????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);
????}?else?{
????????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();
????...
????一些io 處理的事情,可能會更新地址空間
}

可以看到整個線程除了進入kvm沒有加鎖,其他時候都會加鎖。也就是說vcpu線程里處理io事件的時候是會持有這把鎖的。

再看看這把鎖在qemu里的應用:

在os_host_main_loop_wait里有這把鎖的存在:

static?int?os_host_main_loop_wait(int64_t timeout)
{
????...
?????if?(timeout) {
????????spin_counter = 0;
????????qemu_mutex_unlock_iothread();
????}?else?{
????????spin_counter++;
????}
?
????ret = qemu_poll_ns((GPollFD *)gpollfds->data, gpollfds->len, timeout);
?
????if?(timeout) {
????????qemu_mutex_lock_iothread();
????}
????...
}

可以看出,除了poll的時候釋放了鎖,其他時候會占有鎖。而os_host_main_loop_wait這個函數是主線程里循環等待事件的函數節點,

main_loop ()
{
?????do?{
????????...
????????last_io = main_loop_wait(nonblocking);
????????...
????}?while?(!main_loop_should_exit());
}
?
main_loop_wait()
{
????...
????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的調用。

struct?MemoryListener {
????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) */
????unsigned priority;
????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 对虚机的地址空间管理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。