老规矩先看一下操作函数的注册过程
1.ops注册
ops的注册主要在memory_region_init_io
函数:
memory.c
->memory_region_init_io
->memory_region_init
->mr->ops = ops ? ops : &unassigned_mem_ops;
qemu对IO地址空间和内存地址空间分别进行模拟,数据都保存在内存块(MemoryRegion)中。可以说qemu的设备模拟都是以内存快为基础的,内存块中保存了很多内存的描述信息,其中就包括了很重要的内存模拟相关的函数族ops。所以在讲解设备模拟之前有必要先说一下这个结构体。
2.MemoryRegion
struct MemoryRegion {
Object parent_obj;
/* All fields are private - violators will be prosecuted */
/* The following fields should fit in a cache line */
bool romd_mode;
bool ram;
bool subpage;
bool readonly; /* For RAM regions */
bool nonvolatile;
bool rom_device;
bool flush_coalesced_mmio;
bool global_locking;
uint8_t dirty_log_mask;
bool is_iommu;
RAMBlock *ram_block;
Object *owner;
const MemoryRegionOps *ops;
void *opaque;
MemoryRegion *container;
Int128 size;
hwaddr addr;
void (*destructor)(MemoryRegion *mr);
uint64_t align;
bool terminates;
bool ram_device;
bool enabled;
bool warning_printed; /* For reservations */
uint8_t vga_logging_count;
MemoryRegion *alias;
hwaddr alias_offset;
int32_t priority;
QTAILQ_HEAD(subregions, MemoryRegion) subregions;
QTAILQ_ENTRY(MemoryRegion) subregions_link;
QTAILQ_HEAD(coalesced_ranges, CoalescedMemoryRange) coalesced;
const char *name;
unsigned ioeventfd_nb;
MemoryRegionIoeventfd *ioeventfds;
};
内存块中暂时主要要关注is_iommu
,ops这两个成员,这是设备模拟中很重要的两个成员。
之前已经讲过设备模拟的本质,设备模拟的框架无非是匹配上设备地址,然后调用设备的读写函数,理清四个流程就能弄清楚qemu设备模拟框架:io_read
、io_write
、mmio_read
、mmio_write
3.io_read
io_read从KVM_RUN系统调用过后开始分析:
kvm-all.c
->kvm_cpu_exec
->kvm_handle_io
->address_space_rw
exec.c
->address_space_rw
->address_space_read_full
->flatview_read
->flatview_translate
->flatview_read_continue
->memory_region_dispatch_read
io地址对应到设备的逻辑就在flatview_translate
,这个函数会根据地址找到设备对应的内存块,详细逻辑后面分析。
memory.c
->memory_region_dispatch_read
->memory_region_dispatch_read1
->memory_region_read_accessor
->tmp = mr->ops->read(mr->opaque, addr, size);
内存块有了就可以用内存块中注册好的ops对内存数据进行模拟了。下面来看一下flatview_translate中的细节:
exec.c
->flatview_translate
->flatview_do_translate
->address_space_translate_internal
->address_space_lookup_region
->atomic_read(&d->mru_section);
->phys_page_find(d, addr);
->return §ions[lp.ptr];
->atomic_set(&d->mru_section, section);
->subpage = container_of(section->mr, subpage_t, iomem);
->&d->map.sections[subpage->sub_section[SUBPAGE_IDX(addr)]];
atomic操作主要是做一个缓存加速,phys_page_find
遍历地址空间寻找内存页,找到过后返回内存页对应的内存段。找到了内存段就等于找到了设备,可以使用设备的ops函数簇模拟了。这里除了使用phys_page_find
查找内存还会查找subpage
,一旦发现有subpage匹配会覆盖phys_page_find
的结果,所以subpage
的优先级会更高,这个会用在后面的地址空间扩展,比如pic模拟中对内存的管理。
4.io_write
相同部分省略
exec.c
->address_space_rw
->address_space_write
->flatview_write
->flatview_translate
->flatview_write_continue
->memory_region_dispatch_write
后面的跟读的都是一样的,省略了
5.mmio_read/mmio_write
kvm-all.c
->kvm_cpu_exec
->address_space_rw
mmio后面跟普通IO逻辑完全一样,调用的模拟函数也是一模一样。