嵌入式技术论坛
直播中

王芳

7年用户 1400经验值
私信 关注
[经验]

Micropython标识符与相应对象的联系是如何建立的呢

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基本相同,此处不再赘述。

1.jpg

更多回帖

发帖
×
20
完善资料,
赚取积分