Автор оригинала: David Wong.
Автор: Хань Тяньфэн оригинальный адрес: Нажмите для просмотра
На самом деле концепция появилась уже давно По словам Дональда Кнута, термин “сопрограмма” был придуман Мелвином Конвеем в 1958 году, после того как он применил его к созданию программы сборки. Первое опубликованное объяснение сопрограммы появилось позже, в 1963 году. У него более долгая история, чем у языка Си. С точки зрения своей концепции, это своего рода подпрограмма, которая может передавать управляющую мощность программы через yield. Отношения между процессами-это не отношения между вызывающим абонентом и вызываемым абонентом, а симметричные и равные друг другу. Поскольку он полностью управляется пользователем, он также является пользовательским потоком. Процесс запланирован пользователем без опережения, а не операционной системой. Из-за этого в системном планировании нет накладных расходов на переключение контекста, и процесс является легким, эффективным и быстрым. (большинство из них не являются упреждающими, но, например, golang также присоединился к упреждающему планированию в 1.4. У одного из сотрудников есть цикл жизни и смерти, так что другие сотрудники не умрут с голоду. Нужно отказаться от процессора, когда это необходимо)
Благодаря популярности и быстрому развитию golang в Китае, он стал популярным в последние годы. В настоящее время существует множество языков, поддерживающих совместное программирование, таких как: golang, Lua, python, C ා, JavaScript и т.д. Вы также можете использовать очень короткий код с C/C + + вне модели процесса. Конечно, PHP также имеет свою собственную реализацию совместной программы, то есть генератор. Мы не будем обсуждать это здесь.
Сначала swool привлек наше внимание как высокопроизводительный сетевой коммуникационный движок. Свул 1. Xcode-это в основном асинхронный обратный вызов. Хотя производительность очень эффективна, многие разработчики обнаружат, что с увеличением сложности проектирования написание бизнес-кода способом асинхронного обратного вызова противоречит нормальному мышлению человека, особенно когда обратный вызов вложен в несколько уровней, не только затраты на обслуживание разработки выросли экспоненциально, но и вероятность ошибок резко возросла. Идеальный метод кодирования: синхронное кодирование для достижения асинхронного неблокирующего эффекта. Поэтому Свул начал изучать процесс сотрудничества очень рано.
Оригинальная версия совместной работы основана на том, как реализован выход генераторов PHP generator. Пожалуйста, ознакомьтесь с введением сотрудничества в раннем блоге Никиты. Управляемая событиями комбинация PHP и swool может ссылаться на платформу тестирования с открытым исходным кодом Tencent. Мы также используем этот фреймворк во многих производственных проектах, что действительно доставляет вам удовольствие писать асинхронный код способом синхронного программирования. Однако реальность всегда жестока, что имеет несколько фатальных недостатков:
- Вся логика, которая активно передается, требует ключевого слова yield. Это даст программистам отличный шанс ошибиться, что приведет к смещению понимания принципов синтаксиса генераторов.
- Поскольку грамматика не может быть совместима со старым проектом, преобразование старого проекта слишком сложно и дорого.
Таким образом, независимо от того, новые или старые проекты, они не просты в использовании.
После 2. X, все процессы основаны на собственных процессах ядра, без ключевого слова yield. Версия 2.0-это очень важная веха. Он реализует управление стеком PHP. Он глубоко проникает в ядро Zend, чтобы управлять стеком PHP во время создания, переключения и завершения процесса сотрудничества.
2. X в основном использует setjmp/longjmp для реализации процесса сотрудничества. Многие проекты на языке Си в основном используют этот способ для окончательной реализации try catch. Вы также можете обратиться к использованию ядра Zend. Возвращаемое значение первого вызова setjmp равно 0. Когда longjmp переходит, возвращаемое значение setjmp-это значение, переданное longjmp. Setjmp/longjmp имеет только возможность управлять скачком потока. Хотя ПК и указатель стека могут быть восстановлены, кадр стека не может быть восстановлен, поэтому возникнет много проблем. Например, в случае longJMP область действия setjmp существовала, и кадр стека в это время был уничтожен. Возникает неопределенное поведение. Предположим, что существует такая цепочка вызовов:
func0() -> func1() -> ... -> funcN()
Только в случае setjmp в функции {I} () и longjmp в функции {I + K} () можно ожидать поведения программы.
3. X – это версия с очень коротким жизненным циклом. В основном это относится к проекту fiber ext и использует механизм прерывания виртуальной машины php 7. Этот механизм может установить бит маркера в виртуальной машине, проверить бит маркера при выполнении некоторых инструкций (таких как переход и вызов функции и т. Д.) И выполнить соответствующую функцию хука для переключения стека виртуальной машины при попадании, чтобы реализовать сотрудничество.
Начиная с 4. X, swool реализовал ядро совместного программирования в режиме двойного стека. И все операции ввода – вывода и системные операции инкапсулированы на нижний уровень для реализации тщательного совместного программирования ядра. Кроме того, он также предоставляет новый модуль подключения во время выполнения, который может изменить существующий код синхронизации PHP в режим совместного процесса.
Начните с самого простого примера процесса сотрудничества:
//The output content is coro 1 start main flag coro 2 start main end coro 1 exit coro 2 exit
Можно обнаружить, что собственная сопрограмма переходит внутри функции, поток управления переходит со строки 4 на строку 7, затем функция go начинается со строки 8, переходит со строки 10 на строку 13, затем строка 9, а затем строка 15. Почему процессы swool могут выполняться таким образом? Мы проанализируем это шаг за шагом.
Мы знаем, что PHP, как интерпретируемый язык, должен быть скомпилирован в средний байт-код, прежде чем его можно будет выполнить. Сначала сценарий будет скомпилирован в массив кодов операций с помощью лексического и синтаксического анализа, а затем он будет выполнен механизмом виртуальной машины. Здесь мы сосредоточимся только на части выполнения виртуальной машины. В части выполнения необходимо сосредоточиться на нескольких важных структурах данных
struct _zend_op {
Const void * handler; // C processing function corresponding to each opcode
Znode_op OP1; // operand 1
Znode Ou OP op2; // operand 2
Znode_op result; // return value
uint32_t extended_value;
uint32_t lineno;
Zend_charopcode; // opcode instruction
Zend_charop1_type; // operand 1 type
Zend_charop2_type; // operand 2 type
Zend_charresult_type; // return value type
};Легко обнаружить, что коды операций-это, по сути, код с тремя адресами, где код операции-это тип инструкции, с двумя входными операндами и одним, представляющим выходные операнды. Каждая инструкция может использовать все или часть этих операндов. Например, сложение, вычитание, умножение, деление и т.д. Будут использоваться все три операнда! Операция включает в себя только OP1 и результат; вызов функции включает в себя наличие возвращаемого значения и т. Д.
Основной сценарий Zend? ОПЕРАЦИЯ? Массив PHP будет генерировать Zend? ОПЕРАЦИЯ? Массив. Каждая функция, оценка или даже утверждение выражения будут генерировать новую операцию? Массив.
struct _zend_op_array {
/* Common zend_function header here */
/* ... */
Uint32_t last; // number of opcodes in the array
Zend_op * opcodes; // opcode instruction array
Int last_var; // number of CVS
Uint32 T; // number of is TMP VaR and is var
Zend_string * * vars; // array of variable names
/* ... */
Int last_literal; // literal quantity
Zval * literals; // when accessing literal array, read it through "Zend" op "array - > literals + offset
/* ... */
};Мы уже знакомы с функциями PHP, которые имеют свои собственные области применения. Это потому, что каждый Зенд? ОПЕРАЦИЯ? Массив содержит всю информацию о стеке в текущей области. Связь вызовов между функциями также основана на переключении Zend? ОПЕРАЦИЯ? Массив.
Все состояния, необходимые для выполнения PHP, сохраняются в стеках виртуальных машин, связанных со структурой связанного списка. По умолчанию каждый стек будет инициализирован до 256 КБ. Swool может самостоятельно настраивать размер стека (значение протокола по умолчанию-8 КБ). Когда емкость стека недостаточна, он автоматически расширит емкость и по-прежнему будет связывать каждый стек с отношением связанного списка. При каждом вызове функции в пространстве стека виртуальной машины будет применяться новый кадр стека для выполнения текущей области. Структура структуры стековых кадров в памяти выглядит следующим образом:
+----------------------------------------+ | zend_execute_data | +----------------------------------------+ | VAR[0] = ARG[1] | arguments | ... | | VAR[num_args-1] = ARG[N] | | VAR[num_args] = CV[num_args] | remaining CVs | ... | | VAR[last_var-1] = CV[last_var-1] | | VAR[last_var] = TMP[0] | TMP/VARs | ... | | VAR[last_var+T-1] = TMP[T] | | ARG[N+1] (extra_args) | extra arguments | ... | +----------------------------------------+
Последняя структура, которая будет введена, является наиболее важной.
struct _zend_execute_data {
Const zend_op * opline; // the opcode currently executed will start with zend_op_array after initialization
zend_execute_data *call;//
Zval * return_value; // return value
Zend_function * function; // currently executing function (empty when non function call)
zval This;/* this + call_info + num_args */
Zend_class_entry * called_scope; // the class of the current call
zend_execute_data *prev_execute_data;
Zend_array * symbol_table; // global variable symbol table
void **run_time_cache; /* cache op_array->run_time_cache */
zval *literals; /* cache op_array->literals */
};До? Казнить? Данные указывают на предыдущую структуру фрейма стека. После выполнения текущего стека указатель текущего выполнения (аналогично ПК) будет указывать на этот кадр стека. Поток выполнения PHP заключается в загрузке многих Zend? ОПЕРАЦИЯ? Массив для выполнения в кадре стека по очереди. Этот процесс можно разделить на следующие этапы:
- 1: Подайте заявку на текущий кадр стека из стека виртуальной машины для выполнения текущего массива операций. Структура такая же, как и выше. Инициализируйте таблицу символов глобальной переменной и наведите глобальный указатель, например (текущие данные выполнения), на недавно выделенный фрейм стека данных выполнения Zend, а ex (линия) – на начальную позицию массива операций.
2: Начните с ex (opline), чтобы вызвать обработчик обработки C (т. Е. обработчик операций “Zend”) каждого кода операции. После выполнения каждого кода операции ex (opline) + + продолжит выполнение следующего кода операции до тех пор, пока не будут выполнены все коды операций. При обнаружении вызова метода функции или члена класса:
- Извлеките zend’op ‘- массив, соответствующий этой функции в соответствии с именем функции из eg (таблица функций), затем повторите шаг 1, назначьте eg (текущие данные ‘execute’) предыдущим данным новой структуры, а затем укажите eg (текущие данные ‘execute’) на новый кадр стека данных zend’execute, а затем начните выполнять новый кадр стека, начиная с zend’execute.oline. После выполнения функции выполните, например (текущие данные ‘execute’) _Execute? Данные) указывает на бывшую (предыдущую? Казнить? Данные), освобождает выделенный кадр стека запуска и возвращается к следующему в режиме онлайн в конце выполнения функции.
- 3: После выполнения всех кодов операций кадр стека, выделенный 1, будет освобожден, и фаза выполнения завершится.
С приведенными выше подробностями выполнения PHP давайте вернемся к исходному примеру и выясним, что для взаимодействия необходимо изменить исходный режим работы PHP, не переключать кадр стека в конце работы функции, а переключаться на другие кадры стека в любое время во время текущего выполнения функции в массиве op’u (внутренний контроль swool заключается в том, чтобы столкнуться с ожиданием ввода-вывода). Затем мы объединим виртуальную машину Zend и swool, чтобы проанализировать, как создать стек взаимодействия, переключение ввода-вывода, восстановление стека после завершения ввода-вывода и уничтожение кадра стека при завершении взаимодействия. Во – первых, вводится основная структура PHP – части процесса сотрудничества:
- Основная задача PHP
struct php_coro_task
{
/*List only key structures*/
/*...*/
Zval * vm_stack_top; // stack top
Zval * vm_stack_end; // stack bottom
Zend VM stack VM stack; // current collaboration stack pointer
/*...*/
Zend? Execute? Data * execute? Data; // current protocol stack frame
/*...*/
PHP ﹣ Coro ﹣ task * origin ﹣ task; // last process stack frame, similar to prev ﹣ execute ﹣ data
};Переключатель взаимодействия предназначен в основном для сохранения и восстановления контекста при прерывании текущего выполнения стека. В сочетании с процессом выполнения виртуальной машины мы можем знать функции вышеуказанных полей.
- Нет никаких сомнений в том, что указатель на фрейм стека данных выполнения необходимо сохранить и восстановить
- Какова роль серии стека виртуальных машин*? Причина в том, что PHP-это динамический язык. Как мы проанализировали выше, каждый раз, когда новая функция входит и выходит, необходимо создавать и освобождать кадр стека в глобальном стеке. Поэтому соответствующий глобальный указатель стека необходимо правильно сохранить и восстановить, чтобы гарантировать, что каждый кадр стека может быть освобожден без утечки памяти. (после компиляции PHP в режиме отладки каждый выпуск будет проверять, является ли глобальный стек законным.)
- Исходная задача-это предыдущий кадр стека, который должен быть выполнен автоматически после выполнения текущего взаимодействия.
Основными применяемыми методами являются
Создайте фрейм стека приложений для совместной работы в глобальном стеке.
- Создание процедуры сотрудничества заключается в создании функции закрытия и передаче функции (которую можно понимать как массив op’u, подлежащий выполнению) во встроенную функцию go() swool в качестве параметра;
- Выход процесса, выход, ввод-вывод, сохранение контекстной информации текущего кадра стека
- Резюме и ввод-вывод процесса завершены. Восстановите контекстную информацию о процессе, который должен быть выполнен, в состояние до выхода
- Выход, выход и массив операций процесса сотрудничества завершены, освобождая кадр стека и соответствующие данные процесса сотрудничества swool.
После приведенного выше введения у вас должно быть общее представление о прыжке, который может быть реализован в функции во время работы оркестровки свула. Возвращаясь к исходному примеру и приведенным выше деталям выполнения PHP, мы можем знать, что в этом примере будут сгенерированы три массива op’u, которые являются основным сценарием, оркестровкой 1 и оркестровкой 2. Мы можем использовать некоторые инструменты для распечатки кодов операций для интуитивного наблюдения. Обычно мы используем следующие два инструмента
//Opcache, version >= PHP 7.1 php -d opcache.opt_debug_level=0x10000 test.php //VLD, third party extension php -d vld.active=1 test.php
Мы используем кэш операций для наблюдения за кодами операций перед оптимизацией. Мы можем четко видеть детали этих трех групп массива op’u.
php -dopcache.enable_cli=1 -d opcache.opt_debug_level=0x10000 test.php
$_main: ; (lines=11, args=0, vars=0, tmps=4)
; (before optimizer)
; /path-to/test.php:2-6
L0 (2): INIT_FCALL 1 96 string("go")
L1 (2): T0 = DECLARE_LAMBDA_FUNCTION string("")
L2 (6): SEND_VAL T0 1
L3 (6): DO_ICALL
L4 (7): ECHO string("main flag
")
L5 (8): INIT_FCALL 1 96 string("go")
L6 (8): T2 = DECLARE_LAMBDA_FUNCTION string("")
L7 (12): SEND_VAL T2 1
L8 (12): DO_ICALL
L9 (13): ECHO string("main end
")
L10 (14): RETURN int(1)
{closure}: ; (lines=6, args=0, vars=0, tmps=1)
; (before optimizer)
; /path-to/test.php:2-6
L0 (9): ECHO string("coro 2 start
")
L1 (10): INIT_STATIC_METHOD_CALL 1 string("co") string("sleep")
L2 (10): SEND_VAL_EX int(1) 1
L3 (10): do ﹐ fCall // yiled from current op ﹐ array [Coro 1]; resume
L4 (11): ECHO string("coro 2 exit
")
L5 (12): RETURN null
{closure}: ; (lines=6, args=0, vars=0, tmps=1)
; (before optimizer)
; /path-to/test.php:2-6
L0 (3): ECHO string("coro 1 start
")
L1 (4): INIT_STATIC_METHOD_CALL 1 string("co") string("sleep")
L2 (4): SEND_VAL_EX int(1) 1
L3 (4): do ﹐ fCall // yiled from current op ﹐ array [Coro 2]; resume
L4 (5): ECHO string("coro 1 exit
")
L5 (6): RETURN null
coro 1 start
main flag
coro 2 start
main end
coro 1 exit
coro 2 exitПри выполнении Co:: sleep () swool отказывается от текущего права управления и переходит к следующему массиву операций. В сочетании с приведенными выше примечаниями, то есть, когда do’fcall, он прекращает и восстанавливает стек выполнения сотрудничества соответственно, чтобы достичь цели перехода потока управления исходного сотрудничества.
Мы анализируем, как в ядре выполняется инструкция init call do call. Чтобы лучше понять взаимосвязь переключения стека вызовов функций.
Внутренние инструкции виртуальной машины будут специализированы в функции C в соответствии с возвращаемым значением текущего операнда. В этом примере мы имеем следующее соответствующее соотношение
INIT_CALL => ZEND_INIT_FCALL_SPEC_CONST_HANDLER
DO_FCALL => ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER
ZEND_INIT_FCALL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zval *fname = EX_CONSTANT(opline->op2);
zval *func;
zend_function *fbc;
zend_execute_data *call;
fbc = CACHED_PTR(Z_CACHE_SLOT_P(fname));
if (UNEXPECTED(fbc == NULL)) {
func = zend_hash_find(EG(function_table), Z_STR_P(fname));
if (UNEXPECTED(func == NULL)) {
SAVE_OPLINE();
zend_throw_error(NULL, "Call to undefined function %s()", Z_STRVAL_P(fname));
HANDLE_EXCEPTION();
}
fbc = Z_FUNC_P(func);
CACHE_PTR(Z_CACHE_SLOT_P(fname), fbc);
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!fbc->op_array.run_time_cache)) {
init_func_run_time_cache(&fbc->op_array);
}
}
call = zend_vm_stack_push_call_frame_ex(
opline->op1.num, ZEND_CALL_NESTED_FUNCTION,
FBC, offline - > extended [value, null, null); // request the execution stack of the current function from the global stack
Call - > prev? Execute? Data = ex (call); // assign the stack being executed to prev? Execute? Data of the stack to be executed, and recover here after the function execution
Ex (call) = call; // assign the function stack to the global execution stack, the function stack to be executed
ZEND_VM_NEXT_OPCODE();
}ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
Zend_execute_data * call = ex (call); // get the execution stack
Zend_function * FBC = call - > func; // current function
zend_object *object;
zval *ret;
Save? Opline(); // when there is a global register ((execute? Data) - > opline) = opline
Ex (call) = call - > prev ﹣ execute ﹣ data; // the current execution stack execute ﹣ data - > call = ex (call) - > prev ﹣ execute ﹣ data function recovers to the called function after execution
/*...*/
LOAD_OPLINE();
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION)) {
ret = NULL;
if (0) {
ret = EX_VAR(opline->result.var);
ZVAL_NULL(ret);
}
call->prev_execute_data = execute_data;
i_init_func_execute_data(call, &fbc->op_array, ret);
if (EXPECTED(zend_execute_ex == execute_ex)) {
ZEND_VM_ENTER();
} else {
ZEND_ADD_CALL_FLAG(call, ZEND_CALL_TOP);
zend_execute_ex(call);
}
} else if (EXPECTED(fbc->type < ZEND_USER_FUNCTION)) {
zval retval;
call->prev_execute_data = execute_data;
EG(current_execute_data) = call;
/*...*/
ret = 0 ? EX_VAR(opline->result.var) : &retval
ZVAL_NULL(ret);
if (!zend_execute_internal) {
/* saves one function call if zend_execute_internal is not used */
fbc->internal_function.handler(call, ret);
} else {
zend_execute_internal(call, ret);
}
EG(current_execute_data) = execute_data;
Zend? VM? Stack? Free? Args (call); // release local variables
if (!0) {
zval_ptr_dtor(ret);
}
} else { /* ZEND_OVERLOADED_FUNCTION */
/*...*/
}
fcall_end:
/*...*/
}
Zend? VM? Stack? Free? Call? Frame (call); // release the stack
if (UNEXPECTED(EG(exception) != NULL)) {
zend_rethrow_exception(execute_data);
HANDLE_EXCEPTION();
}
ZEND_VM_SET_OPCODE(opline + 1);
ZEND_VM_CONTINUE();
}Swool может быть переключен на уровне PHP в соответствии с описанными выше методами. Что касается ожидания ввода – вывода во время выполнения, для его управления необходимы дополнительные технологии. В следующих статьях мы представим технологию драйверов каждой версии в сочетании с оригинальной моделью событий swool и опишем, как сотрудничество swool развивалось до настоящего времени.
Поскольку наша система состоит из двух частей: стека C и стека PHP, согласованное имя:
- C протокол C управление стеком,
- Протокол PHP Часть управления стеком PHP.
Добавление стека C является наиболее важной и ключевой частью 4. X программа сотрудничества. Предыдущие версии не могут идеально поддерживать синтаксис PHP, потому что они не сохранили информацию о стеке C. Далее мы проанализируем поддержку переключения c-стека. Сначала мы использовали libco Tencent для его поддержки, но в ходе проверки под давлением будут возникать ошибки чтения и записи в памяти, а сообщество с открытым исходным кодом очень неактивно, и есть проблемы, с которыми невозможно справиться вовремя. Таким образом, сборочная часть библиотеки C + + boost, которую мы разделили, основана на этом.
Можно обнаружить, что роль swool заключается в том, чтобы склеить системный API и PHP zendvm для написания высокопроизводительного кода для пользовательского интерфейса PHPer; не только это, но и для поддержки разработки и использования пользователями C + +/C. Для получения более подробной информации, пожалуйста, обратитесь к документу, как разработчики C + + используют swool. Код части C в основном разделен на несколько частей:
- Драйвер ASM для сборки
- Контекст инкапсуляция контекста
- Протокол сокета инкапсуляция сокета
- Инкапсуляция потоковой системы PHP может легко совместно программировать функции, связанные с PHP
- Связующий слой виртуальной машины Zend
Лежащая в основе система свула более иерархична. Сокет станет краеугольным камнем всего сетевого диска. В исходной версии каждому клиенту необходимо поддерживать контекст на основе асинхронного обратного вызова. Поэтому, по сравнению с предыдущей версией, 4. X имеет качественный скачок с точки зрения сложности проекта и стабильности системы. Уровень каталога кодов
$ tree swoole-src/src/coroutine/ swoole-src/src/coroutine/ ├ - base.cc // C orchestration API, which can be called back to PHP orchestration API ├── channel.cc //channel ├ - context.cc // the implementation of the cooperation process is based on ASM make ﹣ fcontext jump ﹣ fcontext ├── hook.cc //hook └ -- socket.cc // network operation process package Swoole Src / swoole_coroutine.cc // zendvm related encapsulation, PHP co programming API
От уровня пользователя до системного уровня у нас есть PHP, C и ASM API. Уровень сокета – это сетевая инкапсуляция совместимого системного API. Мы спускаемся и поднимаемся. Например, архитектура ASM x86-64 имеет 16 64-разрядных общих регистров. Регистры и цели заключаются в следующем:
%Rax обычно используется для хранения возвращаемых результатов вызовов функций, а также для инструкций умножения и деления. В инструкции imul умножение двух 64-битных может дать не более 128-битных результатов. Ему нужны% rax и% RDX, чтобы хранить результаты умножения вместе. В инструкции div делитель равен 128 битам. Ему также нужны% rax и% RDX для совместного хранения делителя.
- %RSP-это регистр указателя стека, который обычно указывает на верхнюю часть стека. Операции pop и push стека реализуются путем изменения значения% RSP, то есть перемещения указателя стека.
- %RBP-это указатель на кадр стека, используемый для определения начальной позиции текущего кадра стека
- %Шесть регистров, RDI,% RSI,% RDX,% RCX,% R8,% R9, используются для хранения шести параметров при вызове функции
- % RBX,% R12,% R13,% 14,% 15 используются в качестве хранилища данных и соответствуют правилам использования вызываемого абонента
%R10,% R11 используются в качестве хранилища данных и следуют правилам вызывающего абонента
То есть, после ввода функции сборки первое значение параметра было помещено в регистр% RDI, второе значение параметра было помещено в регистр% RSI, а указатель стека% RSP указывает на позицию, в которой адрес возврата родительской функции хранится в верхней части стека x86-64 используйте swool Src/третья сторона/boost/ASM/make x86_64_sysv_elf_gas. S
//Create a context at the top of the current stack to execute the third parameter function fn and return the execution environment context after initialization
fcontext_t make_fcontext(void *sp, size_t size, void (*fn)(intptr_t));
make_fcontext:
/* first arg of make_fcontext() == top of context-stack */
movq %rdi, %rax
/* shift address in RAX to lower 16 byte boundary */
andq $-16, %rax
/* reserve space for context-data on context-stack */
/* size for fc_mxcsr .. RIP + return-address for context-function */
/* on context-function entry: (RSP -0x8) % 16 == 0 */
leaq -0x48(%rax), %rax
/* third arg of make_fcontext() == address of context-function */
movq %rdx, 0x38(%rax)
/* save MMX control- and status-word */
stmxcsr (%rax)
/* save x87 control-word */
fnstcw 0x4(%rax)
/* compute abs address of label finish */
leaq finish(%rip), %rcx
/* save address of finish as return-address for context-function */
/* will be entered after context-function returns */
movq %rcx, 0x40(%rax)
Return / * return pointer to context data * returns the stack bottom pointer pointed by rax as context///Save the current context (including stack pointer, PC program counter and register) to * OFC, recover the context from NFC and start execution.
intptr_t jump_fcontext(fcontext_t *ofc, fcontext_t nfc, intptr_t vp, bool preserve_fpu = false);
jump_fcontext:
//Save current register, stack
pushq %rbp /* save RBP */
pushq %rbx /* save RBX */
pushq %r15 /* save R15 */
pushq %r14 /* save R14 */
pushq %r13 /* save R13 */
pushq %r12 /* save R12 */
/* prepare stack for FPU */
leaq -0x8(%rsp), %rsp
/* test for flag preserve_fpu */
cmp $0, %rcx
je 1f
/* save MMX control- and status-word */
stmxcsr (%rsp)
/* save x87 control-word */
fnstcw 0x4(%rsp)
1:
/*Store RSP (pointing to context data) in RDI save the current stack top to RDI, that is, save the current stack top pointer to the first parameter% RDI OFC*/
movq %rsp, (%rdi)
/*Restore RSP (pointing to context data) from RSI changes the top address of the stack to the address of the new protocol, and RSI is the second parameter address*/
movq %rsi, %rsp
/* test for flag preserve_fpu */
cmp $0, %rcx
je 2f
/* restore MMX control- and status-word */
ldmxcsr (%rsp)
/* restore x87 control-word */
fldcw 0x4(%rsp)
2:
/* prepare stack for FPU */
leaq 0x8(%rsp), %rsp
//Register recovery
popq %r12 /* restrore R12 */
popq %r13 /* restrore R13 */
popq %r14 /* restrore R14 */
popq %r15 /* restrore R15 */
popq %rbx /* restrore RBX */
popq %rbp /* restrore RBP */
/*Restore return address put the return address in the R8 register*/
popq %r8
/* use third arg as return-value after jump*/
movq %rdx, %rax
/* use third arg as first arg in context function */
movq %rdx, %rdi
/* indirect jump to context */
jmp *%r8Управление контекстом находится в context.cc, который инкапсулирует ASM и предоставляет два API
bool Context::SwapIn() bool Context::SwapOut()
API окончательной оркестровки находится в base.cc, и основным API является
//Create a c-stack protocol, provide an execution entry function, and enter the execution context of the function //For example, the PHP stack's entry function coroutine:: create (phpcoroutine:: create_func, (void *) & php_coro_args); long Coroutine::create(coroutine_func_t fn, void* args = nullptr); //Cut out from the current context, and call hook functions such as PHP stack switch function void phpcoroutine:: on ˊ yield (void * ARG) void Coroutine::yield() //Cut in from the current context, and call hook functions such as PHP stack switch function void phpcoroutine:: on UUME (void * ARG) void Coroutine::resume() //The execution of the C cooperation procedure ends, and a hook function is called, such as PHP stack cleaning void phpcoroutine:: on close (void * ARG) void Coroutine::close()
Далее, клеевой слой zend vm находится в swool Src/swoole_coroutine.cc
Phpcoroutine is used by the C cooperation program or the underlying interface //Create an entry function with PHP function as the parameter static long create(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv); //C process creation API static void create_func(void *arg); //The last part of the c-coprocess hook function, the c-coprocess of base.cc, is related to the following three hook functions static void on_yield(void *arg); static void on_resume(void *arg); static void on_close(void *arg); //PHP stack management static inline void vm_stack_init(void); static inline void vm_stack_destroy(void); static inline void save_vm_stack(php_coro_task *task); static inline void restore_vm_stack(php_coro_task *task); //Output cache management related static inline void save_og(php_coro_task *task); static inline void restore_og(php_coro_task *task);
Благодаря построению вышеуказанной базовой части и реализации управления стеком в сочетании с ядром PSP вы можете управлять процессом PHP co из процесса C co и реализовать процесс с двойным стеком собственного ядра C stack + PHP stack.
Хань Тяньфэн, основатель проекта swool с открытым исходным кодом и главный архитектор онлайн-школы XRS.
Оригинал: “https://developpaper.com/swoole-journey/”