Предисловие
Привет, читатель! Меня зовут Александр Щербанюк, и я Python-разработчик. Это вторая статья цикла, который посвящен разбору внутреннего устройства CPython.
В рамках прошлой статьи была настроена IDE и разобраны первые несколько функций CPython и используемые в них структуры. Так, повествование дошло до функции pymain_init из Modules/main.c.
Эта же статья будет посвящена разбору части вышеобозначенной функции, а конкретнее — этапу предконфигурации CPython.
Итак, продолжим изучение внутренностей CPython.
Предконфигурация
И первой рассмотренной функцией станет _PyRuntime_Initialize из Python/pylifecycle.c:
_PyRuntimeState _PyRuntime = _PyRuntimeState_INIT(_PyRuntime, _Py_Debug_Cookie); static int runtime_initialized = 0; PyStatus _PyRuntime_Initialize(void) { if (runtime_initialized) { return _PyStatus_OK(); } runtime_initialized = 1; return _PyRuntimeState_Init(&_PyRuntime); }
Прежде чем перейти к рассмотрению непосредственно логики функции, предлагаю обратить внимание на ее интересные особенности. А особенностей у нее всего две. Во-первых, она использует глобальную для модуля переменную runtime_initialized. Эта переменная предотвращает повторные отработки текущей функции, обеспечивая ранний выход.
Зачем нужен флаг, обеспечивающий ранний выход?
В ходе следующих этапов (например, конфигурации) эта функция будет вызвана еще не раз, но необходимость в ее отработке отсутствует. Посему, с целью оптимизации, этот флаг и существует.
Во-вторых, эта функция интересна тем, что в ней впервые встречается использование объекта _PyRuntime, принадлежащего к структуре _PyRuntimeState из Include/internal/pycore_runtime.h. Листинг структуры представлен ниже.
/* Full Python runtime state */ /* _PyRuntimeState holds the global state for the CPython runtime. That data is exposed in the internal API as a static variable (_PyRuntime). */ typedef struct pyruntimestate { _Py_DebugOffsets debug_offsets; int _initialized; int preinitializing; int preinitialized; int core_initialized; int initialized; PyThreadState *_finalizing; unsigned long _finalizing_id; struct pyinterpreters { PyMutex mutex; PyInterpreterState *head; PyInterpreterState *main; int64_t next_id; } interpreters; unsigned long main_thread; PyThreadState *main_tstate; struct _xi_runtime_state xi; struct _pymem_allocators allocators; struct _obmalloc_global_state obmalloc; struct pyhash_runtime_state pyhash_state; struct _pythread_runtime_state threads; struct _signals_runtime_state signals; Py_tss_t autoTSSkey; Py_tss_t trashTSSkey; PyWideStringList orig_argv; struct _parser_runtime_state parser; struct _atexit_runtime_state atexit; struct _import_runtime_state imports; struct _ceval_runtime_state ceval; struct _gilstate_runtime_state gilstate; struct _getargs_runtime_state getargs; struct _fileutils_state fileutils; struct _faulthandler_runtime_state faulthandler; struct _tracemalloc_runtime_state tracemalloc; struct _reftracer_runtime_state ref_tracer; _PyRWMutex stoptheworld_mutex; struct _stoptheworld_state stoptheworld; PyPreConfig preconfig; Py_OpenCodeHookFunction open_code_hook; void *open_code_userdata; struct { PyMutex mutex; _Py_AuditHookEntry *head; } audit_hooks; struct _py_object_runtime_state object_state; struct _Py_float_runtime_state float_state; struct _Py_unicode_runtime_state unicode_state; struct _types_runtime_state types; struct _Py_cached_objects cached_objects; struct _Py_static_objects static_objects; PyInterpreterState _main_interpreter; } _PyRuntimeState;
Переменная _PyRuntime является одной из ключевых сущностей в работе CPython, поскольку она является глобальным и единственным хранилищем состояния всего процесса выполнения CPython. В дальнейшем мы будем часто ссылаться на нее, а посему разбирать назначение каждого поля из листинга выше в данный момент мы не будем. Гораздо удобнее будет возвращаться к полям и их назначению по мере их возникновения в дальнейшем разборе исходного кода.
Дополнительно стоит отметить макрос _PyRuntimeState_INIT из Include/internal/pycore_runtime_init.h, который служит для инициализации _PyRuntime. Выполняется он на этапе компиляции CPython. Листинг самого макроса представлен ниже:
#define _PyRuntimeState_INIT(runtime, debug_cookie) \ { \ .debug_offsets = { \ .cookie = debug_cookie, \ .version = PY_VERSION_HEX, \ .free_threaded = _Py_Debug_Free_Threaded, \ .runtime_state = { \ .size = sizeof(_PyRuntimeState), \ .finalizing = offsetof(_PyRuntimeState, _finalizing), \ .interpreters_head = offsetof(_PyRuntimeState, interpreters.head), \ }, \ .interpreter_state = { \ .size = sizeof(PyInterpreterState), \ .id = offsetof(PyInterpreterState, id), \ .next = offsetof(PyInterpreterState, next), \ .threads_head = offsetof(PyInterpreterState, threads.head), \ .gc = offsetof(PyInterpreterState, gc), \ .imports_modules = offsetof(PyInterpreterState, imports.modules), \ .sysdict = offsetof(PyInterpreterState, sysdict), \ .builtins = offsetof(PyInterpreterState, builtins), \ .ceval_gil = offsetof(PyInterpreterState, ceval.gil), \ .gil_runtime_state = offsetof(PyInterpreterState, _gil), \ .gil_runtime_state_enabled = _Py_Debug_gilruntimestate_enabled, \ .gil_runtime_state_locked = offsetof(PyInterpreterState, _gil.locked), \ gil_runtime_state_holder = offsetof(PyInterpreterState, _gil.last_holder), \ }, \ .thread_state = { \ .size = sizeof(PyThreadState), \ .prev = offsetof(PyThreadState, prev), \ .next = offsetof(PyThreadState, next), \ .interp = offsetof(PyThreadState, interp), \ .current_frame = offsetof(PyThreadState, current_frame), \ .thread_id = offsetof(PyThreadState, thread_id), \ .native_thread_id = offsetof(PyThreadState, native_thread_id), \ .datastack_chunk = offsetof(PyThreadState, datastack_chunk), \ .status = offsetof(PyThreadState, _status), \ }, \ .interpreter_frame = { \ .size = sizeof(_PyInterpreterFrame), \ .previous = offsetof(_PyInterpreterFrame, previous), \ .executable = offsetof(_PyInterpreterFrame, f_executable), \ .instr_ptr = offsetof(_PyInterpreterFrame, instr_ptr), \ .localsplus = offsetof(_PyInterpreterFrame, localsplus), \ .owner = offsetof(_PyInterpreterFrame, owner), \ }, \ .code_object = { \ .size = sizeof(PyCodeObject), \ .filename = offsetof(PyCodeObject, co_filename), \ .name = offsetof(PyCodeObject, co_name), \ .qualname = offsetof(PyCodeObject, co_qualname), \ .linetable = offsetof(PyCodeObject, co_linetable), \ .firstlineno = offsetof(PyCodeObject, co_firstlineno), \ .argcount = offsetof(PyCodeObject, co_argcount), \ .localsplusnames = offsetof(PyCodeObject, co_localsplusnames), \ .localspluskinds = offsetof(PyCodeObject, co_localspluskinds), \ .co_code_adaptive = offsetof(PyCodeObject, co_code_adaptive), \ }, \ .pyobject = { \ .size = sizeof(PyObject), \ .ob_type = offsetof(PyObject, ob_type), \ }, \ .type_object = { \ .size = sizeof(PyTypeObject), \ .tp_name = offsetof(PyTypeObject, tp_name), \ .tp_repr = offsetof(PyTypeObject, tp_repr), \ .tp_flags = offsetof(PyTypeObject, tp_flags), \ }, \ .tuple_object = { \ .size = sizeof(PyTupleObject), \ .ob_item = offsetof(PyTupleObject, ob_item), \ .ob_size = offsetof(PyTupleObject, ob_base.ob_size), \ }, \ .list_object = { \ .size = sizeof(PyListObject), \ .ob_item = offsetof(PyListObject, ob_item), \ .ob_size = offsetof(PyListObject, ob_base.ob_size), \ }, \ .dict_object = { \ .size = sizeof(PyDictObject), \ .ma_keys = offsetof(PyDictObject, ma_keys), \ .ma_values = offsetof(PyDictObject, ma_values), \ }, \ .float_object = { \ .size = sizeof(PyFloatObject), \ .ob_fval = offsetof(PyFloatObject, ob_fval), \ }, \ .long_object = { \ .size = sizeof(PyLongObject), \ .lv_tag = offsetof(PyLongObject, long_value.lv_tag), \ .ob_digit = offsetof(PyLongObject, long_value.ob_digit), \ }, \ .bytes_object = { \ .size = sizeof(PyBytesObject), \ .ob_size = offsetof(PyBytesObject, ob_base.ob_size), \ .ob_sval = offsetof(PyBytesObject, ob_sval), \ }, \ .unicode_object = { \ .size = sizeof(PyUnicodeObject), \ .state = offsetof(PyUnicodeObject, _base._base.state), \ .length = offsetof(PyUnicodeObject, _base._base.length), \ .asciiobject_size = sizeof(PyASCIIObject), \ }, \ .gc = { \ .size = sizeof(struct _gc_runtime_state), \ .collecting = offsetof(struct _gc_runtime_state, collecting), \ }, \ }, \ .allocators = { \ .standard = _pymem_allocators_standard_INIT(runtime), \ .debug = _pymem_allocators_debug_INIT, \ .obj_arena = _pymem_allocators_obj_arena_INIT, \ .is_debug_enabled = _pymem_is_debug_enabled_INIT, \ }, \ .obmalloc = _obmalloc_global_state_INIT, \ .pyhash_state = pyhash_state_INIT, \ .threads = _pythread_RUNTIME_INIT(runtime.threads), \ .signals = _signals_RUNTIME_INIT, \ .interpreters = { \ .next_id = -1, \ }, \ .xi = { \ .registry = { \ .global = 1, \ }, \ }, \ .autoTSSkey = Py_tss_NEEDS_INIT, \ .parser = _parser_runtime_state_INIT, \ .ceval = { \ .pending_mainthread = { \ .max = MAXPENDINGCALLS_MAIN, \ .maxloop = MAXPENDINGCALLSLOOP_MAIN, \ }, \ .perf = _PyEval_RUNTIME_PERF_INIT, \ }, \ .gilstate = { \ .check_enabled = 1, \ }, \ .fileutils = { \ .force_ascii = -1, \ }, \ .faulthandler = _faulthandler_runtime_state_INIT, \ .tracemalloc = _tracemalloc_runtime_state_INIT, \ .ref_tracer = { \ .tracer_func = NULL, \ .tracer_data = NULL, \ }, \ .stoptheworld = { \ .is_global = 1, \ }, \ .float_state = { \ .float_format = _py_float_format_unknown, \ .double_format = _py_float_format_unknown, \ }, \ .types = { \ .next_version_tag = 1, \ }, \ .static_objects = { \ .singletons = { \ .small_ints = _Py_small_ints_INIT, \ .bytes_empty = _PyBytes_SIMPLE_INIT(0, 0), \ .bytes_characters = _Py_bytes_characters_INIT, \ .strings = { \ .literals = _Py_str_literals_INIT, \ .identifiers = _Py_str_identifiers_INIT, \ .ascii = _Py_str_ascii_INIT, \ .latin1 = _Py_str_latin1_INIT, \ }, \ .tuple_empty = { \ .ob_base = _PyVarObject_HEAD_INIT(&PyTuple_Type, 0), \ }, \ .hamt_bitmap_node_empty = { \ .ob_base = _PyVarObject_HEAD_INIT(&_PyHamt_BitmapNode_Type, 0), \ }, \ .context_token_missing = { \ .ob_base = _PyObject_HEAD_INIT(&_PyContextTokenMissing_Type), \ }, \ }, \ }, \ ._main_interpreter = _PyInterpreterState_INIT(runtime._main_interpreter), \ }
На этом интересные особенности функции исчерпаны. Но что же эта функция делает?
Во-первых, она инициализирует поле preconfig в _PyRuntime. Оно принадлежит к типу PyPreConfig из Include/cpython/initconfig.h, и его листинг представлен ниже:
typedef struct PyPreConfig { int _config_init; int parse_argv; int isolated; int use_environment; int configure_locale; int coerce_c_locale; int coerce_c_locale_warn; int utf8_mode; int dev_mode; int allocator; } PyPreConfig;
Реальными данными это поле будет заполнено позже.
Во-вторых, заполняется поле main_thread в _PyRuntime. Оно заполняется результатом работы функции PyThread_get_thread_ident из Python/thread_pthread.h. Функция возвращает уникальный в рамках процесса идентификатор текущего (вызывающего) потока.
Следующая функция на очереди - _Py_PreInitializeFromPyArgv из Python/pylifecycle.c. В начале для нее создается промежуточная переменная типа PyPreConfig, которая заполняется на основе параметров, полученных из консоли и переменных окружения. Например:
use_environmentзаполняется на основе опции запуска Python-Eisolatedзаполняется на основе опции запуска Python-Idev_modeзаполняется на основе опции запуска Python-X devcoerce_c_localeиcoerce_c_locale_warnзаполняются на основе переменной окруженияPYTHONCOERCECLOCALEutf8_modeзаполняется на основе переменной окруженияPYTHONUTF8или, если она не задана, на основе опции запуска-X utf8allocatorзаполняется на основе переменной окруженияPYTHONMALLOC
Дополнительный источник информации о флагах
Помимо официальной документации, ознакомиться с тем, за что отвечают эти и другие флаги конфигурации, можно вызвав справку Python с помощью командыpython -h.
Затем, на основе поля allocator промежуточной переменной, упомянутой ранее, заполняется _PyRuntime.allocators.standard:
// Include/internal/pycore_runtime.h typedef struct pyruntimestate { ... struct _pymem_allocators allocators; ... } _PyRuntimeState; // Include/internal/pycore_pymem.h struct _pymem_allocators { ... struct { PyMemAllocatorEx raw; PyMemAllocatorEx mem; PyMemAllocatorEx obj; } standard; ... }; // Include/cpython/pymem.h typedef struct { void *ctx; void* (*malloc) (void *ctx, size_t size); void* (*calloc) (void *ctx, size_t nelem, size_t elsize); void* (*realloc) (void *ctx, void *ptr, size_t new_size); void (*free) (void *ctx, void *ptr); } PyMemAllocatorEx;
Подробнее об аллокаторах мы поговорим в рамках отдельных статей, а пока лишь скажу, что с помощью переменной окружения PYTHONMALLOC мы можем выбирать, какие аллокаторы для каких задач будут использоваться внутри CPython.
Забегая вперед, эта вариативность описана в функции
set_up_allocators_unlockedизObjects/obmalloc.c.
Далее, функция сохраняет промежуточную переменную типа PyPreConfig в _PyRuntime.preconfig. На этом работа функции _Py_PreInitializeFromPyArgv окончена, как и окончен этап предконфигурации CPython.
Итоги
Во-первых, в статье был введен и рассмотрен один из краеугольных объектов всего CPython - переменная _PyRuntime. Она является глобальным и единственным хранилищем состояния всего процесса выполнения CPython. Во-вторых, был рассмотрен процесс предконфигурации CPython - заполнение поля preconfig переменной _PyRuntime. Это поле хранит лишь малую часть конфигурационных параметров, включенных в CPython. Остальные параметры будут введены в следующих статьях, посвященных этапу конфигурации.
Ну и наконец, было введено ключевое слово "аллокатор", смысл и назначение которого будут рассмотрены в рамках отдельных статей.
Мой ТГ-контакт для связи: https://t.me/AlexandrShherbanjuk
