QEMU學習筆記-源碼分析01-module infrastracture
前面的話
只是一個開始,希望自己能通過這樣的方法,歸納整理,方便自己和需要的人(當然只是自己的理解,歡迎指正和討論)
最開始搜QEMU的時候,就有很多人貼出了關於QEMU的源碼框架結構了,所以那些我這裏就不再多說了。
這裏主要整理的關於Module的一些理解。
分析的源碼版本是3.0.0
在QEMU中定義了4種Module: BLOCK,OPTS,QOM,TRACE,用元組的方式定義如下:【但這四種方式代表的具體意義,我還沒有弄明白】
1 typedef enum { 2 MODULE_INIT_BLOCK, 3 MODULE_INIT_OPTS, 4 MODULE_INIT_QOM,5 MODULE_INIT_TRACE, 6 MODULE_INIT_MAX 7 } module_init_type;
針對每種module的初始化結構,都是C語言的宏:
#define block_init(function) module_init(function, MODULE_INIT_BLOCK) #define opts_init(function) module_init(function, MODULE_INIT_OPTS) #define type_init(function) module_init(function, MODULE_INIT_QOM) #definetrace_init(function) module_init(function, MODULE_INIT_TRACE)
#define module_init(function, type) static void __attribute__((constructor)) do_qemu_init_ ## function(void) \ ##可以看成字符串拼接符號 { register_dso_module_init(function, type); }#else /* This should not be used directly. Use block_init etc. instead. */ #define module_init(function, type) static void __attribute__((constructor)) do_qemu_init_ ## function(void) { register_module_init(function, type); } #endif
這裏應用了這個是利用了GNU Compiler Collection (GCC)的擴展屬性,”GCC 8.2 Manual” -> “6 Extensions to the C Language Family” -> “6.31 Declaring Attributes of Functions” -> “6.31.1 Common Function Attributes”
構造函數屬性使函數在執行進入main函數之前被自動調用。類似地,析構函數屬性在調用main完成或exit()被調用之後會自動調用函數。具有這些屬性的函數對於初始化在程序執行過程中隱式使用的數據是有用的。您可以提供一個可選的整數優先級來控制構造函數和析構函數運行的順序。具有較小優先級號的構造函數在具有較大優先級號的構造函數之前運行,而析構函數的關系則相反。因此,如果具有分配資源的構造函數和釋放相同資源的析構函數,則這兩個函數通常具有相同的優先級。構造函數和析構函數的優先級與命名空間範圍C++對象所指定的優先級(見C++屬性)相同。但是,當前調用具有靜態存儲持續時間和用屬性構造函數裝飾的C++對象的構造函數的順序是未指定的。在混合聲明中,屬性InIt優先級可以用於強制特定的排序。
由以上幾段代碼可以知道如下的調用關系:每一種module的init函數,最後都會生成一個對應的構造器
以type_init 為例, 這些文件中,定義完硬件只夠,在最後都會調用type_init函數
由上面的關系圖可以知道,type_init將會調用module_init,而module_init將會調用構造器 do_qemu_init_##func (void)
而register_module_init(void(*fn)(void),module_init_type type)在module.c中被調用,ModuleEntry,ModuleTypeList的定義在後面有貼源碼
void register_module_init(void (*fn)(void), module_init_type type)
{
ModuleEntry *e;
ModuleTypeList *l;
e = g_malloc0(sizeof(*e));
e->init = fn;
e->type = type;
l = find_type(type);
QTAILQ_INSERT_TAIL(l, e, node);
}
其中的g_malloc0
void *g_malloc0(size_t size)
{
return g_malloc0_n(1, size);
}
void *g_malloc0_n(size_t nmemb, size_t size)
{
size_t sz;
void *ptr;
__coverity_negative_sink__(nmemb);
__coverity_negative_sink__(size);
sz = nmemb * size;
ptr = __coverity_alloc__(sz);
__coverity_writeall0__(ptr);
__coverity_mark_as_afm_allocated__(ptr, "g_free");
return ptr;
}
這段代碼沒看太懂,估計就是給每個module分配內存空間吧
QTAILQ_INSERT_TAIL(head , elm, field)
#define QTAILQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.tqe_next = NULL; (elm)->field.tqe_prev = (head)->tqh_last; *(head)->tqh_last = (elm); (head)->tqh_last = &(elm)->field.tqe_next; } while (/*CONSTCOND*/0)
也就是說,每個構造函數將生成一個節點,放入func和type,然後把節點插入到type對應的隊列中。數據結構為:
前面已經得出:
- 每個module_init()都會生成一個對應的構造函數
- 每個構造函數都由OS在main之前調用
- 每個構造函數生成一個節點,放入func和type,然後把節點插入到type對應的隊列中
而這每個節點中func(即type_init()的參數)是怎麽調用的呢?
原來在main()中,會先後初始化TRACE/QOM/OPTS:
int main(int argc, char **argv, char **envp)
{
...
module_call_init(MODULE_INIT_TRACE);
module_call_init(MODULE_INIT_QOM);
module_call_init(MODULE_INIT_OPTS);
}
給出module_call_init的函數調用關系圖:
在module.c中,定義了module_call_init()
void module_call_init(module_init_type type) { ModuleTypeList *l; ModuleEntry *e; l = find_type(type); QTAILQ_FOREACH(e, l, node) { e->init(); } }
其中,moduleTypeList 也在module.c中定義
typedef QTAILQ_HEAD(, ModuleEntry) ModuleTypeList;
在這裏用typedef 將 QTAILQ_HEAD(, ModuleEntry) 重命名為moduleTypeList。而QTAILQ_HEAD在queue.h中被宏定義
#define QTAILQ_HEAD(name, type) Q_TAILQ_HEAD(name, struct type,)
Q_TAILQ_HEAD(name, struct type,)被定義為
#define Q_TAILQ_HEAD(name, type, qual) struct name { qual type *tqh_first; /* first element */ qual type *qual *tqh_last; /* addr of last next element */ }
而ModuleEntry在module.c中被定義
typedef struct ModuleEntry { void (*init)(void); QTAILQ_ENTRY(ModuleEntry) node; module_init_type type; } ModuleEntry;
l = find_type(type); 中find_type在module.c定義
static ModuleTypeList *find_type(module_init_type type) { init_lists(); return &init_type_list[type]; }
其中的init_lists() 在module.c中
static void init_lists(void) { static int inited; int i; if (inited) { return; } for (i = 0; i < MODULE_INIT_MAX; i++) { QTAILQ_INIT(&init_type_list[i]); } QTAILQ_INIT(&dso_init_list); inited = 1; }
其中的 init_type_list
static ModuleTypeList init_type_list[MODULE_INIT_MAX];
dso_init_list
static ModuleTypeList dso_init_list;
而QTAILQ_INIT在Queue.h中被定義
/* * Tail queue functions. */ #define QTAILQ_INIT(head) do { \ (head)->tqh_first = NULL; (head)->tqh_last = &(head)->tqh_first; } while (/*CONSTCOND*/0) //這是啥意思?感覺是給指針賦值
對於module_call_init(module_init_type type)中的
QTAILQ_FOREACH(e, l, node) {
e->init();
}
#define QTAILQ_FOREACH(var, head, field) for ((var) = ((head)->tqh_first); (var); (var) = ((var)->field.tqe_next))
QTAILA_FOREACH更像是對節點的遍歷方法吧
直到此: e->init()
那麽這之後又發生了什麽呢?
記得在register_module_init()裏
e->init = fn;
do_qemu_init_Mac_machine_register_type為例分析:假設在QOM中已經在鏈表中生成了mac_machine這個節點,
此時:type_init (mac_machine_register_types)
static void mac_machine_register_types(void) { type_register_static(&core99_machine_info); }
TypeImpl *type_register_static(const TypeInfo *info) { return type_register(info); }
typeTmpl
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_post_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]; };
調用type_register()
TypeImpl *type_register(const TypeInfo *info) { assert(info->parent); return type_register_internal(info); }
type_register_internal()
static TypeImpl *type_register_internal(const TypeInfo *info) { TypeImpl *ti; ti = type_new(info); type_table_add(ti); return ti; }
調用關系如下所示:
而已經有了TypeInfo,為什麽還要用TypeImpl,可能是為了速度(各個TypeInfo單獨存在, 而TypeImpl是采用的Hash組織起來的):
即TypeImpl采用的關鍵字為對象名字與對象之間建立Hash關系。
至此
關於module的框架結構就分析到此,這裏只是分析了module在main()函數之前的初始化和main()中調用的關系
但仍有的問題是:
1. C語言真的博大精深,很多代碼讀不懂
2. 目前只是清楚了調用關系,但是並沒有弄清楚TRACE, QOM, OPTS這些模式的意義,以及更深入的東西,這只是一個開始,只是冰山一角
仍需加油
QEMU學習筆記-源碼分析01-module infrastracture