Предисловие
Привет, читатель! Меня зовут Александр Щербанюк, и я 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