Уже немного поздно, но я сам интересовался этой темой.
На самом деле, нет ничего особенного в потоках, которые требуют, чтобы ядро вмешивалось, КРОМЕ для распараллеливания / производительности.
Обязательный BLUF :
Q1: Нет. Для создания нескольких потоков ядра в разных ядрах / гиперпотоках ЦП необходимы как минимум начальные системные вызовы.
Q2: Это зависит. Если вы создаете / уничтожаете потоки, которые выполняют крошечные операции, то вы тратите впустую ресурсы (процесс создания потока будет значительно превышать время, используемое протектором до его выхода). Если вы создаете N потоков (где N - это ~ число ядер / гиперпотоков в системе) и повторно назначаете их, тогда ответ МОЖЕТ быть положительным в зависимости от вашей реализации.
Q3: Вы МОЖЕТЕ оптимизировать работу, если заранее ЗНАЕТЕ точный метод заказа операций. В частности, вы можете создать то, что составляет ROP-цепочку (или цепочку прямых вызовов, но на самом деле это может оказаться более сложным для реализации). Эта ROP-цепочка (как выполняемая потоком) будет непрерывно выполнять команды 'ret' (для своего собственного стека), где этот стек непрерывно добавляется (или добавляется в случае, когда он переносится в начало). В такой (странной!) Модели планировщик хранит указатель на «конец ROP-цепочки» каждого потока и записывает в него новые значения, в результате чего код обтекает память, выполняя код функции, что в конечном итоге приводит к команде ret. Опять же, это странная модель, но, тем не менее, интригует.
На мой контент на 2 цента.
Я недавно создал то, что эффективно работает как потоки в чистой сборке, управляя различными областями стека (созданными с помощью mmap) и поддерживая выделенную область для хранения информации управления / индивидуализации для «потоков». Возможно, хотя я и не проектировал это таким образом, с помощью mmap можно создать один большой блок памяти, который я подразделяю на «приватную» область каждого потока. Таким образом, потребуется только один системный вызов (хотя защитные страницы между ними будут разумными, для них потребуются дополнительные системные вызовы).
В этой реализации используется только базовый поток ядра, созданный при запуске процесса, и на протяжении всего выполнения программы существует только один поток пользовательского режима. Программа обновляет свое собственное состояние и планирует себя через внутреннюю структуру управления. Ввод-вывод и тому подобное обрабатываются с помощью опций блокировки, когда это возможно (чтобы уменьшить сложность), но это строго не требуется. Конечно, я использовал мьютексы и семафоры.
Для реализации этой системы (полностью в пользовательском пространстве, а также через некорневой доступ при желании) требовалось следующее:
Понятие о том, что потоки сводятся к:
Стек для стековых операций (вроде самоочевидно и очевидно)
Набор инструкций для выполнения (также очевидно)
Небольшой блок памяти для хранения отдельных регистров содержимого
К чему сводится планировщик:
Менеджер для ряда потоков (обратите внимание, что процессы никогда не выполняются, а выполняются только их потоки) в указанном планировщиком упорядоченном списке (обычно приоритет).
Переключатель контекста потока:
MACRO, внедренный в различные части кода (я обычно помещаю их в конец сверхмощных функций), который примерно равен «выходу потока», который сохраняет состояние потока и загружает состояние другого потока.
Таким образом, действительно возможно (полностью в сборке и без системных вызовов, кроме исходных mmap и mprotect) создавать потоковые конструкции пользовательского режима в некорневом процессе.
Я только добавил этот ответ, потому что вы специально упомянули сборку x86, и этот ответ был полностью получен с помощью автономной программы, написанной полностью на сборке x86, которая достигает целей (за исключением многоядерных возможностей) минимизации системных вызовов, а также минимизирует систему верхняя резьба.