qemu源碼編輯注意事項—暨HACKING文檔翻譯

1. 預處理器


1.1. 變體宏


對於變體宏來說,堅持使用類似C99的語法:


#define DPRINTF(fmt, ...)                                       \
    do { printf("IRQ: " fmt, ## __VA_ARGS__); } while (0)


1.2. include指令


按照順序的include指令使用如下:


#include "qemu/osdep.h"  /* Always first... */
#include <...>           /* then system headers... */
#include "..."           /* and finally QEMU headers. */


"qemu/osdep.h"包含預處理器宏,這些宏影響核心的系統頭文件,比如<stdint.h>。該頭文件必須第一個被include,以使被其他函數庫所引用的核心繫統頭文件可以得到正確的qemu依賴的預處理宏。


不要在.h文件中include “qemu/osdep.h”,因爲.c文件中已經include了。


2. C 類型


關於使用正確的數據類型,已經有一些共識存在,我們也在這裏收集了一些有用的指導。


2.1. 常量類型


如果你正在使用"int"或者"long",無符號數是一個很好的類型。如果一個變量是一個計數器,它應該是一個unsigned類型。


如果變量是memory-size相關的,size_t應該是一個很好的選擇(沒有特殊要求只使用ssize_t)。客戶機RAM內存偏移的表示使用ram_addr_t,但是隻是限於RAM,該類型可能不會覆蓋整個的客戶機地址空間。


如果它是一個file-size相關的,使用off_t。
如果它是file_offset相關的,使用off_t。
如果它只包含小的數字,使用"unsigned int"(在除了嵌入式系統外,你能夠假設這個類型至少4個字節長)。



如果你需要一個特殊寬度的類型,使用一個標準的類型,比如:int32_t, uint32_t, uint64_t等。這些特殊的類型被強制性包含進了VMState結構體中。


不要使用Linux內核的內部類型,比如:u32, __u32或 __le32。

客戶機物理地址使用hwaddr(PCI地址使用pcibus_t)。另外,ram_addr_t是qemu的內部地址空間,該地址空間是用來映射guest RAM physical address到一箇中間地址空間(改中間地址空間能夠被映射到host virtual address)。
一般來講,客戶機內存的大小總是能夠與ram_addr_t適配,但是在ram_addr_t中存儲一個真實的guest physical addr是不正確的。


對於CPU虛擬地址來說,有很多可能的類型。

在target-independent代碼中,vaddr是最好的數據類型來保存一個CPU虛擬地址。該類型是足夠大的,保證能夠保存任何目標客戶機的虛擬地址,並且,從一個target到另一個target,該類型的size不變。

target_ulong是一個表示CPU虛擬地址大小的數據類型,這意味着針對不同的目標主機,target_ulong可能是32或者64.
因此,target_ulong只能被target-specific代碼和performance-critical built-per-target代碼中使用,比如:TLB代碼(translation lookaside buffer,快表)。

target_long是有符號的版本。

abi_ulong是用於*-user target,並且代表了目標ABI(應用程序二進制接口)的'void *'的大小。(當類似於sparc32plus這樣的target ABI在64位的CPU上使用32位的指針的時候,abi_ulong可能會與full CPU virtual address的大小不一樣。)
所有適配目標ABI的結構體的定義,必須使用abi_ulong類型爲各種變量(變量在target上被定義爲'unsigned long'或者一個指針類型)。

abi_long是一個有符號的類型。


當然,可以把上面所說當作一種建議。如果你將要使用一些系統接口,這些接口要求size_t, pid_t, off_t的類型,記得爲對應的變量使用匹配的類型。


如果你嘗試使用"unsigned int"作爲一個類型,該類型是與一些相關變量的類型衝突的,但是有時候,使用這些"wrong"類型卻是最好的選擇,尤其是"pulling the thread"和fixing all related variables would be too invasive的時候。

最後,使用上面所描述的類型是很重要的,無論你使用了什麼類型,如果出現了warning,請三思而後行。

2.2. 指針


確保你的所有指針是"const-correct"。除非一個指針用於修改被指向的存儲,否則給指針添加屬性const。如此一來,讀者便知道這是一個只讀的指針。
你要保證,當一個指針沒有const屬性的時候,它被用於修改存儲的內容或者它只是另一個指針的別名。


2.3. 類型定義

類型定義被用於消除冗餘的'struct'關鍵詞。

2.4. 在C和POSIX中預留的命名空間

下劃線大寫、雙下劃線、下劃線't'後綴應該被忽略。


3. 低層次的內存管理


在qemu的代碼中,malloc/free/realloc/calloc/valloc/memalign/posix_memalign等API的使用時不被允許的。
使用glib內存申請的原則g_malloc/g_malloc0/g_new/g_new0/g_realloc/g_free or QEMU's qemu_memalign/qemu_blockalign/qemu_vfree等API。


注意,使用g_malloc申請內存如果失敗的話,g_malloc會退出,因此沒有必要檢測g_malloc是否失敗。給g_malloc傳入參數zero是合法的,將會返回NULL。


通過使用qemu_memalign或者qemu_blockalign申請的內存必須使用qemu_vfree釋放掉,因此,不遵守這個規定的話將會在win32上產生問題。


4. 字符串操作

不要使用strncpy函數,因爲它不保證一個NULL結尾的緩衝區,於是很危險。該函數也在目標緩衝區中使用0填充到特定長度的後面。
所以我們使用void pstrcpy(char *dest, int dest_buf_size, const char *src)來代替strncpy這個函數。


不要使用strcat,因爲它不檢查buffer overflow,使用:
char *pstrcat(char *buf, int buf_size, const char *s)


同樣使用snprintf和vsnprintf來替換sprintf和vsprintf。


qemu提供了其他的有用的字符串操作函數:
int strstart(const char *str, const char *val, const char **ptr)
int stristart(const char *str, const char *val, const char **ptr)
int qemu_strnlen(const char *s, int max_len)

對於一些字母操作宏(isxyz和toxyz)也有一些替換,比如:isalnum被替換爲qemu_isalnum。


因爲內存管理的原則,你必須使用g_strdup/g_strndup來代替strdup/strndup。


5. printf相關的函數


Whenever you add a new printf-style function, i.e., one with a format
string argument and following "..." in its prototype, be sure to use
gcc's printf attribute directive in the prototype.




This makes it so gcc's -Wformat and -Wformat-security options can do
their jobs and cross-check format strings with the number and types
of arguments.


6. C standard, implementation defined and undefined behaviors


C code in QEMU should be written to the C99 language specification. A copy
of the final version of the C99 standard with corrigenda TC1, TC2, and TC3
included, formatted as a draft, can be downloaded from:
 http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf
 
The C language specification defines regions of undefined behavior and
implementation defined behavior (to give compiler authors enough leeway to
produce better code).  In general, code in QEMU should follow the language
specification and avoid both undefined and implementation defined
constructs. ("It works fine on the gcc I tested it with" is not a valid
argument...) However there are a few areas where we allow ourselves to
assume certain behaviors because in practice all the platforms we care about
behave in the same way and writing strictly conformant code would be
painful. These are:
 * you may assume that integers are 2s complement representation
 * you may assume that right shift of a signed integer duplicates
   the sign bit (ie it is an arithmetic shift, not a logical shift)
   
   
In addition, QEMU assumes that the compiler does not use the latitude
given in C99 and C11 to treat aspects of signed '<<' as undefined, as
documented in the GNU Compiler Collection manual starting at version 4.0.

7. 錯誤處理和報告


7.1. 報告錯誤


不要使用printf(), fprintf(), monitor_printf(),使用頭文件error-report.h中的error_report(), error_vreport()。
這些函數保證了錯誤被輸出到正確的地方(當前的模擬器或者stderr),並且輸出形式也是規範的。


使用error_printf()以及類似的函數輸出額外的信息。


error_report()輸出當前的位置,在一些應用場景(比如解析命令行的函數),當前的執行位置可以這樣被跟蹤。
手動操作這些函數的話,使用error-report.h中的loc_*()。

7.2. 傳播錯誤

一個錯誤不能總是在出錯的地方被輸出,而經常需要追尋函數調用的鏈條,這個可以在多種不同的方式上完成。

最方便的方式是Error object,使用方法可以餐卡error.h。


使用簡單適用的方法去和調用者交流success/failure,
堅持一個常見的原則:non-negative on success / -1 on error, non-negative / -errno, non-null / null, or Error objects。

舉例:當一個函數成功執行之後將會返回一個not-null的指針,那麼它只能在一種情況下失敗(如果調用者關注的話),返回null。

舉例:當一個函數的調用者需要失敗的細節的時候,使用Error **並且設置合適的錯誤。


當你能夠將錯誤傳遞給其他函數處理的時候,不要輸出給用戶。

7.3. 處理錯誤


在啓動階段,處理配置錯誤的方式應該是調用exit()。在正常操作執行階段,這樣的處理是有問題的,特殊情況下,模擬器應該永遠不退出。


被guest觸發的錯誤不要調用exit()或者abort()來退出(例如:客戶機代碼翻譯或者設備模擬時出現的錯誤),guest不應該能夠中指qemu的運行。

注意&error_fatal只是exit(1)的另一種使用方法,同樣,&error_abort只是abort()的另一種使用方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章