前言
最近在研究micropython的源码编译过程,简单记录下关于qstr部分内容,本篇文章基于micropython1.18版本源码,1.19版本及之后可能会略有差异。
标识符与相应对象的联系
Micropython中有很多标识符,例如lcd.py中出现的标识符有:import、lcd、init、print、“hello”。这些标识符最终都需要与某个对象或操作联系起来。那么这种联系是如何建立的呢?那就是通过QSTR。
QSTR是uniQue STRing的简称,是一种字符串内存驻留方法。我们知道同一个标识符可能在源代码中出现多次,如果我们在每个出现的地方都要保留一份这个标识符的拷贝,就会相当占用存储空间。
Micropython采取的方式是在存储空间内仅保留一份标识符主体,而每个标识符主体都有一个索引号,代码中凡是使用这个标识符的地方,都使用其索引号代替。而最终执行时就通过这个索引号去内存中寻找对应的标识符。这就是Micropython中所谓的QSTR。
Micropython的C代码中所有需要使用QSTR的地方都用MP_QSTR_xxx表示,比如lcd就用MP_QSTR_lcd表示,这个MP_QSTR_lcd就是lcd这个标识符的索引号。Micropython的Makefile会搜索C源文件中的所有MP_QSTR_xxx,并生成QSTR池,其中存放了QSTR对应的标识符的长度和哈希值。
以我编译unix移植为例Micropython的C代码中的QSTR定义最终会被放入unix/build-standard/genhdr/qstrdefs.generated.h
中。
分析下qstrdefs.generated.h的生成过程,可以看到改文件头记录着这样一句话
// This file was automatically generated by makeqstrdata.py
QDEF0(MP_QSTRnull, (const byte*)"\x00\x00\x00" "")
QDEF0(MP_QSTR_, (const byte*)"\x05\x15\x00" "")
QDEF0(MP_QSTR___dir__, (const byte*)"\x7a\x8f\x07" "__dir__")
...
其中是一系列的宏定义,格式为:
QDEF(MP_QSTR_xxx, (const byte*)"哈希值(2字节)长度(1字节)" "对应的字符串"
这个文件是由makeqstrdata.py生成的,该文件位于py/makeqstrdata.py,py目录下有几个python脚本用于代码预处理c代码,包括生成qstr池,注册c模组。
QSTR生成流程
- 首先通过
py/makeqstrdefs.py
查找C源码中的MP_QSTR_XXX
关键字,然后提取到ports/unix/build-standard/genhdr/qstr
目录下建立新文件,增加条目Q(XXX)
。 - 然后将qstr目录下若干文件整合成一个文件
ports/unix/build-standard/genhdr/qstrdefs.collected.h
。 - py/makeqstrdata.py将根据
ports/unix/build-standard/genhdr/qstrdefs.collected.h
生成QSTR池文件unix/build-standard/genhdr/qstrdefs.generated.h
QSTR的hash生成设置
C源码中计算qstr hash的位置位于qstr.c中
// this must match the equivalent function in makeqstrdata.py
mp_uint_t qstr_compute_hash(const byte *data, size_t len) {
// djb2 algorithm; see http://www.cse.yorku.ca/~oz/hash.html
mp_uint_t hash = 5381;
for (const byte *top = data + len; data < top; data++) {
hash = ((hash << 5) + hash) ^ (*data); // hash * 33 ^ data
}
hash &= Q_HASH_MASK;
// Make sure that valid hash is never zero, zero means "hash not computed"
if (hash == 0) {
hash++;
}
return hash;
}
这个计算方法跟makeqstrdata.py脚本中一致的。
micropython编译前可以通过宏定义设置,控制hash输出按1字节还是2字节,字符串长度值占用多少字节。
hash长度设置
在qstr.c中能看到MICROPY_QSTR_BYTES_IN_HASH
宏判断,该值可赋1或2,但如果不设置则通过其他方式确定,在py/mpconfig.h
文件中通过设置MICROPY_CONFIG_ROM_LEVEL
来控制hash 长度是1 还是2,参见
// Number of bytes used to store qstr hash
#ifndef MICROPY_QSTR_BYTES_IN_HASH
#if MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES
#define MICROPY_QSTR_BYTES_IN_HASH (2)
#else
#define MICROPY_QSTR_BYTES_IN_HASH (1)
#endif
#endif
当MICROPY_QSTR_BYTES_IN_HASH
未被定义时参考MICROPY_CONFIG_ROM_LEVEL
的设置,micropython给目标机划分了6个级别,用于适应不同硬件资源的编译。
// Disable all optional features (i.e. minimal port).
#define MICROPY_CONFIG_ROM_LEVEL_MINIMUM (0)
// Only enable core features (constrained flash, e.g. STM32L072)
#define MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES (10)
// Enable most common features (small on-device flash, e.g. STM32F411)
#define MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES (20)
// Enable convenience features (medium on-device flash, e.g. STM32F405)
#define MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES (30)
// Enable all common features (large/external flash, rp2, unix)
#define MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES (40)
// Enable everything (e.g. coverage)
#define MICROPY_CONFIG_ROM_LEVEL_EVERYTHING (50)
// Ports/boards should set this, but default to level=core.
#ifndef MICROPY_CONFIG_ROM_LEVEL
#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES)
#endif
// Helper macros for "have at least this level".
#define MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES (MICROPY_CONFIG_ROM_LEVEL >= MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES)
#define MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES (MICROPY_CONFIG_ROM_LEVEL >= MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES)
#define MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES (MICROPY_CONFIG_ROM_LEVEL >= MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES)
#define MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_FULL_FEATURES (MICROPY_CONFIG_ROM_LEVEL >= MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES)
#define MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING (MICROPY_CONFIG_ROM_LEVEL >= MICROPY_CONFIG_ROM_LEVEL_EVERYTHING)
由此可以得知,MICROPY_CONFIG_ROM_LEVEL
决定的hash长度,在6个级别中除了MINIMUM长度为1外其他都为2。
对象字符串长度字段的字节长度
前面我们分析过qstr宏定义的组成
QDEF(MP_QSTR_xxx, (const byte*)"哈希值(2字节)长度(1字节)" "对应的字符串"
1字节的长度位表示的事后面对象或函数名字字符串的长度,1字节长度意味着我们对象名字符串最大不超过256,如果超过那就要用2字节来表示了。
可以看到mpconfig.h中有这样的宏定义
// Number of bytes used to store qstr length
// Dictates hard limit on maximum Python identifier length, but 1 byte
// (limit of 255 bytes in an identifier) should be enough for everyone
#ifndef MICROPY_QSTR_BYTES_IN_LEN
#define MICROPY_QSTR_BYTES_IN_LEN (1)
#endif
这样我们可以在mpconfigport.h中添加MICROPY_QSTR_BYTES_IN_LEN
宏的设置就可以更改字节位长度。不过一般来说不会有超过256长度函数名,所以一般不用设置该项,只需留意有这个配置就行。