QEMU 設備模擬

設備模擬目的

我們好像不會幹一件事而毫無目的,就算不停刷微信朋友圈也是爲了打發你無聊的時間。
其實最裝B的回答是:設備模擬的目的就是模擬設備。這話是屁話,不過也能說明些什麼,確實是模擬設備,用軟件的方式提供硬件設備具備的功能。
對於和PC機交互的硬件設備,主要要幹兩件事,一是提供IRQ中斷,二是響應IO輸入輸出。IO包括PIO/MMIO/DMA等(DMA算不算IO?)
以i8254.c實現的pit爲例,主要提供了IRQ注入和PIO響應,見初始化函數pit_initfn:

static const MemoryRegionOps pit_ioport_ops = { .read = pit_ioport_read, .write = pit_ioport_write, .impl = { .min_access_size = 1, .max_access_size = 1, }, .endianness = DEVICE_LITTLE_ENDIAN, }; static int pit_initfn(PITCommonState *pit) { PITChannelState *s; s = &pit->channels[0]; /* the timer 0 is connected to an IRQ */ //這裏有個irq_timer,用於qemu_set_irq提供中斷注入 s->irq_timer = qemu_new_timer_ns(vm_clock, pit_irq_timer, s); qdev_init_gpio_out(&pit->dev.qdev, &s->irq, 1); memory_region_init_io(&pit->ioports, &pit_ioport_ops, pit, "pit", 4); qdev_init_gpio_in(&pit->dev.qdev, pit_irq_control, 1); return 0; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
staticconstMemoryRegionOpspit_ioport_ops={
    .read=pit_ioport_read,
    .write=pit_ioport_write,
    .impl={
        .min_access_size=1,
        .max_access_size=1,
    },
    .endianness=DEVICE_LITTLE_ENDIAN,
};
 
staticintpit_initfn(PITCommonState*pit)
{
    PITChannelState*s;
 
    s=&pit->channels[0];
    /* the timer 0 is connected to an IRQ */
    //這裏有個irq_timer,用於qemu_set_irq提供中斷注入
    s->irq_timer=qemu_new_timer_ns(vm_clock,pit_irq_timer,s);
    qdev_init_gpio_out(&pit->dev.qdev,&s->irq,1);
 
    memory_region_init_io(&pit->ioports,&pit_ioport_ops,pit,"pit",4);
    qdev_init_gpio_in(&pit->dev.qdev,pit_irq_control,1);
    return0;
}

這裏的pit_ioport_ops,主要註冊GUEST操作系統讀寫PIO時候的回調函數。

模塊註冊

QEMU要模擬模塊那麼多,以程序員的喜好,至少得來一套管理這些模擬設備模塊的接口,以示設計良好。
QEMU將被模擬的模塊分爲了四類:

typedef enum { MODULE_INIT_BLOCK, MODULE_INIT_MACHINE, MODULE_INIT_QAPI, MODULE_INIT_QOM, MODULE_INIT_MAX } module_init_type;
1
2
3
4
5
6
7
typedefenum{
    MODULE_INIT_BLOCK,
    MODULE_INIT_MACHINE,
    MODULE_INIT_QAPI,
    MODULE_INIT_QOM,
    MODULE_INIT_MAX
}module_init_type;
  • BLOCK
    比如磁盤IO就屬於BLOCK類型,e.g: block_init(bdrv_qcow2_init); block_init(iscsi_block_init);
  • MACHINE
    PC虛擬machine_init(pc_machine_init); XEN半虛擬化machine_init(xenpv_machine_init); MIPS虛擬machine_init(mips_machine_init);
  • QAPI
    QEMU GUEST AGENT模塊裏面會執行QAPI註冊的回調
  • QOM
    QOM樹大枝多,兒孫滿堂,應該是這裏面最複雜的一個,我們重點介紹。
    e.g:
    ObjectClass -> PCIDeviceClass //顯卡type_init(cirrus_vga_register_types),網卡 type_init(rtl8139_register_types) IDEDeviceClass //IDE硬盤或CD-ROM type_init(ide_register_types) ISADeviceClass //鼠標鍵盤type_init(i8042_register_types),RTC時鐘type_init(pit_register) SysBusDeviceClass //MMIO IDE(IDE設備直接連接CPU bus而不是連接IDE controller)type_init(mmio_ide_register_types) CPUClass -> X86CPUClass //X86 CPU架構 -> CRISCPUClass
    1
    2
    3
    4
    5
    ObjectClass->  PCIDeviceClass    //顯卡type_init(cirrus_vga_register_types),網卡type_init(rtl8139_register_types)
                    IDEDeviceClass  //IDE硬盤或CD-ROM type_init(ide_register_types)
                    ISADeviceClass  //鼠標鍵盤type_init(i8042_register_types),RTC時鐘type_init(pit_register)
                    SysBusDeviceClass//MMIO IDE(IDE設備直接連接CPU bus而不是連接IDE controller)type_init(mmio_ide_register_types)                       CPUClass -> X86CPUClass //X86 CPU架構
                    ->  CRISCPUClass

註冊QOM設備的時候,使用QEMU提供的宏,type_init宏進行註冊:

#define type_init(function) module_init(function, MODULE_INIT_QOM) #define module_init(function, type) \ static void __attribute__((constructor)) do_qemu_init_ ## function(void) { \ register_module_init(function, type); \ }
1
2
3
4
5
#define type_init(function) module_init(function, MODULE_INIT_QOM)
#define module_init(function, type)    \
    staticvoid__attribute__((constructor))do_qemu_init_## function(void) { \
    register_module_init(function,type);\
}

這和寫LINUX驅動類似,一般寫在一個模塊實現文件的最底部,以pit爲例,寫的是type_init(pit_register_types)展開後爲:

static void __attribute__((constructor)) do_qemu_init_pit_register_types(void) { register_module_init(pit_register_types, MODULE_INIT_QOM); }
1
2
3
4
staticvoid__attribute__((constructor))do_qemu_init_pit_register_types(void)
{
    register_module_init(pit_register_types,MODULE_INIT_QOM);
}

那麼,這個do_qemu_init_pit_register_types何時調用?
在gcc裏面,給函數加上__attribute__((destructor)),表示此函數需要在main開始前自動調用,測試調用順序是: 全局對象構造函數 -> __attribute__((constructor)) -> main -> 全局對象析構函數 -> __attribute__((destructor))。
調用register_module_init就是將pit_register_types回調函數插入util\module.c裏定義的init_type_list[MODULE_INIT_QOM]鏈表內。

void register_module_init(void (*fn)(void), module_init_type type) { ModuleEntry *e; ModuleTypeList *l; e = g_malloc0(sizeof(*e)); e->init = fn; //init指針被設置爲fn l = find_type(type); QTAILQ_INSERT_TAIL(l, e, node); }
1
2
3
4
5
6
7
8
9
voidregister_module_init(void(*fn)(void),module_init_typetype)
{
    ModuleEntry*e;
    ModuleTypeList*l;
    e=g_malloc0(sizeof(*e));
    e->init=fn;//init指針被設置爲fn
    l=find_type(type);
    QTAILQ_INSERT_TAIL(l,e,node);
}

通過下面main函數的部分代碼可以看出,模塊初始化順序是QOM->MACHINE->BLOCK,至於QAPI,在這個流程裏沒看到。

void main() { module_call_init(MODULE_INIT_QOM); //初始化設備 qemu_add_opts //初始化默認選項 module_call_init(MODULE_INIT_MACHINE); //初始化機器類型 machine = find_default_machine(); //這裏對machine賦值,下面還會通過參數更改machine vtp_script_execute(g_qemu_start_hook_path, g_fairsched_string, TYPE_START); //開機啓動腳本的調用 深度分析啓動參數 bdrv_init_with_whitelist -> bdrv_init -> module_call_init(MODULE_INIT_BLOCK); //初始化BLOCK設備 machine->init(&args); //初始化machine qemu_run_machine_init_done_notifiers(); //初始化成功回調通知 qemu_system_reset(VMRESET_SILENT);//system reset 啓動運行 if (loadvm) { load_vmstate(loadvm); } else if (loadstate) { load_state_from_blockdev(loadstate); } resume_all_vcpus(); main_loop(); //進入主循環 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
voidmain()
{
    module_call_init(MODULE_INIT_QOM);//初始化設備
    qemu_add_opts//初始化默認選項
    module_call_init(MODULE_INIT_MACHINE);//初始化機器類型
    machine=find_default_machine();//這裏對machine賦值,下面還會通過參數更改machine    
    vtp_script_execute(g_qemu_start_hook_path,g_fairsched_string,TYPE_START);//開機啓動腳本的調用
    深度分析啓動參數
    bdrv_init_with_whitelist->bdrv_init->module_call_init(MODULE_INIT_BLOCK);//初始化BLOCK設備
    machine->init(&args);//初始化machine
 
    qemu_run_machine_init_done_notifiers();//初始化成功回調通知
    qemu_system_reset(VMRESET_SILENT);//system reset 啓動運行
    if(loadvm){
        load_vmstate(loadvm);
    }elseif(loadstate){
        load_state_from_blockdev(loadstate);
    }
 
    resume_all_vcpus();
    main_loop();//進入主循環
}

在main函數進來的時候,首先調用module_call_init(MODULE_INIT_QOM);

void module_call_init(module_init_type type) { ModuleTypeList *l; ModuleEntry *e; l = find_type(type); QTAILQ_FOREACH(e, l, node) { e->init(); //這裏,就是調用剛纔註冊的回調,例如,對於kvm-pit來說,調用的是pit_register } }
1
2
3
4
5
6
7
8
9
voidmodule_call_init(module_init_typetype)
{
    ModuleTypeList*l;
    ModuleEntry*e;
    l=find_type(type);
    QTAILQ_FOREACH(e,l,node){
        e->init();//這裏,就是調用剛纔註冊的回調,例如,對於kvm-pit來說,調用的是pit_register
    }
}

此module_call_init將依次調用註冊的回調,如PIT的pit_register_types:

static const TypeInfo pit_info = { .name = "isa-pit", //做爲type_table的key .parent = "pit-common", //父類型,這個比較重要,如果本TypeInfo沒有設置class_size,會根據parent獲取parent TypeImpl的class_size .instance_size = sizeof(PITCommonState),//分配實例的大小 .class_init = pit_class_init, //初始化函數 }; static void pit_register_types(void) { type_register_static(&pit_info); }
1
2
3
4
5
6
7
8
9
10
11
staticconstTypeInfopit_info={
    .name          ="isa-pit",          //做爲type_table的key
    .parent        ="pit-common",      //父類型,這個比較重要,如果本TypeInfo沒有設置class_size,會根據parent獲取parent TypeImpl的class_size
    .instance_size=sizeof(PITCommonState),//分配實例的大小
    .class_init=pit_class_init,    //初始化函數
};
 
staticvoidpit_register_types(void)
{
    type_register_static(&pit_info);
}

pit_register_types又進一步調用type_register_static -> type_register -> type_register_internal,這個函數完成的功能其實只是在qom\object.c的type_table裏插入了一個HASH鍵值 對,以TypeInfo的name爲KEY,malloc了一個TypeInfo結構的超集TypeImpl爲VALUE,在以name爲KEY回溯 parent時需要TypeImpl,其實這個hash也可以做成一個tree。

QOM的Object模型

以pit爲例,通過回溯parent你可以看到,其定義TypeInfo最終形成一個繼承關係:
"isa-pit" -> "pit-common" -> "isa-device" -> "device" -> "object"

qom\object.c

static TypeInfo object_info = { .name = "object", .instance_size = sizeof(Object), .instance_init = object_instance_init, .abstract = true, };
1
2
3
4
5
6
staticTypeInfoobject_info={
    .name="object",
    .instance_size=sizeof(Object),
    .instance_init=object_instance_init,
    .abstract=true,
};

hw\qdev.c

static const TypeInfo device_type_info = { .name = "device", .parent = "object" , .instance_size = sizeof(DeviceState), .instance_init = device_initfn, .instance_finalize = device_finalize, .class_base_init = device_class_base_init, .class_init = device_class_init, .abstract = true, .class_size = sizeof(DeviceClass), };
1
2
3
4
5
6
7
8
9
10
11
staticconstTypeInfodevice_type_info={
    .name="device",
    .parent="object",
    .instance_size=sizeof(DeviceState),
    .instance_init=device_initfn,
    .instance_finalize=device_finalize,
    .class_base_init=device_class_base_init,
    .class_init=device_class_init,
    .abstract=true,
    .class_size=sizeof(DeviceClass),
};

hw\isa-bus.c

static const TypeInfo isa_device_type_info = { .name = "isa-device", .parent = "device", .instance_size = sizeof(ISADevice), .abstract = true, .class_size = sizeof(ISADeviceClass), .class_init = isa_device_class_init, };
1
2
3
4
5
6
7
8
staticconstTypeInfoisa_device_type_info={
    .name="isa-device",
    .parent="device",
    .instance_size=sizeof(ISADevice),
    .abstract=true,
    .class_size=sizeof(ISADeviceClass),
    .class_init=isa_device_class_init,
};

hw\i8254_common.c

static const TypeInfo pit_common_type = { .name = "pit-common", .parent = "isa-device", .instance_size = sizeof(PITCommonState), .class_size = sizeof(PITCommonClass), .class_init = pit_common_class_init, .abstract = true, };
1
2
3
4
5
6
7
8
staticconstTypeInfopit_common_type={
    .name          ="pit-common",
    .parent        ="isa-device",
    .instance_size=sizeof(PITCommonState),
    .class_size    =sizeof(PITCommonClass),
    .class_init    =pit_common_class_init,
    .abstract      =true,
};

hw\i8254.c

static const TypeInfo pit_info = { .name = "isa-pit", .parent = "pit-common", .instance_size = sizeof(PITCommonState), .class_init = pit_class_initfn, };
1
2
3
4
5
6
staticconstTypeInfopit_info={
    .name          ="isa-pit",
    .parent        ="pit-common",
    .instance_size=sizeof(PITCommonState),
    .class_init    =pit_class_initfn,
};

由於TypeInfo只是註冊時臨時使用,而TypeImpl是TypeInfo的超集,所以,這層關係也反應了TypeImpl的繼承關係。

struct TypeImpl { const char *name; size_t class_size; size_t instance_size; void (*class_init)(ObjectClass *klass, void *data); void (*class_base_init)(ObjectClass *klass, void *data); void (*class_finalize)(ObjectClass *klass, void *data); void *class_data; void (*instance_init)(Object *obj); void (*instance_finalize)(Object *obj); bool abstract; const char *parent; TypeImpl *parent_type; ObjectClass *class; int num_interfaces; InterfaceImpl interfaces[MAX_INTERFACES]; };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
structTypeImpl
{
    constchar*name;
    size_tclass_size;
    size_tinstance_size;
    void(*class_init)(ObjectClass*klass,void*data);
    void(*class_base_init)(ObjectClass*klass,void*data);
    void(*class_finalize)(ObjectClass*klass,void*data);
    void*class_data;
    void(*instance_init)(Object*obj);
    void(*instance_finalize)(Object*obj);
    boolabstract;
    constchar*parent;
    TypeImpl*parent_type;
    ObjectClass*class;
    intnum_interfaces;
    InterfaceImplinterfaces[MAX_INTERFACES];
};


Figure 1 TypeImpl圖解
打印查看TypeImpl屬性:

(gdb) p *obj->class->type //struct TypeImpl * type
$13 = {name = 0x5555566e5e30 "mc146818rtc", class_size = 128, instance_size = 664, class_init = 0x55555579b790 , class_base_init = 0,
class_finalize = 0, class_data = 0x0, instance_init = 0, instance_finalize = 0, abstract = false, parent = 0x5555566e5e50 "isa-device",
parent_type = 0x5555566d8bd0, class = 0x555556a50e50, num_interfaces = 0, interfaces = {{typename = 0x0} }}

其主要包含如下部分:

  • name/parent/parent_type 表示自己的,父親的KEY和TypeImpl指針。
  • class/class_size/class_init/class_base_init/class_finalize/class_data 和ObjectClass聯繫,組成繼承關係。
  • instance_init/instance_finalize和ObjectClass有裙帶關係的Object,共同完成繼承體系。
  • num_interfaces/interfaces 用於管理接口。

Object和ObjectClass的關係

還是通過這條繼承鏈來看:
"isa-pit" -> "pit-common" -> "isa-device" -> "device" -> "object"
其中ObjectClass鏈的定義爲:

struct ObjectClass { /*< private >*/ Type type; GSList *interfaces; ObjectUnparent *unparent; }; typedef struct DeviceClass { /*< private >*/ ObjectClass parent_class; /*< public >*/ const char *fw_name; const char *desc; Property *props; int no_user; /* callbacks */ void (*reset)(DeviceState *dev); DeviceRealize realize; DeviceUnrealize unrealize; /* device state */ const struct VMStateDescription *vmsd; /* Private to qdev / bus. */ qdev_initfn init; /* TODO remove, once users are converted to realize */ qdev_event unplug; qdev_event exit; const char *bus_type; } DeviceClass; typedef struct ISADeviceClass { DeviceClass parent_class; int (*init)(ISADevice *dev); } ISADeviceClass; typedef struct PITCommonClass { ISADeviceClass parent_class; int (*init)(PITCommonState *s); void (*set_channel_gate)(PITCommonState *s, PITChannelState *sc, int val); void (*get_channel_info)(PITCommonState *s, PITChannelState *sc, PITChannelInfo *info); void (*pre_save)(PITCommonState *s); void (*post_load)(PITCommonState *s); } PITCommonClass;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
structObjectClass
{
    /*< private >*/
    Typetype;
    GSList*interfaces;
    ObjectUnparent*unparent;
};
typedefstructDeviceClass{
    /*< private >*/
    ObjectClassparent_class;
    /*< public >*/
    constchar*fw_name;
    constchar*desc;
    Property*props;
    intno_user;
    /* callbacks */
    void(*reset)(DeviceState*dev);
    DeviceRealizerealize;
    DeviceUnrealizeunrealize;
    /* device state */
    conststructVMStateDescription*vmsd;
    /* Private to qdev / bus.  */
    qdev_initfninit;/* TODO remove, once users are converted to realize */
    qdev_eventunplug;
    qdev_eventexit;
    constchar*bus_type;
}DeviceClass;
typedefstructISADeviceClass{
    DeviceClassparent_class;
    int(*init)(ISADevice*dev);
}ISADeviceClass;
typedefstructPITCommonClass{
    ISADeviceClassparent_class;
    int(*init)(PITCommonState*s);
    void(*set_channel_gate)(PITCommonState*s,PITChannelState*sc,intval);
    void(*get_channel_info)(PITCommonState*s,PITChannelState*sc,
        PITChannelInfo*info);
    void(*pre_save)(PITCommonState*s);
    void(*post_load)(PITCommonState*s);
}PITCommonClass;

下層定義包含上層,很明顯的繼承模型,ObjectClass更像C++的CLASS,而Object鏈的定義爲:

struct Object { /*< private >*/ ObjectClass *class; ObjectFree *free; QTAILQ_HEAD(, ObjectProperty) properties; uint32_t ref; Object *parent; }; struct DeviceState { /*< private >*/ Object parent_obj; /*< public >*/ const char *id; bool realized; QemuOpts *opts; int hotplugged; BusState *parent_bus; int num_gpio_out; qemu_irq *gpio_out; int num_gpio_in; qemu_irq *gpio_in; QLIST_HEAD(, BusState) child_bus; int num_child_bus; int instance_id_alias; int alias_required_for_version; }; struct ISADevice { DeviceState qdev; uint32_t isairq[2]; int nirqs; int ioport_id; }; typedef struct PITCommonState { ISADevice dev; MemoryRegion ioports; uint32_t iobase; PITChannelState channels[3]; } PITCommonState;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
structObject
{
    /*< private >*/
    ObjectClass*class;
    ObjectFree*free;
    QTAILQ_HEAD(,ObjectProperty)properties;
    uint32_tref;
    Object*parent;
};
structDeviceState{
    /*< private >*/
    Objectparent_obj;
    /*< public >*/
    constchar*id;
    boolrealized;
    QemuOpts*opts;
    inthotplugged;
    BusState*parent_bus;
    intnum_gpio_out;
    qemu_irq*gpio_out;
    intnum_gpio_in;
    qemu_irq*gpio_in;
    QLIST_HEAD(,BusState)child_bus;
    intnum_child_bus;
    intinstance_id_alias;
    intalias_required_for_version;
};
structISADevice{
    DeviceStateqdev;
    uint32_tisairq[2];
    intnirqs;
    intioport_id;
};
typedefstructPITCommonState{
    ISADevicedev;
    MemoryRegionioports;
    uint32_tiobase;
    PITChannelStatechannels[3];
}PITCommonState;

有了ObjectClass爲什麼還要有個Object?從代碼看,ObjectClass只有一份實例,而Object是可以多個實例 的,Object引用ObjectClass獲得ObjectClass的特徵,但是同時又節約了初始化和存放ObjectClass的CPU和空間,相 同的ObjectClass可以被多個Object引用,例如scsi-disk.c裏面有"scsi-hd","scsi-cd","scsi- block","scsi-disk"四種Object共同引用了"scsi-device"。這裏可以想象成C++的虛繼承,ObjectClass是 virtual class而Object是class。其實兩者是可以柔和在一起的,Object也有對應的繼承關係,用來保存特定屬性。

Figure 2 ObjectClass和Object 關係

Object和ObjectClass的初始化

上面講的Object和ObjectClass主要是完成一個對象繼承模型,從代碼看QEMU的這個模型實現並不非常很優雅,封裝不夠徹底,就像你媽給你做了條褲子,卻沒有做褲腰帶,還得提着上路。
Object和ObjectClass的初始化方式並不一致,需要分別初始化,ObjectClass通常使用object_class_by_name 獲取,此函數會根據提供的KEY去查找TypeImpl並初始化ObjectClass指針;而Object的初始化是使用的object_new,通過 參數KEY查找TypeImpl然後malloc 實例。以qdev_try_create獲取"isa-pit"的Object DeviceState實例來說,其獲取DeviceState的函數如此定義:

DeviceState *qdev_try_create(BusState *bus, const char *type) { DeviceState *dev; //這個type爲TypeInfo.name,例如"isa-pit" if (object_class_by_name(type) == NULL) { return NULL; } //type_initialize完成後,object_new用來實例化一個instance dev = DEVICE(object_new(type));// = DEVICE(object_new_with_type(type_get_by_name(typename))) if (!dev) { return NULL; } if (!bus) { bus = sysbus_get_default(); } qdev_set_parent_bus(dev, bus); object_unref(OBJECT(dev)); return dev; } ObjectClass *object_class_by_name(const char *typename) { //之前在type_register_static的時候,註冊了TypeInfo.name,例如"isa-pit"爲key的TypeImpl TypeImpl *type = type_get_by_name(typename); if (!type) { return NULL; } type_initialize(type); //這裏面,初始化class, return type->class; } //其實這個函數更應該叫做new_TypeInfo_class() static void type_initialize(TypeImpl *ti) { TypeImpl *parent; if (ti->class) { return; } /* type_class_get_size 首先獲取自己的class_size變量,如果沒有,再找parent類型所指的TypeImpl的class_size,直到找到爲止 比如"isa-pit"沒有設置class_size,那麼獲取的是"pit-common"的class_size, 而type_object_get_size也是類似 static const TypeInfo pit_common_type = { .name = "pit-common", .parent = "isa-device", .instance_size = sizeof(PITCommonState), .class_size = sizeof(PITCommonClass), .class_init = pit_common_class_init, .abstract = true, }; */ ti->class_size = type_class_get_size(ti); ti->instance_size = type_object_get_size(ti); ti->class = g_malloc0(ti->class_size); parent = type_get_parent(ti); if (parent) { //1,保證parent初始化了 type_initialize(parent); GSList *e; int i; //2,將parent的class內容memcpy一份給自己的對應的parent區域 g_assert(parent->class_size <= ti->class_size); memcpy(ti->class, parent->class, parent->class_size); //3,將parent裏面的class的interfaces做一次深度複製,複製給自己 for (e = parent->class->interfaces; e; e = e->next) { ObjectClass *iface = e->data; type_initialize_interface(ti, object_class_get_name(iface)); } //4.如果本類型有自己的interfaces,初始化 for (i = 0; i < ti->num_interfaces; i++) { TypeImpl *t = type_get_by_name(ti->interfaces[i].typename); for (e = ti->class->interfaces; e; e = e->next) { TypeImpl *target_type = OBJECT_CLASS(e->data)->type; if (type_is_ancestor(target_type, t)) { break; } } if (e) { continue; } type_initialize_interface(ti, ti->interfaces[i].typename); } } ti->class->type = ti; while (parent) { if (parent->class_base_init) { //回溯回調parent的class_base_init函數 parent->class_base_init(ti->class, ti->class_data); } parent = type_get_parent(parent); } if (ti->class_init) { /* 如果本類設置了class_init,回調它,ti->class_data是一個void*的參數 比如"isa-pit"我們設置了pit_class_initfn 這個函數主要幹啥?主要填充class裏的其他該填充的地方。 malloc之後你總得調用構造函數吧,調用構造函數的第一句都是super(xxx) 這工作前面,2,3步驟已經做了,然後幹你自己的活。見pit_class_initfn定義 */ ti->class_init(ti->class, ti->class_data); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
DeviceState*qdev_try_create(BusState*bus,constchar*type)
{
    DeviceState*dev;
    //這個type爲TypeInfo.name,例如"isa-pit"
    if(object_class_by_name(type)==NULL){
        returnNULL;
    }
    //type_initialize完成後,object_new用來實例化一個instance
    dev=DEVICE(object_new(type));// = DEVICE(object_new_with_type(type_get_by_name(typename)))
    if(!dev){
        returnNULL;
    }
    if(!bus){
        bus=sysbus_get_default();
    }
    qdev_set_parent_bus(dev,bus);
    object_unref(OBJECT(dev));
    returndev;
}
 
ObjectClass*object_class_by_name(constchar*typename)
{
    //之前在type_register_static的時候,註冊了TypeInfo.name,例如"isa-pit"爲key的TypeImpl
    TypeImpl*type=type_get_by_name(typename);
    if(!type){
        returnNULL;
    }
    type_initialize(type);//這裏面,初始化class,
    returntype->class;
}
 
//其實這個函數更應該叫做new_TypeInfo_class()
staticvoidtype_initialize(TypeImpl*ti)
{
    TypeImpl*parent;
    if(ti->class){
        return;
    }
    /*
    type_class_get_size 首先獲取自己的class_size變量,如果沒有,再找parent類型所指的TypeImpl的class_size,直到找到爲止
    比如"isa-pit"沒有設置class_size,那麼獲取的是"pit-common"的class_size, 而type_object_get_size也是類似
    static const TypeInfo pit_common_type = {
        .name          = "pit-common",
        .parent        = "isa-device",
        .instance_size = sizeof(PITCommonState),
        .class_size    = sizeof(PITCommonClass),
        .class_init    = pit_common_class_init,
        .abstract      = true,
    };
    */
    ti->class_size=type_class_get_size(ti);
    ti->instance_size=type_object_get_size(ti);
    ti->class=g_malloc0(ti->class_size);
    parent=type_get_parent(ti);
    if(parent){
        //1,保證parent初始化了
        type_initialize(parent);
        GSList*e;
        inti;
        
        //2,將parent的class內容memcpy一份給自己的對應的parent區域
        g_assert(parent->class_size<=ti->class_size);
        memcpy(ti->class,parent->class,parent->class_size);
 
        //3,將parent裏面的class的interfaces做一次深度複製,複製給自己
        for(e=parent->class->interfaces;e;e=e->next){
            ObjectClass*iface=e->data;
            type_initialize_interface(ti,object_class_get_name(iface));
        }
        
        //4.如果本類型有自己的interfaces,初始化
        for(i=0;inum_interfaces;i++){
            TypeImpl*t=type_get_by_name(ti->interfaces[i].typename);
            for(e=ti->class->interfaces;e;e=e->next){
                TypeImpl*target_type=OBJECT_CLASS(e->data)->type;
                if(type_is_ancestor(target_type,t)){
                    break;
                }
            }
            if(e){
                continue;
            }
            type_initialize_interface(ti,ti->interfaces[i].typename);
        }
    }
 
    ti->class->type=ti;
    while(parent){
        if(parent->class_base_init){
            //回溯回調parent的class_base_init函數
            parent->class_base_init(ti->class,ti->class_data);
        }
        parent=type_get_parent(parent);
    }
 
    if(ti->class_init){
        /*
        如果本類設置了class_init,回調它,ti->class_data是一個void*的參數
        比如"isa-pit"我們設置了pit_class_initfn
        這個函數主要幹啥?主要填充class裏的其他該填充的地方。        
        malloc之後你總得調用構造函數吧,調用構造函數的第一句都是super(xxx)
        這工作前面,2,3步驟已經做了,然後幹你自己的活。見pit_class_initfn定義
        */
        ti->class_init(ti->class,ti->class_data);
    }
}

上述代碼把object_class_by_name的流程說完了,再看看object_new(type) = object_new_with_type(type_get_by_name(typename))的流程:

Object *object_new_with_type(Type type) { Object *obj; g_assert(type != NULL); type_initialize(type); obj = g_malloc(type->instance_size); //這個instance_size是初始化TypeInfo的時候設置的sizeof(PITCommonState) object_initialize_with_type(obj, type); obj->free = g_free; return obj; } void object_initialize_with_type(void *data, TypeImpl *type) { Object *obj = data; g_assert(type != NULL); type_initialize(type); g_assert(type->instance_size >= sizeof(Object)); g_assert(type->abstract == false); memset(obj, 0, type->instance_size); obj->class = type->class; //instace的類型通過class指針指定 object_ref(obj); QTAILQ_INIT(&obj->properties); object_init_with_type(obj, type); //深度遞歸調用TypeImpl及其parent的instance_init函數指針,相當於new instance的構造函數 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Object*object_new_with_type(Typetype)
{
    Object*obj;
    g_assert(type!=NULL);
    type_initialize(type);
    obj=g_malloc(type->instance_size);//這個instance_size是初始化TypeInfo的時候設置的sizeof(PITCommonState)
    object_initialize_with_type(obj,type);
    obj->free=g_free;
    returnobj;
}
voidobject_initialize_with_type(void*data,TypeImpl*type)
{
    Object*obj=data;
    g_assert(type!=NULL);
    type_initialize(type);
    g_assert(type->instance_size>=sizeof(Object));
    g_assert(type->abstract==false);
    memset(obj,0,type->instance_size);
    obj->class=type->class;//instace的類型通過class指針指定
    object_ref(obj);
    QTAILQ_INIT(&obj->properties);
    object_init_with_type(obj,type);//深度遞歸調用TypeImpl及其parent的instance_init函數指針,相當於new instance的構造函數
}

qdev_try_create->object_class_by_name->type_initialize的調用流程,如果父 ObjectClass沒初始化,會初始化父ObjectClass,此時調用到父ObjectClass對應name的TypeImpl的 class_init函數,例如"pit-common"的class_init回調pit_common_class_init,此回調會設置 ISADeviceClass的init回調爲pit_init_common。

Object類型轉換

再解釋下Object裏常用的一個宏:OBJECT_CHECK,以ISA_DEVICE這段代碼爲例:

static const TypeInfo mc146818rtc_info = { .name = "mc146818rtc", .parent = "isa-device", .instance_size = sizeof(RTCState), .class_init = rtc_class_initfn, }; ISADevice *isa_create(ISABus *bus, const char *name) { DeviceState *dev; if (!bus) { hw_error("Tried to create isa device %s with no isa bus present.", name); } dev = qdev_create(&bus->qbus, name); return ISA_DEVICE(dev);//毫無疑問,"mc146818rtc"肯定可以轉換爲"isa-device",why?見mc146818rtc_info定義 } ISADevice *dev = isa_create(bus, "mc146818rtc");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
staticconstTypeInfomc146818rtc_info={
    .name          ="mc146818rtc",
    .parent        ="isa-device",
    .instance_size=sizeof(RTCState),
    .class_init    =rtc_class_initfn,
};
ISADevice*isa_create(ISABus*bus,constchar*name)
{
    DeviceState*dev;
    if(!bus){
        hw_error("Tried to create isa device %s with no isa bus present.",name);
    }
    dev=qdev_create(&bus->qbus,name);    
    returnISA_DEVICE(dev);//毫無疑問,"mc146818rtc"肯定可以轉換爲"isa-device",why?見mc146818rtc_info定義
}
ISADevice*dev=isa_create(bus,"mc146818rtc");

這裏的ISA_DEVICE體現了OBJECT的類型轉換功能,宏定義爲:

#define OBJECT(obj) \ ((Object *)(obj)) #define OBJECT_CHECK(type, obj, name) \ ((type *)object_dynamic_cast_assert(OBJECT(obj), (name))) #define ISA_DEVICE(obj) \ OBJECT_CHECK(ISADevice, (obj), TYPE_ISA_DEVICE)
1
2
3
4
5
6
#define OBJECT(obj) \
    ((Object*)(obj))
#define OBJECT_CHECK(type, obj, name) \
    ((type*)object_dynamic_cast_assert(OBJECT(obj),(name)))
#define ISA_DEVICE(obj) \
    OBJECT_CHECK(ISADevice,(obj),TYPE_ISA_DEVICE)

展開後爲:

#define ISA_DEVICE(dev) (ISADevice*)object_class_dynamic_cast(((Object *)dev)->class, "isa-device")
1
#define ISA_DEVICE(dev) (ISADevice*)object_class_dynamic_cast(((Object *)dev)->class, "isa-device")

object.c裏面定義了object_class_dynamic_cast函數,其實此函數功能比較簡單,就是通過遍歷parent看當前class是否有一個祖先是typename,其定義如下:

ObjectClass *object_class_dynamic_cast(ObjectClass *class, const char *typename) { TypeImpl *target_type = type_get_by_name(typename); //找到"isa-device"對應的TypeImpl* TypeImpl *type = class->type; //本ObjectClass真實的TypeImpl*,其實這裏是"mc146818rtc" ObjectClass *ret = NULL; /* (gdb) p *target_type $31 = {name = 0x5555566d0b20 "isa-device", class_size = 128, instance_size = 160, class_init = 0x55555568ddd0 , class_base_init = 0, class_finalize = 0, class_data = 0x0, instance_init = 0, instance_finalize = 0, abstract = true, parent = 0x5555566d0a40 "device", parent_type = 0x5555566da730, class = 0x555556a14750, num_interfaces = 0, interfaces = {{typename = 0x0} }} (gdb) p *type_interface $33 = {name = 0x5555566e40b0 "interface", class_size = 32, instance_size = 0, class_init = 0, class_base_init = 0, class_finalize = 0, class_data = 0x0, instance_init = 0, instance_finalize = 0, abstract = true, parent = 0x0, parent_type = 0x0, class = 0x0, num_interfaces = 0, interfaces = {{ typename = 0x0} }} type_is_ancestor用於判斷,"interface"是否是"isa-device"的祖先(判斷方法是遞歸遍歷"isa-device" 的parent,比較是否有"interface"),如果是,那麼要考慮interface的情況,這裏不是,且 type->num_interfaces爲0 */ if (type->num_interfaces && type_is_ancestor(target_type, type_interface)) { int found = 0; GSList *i; for (i = class->interfaces; i; i = i->next) { ObjectClass *target_class = i->data; if (type_is_ancestor(target_class->type, target_type)) { ret = target_class; found++; } } /* The match was ambiguous, don't allow a cast */ if (found > 1) { ret = NULL; } } else if (type_is_ancestor(type, target_type)) { /* 判斷type="mc146818rtc"的祖先是否是target_type="isa-device", 如果是,這裏表示子類class="mc146818rtc"能成功轉換爲父類typename="isa-device"所指的ObjectClass */ ret = class; } return ret; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
ObjectClass*object_class_dynamic_cast(ObjectClass*class,
                                      constchar*typename)
{
    TypeImpl*target_type=type_get_by_name(typename);//找到"isa-device"對應的TypeImpl*
    TypeImpl*type=class->type;//本ObjectClass真實的TypeImpl*,其實這裏是"mc146818rtc"
    ObjectClass*ret=NULL;    
    /*
    (gdb) p *target_type
$31 = {name = 0x5555566d0b20 "isa-device", class_size = 128, instance_size = 160, class_init = 0x55555568ddd0 , class_base_init = 0,
  class_finalize = 0, class_data = 0x0, instance_init = 0, instance_finalize = 0, abstract = true, parent = 0x5555566d0a40 "device", parent_type = 0x5555566da730,
  class = 0x555556a14750, num_interfaces = 0, interfaces = {{typename = 0x0} }}
    (gdb) p *type_interface
$33 = {name = 0x5555566e40b0 "interface", class_size = 32, instance_size = 0, class_init = 0, class_base_init = 0, class_finalize = 0, class_data = 0x0,
  instance_init = 0, instance_finalize = 0, abstract = true, parent = 0x0, parent_type = 0x0, class = 0x0, num_interfaces = 0, interfaces = {{
      typename = 0x0} }}
    type_is_ancestor 用於判斷,"interface"是否是"isa-device"的祖先(判斷方法是遞歸遍歷"isa-device"的parent,比較是否 有"interface"),如果是,那麼要考慮interface的情況,這裏不是,且type->num_interfaces爲0
    */
    if(type->num_interfaces&&type_is_ancestor(target_type,type_interface)){
        intfound=0;
        GSList*i;
        for(i=class->interfaces;i;i=i->next){
            ObjectClass*target_class=i->data;
            if(type_is_ancestor(target_class->type,target_type)){
                ret=target_class;
                found++;
            }
        }
        /* The match was ambiguous, don't allow a cast */
        if(found>1){
            ret=NULL;
        }
    }elseif(type_is_ancestor(type,target_type)){
        /*
        判斷type="mc146818rtc"的祖先是否是target_type="isa-device",
        如果是,這裏表示子類class="mc146818rtc"能成功轉換爲父類typename="isa-device"所指的ObjectClass
        */
        ret=class;
    }
    returnret;
}

GDB顯示其內部數據爲:

(gdb) p *obj //Object *obj
$11 = {class = 0x555556a50e50, free = 0x7ffff7424020 , properties = {tqh_first = 0x555556a17da0, tqh_last = 0x555556a50fd0}, ref = 1, parent = 0x0}
(gdb) p *obj->class //ObjectClass * class
$12 = {type = 0x5555566e5cb0, interfaces = 0x0, unparent = 0x5555556b1ac0 }
(gdb) p *obj->class->type //struct TypeImpl * type
$13 = {name = 0x5555566e5e30 "mc146818rtc", class_size = 128, instance_size = 664, class_init = 0x55555579b790 , class_base_init = 0,
class_finalize = 0, class_data = 0x0, instance_init = 0, instance_finalize = 0, abstract = false, parent = 0x5555566e5e50 "isa-device",
parent_type = 0x5555566d8bd0, class = 0x555556a50e50, num_interfaces = 0, interfaces = {{typename = 0x0} }}

QOM設備初始化

基於Object和ObjectClass實現的QOM設備,何時觸發他的初始化,以PIT爲例,將之前的Object和ObjectClass想象成C++,那麼PIT對應的PitCommonState定義應該類似如下所示:

class PITCommonClass : public ISADeviceClass { public: virtual int init(PITCommonState *s) = 0; }; class ISADevice : public DeviceState { public: int nirqs; int ioport_id; }; class PITCommonState : public ISADevice, public PITCommonClass { int init(PITCommonState *s); };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
classPITCommonClass:publicISADeviceClass{
public:
    virtualintinit(PITCommonState*s)=0;
};
 
classISADevice:publicDeviceState{
public:
    intnirqs;
    intioport_id;
};
 
classPITCommonState:publicISADevice,publicPITCommonClass{
    intinit(PITCommonState*s);
};

看吧,QEMU繞了這麼大一個圈子,就想實現這樣一個結構,所以有的時候用C++還是有好處的(雖然本人生理週期現正處於不太喜歡C++時間)。
那麼,何處調用了new PITCommonState()?
這得從main函數開始看,main函數裏面,有machine->init(&args);函數調用,這是對註冊的machine的初始 化,而默認的machine是在pc_piix.c裏面pc_machine_init函數註冊的第一個machine,即:

static QEMUMachine pc_i440fx_machine_v1_4 = { .name = "pc-i440fx-1.4", .alias = "pc", .desc = "Standard PC (i440FX + PIIX, 1996)", .init = pc_init_pci, .max_cpus = 255, .is_default = 1, .default_machine_opts = KVM_MACHINE_OPTIONS, DEFAULT_MACHINE_OPTIONS, }; qemu_register_machine(&pc_i440fx_machine_v1_4);
1
2
3
4
5
6
7
8
9
10
11
staticQEMUMachinepc_i440fx_machine_v1_4={
    .name="pc-i440fx-1.4",
    .alias="pc",
    .desc="Standard PC (i440FX + PIIX, 1996)",
    .init=pc_init_pci,
    .max_cpus=255,
    .is_default=1,
    .default_machine_opts=KVM_MACHINE_OPTIONS,
    DEFAULT_MACHINE_OPTIONS,
};
qemu_register_machine(&pc_i440fx_machine_v1_4);

當main函數調用machine->init時,我的實驗環境默認情況其實就是調用的pc_i440fx_machine_v1_4的初始化回調pc_init_pci -> pc_init1,這個函數主要初始化相關PC硬件:

static void pc_init1(MemoryRegion *system_memory, MemoryRegion *system_io, ram_addr_t ram_size, const char *boot_device, const char *kernel_filename, const char *kernel_cmdline, const char *initrd_filename, const char *cpu_model, int pci_enabled, int kvmclock_enabled) { //CPU類型初始化-> cpu_x86_init -> mce_init/qemu_init_vcpu,初始化VCPU pc_cpus_init(cpu_model); //初始化acpi_tables pc_acpi_init("acpi-dsdt.aml"); if (!xen_enabled()) { //ROM, BIOS, RAM相關初始化 fw_cfg = pc_memory_init(system_memory, kernel_filename, kernel_cmdline, initrd_filename, below_4g_mem_size, above_4g_mem_size, rom_memory, &ram_memory); } //IRQ,初始化 //VGA初始化 pc_vga_init(isa_bus, pci_enabled ? pci_bus : NULL); /* init basic PC hardware */ pc_basic_device_init(isa_bus, gsi, &rtc_state, &floppy, xen_enabled()); //這裏調用pit_init //初始化網卡 pc_nic_init(isa_bus, pci_bus); //初始化硬盤,音頻設備 //初始化cmos數據,比如設置cmos rtc時鐘,是否提供PS/2設備 pc_cmos_init(below_4g_mem_size, above_4g_mem_size, boot_device, floppy, idebus[0], idebus[1], rtc_state); //初始化USB if (pci_enabled && usb_enabled(false)) { pci_create_simple(pci_bus, piix3_devfn + 2, "piix3-usb-uhci"); } } void pc_basic_device_init(ISABus *isa_bus, qemu_irq *gsi, ISADevice **rtc_state, ISADevice **floppy, bool no_vmport) { //初始化HPET //初始化mc146818 rtc //初始化i8042 PIT pit = pit_init(isa_bus, 0x40, pit_isa_irq, pit_alt_irq); //初始化串口,並口 //初始化vmmouse ps2_mouse }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
staticvoidpc_init1(MemoryRegion*system_memory,
                    MemoryRegion*system_io,
                    ram_addr_tram_size,
                    constchar*boot_device,
                    constchar*kernel_filename,
                    constchar*kernel_cmdline,
                    constchar*initrd_filename,
                    constchar*cpu_model,
                    intpci_enabled,
                    intkvmclock_enabled)
{
    //CPU類型初始化-> cpu_x86_init -> mce_init/qemu_init_vcpu,初始化VCPU
    pc_cpus_init(cpu_model);
    //初始化acpi_tables
    pc_acpi_init("acpi-dsdt.aml");
    if(!xen_enabled()){
        //ROM, BIOS, RAM相關初始化
        fw_cfg=pc_memory_init(system_memory,
            kernel_filename,kernel_cmdline,initrd_filename,
            below_4g_mem_size,above_4g_mem_size,
            rom_memory,&ram_memory);
    }
    //IRQ,初始化
    //VGA初始化
    pc_vga_init(isa_bus,pci_enabled?pci_bus:NULL);
    /* init basic PC hardware */
    pc_basic_device_init(isa_bus,gsi,&rtc_state,&floppy,xen_enabled());//這裏調用pit_init
    //初始化網卡
    pc_nic_init(isa_bus,pci_bus);
    //初始化硬盤,音頻設備
    //初始化cmos數據,比如設置cmos rtc時鐘,是否提供PS/2設備
    pc_cmos_init(below_4g_mem_size,above_4g_mem_size,boot_device,
        floppy,idebus[0],idebus[1],rtc_state);
    //初始化USB
    if(pci_enabled&&usb_enabled(false)){
        pci_create_simple(pci_bus,piix3_devfn+2,"piix3-usb-uhci");
    }
}
 
voidpc_basic_device_init(ISABus*isa_bus,qemu_irq*gsi,
                          ISADevice**rtc_state,
                          ISADevice**floppy,
                          boolno_vmport)
{
    //初始化HPET
    //初始化mc146818 rtc
    //初始化i8042 PIT
    pit=pit_init(isa_bus,0x40,pit_isa_irq,pit_alt_irq);
    //初始化串口,並口
    //初始化vmmouse ps2_mouse
}

接下來的流程是pit_init -> isa_create(bus, "isa-pit") -> qdev_create -> qdev_try_create,qdev_try_create的實現在前面已經講了,如上節所述,它分別使用 object_class_by_name和object_new來初始化ObjectClass和Object。

屬性設置

Object同ObjectClass的顯著區別就是Object提供了屬性的概念,以MC146818爲例,其定義時設置了"base_year"和"lost_tick_policy":

static Property mc146818rtc_properties[] = { DEFINE_PROP_INT32("base_year", RTCState, base_year, 1980), DEFINE_PROP_LOSTTICKPOLICY("lost_tick_policy", RTCState, lost_tick_policy, LOST_TICK_DISCARD), DEFINE_PROP_END_OF_LIST(), };
1
2
3
4
5
6
staticPropertymc146818rtc_properties[]={
    DEFINE_PROP_INT32("base_year",RTCState,base_year,1980),
    DEFINE_PROP_LOSTTICKPOLICY("lost_tick_policy",RTCState,
    lost_tick_policy,LOST_TICK_DISCARD),
    DEFINE_PROP_END_OF_LIST(),
};

但是用GDB一看:

(gdb) p *obj->properties.tqh_first
$15 = {name = 0x555556a17df0 "type", type = 0x555556a17e10 "string", get = 0x555555727ae0 , set = 0,
release = 0x555555727980 , opaque = 0x555556a17d80, node = {tqe_next = 0x555556a17e50, tqe_prev = 0x555556a17af0}}
(gdb) p *obj->properties.tqh_first.node.tqe_next
$21 = {name = 0x555556a17ea0 "realized", type = 0x555556a17ec0 "bool", get = 0x5555557279c0 , set = 0x555555727a40 ,
release = 0x555555727940 , opaque = 0x555556a17e30, node = {tqe_next = 0x555556a17ee0, tqe_prev = 0x555556a17dd0}}
(gdb) p *obj->properties.tqh_first.node.tqe_next.node.tqe_next
$22 = {name = 0x555556a17f30 "base_year", type = 0x555556a1be10 "int32", get = 0x5555556af370 , set = 0x5555556afc60 , release = 0,
opaque = 0x555555d4e1a0, node = {tqe_next = 0x555556a50ee0, tqe_prev = 0x555556a17e80}}
(gdb) p *obj->properties.tqh_first.node.tqe_next.node.tqe_next.node.tqe_next
$23 = {name = 0x555556a1be30 "lost_tick_policy", type = 0x555556a1be50 "LostTickPolicy", get = 0x5555556af400 , set = 0x5555556af990 ,
release = 0, opaque = 0x555555d4e1c0, node = {tqe_next = 0x555556a50fa0, tqe_prev = 0x555556a17f10}}
(gdb) p *obj->properties.tqh_first.node.tqe_next.node.tqe_next.node.tqe_next.node.tqe_next
$24 = {name = 0x555556a50f80 "parent_bus", type = 0x55555680b1d0 "link", get = 0x555555729e50 ,
set = 0x55555572a320 , release = 0, opaque = 0x555556a17b30, node = {tqe_next = 0x0, tqe_prev = 0x555556a50f10}}
(gdb) p *obj->properties.tqh_first.node.tqe_next.node.tqe_next.node.tqe_next.node.tqe_next.node.tqe_next
Cannot access memory at address 0x0

事實上卻多了"type" "realized" "parent_bus",這些屬性都是動態添加的。

在"object"類型的,instance_init = object_instance_init回調處,添加了

object_property_add_str(obj, "type", qdev_get_type, NULL, NULL);
1
object_property_add_str(obj,"type",qdev_get_type,NULL,NULL);

在"device"類型的instance_init = device_initfn回調處,添加了

object_property_add_bool(obj, "realized", device_get_realized, device_set_realized, NULL); bject_property_add_link(OBJECT(dev), "parent_bus", TYPE_BUS, (Object **)&dev->parent_bus, NULL);
1
2
3
4
object_property_add_bool(obj,"realized",
                        device_get_realized,device_set_realized,NULL);
bject_property_add_link(OBJECT(dev),"parent_bus",TYPE_BUS,
                        (Object**)&dev->parent_bus,NULL);

設置屬性的時候,調用類似qdev_prop_set_int32(&dev->qdev, "base_year", base_year);進行設置,這裏,注意第一個參數爲什麼是DeviceState* dev->qdev而不是ISADevice *dev?
因爲rtc_class_initfn裏初始化props是給 DeviceClass *dc初始化的,所以對應的應該是DeviceState而不是子類ISADevice。

設置屬性是如何實現的?
以"realized"的bool屬性設置爲例,調用順序爲object_property_set_bool -> object_property_set_qobject -> object_property_set,此函數定義:

void object_property_set(Object *obj, Visitor *v, const char *name, Error **errp) { //obj還是"mc146818rtc"的實例,name爲"realized",object_property_find其實就是查找obj的properties鏈表裏是否存在名字爲name的屬性 ObjectProperty *prop = object_property_find(obj, name, errp); if (prop == NULL) { return; } if (!prop->set) {//如果存在,且沒有設置過set handler,錯誤 error_set(errp, QERR_PERMISSION_DENIED); } else {//"realized"的set函數爲property_set_bool prop->set(obj, v, prop->opaque, name, errp); } } static void property_set_bool(Object *obj, Visitor *v, void *opaque, const char *name, Error **errp) { BoolProperty *prop = opaque; bool value; Error *local_err = NULL; visit_type_bool(v, &value, name, &local_err); if (local_err) { error_propagate(errp, local_err); return; } prop->set(obj, value, errp); //對於realized來說,其實就是調用device_set_realized }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
voidobject_property_set(Object*obj,Visitor*v,constchar*name,
                        Error**errp)
{
    //obj還是"mc146818rtc"的實例,name爲"realized",object_property_find其實就是查找obj的properties鏈表裏是否存在名字爲name的屬性
    ObjectProperty*prop=object_property_find(obj,name,errp);
    if(prop==NULL){
        return;
    }
    if(!prop->set){//如果存在,且沒有設置過set handler,錯誤
        error_set(errp,QERR_PERMISSION_DENIED);
    }else{//"realized"的set函數爲property_set_bool
        prop->set(obj,v,prop->opaque,name,errp);
    }
}
 
staticvoidproperty_set_bool(Object*obj,Visitor*v,void*opaque,
                              constchar*name,Error**errp)
{
    BoolProperty*prop=opaque;
    boolvalue;
    Error*local_err=NULL;
    visit_type_bool(v,&value,name,&local_err);
    if(local_err){
        error_propagate(errp,local_err);
        return;
    }
    prop->set(obj,value,errp);//對於realized來說,其實就是調用device_set_realized
}

而CALLBACK=device_set_realized 又會調用CALLBACK=device_realize。

模塊PIO回調流程

註冊回調

以mc146818 rtc爲例,在rtc_initfn的時候,註冊回調的代碼如下:

static const MemoryRegionOps cmos_ops = { .read = cmos_ioport_read, .write = cmos_ioport_write, .impl = { .min_access_size = 1, .max_access_size = 1, }, .endianness = DEVICE_LITTLE_ENDIAN, }; void isa_register_ioport(ISADevice *dev, MemoryRegion *io, uint16_t start) { memory_region_add_subregion(isabus->address_space_io, start, io); isa_init_ioport(dev, start); } memory_region_init_io(&s->io, &cmos_ops, s, "rtc", 2); isa_register_ioport(dev, &s->io, base);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
staticconstMemoryRegionOpscmos_ops={
    .read=cmos_ioport_read,
    .write=cmos_ioport_write,
    .impl={
        .min_access_size=1,
        .max_access_size=1,
    },
    .endianness=DEVICE_LITTLE_ENDIAN,
};
voidisa_register_ioport(ISADevice*dev,MemoryRegion*io,uint16_tstart)
{
    memory_region_add_subregion(isabus->address_space_io,start,io);
    isa_init_ioport(dev,start);
}
 
memory_region_init_io(&s->io,&cmos_ops,s,"rtc",2);
isa_register_ioport(dev,&s->io,base);

其中s->io是MemoryRegion類型,MemoryRegion是可以像樹一樣,多級掛載,比如,現在將rtc的 MemoryRegion掛載在isabus的address_space_io這個MemoryRegion下,其start參數爲offset在整個 isabus->address_space_io MemoryRegion中的偏移,即0x70,那麼END呢?END在memory_region_init_io的時候已經存儲到 MemoryRegion的size裏面了。

再看看isabus內容,有個更深入的性感的認識:

(gdb) p *isabus
$8 = {qbus = {obj = {class = 0x555556a14e70, free = 0x7ffff7424020 , properties = {tqh_first = 0x555556a14f60, tqh_last = 0x555556a1eab0}, ref = 4,
parent = 0x55555698d740}, parent = 0x55555698d740, name = 0x555556a14ff0 "isa.0", allow_hotplug = 0, max_index = 3, children = {tqh_first = 0x555556a50f30,
tqh_last = 0x555556a16450}, sibling = {le_next = 0x0, le_prev = 0x55555698d7b8}}, address_space_io = 0x555556708930, irqs = 0x55555699c4f0}
(gdb) p *isabus->address_space_io
$9 = {ops = 0x0, opaque = 0x0, parent = 0x0, size = {lo = 65536, hi = 0}, addr = 0, destructor = 0x5555557cb8e0 , ram_addr = 0,
subpage = false, terminates = false, readable = true, ram = false, readonly = false, enabled = true, rom_device = false, warning_printed = false,
flush_coalesced_mmio = false, alias = 0x0, alias_offset = 0, priority = 0, may_overlap = false, subregions = {tqh_first = 0x55555698de48,
tqh_last = 0x55555698bf60}, subregions_link = {tqe_next = 0x0, tqe_prev = 0x0}, coalesced = {tqh_first = 0x0, tqh_last = 0x5555567089b8},
name = 0x5555566f7220 "io", dirty_log_mask = 0 '\000', ioeventfd_nb = 0, ioeventfds = 0x0, updateaddr = 0, updateopaque = 0x0}

回調流程

QEMU通過kvm_cpu_exec -> kvm_vcpu_ioctl(cpu, KVM_RUN, 0) 執行GUEST機CODE,當GUEST遇到IO等操作需要退出,會先在KVM裏處理,KVM不能處理,kvm_vcpu_ioctl就返回,給QEMU 處理,QEMU根據返回的run->exit_reason進行分派,比如PROT READ 0x71操作,退出時其exit_reason爲KVM_EXIT_IO,kvm_handle_io裏,根據direction判斷是read還是 write,根據read的長度,判斷該回調哪個函數。比如0x71 read 1字節的時候,調用的是:
stb_p(ptr, cpu_inb(port));
stb_p是將第二個參數cpu_inb(port)的結果轉換爲一個字節大小賦值給第一個參數ptr所指內存。

cpu_inb (addr=addr@entry=113)
cpu_inb和cpu_inw和cpu_inl是一家人的三兄弟,長得極其神似,我們看cpu_inb,在他調用的ioport_read的時候,第一 個參數叫index=0,這是和他的兄弟cpu_inw, cpu_inl區別開來的關鍵特徵。本來這裏應該沒有index啥事的,但總有人偷懶不設置對應addr的handler,index就是對專門爲這種懶 人擦屁股,沒有handler的時候,給選擇一個默認handler,你大概也看明白了,就index的話,三兄弟的差別在於inb=0, inw=1, inl=2。

uint8_t cpu_inb(pio_addr_t addr) { uint8_t val; val = ioport_read(0, addr); return val; }
1
2
3
4
5
6
uint8_tcpu_inb(pio_addr_taddr)
{
    uint8_tval;
    val=ioport_read(0,addr);
    returnval;
}

read 的address比較重要,例如rtc的0x71,index其實是用來選擇默認handler的,當在對應的ioport_read_table裏面沒有註冊函數的時候就根據index的值,分別選擇readb, readw, readl來做默認操作。

static uint32_t ioport_read(int index, uint32_t address) { static IOPortReadFunc * const default_func[3] = { default_ioport_readb, default_ioport_readw, default_ioport_readl }; IOPortReadFunc *func = ioport_read_table[index][address]; if (!func) func = default_func[index]; /* func一般都是ioport_readb_thunk 關鍵在於ioport_opaque[address]這裏面存放的是不同端口的IORange* 這個ioport_opaque的每個值,都存儲的該端口對應的IORange* 當讀寫此端口的時候,就會找到之前註冊的IORange回調,比如mc146818的IORange爲 (gdb) p *(IORange *)ioport_opaque[0x71] $22 = {ops = 0x7fca4a51c380, base = 112, len = 2} (gdb) p *(IORangeOps*)0x7fca4a51c380 $25 = {read = 0x7fca49fe1190 , write = 0x7fca49fe1050 , destructor = 0x7fca49fdfcb0 } 由於x70,0x71都是readb,所以在mc146818設備的時候,這個func其實爲ioport_readb_thunk (gdb) p ioport_read_table[0][0x70] $67 = (IOPortReadFunc *) 0x5555557c6980 (gdb) p ioport_read_table[0][0x71] $68 = (IOPortReadFunc *) 0x5555557c6980 */ return func(ioport_opaque[address], address); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
staticuint32_tioport_read(intindex,uint32_taddress)
{
    staticIOPortReadFunc*constdefault_func[3]={
        default_ioport_readb,
        default_ioport_readw,
        default_ioport_readl
    };
    IOPortReadFunc*func=ioport_read_table[index][address];
    if(!func)
        func=default_func[index];
    /*
     func一般都是ioport_readb_thunk 關鍵在於ioport_opaque[address]這裏面存放的是不同端口的IORange*
     這個ioport_opaque的每個值,都存儲的該端口對應的IORange*
     當讀寫此端口的時候,就會找到之前註冊的IORange回調,比如mc146818的IORange爲
     (gdb) p *(IORange *)ioport_opaque[0x71]
     $22 = {ops = 0x7fca4a51c380, base = 112, len = 2}
    (gdb) p *(IORangeOps*)0x7fca4a51c380
    $25 = {read = 0x7fca49fe1190 , write = 0x7fca49fe1050 ,
    destructor = 0x7fca49fdfcb0 }
    
    由於x70,0x71都是readb,所以在mc146818設備的時候,這個func其實爲ioport_readb_thunk
(gdb) p ioport_read_table[0][0x70]
$67 = (IOPortReadFunc *) 0x5555557c6980
(gdb) p ioport_read_table[0][0x71]
$68 = (IOPortReadFunc *) 0x5555557c6980
    */
    returnfunc(ioport_opaque[address],address);
}

ioport_readb_thunk (opaque=, addr=)
ioport_register裏面,註冊了對一個字節的ioport read handler爲ioport_readb_thunk,其實這個函數非常簡單
就是調用了ops->read的時候,將width設置爲1,和ioport_readw_thunk,ioport_readl_thunk之類的就差一個width的區別
爲什麼要搞這麼複雜?這是爲了64K的read空間設計的回調,因爲不同的offset位置,我們需要知道是應該調用readb還是readw還是readl。

static IOPortReadFunc *ioport_read_table[3][64 * 1024] static uint32_t ioport_readb_thunk(void *opaque, uint32_t addr) { IORange *ioport = opaque; uint64_t data; //read is memory_region_iorange_read when input char to ps/2 keyboard //比如,mc146818的時候,addr爲x71,ioport->base爲x71, ioport->len=2 ioport->ops->read(ioport, addr - ioport->base, 1, &data); return data; }
1
2
3
4
5
6
7
8
9
10
staticIOPortReadFunc*ioport_read_table[3][64*1024]
staticuint32_tioport_readb_thunk(void*opaque,uint32_taddr)
{
    IORange*ioport=opaque;
    uint64_tdata;
    //read is memory_region_iorange_read when input char to ps/2 keyboard
    //比如,mc146818的時候,addr爲x71,ioport->base爲x71, ioport->len=2
    ioport->ops->read(ioport,addr-ioport->base,1,&data);
    returndata;
}

上面的回調爲memory_region_iorange_read (iorange=0x55555680b1a0, offset=1, width=1, data=0x7fffec1fdc00)

static const MemoryRegionOps cmos_ops = { .read = cmos_ioport_read, .write = cmos_ioport_write, .impl = { .min_access_size = 1, .max_access_size = 1, }, .endianness = DEVICE_LITTLE_ENDIAN, }; static void memory_region_iorange_read(IORange *iorange, uint64_t offset, unsigned width, uint64_t *data) { MemoryRegionIORange *mrio = container_of(iorange, MemoryRegionIORange, iorange); /* 一段MemoryRegionIORange裏包含了IORange iorange和MemoryRegion* mr (gdb) p *mrio $58 = {iorange = {ops = 0x555555d16200, base = 0x70, len = 2}, mr = 0x555556a17b80, offset = 0} (gdb) p *mrio->iorange.ops $59 = {read = 0x5555557ccf50 , write = 0x5555557cce10 , destructor = 0x5555557cba70 } (gdb) p *mrio->mr $60 = {ops = 0x555555d14ba0, opaque = 0x555556a17ae0, parent = 0x555556708930, size = {lo = 2, hi = 0}, addr = 112, destructor = 0x5555557cb910 , ram_addr = 18446744073709551615, subpage = false, terminates = true, readable = true, ram = false, readonly = false, enabled = true, rom_device = false, warning_printed = false, flush_coalesced_mmio = false, alias = 0x0, alias_offset = 0, priority = 0, may_overlap = false, subregions = {tqh_first = 0x0, tqh_last = 0x555556a17be8}, subregions_link = {tqe_next = 0x555556a50d80, tqe_prev = 0x555556a1c1f8}, coalesced = {tqh_first = 0x0, tqh_last = 0x555556a17c08}, name = 0x55555680b300 "rtc", dirty_log_mask = 0 '\000', ioeventfd_nb = 0, ioeventfds = 0x0, updateaddr = 0, updateopaque = 0x0} (gdb) p *mrio->mr->ops $61 = {read = 0x55555579ea20 , write = 0x55555579e040 , endianness = DEVICE_LITTLE_ENDIAN, valid = {min_access_size = 0, max_access_size = 0, unaligned = false, accepts = 0}, impl = {min_access_size = 1, max_access_size = 1, unaligned = false}, old_portio = 0x0, old_mmio = { read = {0, 0, 0}, write = {0, 0, 0}}} */ MemoryRegion *mr = mrio->mr; //如果mrio還有offset,要加上這個偏移,這個offset其實是當成地址來用的,比如,我認爲read 0x71應該在已有offset=1的基礎上加上x70,但是他沒加 offset += mrio->offset; if (mr->ops->old_portio) {//對"mc146818rtc"已經沒有old_portio的CALLBACK了,跳過 const MemoryRegionPortio *mrp = find_portio(mr, offset - mrio->offset, width, false); *data = ((uint64_t)1 << (width * 8)) - 1; if (mrp) { *data = mrp->read(mr->opaque, offset); } else if (width == 2) { mrp = find_portio(mr, offset - mrio->offset, 1, false); assert(mrp); *data = mrp->read(mr->opaque, offset) | (mrp->read(mr->opaque, offset + 1) << 8); } return; } *data = 0;//這是read後的返回值存儲區域,提前清零 access_with_adjusted_size(offset, data, width, mr->ops->impl.min_access_size, //這個min_access_size和max_access_size是在設置ops的時候定義的,見cmos_ops mr->ops->impl.max_access_size, memory_region_read_accessor, mr); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
staticconstMemoryRegionOpscmos_ops={
    .read=cmos_ioport_read,
    .write=cmos_ioport_write,
    .impl={
        .min_access_size=1,
        .max_access_size=1,
    },
    .endianness=DEVICE_LITTLE_ENDIAN,
};
 
staticvoidmemory_region_iorange_read(IORange*iorange,
                                      uint64_toffset,
                                      unsignedwidth,
                                      uint64_t*data)
{
    MemoryRegionIORange*mrio
        =container_of(iorange,MemoryRegionIORange,iorange);
    /*
    一段MemoryRegionIORange裏包含了IORange iorange和MemoryRegion* mr
(gdb) p *mrio
$58 = {iorange = {ops = 0x555555d16200, base = 0x70, len = 2}, mr = 0x555556a17b80, offset = 0}
(gdb) p *mrio->iorange.ops
$59 = {read = 0x5555557ccf50 , write = 0x5555557cce10 ,
  destructor = 0x5555557cba70 }
(gdb) p *mrio->mr
$60 = {ops = 0x555555d14ba0, opaque = 0x555556a17ae0, parent = 0x555556708930, size = {lo = 2, hi = 0}, addr = 112,
  destructor = 0x5555557cb910 , ram_addr = 18446744073709551615, subpage = false, terminates = true, readable = true, ram = false,
  readonly = false, enabled = true, rom_device = false, warning_printed = false, flush_coalesced_mmio = false, alias = 0x0, alias_offset = 0, priority = 0,
  may_overlap = false, subregions = {tqh_first = 0x0, tqh_last = 0x555556a17be8}, subregions_link = {tqe_next = 0x555556a50d80, tqe_prev = 0x555556a1c1f8},
  coalesced = {tqh_first = 0x0, tqh_last = 0x555556a17c08}, name = 0x55555680b300 "rtc", dirty_log_mask = 0 '\000', ioeventfd_nb = 0, ioeventfds = 0x0,
  updateaddr = 0, updateopaque = 0x0}
(gdb) p *mrio->mr->ops
$61 = {read = 0x55555579ea20 , write = 0x55555579e040 , endianness = DEVICE_LITTLE_ENDIAN, valid = {min_access_size = 0,
    max_access_size = 0, unaligned = false, accepts = 0}, impl = {min_access_size = 1, max_access_size = 1, unaligned = false}, old_portio = 0x0, old_mmio = {
    read = {0, 0, 0}, write = {0, 0, 0}}}    
    */
    MemoryRegion*mr=mrio->mr;
 
    //如果mrio還有offset,要加上這個偏移,這個offset其實是當成地址來用的,比如,我認爲read 0x71應該在已有offset=1的基礎上加上x70,但是他沒加
    offset+=mrio->offset;
    if(mr->ops->old_portio){//對"mc146818rtc"已經沒有old_portio的CALLBACK了,跳過
        constMemoryRegionPortio*mrp=find_portio(mr,offset-mrio->offset,
                                                    width,false);
 
        *data=((uint64_t)1<<(width*8))-1;
        if(mrp){
            *data=mrp->read(mr->opaque,offset);
        }elseif(width==2){
            mrp=find_portio(mr,offset-mrio->offset,1,false);
            assert(mrp);
            *data=mrp->read(mr->opaque,offset)|
                    (mrp->read(mr->opaque,offset+1)<<8);
        }
        return;
    }
    *data=0;//這是read後的返回值存儲區域,提前清零
    access_with_adjusted_size(offset,data,width,
                              mr->ops->impl.min_access_size,//這個min_access_size和max_access_size是在設置ops的時候定義的,見cmos_ops
                              mr->ops->impl.max_access_size,
                              memory_region_read_accessor,mr);
}

從參數看,這裏的access參數爲memory_region_read_accessor,而這個value參數,用來存放read的返回值。接下來進入
access_with_adjusted_size (addr=addr@entry=1, value=value@entry=0x7fffec1fdc00, size=1, access_size_min=, access_size_max=, access=access@entry=0x5555557cbf70 , opaque=opaque@entry=0x555556a17b80),其access參數非常重要,繼續回調的就是access。

static void access_with_adjusted_size(hwaddr addr, uint64_t *value, unsigned size, unsigned access_size_min, unsigned access_size_max, void (*access)(void *opaque, hwaddr addr, uint64_t *value, unsigned size, unsigned shift, uint64_t mask), void *opaque) { uint64_t access_mask; unsigned access_size; unsigned i; if (!access_size_min) { access_size_min = 1; } if (!access_size_max) { access_size_max = 4; } access_size = MAX(MIN(size, access_size_max), access_size_min);//size其實在參數裏面已經指定了,但是爲了安全,要確保access_size區間爲[1,4]字節 access_mask = -1ULL >> (64 - access_size * 8);//作爲mask,對Read的幾個mask,確保結果大小爲預期大小 for (i = 0; i < size; i += access_size) { /* 最大返回結果其實只有sizeof(value) = 64bit,這裏的設計是,每次取一個字節的返回結果 但是access_size可以不爲bit,比如read 0x100,假設read範圍爲bit,就是x100-0x104,access_size可以爲, 這樣就分兩步走,第一步Read 0x100-0x102返回個字節的結果,存儲到value的低字節 第二步Read 0x103-0x104返回個字節的結果,存儲到value的高字節 最後返回的value就存儲了兩次的Read值,只佔用了bit,不會超過bit */ access(opaque, addr + i, value, access_size, i * 8, access_mask); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
staticvoidaccess_with_adjusted_size(hwaddraddr,
                                      uint64_t*value,
                                      unsignedsize,
                                      unsignedaccess_size_min,
                                      unsignedaccess_size_max,
                                      void(*access)(void*opaque,
                                      hwaddraddr,
                                      uint64_t*value,
                                      unsignedsize,
                                      unsignedshift,
                                      uint64_tmask),
                                      void*opaque)
{
    uint64_taccess_mask;
    unsignedaccess_size;
    unsignedi;
 
    if(!access_size_min){
        access_size_min=1;
    }
    if(!access_size_max){
        access_size_max=4;
    }
    access_size=MAX(MIN(size,access_size_max),access_size_min);//size其實在參數裏面已經指定了,但是爲了安全,要確保access_size區間爲[1,4]字節
    access_mask=-1ULL>>(64-access_size*8);//作爲mask,對Read的幾個mask,確保結果大小爲預期大小
    for(i=0;i<size;i+=access_size){ <="" div="" style="word-wrap: break-word;">
        /*
          最大返回結果其實只有sizeof(value) = 64bit,這裏的設計是,每次取一個字節的返回結果
           但是access_size可以不爲bit,比如read 0x100,假設read範圍爲bit,就是x100-0x104,access_size可以爲,
           這樣就分兩步走,第一步Read 0x100-0x102返回個字節的結果,存儲到value的低字節
          第二步Read 0x103-0x104返回個字節的結果,存儲到value的高字節
         最後返回的value就存儲了兩次的Read值,只佔用了bit,不會超過bit
        */
        access(opaque,addr+i,value,access_size,i*8,access_mask);
    }
}

上面的access_with_adjusted_size的access參數其實就是memory_region_read_accessor,可以通過GDB打印出來:

memory_region_read_accessor (opaque=0x555556a17b80, addr=, value=0x7fffec1fdc00, size=1, shift=0, mask=255)
(gdb) p *mr
$52 = {ops = 0x555555d14ba0, opaque = 0x555556a17ae0, parent = 0x555556708930, size = {lo = 2, hi = 0}, addr = 112,
destructor = 0x5555557cb910 , ram_addr = 18446744073709551615, subpage = false, terminates = true, readable = true, ram = false,
readonly = false, enabled = true, rom_device = false, warning_printed = false, flush_coalesced_mmio = false, alias = 0x0, alias_offset = 0, priority = 0,
may_overlap = false, subregions = {tqh_first = 0x0, tqh_last = 0x555556a17be8}, subregions_link = {tqe_next = 0x555556a50d80, tqe_prev = 0x555556a1c1f8},
coalesced = {tqh_first = 0x0, tqh_last = 0x555556a17c08}, name = 0x55555680b300 "rtc", dirty_log_mask = 0 '\000', ioeventfd_nb = 0, ioeventfds = 0x0,
updateaddr = 0, updateopaque = 0x0}
(gdb) p *mr->ops
$53 = {read = 0x55555579ea20 , write = 0x55555579e040 , endianness = DEVICE_LITTLE_ENDIAN, valid = {min_access_size = 0,
max_access_size = 0, unaligned = false, accepts = 0}, impl = {min_access_size = 1, max_access_size = 1, unaligned = false}, old_portio = 0x0, old_mmio = {
read = {0, 0, 0}, write = {0, 0, 0}}}

繞了這麼多,memory_region_read_accessor裏的mr->ops->read終於到了我們註冊的函數,如 mc146818的cmos_ioport_read (opaque=0x555556a17ae0, addr=1, size=1)

static void memory_region_read_accessor(void *opaque, hwaddr addr, uint64_t *value, unsigned size, unsigned shift, uint64_t mask) { MemoryRegion *mr = opaque; uint64_t tmp; if (mr->flush_coalesced_mmio) { qemu_flush_coalesced_mmio_buffer(); } tmp = mr->ops->read(mr->opaque, addr, size); *value |= (tmp & mask) << shift; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
staticvoidmemory_region_read_accessor(void*opaque,
                                        hwaddraddr,
                                        uint64_t*value,
                                        unsignedsize,
                                        unsignedshift,
                                        uint64_tmask)
{
    MemoryRegion*mr=opaque;
    uint64_ttmp;
 
    if(mr->flush_coalesced_mmio){
        qemu_flush_coalesced_mmio_buffer();
    }
    tmp=mr->ops->read(mr->opaque,addr,size);
    *value|=(tmp&mask)<<shift; <="" div="" style="word-wrap: break-word;">
}
read/write回調函數,就是純功能邏輯,比如mc146818主要幹注入時鐘,寫入寄存器,讀取日期
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章