Предисловие

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

  • isolated заполняется на основе опции запуска Python -I

  • dev_mode заполняется на основе опции запуска Python -X dev

  • coerce_c_locale и coerce_c_locale_warn заполняются на основе переменной окружения PYTHONCOERCECLOCALE

  • utf8_mode заполняется на основе переменной окружения PYTHONUTF8 или, если она не задана, на основе опции запуска -X utf8

  • allocator заполняется на основе переменной окружения 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

Комментарии (0)