Ответ 1
Вы действительно можете. Есть несколько способов сделать это. Сложная часть изначально получает jmpbufs, которые указывают на другие стеки. Longjmp определяется только для аргументов jmpbuf, которые были созданы setjmp, поэтому нет никакого способа сделать это без использования сборки или использования поведения undefined. Потоки пользовательского уровня по своей сути не переносимы, поэтому переносимость не является сильным аргументом в пользу того, что вы не делаете этого действительно.
шаг 1 Вам нужно место для хранения контекстов разных потоков, поэтому создайте очередь jmpbuf stuctures для сколько угодно потоков.
Шаг 2 Вам нужно выстроить стек для каждого из этих потоков.
Шаг 3 Вам нужно получить некоторые контексты jmpbuf, у которых есть указатели стека в выделенных вами ячейках памяти. Вы можете проверить структуру jmpbuf на своем компьютере, узнать, где он хранит указатель стека. Вызовите setjmp, а затем измените его содержимое, чтобы указатель стека находился в одном из ваших выделенных стеков. Стеки обычно растут, поэтому вы, вероятно, хотите, чтобы указатель стека находился где-то рядом с самой высокой ячейкой памяти. Если вы пишете базовую программу на C и используете отладчик, чтобы разобрать ее, а затем найти инструкции, которые она выполняет при возврате из функции, вы можете узнать, какое должно быть смещение. Например, с соглашениями о вызовах системы V на x86 вы увидите, что он выдает% ebp (указатель кадра), а затем вызывает ret, который выдает адрес возврата из стека. Таким образом, при входе в функцию он выталкивает адрес возврата и указатель кадра. Каждое нажатие перемещает указатель стека вниз на 4 байта, поэтому вы хотите, чтобы указатель стека начинался с высокого адреса выделенной области, -8 байтов (как будто вы только что вызвали функцию, чтобы попасть туда). Мы заполним следующие 8 байтов.
Другое, что вы можете сделать, это написать небольшую (одну строку) встроенную сборку для управления указателем стека, а затем вызвать setjmp. Это на самом деле более портативно, потому что во многих системах указатели в jmpbuf искажены для обеспечения безопасности, поэтому вы не можете легко их изменить.
Я не пробовал, но вы могли бы избежать asm, просто преднамеренно переполнив стек, объявив очень большой массив и тем самым перемещая указатель стека.
Шаг 4 Вам нужно выйти из потоков, чтобы вернуть систему в безопасное состояние. Если вы этого не сделаете, и один из потоков вернется, он переместит адрес прямо над вашим выделенным стекем в качестве обратного адреса и переместится к некоторому месту мусора и, вероятно, segfault. Поэтому сначала вам нужно безопасное место для возвращения. Получите это, вызвав setjmp в основном потоке и сохраняя jmpbuf в глобально доступном месте. Определите функцию, которая не принимает аргументов и просто вызывает longjmp с сохраненным глобальным jmpbuf. Получите адрес этой функции и скопируйте ее в выделенные стеки, где вы оставили место для обратного адреса. Вы можете оставить указатель кадра пустым. Теперь, когда поток возвращается, он перейдет к той функции, которая вызывает longjmp, и прыгайте обратно в основной поток, где вы вызывали setjmp, каждый раз.
Шаг 5 Сразу после основного потока setjmp вы хотите иметь некоторый код, который определяет, какой поток следует переходить к следующему, вытаскивая соответствующий jmpbuf из очереди и вызывая longjmp туда. Когда в этой очереди нет нити, программа завершена.
Шаг 6 Напишите функцию переключения контекста, которая вызывает setjmp и сохраняет текущее состояние в очереди, а затем longjmp на другом jmpbuf из очереди.
Заключение Это основа. До тех пор, пока потоки продолжают вызывать контекстный коммутатор, очередь продолжает переполняться, и запускаются разные потоки. Когда поток возвращается, если есть какие-либо оставленные для запуска, один выбирается основным потоком, и если ни один не остается, процесс завершается. С относительно небольшим кодом у вас может быть довольно простая совместная многозадачность. Есть больше вещей, которые вы, вероятно, захотите сделать, например, реализовать функцию очистки, чтобы освободить стопку мертвой нити и т.д. Вы также можете реализовать preemption с использованием сигналов, но это намного сложнее, потому что setjmp не сохраняет регистр с плавающей запятой состояния или регистров флагов, которые необходимы, когда программа прерывается асинхронно.