Реализация пакета потоков на уровне пользователя

В классе мне было поручено создать библиотеку потоков пользовательского уровня на языке C. Мне было интересно, может ли кто-нибудь дать мне список вещей, которые можно прочитать, чтобы выполнить это. У меня есть хорошая идея относительно того, с чего начать, но любые ресурсы в потоках уровня пользователя и некоторые применимые аспекты языка C, которые могут помочь, были бы чрезвычайно полезны.

Мне очень непонятно, как мне реализовать планировщик для таких целей. Предположим, у меня есть довольно хорошее понимание языка Си и некоторых его более полезных библиотечных функций.

3 ответа

Решение

Я сделал это для домашнего задания, не написав никакого ассемблера. Механизм переключения резьбы был setjmp/longjmp, Для этого потребовалось выделить память для стека каждого потока, а затем очень тщательно массировать значения в jmp_buff поэтому выполнение переходит к стеку следующего потока.

Смотрите также довольно читаемую либстаску Расс Кокса.

Отредактируйте в ответ на комментарий ОП: При решении, когда переключать потоки, есть два основных направления: преимущественное и совместное. В вытесняющей модели у вас будет что-то вроде сигнала таймера, который заставит поток выполнения перейти к центральному потоку диспетчера, который выбирает следующий поток для запуска. В кооперативной модели потоки "уступают" друг другу либо явно (например, вызывая yield() функция, которую вы предоставите) или неявно (например, запрос блокировки, удерживаемой другим потоком).

Взгляните на API libtask для примера кооперативной модели, в частности, описания функции taskyield(), Это явный выход, который я упомянул. Существуют также неблокирующие функции ввода-вывода, которые включают в себя неявный выход - текущая "задача" приостанавливается до завершения ввода-вывода, но другие задачи получают шанс на выполнение.

Простой кооперативный планировщик может быть выполнен в C с использованием swapcontext, посмотрите на пример на странице man swapcontext здесь, это его вывод:

$ ./a.out
main: swapcontext(&uctx_main, &uctx_func2)
func2: started
func2: swapcontext(&uctx_func2, &uctx_func1)
func1: started
func1: swapcontext(&uctx_func1, &uctx_func2)
func2: returning
func1: returning
main: exiting

Итак, как вы можете видеть, это вполне выполнимо.

Примечание: если вы меняете контекст внутри обработчика сигнала таймера, то у вас есть упреждающий планировщик, но я не уверен, что это безопасно или возможно сделать это.

Редактировать: я нашел это на странице руководства sigaction, которая предполагает, что возможно переключить контекст внутри обработчика сигнала:

Если SA_SIGINFO указан в sa_flags, тогда sa_sigaction (вместо sa_handler) определяет функцию обработки сигнала для signum. Эта функция получает номер сигнала в качестве первого аргумента, указатель на siginfo_t в качестве второго аргумента и указатель на ucontext_t (приведенный к void *) в качестве третьего аргумента.

Вы можете посмотреть реализацию Apple с открытым исходным кодом. Обратите внимание, что большая часть кода на самом деле представляет собой ассемблерный код, потому что он требует некоторых специализированных вещей, которые вы не можете сделать в C, таких как получение адреса возврата стекового фрейма или переход на произвольный адрес.

Пользовательские потоки (также обычно называемые "волокнами") обычно используют кооперативную модель; то есть потоки выполняются до тех пор, пока не решат, что у них достаточно времени, а затем уступят другому потоку. Используя очередь с приоритетами, вы можете реализовать планировщик, который выполняет задачу, которая выполнялась в течение кратчайшего времени. (Планировщик отслеживает запущенные задачи, и запущенная задача возвращается, когда решает, что ее достаточно. Планировщик обновляет количество времени, в течение которого выполнялась задача, затем уступает той, у которой было наименьшее время выполнения.)

Другие вопросы по тегам