Я решил, что должен попытаться реализовать сопрограммы (я думаю, что я должен их назвать) для удовольствия и прибыли. Я ожидаю, что придется использовать ассемблер и, возможно, некоторые C, если я хочу сделать это полезным для всего.
Имейте в виду, что это для образовательных целей. Использование уже построенной библиотеки coroutine слишком просто (и действительно не весело).
Вы, ребята, знаете setjmp
и longjmp
? Они позволяют вам разматывать стек до предопределенного местоположения и возобновлять выполнение оттуда. Однако он не может перемотать "позже" в стек. Вернитесь раньше.
jmpbuf_t checkpoint;
int retval = setjmp(&checkpoint); // returns 0 the first time
/* lots of stuff, lots of calls, ... We're not even in the same frame anymore! */
longjmp(checkpoint, 0xcafebabe); // execution resumes where setjmp is, and now it returns 0xcafebabe instead of 0
То, что мне нужно - это способ запускать, без потоковой передачи, две функции на разных стеках. (Очевидно, только один запускается за один раз. Нет нити, я сказал.) Эти две функции должны иметь возможность возобновить другое исполнение (и остановить их собственное). Скорее всего, если они были друг с другом. Когда он вернется к другой функции, он должен вернуться туда, где он остался (то есть во время или после вызова, который дал управление другой функции), немного похоже на то, как longjmp
возвращается к setjmp
.
Вот как я это думал:
- Функция
A
создает и обнуляет параллельный стек (выделяет память и все такое). - Функция
A
перетаскивает все свои регистры в текущий стек. - Функция
A
устанавливает указатель стека и базовый указатель на это новое местоположение и вызывает таинственную структуру данных, указывающую, куда перепрыгивать назад и где указывать указатель команд назад. - Функция
A
обнуляет большинство своих регистров и устанавливает указатель на начало функцииB
.
Это для инициализации. Теперь следующая ситуация будет бесконечно замкнутой:
- Функция
B
работает в этом стеке, выполняет любую работу, в которой она нуждается. - Функция
B
переходит в точку, где требуется прервать и снова дать командуA
. - Функция
B
подталкивает все свои регистры к своему стеку, берет таинственную структуру данныхA
дала ее в самом начале и устанавливает указатель стека и указатель на инструкцию, где ему было указаноA
. В этом процессе он возвращаетA
новую модифицированную структуру данных, которая сообщает, где возобновитьB
. - Функция
A
просыпается, возвращая все регистры, которые она вставляет в свой стек, и работает до тех пор, пока не дойдет до точки, где ей необходимо прервать и снова дать командуB
.
Все это звучит хорошо для меня. Тем не менее, есть ряд вещей, с которыми я не совсем согласен.
- По-видимому, на добром ol 'x86 была эта инструкция
pusha
, которая отправила бы все регистры в стек. Однако архитектуры процессоров развиваются, и теперь с x86_64 у нас есть намного больше универсальных регистров и, вероятно, несколько регистров SSE. Я не мог найти никаких доказательств того, чтоpusha
действительно их подталкивает. В mordern x86 CPU имеется около 40 публичных регистров. Должен ли я сам делать всеpush
es? Более того, для SSE-регистров нетpush
(хотя он должен быть эквивалентным - я новый для всего этого "ассемблера x86" ). -
Меняет указатель на инструкцию так же просто, как сказать? Могу ли я сделать, например,Это простоmov rip, rax
(синтаксис Intel)? Кроме того, получение значения от него должно быть несколько особенным, поскольку оно постоянно изменяется. Если мне больше нравитсяmov rax, rip
(синтаксис Intel снова), будетrip
располагаться в командеmov
, инструкции после него или где-то между?jmp foo
. Пустышка. - Я несколько раз упоминал о таинственной структуре данных. До сих пор я предполагал, что он должен содержать как минимум три вещи: базовый указатель, указатель стека и указатель команд. Есть ли что-нибудь еще?
- Я что-то забыл?
- Пока я действительно хотел бы понять, как все работает, я уверен, что есть несколько библиотек, которые делают именно это. Вы знаете что-нибудь? Есть ли какой-либо стандартный способ для POSIX или BSD, например
pthread
для потоков?
Спасибо, что прочитали мой <забавный > вопрос textwall.