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()的另一种使用方法。

猜你喜欢

转载自blog.csdn.net/u011414616/article/details/72956890