Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
MicroPython
粗浅理解Micropython原理(四)
发布于 2021-05-26 09:26:25 浏览:890
订阅该版
[tocm] # 6. 标识符与相应对象的联系 Micropython中有很多标识符,我们的例子`lcd.py`中出现的标识符有:import、lcd、init、print、"hello"。这些标识符最终都需要与某个对象或操作联系起来。那么这种联系是如何建立的呢? ## 6.1 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对应的标识符的长度和哈希值。 Micropython的C代码中的QSTR定义最终会被放入`mpy-cross/build/genhdr/qstrdefs.generated.h`中: ``` // This file was automatically generated by makeqstrdata.py QDEF(MP_QSTRnull, (const byte*)"\x00\x00\x00" "") QDEF(MP_QSTR_, (const byte*)"\x05\x15\x00" "") QDEF(MP_QSTR___dir__, (const byte*)"\x7a\x8f\x07" "__dir__") QDEF(MP_QSTR__0x0a_, (const byte*)"\xaf\xb5\x01" "\x0a") QDEF(MP_QSTR__space_, (const byte*)"\x85\xb5\x01" " ") QDEF(MP_QSTR__star_, (const byte*)"\x8f\xb5\x01" "*") QDEF(MP_QSTR__slash_, (const byte*)"\x8a\xb5\x01" "/") QDEF(MP_QSTR__lt_module_gt_, (const byte*)"\xbd\x94\x08" "
") QDEF(MP_QSTR__, (const byte*)"\xfa\xb5\x01" "_") QDEF(MP_QSTR___call__, (const byte*)"\xa7\xf9\x08" "__call__") QDEF(MP_QSTR___class__, (const byte*)"\x2b\xc5\x09" "__class__") QDEF(MP_QSTR___delitem__, (const byte*)"\xfd\x35\x0b" "__delitem__") QDEF(MP_QSTR___enter__, (const byte*)"\x6d\xba\x09" "__enter__") QDEF(MP_QSTR___exit__, (const byte*)"\x45\xf8\x08" "__exit__") QDEF(MP_QSTR___getattr__, (const byte*)"\x40\xf8\x0b" "__getattr__") QDEF(MP_QSTR___getitem__, (const byte*)"\x26\x39\x0b" "__getitem__") QDEF(MP_QSTR___hash__, (const byte*)"\xf7\xc8\x08" "__hash__") QDEF(MP_QSTR___init__, (const byte*)"\x5f\xa5\x08" "__init__") QDEF(MP_QSTR___int__, (const byte*)"\x16\x1b\x07" "__int__") QDEF(MP_QSTR___iter__, (const byte*)"\xcf\x32\x08" "__iter__") QDEF(MP_QSTR___len__, (const byte*)"\xe2\xb0\x07" "__len__") QDEF(MP_QSTR___main__, (const byte*)"\x8e\x13\x08" "__main__") QDEF(MP_QSTR___module__, (const byte*)"\xff\x30\x0a" "__module__") ... ``` 其中是一系列的宏定义,格式为: ``` QDEF(MP_QSTR_xxx, (const byte*)"哈希值(2字节)长度(1字节)" "对应的字符串" ``` 以`QDEF(MP_QSTR___module__, (const byte*)"\xff\x30\x0a" "__module__")`为例, `MP_QSTR___module__`是这个QSTR的索引号,`\xff\x30`是"__module__"的哈希值,而`\x0a`是"__module__"的长度。 这些宏定义是怎么被安排进QSTR池的呢? QSTR池在`py/qstr.c`中定义: ``` /* py/qstr.c */ const qstr_pool_t mp_qstr_const_pool = { NULL, // no previous pool 0, // no previous pool MICROPY_ALLOC_QSTR_ENTRIES_INIT, MP_QSTRnumber_of, // corresponds to number of strings in array just below { #ifndef NO_QSTR #define QDEF(id, str) str, #include "genhdr/qstrdefs.generated.h" #undef QDEF #endif }, }; ``` 宏展开后为: ``` const qstr_pool_t mp_qstr_const_pool = { NULL, // no previous pool 0, // no previous pool MICROPY_ALLOC_QSTR_ENTRIES_INIT, MP_QSTRnumber_of, // corresponds to number of strings in array just below { #ifndef NO_QSTR (const byte*)"\x00\x00\x00" "", (const byte*)"\x05\x15\x00" "", (const byte*)"\x7a\x8f\x07" "__dir__", ... #endif }, ``` 这些QSTR的索引号定义在`py/qstr.h`中,用一个枚举体表示: ``` /* py/qstr.h */ // first entry in enum will be MP_QSTRnull=0, which indicates invalid/no qstr enum { #ifndef NO_QSTR #define QDEF(id, str) id, #include "genhdr/qstrdefs.generated.h" #undef QDEF #endif MP_QSTRnumber_of, // no underscore so it can't clash with any of the above }; ``` 宏展开后为: ``` enum { #ifndef NO_QSTR MP_QSTRnull, MP_QSTR_, MP_QSTR___dir__, ... #endif MP_QSTRnumber_of, // no underscore so it can't clash with any of the above }; ``` 这样就可以用MP_QSTR_xxx为索引去QSTR池中找到相应的QSTR了。 当然这些是C代码中已经出现的QSTR,而Micropython源代码中新出现的标识符,也会被转换成相应的QSTR加入另一个QSTR池,这个QSTR池会通过上面`mp_qstr_const_pool`的前两个字段与其链接起来。 ## 6.2 查找标识符 我们以例子`lcd.py`里出现的标识符为例,依次看一下找到这些标识符对应的对象或操作的过程。 ### 6.2.1 import lcd 在“5. 执行”章节的末尾,我们知道源代码中的import最终会执行`mp_import_name`函数: ``` /* py/runtime.c */ mp_obj_t mp_import_name(qstr name, mp_obj_t fromlist, mp_obj_t level) { DEBUG_printf("import name '%s' level=%d\n", qstr_str(name), MP_OBJ_SMALL_INT_VALUE(level)); // build args array mp_obj_t args[5]; args[0] = MP_OBJ_NEW_QSTR(name); args[1] = mp_const_none; // TODO should be globals args[2] = mp_const_none; // TODO should be locals args[3] = fromlist; args[4] = level; #if MICROPY_CAN_OVERRIDE_BUILTINS // Lookup __import__ and call that if it exists mp_obj_dict_t *bo_dict = MP_STATE_VM(mp_module_builtins_override_dict); if (bo_dict != NULL) { mp_map_elem_t *import = mp_map_lookup(&bo_dict->map, MP_OBJ_NEW_QSTR(MP_QSTR___import__), MP_MAP_LOOKUP); if (import != NULL) { return mp_call_function_n_kw(import->value, 5, 0, args); } } #endif return mp_builtin___import__(5, args); } ``` 最后调用`mp_builtin___import__`完成`lcd`模块的加载。 **注**:在我们的例子中,`lcd`模块是作为内置模块存在的。 ``` /* py/builtinimport.c */ mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { ... mp_obj_t module_name = args[0]; ... size_t mod_len; const char *mod_str = mp_obj_str_get_data(module_name, &mod_len); ... if (mod_len == 0) { mp_raise_ValueError(NULL); } // check if module already exists qstr module_name_qstr = mp_obj_str_get_qstr(module_name); mp_obj_t module_obj = mp_module_get(module_name_qstr); if (module_obj != MP_OBJ_NULL) { DEBUG_printf("Module already loaded\n"); // If it's not a package, return module right away char *p = strchr(mod_str, '.'); if (p == NULL) { return module_obj; } ... } ... } ``` `mp_module_get`函数查找模块是否已加载: ``` /* py/objmodule.c */ // returns MP_OBJ_NULL if not found mp_obj_t mp_module_get(qstr module_name) { mp_map_t *mp_loaded_modules_map = &MP_STATE_VM(mp_loaded_modules_dict).map; // lookup module mp_map_elem_t *el = mp_map_lookup(mp_loaded_modules_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP); if (el == NULL) { // module not found, look for builtin module names el = mp_map_lookup((mp_map_t *)&mp_builtin_module_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP); if (el == NULL) { return MP_OBJ_NULL; } mp_module_call_init(module_name, el->value); } // module found, return it return el->value; } ``` 最终在`mp_builtin_module_map`中找到`lcd`模块并返回。在`mp_builtin_module_map`中我们会看到类似如下的一行: ``` {MP_ROM_QSTR(MP_QSTR_lcd), MP_ROM_PTR(&lcd_module)} ``` `lcd_module`就是我们定义的`lcd`模块,上面这行就把在`lcd`这个名字与`lcd`模块间建立了联系。 ### 6.2.2 lcd.init() 从`lcd.py`生成的`bytecode`里可以看到,在`MP_BC_IMPORT_NAME qstr_lcd[0:7] qstr_lcd[8:15]`之后是`MP_BC_STORE_NAME qstr_lcd[0:7] qstr_lcd[8:15]`,从字面意思看是要把QSTR_lcd存储起来。在`mp_execute_bytecode`中会执行如下分支: ``` ENTRY(MP_BC_STORE_NAME): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_store_name(qst, POP()); DISPATCH(); } ``` 调用`mp_store_name`: ``` /* py/runtime.c */ void mp_store_name(qstr qst, mp_obj_t obj) { DEBUG_OP_printf("store name %s <- %p\n", qstr_str(qst), obj); mp_obj_dict_store(MP_OBJ_FROM_PTR(mp_locals_get()), MP_OBJ_NEW_QSTR(qst), obj); } ``` 也就是把QSTR_lcd存到了虚拟机的局部符号表里,而此时局部符号表与全局符号表其实是同一个表。 当要调用`lcd`的`init`方法时,要先找到`lcd`模块,在`bytecode`中对应`MP_BC_LOAD_NAME`。在`mp_execute_bytecode`中会执行如下分支: ``` ENTRY(MP_BC_LOAD_NAME): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; PUSH(mp_load_name(qst)); DISPATCH(); } ``` `mp_load_name`->`mp_load_global`->`mp_map_lookup(&mp_globals_get()->map,...)`,最后会在虚拟机的全局符号表中找到`lcd`模块,因为前面`MP_BC_STORE_NAME`已经把存入了。 调用`init`方法对应的`bytecode`首先是`MP_BC_LOAD_METHOD qstr_init[0:7] qstr_init[8:15]`,在`mp_execute_bytecode`中会执行如下分支: ``` ENTRY(MP_BC_LOAD_METHOD): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_load_method(*sp, qst, sp); sp += 1; DISPATCH(); } ``` `mp_load_method`->`mp_load_method_maybe`->`type->attr(...)`,`type`对应的是`lcd`模块的类型,即`mp_type_module`。因此最后调用的就是`mp_type_module`的`attr`方法: ``` /* py/objmodule.c */ const mp_obj_type_t mp_type_module = { { &mp_type_type }, .name = MP_QSTR_module, .print = module_print, .attr = module_attr, }; ``` `mp_type_module`的`attr`方法实际是调用`module_attr`: ``` /* py/objmodule.c */ STATIC void module_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { mp_obj_module_t *self = MP_OBJ_TO_PTR(self_in); if (dest[0] == MP_OBJ_NULL) { // load attribute mp_map_elem_t *elem = mp_map_lookup(&self->globals->map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP); if (elem != NULL) { dest[0] = elem->value; ... } } else { // delete/store attribute ... } } ``` 可见这里就是在`lcd`模块的全局符号表中查找`init`,在`lcd`模块的全局符号表中会有如下一行: ``` static const mp_map_elem_t globals_dict_table[] = { ... { MP_OBJ_NEW_QSTR(MP_QSTR_init), (mp_obj_t)&py_lcd_init_obj }, ... } ``` `py_lcd_init_obj`就是用于初始化的函数对象,这样就在`init`这个名字与`lcd`模块的`init`方法之间建立了联系。 `bytecode`中接下来是`MP_BC_CALL_METHOD`,在`mp_execute_bytecode`中会执行如下分支: ``` ENTRY(MP_BC_CALL_METHOD): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_UINT; // unum & 0xff == n_positional // (unum >> 8) & 0xff == n_keyword sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 1; #if MICROPY_STACKLESS if (mp_obj_get_type(*sp) == &mp_type_fun_bc) { code_state->ip = ip; code_state->sp = sp; code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(exc_stack, exc_sp); size_t n_args = unum & 0xff; size_t n_kw = (unum >> 8) & 0xff; int adjust = (sp[1] == MP_OBJ_NULL) ? 0 : 1; mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(*sp, n_args + adjust, n_kw, sp + 2 - adjust); #if !MICROPY_ENABLE_PYSTACK if (new_state == NULL) { // Couldn't allocate codestate on heap: in the strict case raise // an exception, otherwise just fall through to stack allocation. #if MICROPY_STACKLESS_STRICT goto deep_recursion_error; #endif } else #endif { new_state->prev = code_state; code_state = new_state; nlr_pop(); goto run_code_state; } } #endif SET_TOP(mp_call_method_n_kw(unum & 0xff, (unum >> 8) & 0xff, sp)); DISPATCH(); } ``` `mp_call_method_n_kw`->`mp_call_function_n_kw`->`type->call`,这里的`type`是`init`对应的函数对象`py_lcd_init_obj`的类型。 在Micropython中定义函数对象的方法如下: ``` static mp_obj_t py_lcd_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { ... } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_lcd_init_obj, 0, py_lcd_init); ``` - 首先定义函数主体; - 然后通过`MP_DEFINE_CONST_FUN_OBJ_KW`宏定义函数对象。 除了`MP_DEFINE_CONST_FUN_OBJ_KW`可以定义函数对象,还有另外几个宏也完成类似的功能,包括:`MP_DEFINE_CONST_FUN_OBJ_0`、`MP_DEFINE_CONST_FUN_OBJ_1`、`MP_DEFINE_CONST_FUN_OBJ_2`、`MP_DEFINE_CONST_FUN_OBJ_3`、`MP_DEFINE_CONST_FUN_OBJ_VAR`、`MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN`。这些宏之间的区别是对于函数可以接受的参数个数有不同的约束。 这里以`MP_DEFINE_CONST_FUN_OBJ_KW`为例,看一下其定义: ``` /* py/obj.h */ #define MP_DEFINE_CONST_FUN_OBJ_KW(obj_name, n_args_min, fun_name) \ const mp_obj_fun_builtin_var_t obj_name = \ {{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, true), .fun.kw = fun_name} ``` 将上面的`py_lcd_init_obj`例子代入展开后为: ``` const mp_obj_fun_builtin_var_t py_lcd_init_obj= { {&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(0, MP_OBJ_FUN_ARGS_MAX, true), .fun.kw = py_lcd_init } ``` 可见,这个宏实际是定义了一个`mp_obj_fun_builtin_var_t`对象,其类型为`mp_type_fun_builtin_var`,并把函数指针赋值给其`.fun.kw`字段。 所以执行`MP_BC_CALL_METHOD`最后的`type->call`,实际就是调用的`mp_type_fun_builtin_var`类型的`call`方法: ``` /* py/objfun.c */ const mp_obj_type_t mp_type_fun_builtin_var = { { &mp_type_type }, .flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_BUILTIN_FUN, .name = MP_QSTR_function, .call = fun_builtin_var_call, .unary_op = mp_generic_unary_op, }; ``` 对应调用的是`fun_builtin_var_call`函数: ``` /* py/objfun.c */ STATIC mp_obj_t fun_builtin_var_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_var)); mp_obj_fun_builtin_var_t *self = MP_OBJ_TO_PTR(self_in); // check number of arguments mp_arg_check_num_sig(n_args, n_kw, self->sig); if (self->sig & 1) { // function allows keywords // we create a map directly from the given args array mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); return self->fun.kw(n_args, args, &kw_args); } else { // function takes a variable number of arguments, but no keywords return self->fun.var(n_args, args); } } ``` 在其中会调用函数对象的fun.kw方法,在我们的例子中也就是调用了`py_lcd_init`函数。 ## 6.2.3 print("hello") 首先要找到`print`,在`bytecode`中对应`MP_BC_LOAD_NAME qstr_print[0:7] qstr_print[8:15]`,与查找`lcd`的过程类似,通过`mp_load_name`->`mp_load_global`->`mp_map_lookup((mp_map_t *)&mp_module_builtins_globals.map,...)`,在`mp_module_builtins_globals.map`中找到: ``` STATIC const mp_rom_map_elem_t mp_module_builtins_globals_table[] = { ... { MP_ROM_QSTR(MP_QSTR_print), MP_ROM_PTR(&mp_builtin_print_obj) }, ... } MP_DEFINE_CONST_DICT(mp_module_builtins_globals, mp_module_builtins_globals_table); ``` 接下来要找到"hello",在`bytecode`中对应`MP_BC_LOAD_CONST_STRING qstr_hello[0:7] qstr_hello[8:15]`,在`mp_execute_bytecode`中会执行如下分支: ``` ENTRY(MP_BC_LOAD_CONST_STRING): { DECODE_QSTR; PUSH(MP_OBJ_NEW_QSTR(qst)); DISPATCH(); } ``` 这个比较简单,就是根据"hello"的QSTR索引号生成一个QSTR对象。 最后是对"hello"调用`print`函数,对应`bytecode`里的`MP_BC_CALL_FUNCTION`,在`mp_execute_bytecode`中会执行如下分支: ``` ENTRY(MP_BC_CALL_FUNCTION): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_UINT; // unum & 0xff == n_positional // (unum >> 8) & 0xff == n_keyword sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe); #if MICROPY_STACKLESS if (mp_obj_get_type(*sp) == &mp_type_fun_bc) { code_state->ip = ip; code_state->sp = sp; code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(exc_stack, exc_sp); mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(*sp, unum & 0xff, (unum >> 8) & 0xff, sp + 1); #if !MICROPY_ENABLE_PYSTACK if (new_state == NULL) { // Couldn't allocate codestate on heap: in the strict case raise // an exception, otherwise just fall through to stack allocation. #if MICROPY_STACKLESS_STRICT deep_recursion_error: mp_raise_recursion_depth(); #endif } else #endif { new_state->prev = code_state; code_state = new_state; nlr_pop(); goto run_code_state; } } #endif SET_TOP(mp_call_function_n_kw(*sp, unum & 0xff, (unum >> 8) & 0xff, sp + 1)); DISPATCH(); } ``` 实际调用过程与上述`init`基本相同,此处不再赘述。
0
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
Remember
这家伙很懒,什么也没写!
文章
4
回答
60
被采纳
16
关注TA
发私信
相关文章
1
请问rt-thread有没有移植micropython呢
2
micropython import 文件名的方式执行脚本问题
3
第一篇:Micropython 的起源和发展
4
第二篇:RT-Thread Micropython 简介
5
第三篇:RT-Thread Micropython 快速入门
6
第四篇:Micropython DIY 项目汇总
7
第五篇:Micropython 教程和资源
8
第六篇: RT-Thread MicroPython 学习经验和学习路线
9
RT-Thread MicroPython 最新开发板固件汇总【已失效】
10
有Mpy专门的板块啦~
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
DMA
USB
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
rt-smart
FAL
I2C_IIC
UART
ESP8266
cubemx
WIZnet_W5500
ota在线升级
PWM
BSP
flash
freemodbus
packages_软件包
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
编译报错
中断
Debug
rt_mq_消息队列_msg_queue
keil_MDK
ulog
SFUD
msh
C++_cpp
MicroPython
本月问答贡献
RTT_逍遥
10
个答案
3
次被采纳
xiaorui
3
个答案
2
次被采纳
winfeng
2
个答案
2
次被采纳
三世执戟
8
个答案
1
次被采纳
KunYi
8
个答案
1
次被采纳
本月文章贡献
catcatbing
3
篇文章
5
次点赞
lizimu
2
篇文章
9
次点赞
swet123
1
篇文章
4
次点赞
Days
1
篇文章
4
次点赞
YZRD
1
篇文章
2
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部