QEMU在main函式前對模組的初始化過程
初始化的難題
QEMU中包含了大量的初始化函式,比如使用QOM模型設計的很多類(CPU、裝置等都是利用QOM模型設計實現模擬的),這些類需要註冊到管理型別的全域性的hash表中,這個註冊的過程需要在初始化函式中完成。
想象一下,如果我們把這些註冊過程都放到main函式裡面呼叫,main函式中就會有非常長的一段篇幅,僅僅是用於呼叫大量的初始化函式,這樣對於QEMU的程式碼維護非常不利,因此QEMU將這些初始化函式的指標儲存到了連結串列資料結構中,這樣只要遍歷一遍連結串列就可以執行全部的初始化函式。說起來簡單,但是其中的設計和實現,以及對GCC中相關特性的使用都有我們值得學習和研究的地方。
模組化管理初始化工作
QEMU中的程式碼的初始化管理是分模組的,實現這種模組化的程式碼檔案包括include/qemu/module.h 和util/module.c。在include/qemu/module.h中所涉及的相關的程式碼模組包括:
block (QEMU中的塊操作實現程式碼)
machine (QEMU中模擬裝置的實現程式碼模組)
qapi (QEMU向上層呼叫者提供的介面的程式碼模組)
type 或者qom(QEMU中的QOM模型所涉及的程式碼模組)
重要的全域性變數: init_type_list
該變數的定義如下:
//這個資料結構儲存了某種型別的程式碼模組的一個初始化函式中指標
typedef struct ModuleEntry
{
void (*init)(void);
QTAILQ_ENTRY(ModuleEntry) node;
module_init_type type;
} ModuleEntry;
typedef QTAILQ_HEAD(, ModuleEntry) ModuleTypeList;
//其中MODULE_INIT_MAX為4,也就是4種模組,每一種對應一個ModuleTypeList
//每個ModuleTypeList將儲存一種程式碼模組中所有初始化函式指標
static ModuleTypeList init_type_list[MODULE_INIT_MAX];
qemu中的module實現了QEMU在main函式之前需要執行的大量函式
首先普及一下gcc的一些知識
gcc中的建構函式屬性和解構函式屬性
gcc為函式提供了幾種型別的屬性,其中就包括建構函式(contructors),使用者可以定義一個函式:
static void __attribute__((constructor)) start(void)
這樣start函式就會在main函式執行之前執行。
同理gcc中也有__attribute__((destructor)),會在main函式之後執行。
gcc中巨集的##和#擴充套件符
在gcc的巨集中,#擴充套件符可以將巨集字串化,##擴充套件符可以將它左右兩邊連線起來。比如:
#define SSVAR(X,Y) const char X[]=#Y
SSVAR(InternetGatewayDevice, InternetGatewayDevice.);
等價於如下程式碼:
const char InternetGatewayDevice[]="InternetGatewayDevice.";
又比如:
#define DEV_FILE_NAME "/dev/test_kft"
#define OPEN_FILE(fd, n) \
{ \
fd = open(DEV_FILE_NAME ##n, 0); \
if (fd < 0) \
{ \
printf("Open device error/n"); \
return 0; \
} \
}
OPEN_FILE(fd1, 1);
OPEN_FILE(fd2, 2);
它等價於:
{ fd1 = open(DEV_FILE_NAME1, 0); if (fd1 < 0) { printf("Open device error/n"); return 0; } };
{ fd2 = open(DEV_FILE_NAME2, 0); if (fd2 < 0) { printf("Open device error/n"); return 0; } };
言歸正傳
要想看QEMU在main函式之前做了什麼,可以直接查詢__attribute__((constructor))。(感興趣的讀者可以自行查詢)
通過查詢,我們可以發現只有少數幾個檔案中定義了具有constructor屬性的函式,其中就包括include/qemu/module.h檔案中do_qemu_init_ ## function(void),它是一個巨集,而不是一個具體的函式。它的定義如下:(程式碼在include/qemu/module.h中)
#define module_init(function, type) \
static void __attribute__((constructor)) do_qemu_init_ ## function(void) \
{ \
register_module_init(function, type); \
}
#endif
我們進一步看一下register_module_init的實現:(程式碼在util/module.c中)。這段程式碼實際上是將型別為type的程式碼模組中一個初始化函式的指標存入全域性變數init_type_list對應型別的連結串列中。
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); //找到全域性變數init_type_list型別為type的ModuleTypeList連結串列
QTAILQ_INSERT_TAIL(l, e, node);
}
這將意味著,如果在全域性的程式碼中呼叫module_init(f, t),系統就會定義一個名為module_init_f的函式,並且該函式是constructor,必須在main函式之前執行,該函式要執行的程式碼就是下列程式碼。這個函式最終將函式指標f存入全域性變數init_type_list陣列中的type為t的連結串列中。
register_module_init(f, t);
而根據這個module_init巨集,qemu分別針對四個程式碼模組定義了四種巨集:
typedef enum {
MODULE_INIT_BLOCK,
MODULE_INIT_MACHINE,
MODULE_INIT_QAPI,
MODULE_INIT_QOM,
MODULE_INIT_MAX
} module_init_type;
#define block_init(function) module_init(function, MODULE_INIT_BLOCK)
#define machine_init(function) module_init(function, MODULE_INIT_MACHINE)
#define qapi_init(function) module_init(function, MODULE_INIT_QAPI)
#define type_init(function) module_init(function, MODULE_INIT_QOM)
也就是說,在全域性的程式碼中如果呼叫上述任何一個函式,就會在系統中自動生成一個具有constructor屬性的函式,這個函式會在main函式之前執行。舉一個例子,我們在qom/object.c中看到了type_init()函式的一個呼叫:
static void register_types(void)
{
static TypeInfo interface_info = {
.name = TYPE_INTERFACE,
.class_size = sizeof(InterfaceClass),
.abstract = true,
};
static TypeInfo object_info = {
.name = TYPE_OBJECT,
.instance_size = sizeof(Object),
.instance_init = object_instance_init,
.abstract = true,
};
type_interface = type_register_internal(&interface_info);
type_register_internal(&object_info);
}
type_init(register_types)
這樣就會在系統中生成一個如下函式:
static void __attribute__((constructor)) do_qemu_init_register_type(void)
{
register_module_init(register_type, MODULE_INIT_QOM);
}
qemu中如何利用module實現大量程式碼的初始化工作
通過上述定義constructor屬性的函式的方式,qemu會在main函式之前,將所有程式碼模組中的所有的初始化函式指標儲存到init_type_list陣列中的對應型別的連結串列中。我們只需呼叫每種型別連結串列中每個entry儲存的初始化函式指標,就可以實現呼叫所有程式碼模組中大量的初始化函式指標。
void module_call_init(module_init_type type)
{
ModuleTypeList *l;
ModuleEntry *e;
module_load(type);
l = find_type(type);
QTAILQ_FOREACH(e, l, node) {
e->init();
}
}
總結
qemu中通過設計module的機制,將大量的初始化函式分模組地儲存到連結串列中,簡化了這些程式碼模組的初始化工作,也簡化了實現的流程,提高了程式碼的可維護性。
---------------------
原文:https://blog.csdn.net/u011364612/article/details/53581501
版權宣告:本文為博主原創文章,轉載請附上博文連結!