Реализация библиотеки потоков на уровне пользователя. Начало новой темы [Домашнее задание].
Я видел это: Реализация пакета потоков на уровне пользователя, и он не применяется.
Во время реализации Thread_new(int func(void*)), который назначает поток и создает стек, я не могу придумать, как установить счетчик программы (%eip), если я прав, поэтому, когда поток запускается планировщиком, он начинается с точки входа данной функции (func).
Хотя я видел много реализаций c-only (без сборки), нам был предоставлен следующий код (x86):
_thrstart:
pushl %edi
call *%esi
pushl %eax
call Thread_exit
Есть ли конкретная причина для добавления%edi в стек? Я не могу найти другое применение для ESI / EDI, кроме байтового копирования.
Я понимаю, что косвенный вызов *% esi, вероятно, используется для вызова функции из контекста нового потока, но кроме этого я не понимаю, как (или что)% esi указывает на то, что она является допустимой функцией адрес при вызове _thrstart из Thread_new
ЗАМЕТКИ:
Thread_exit - поток очистки, реализованный в c.
Это домашняя работа
3 ответа
Кажется, что проблема не была такой сложной, как раньше.
Основываясь на ответе, который дал @Martin James, стек подготовлен так, чтобы адрес возврата был функцией _thrstart. На основе сборки, используемой для переключения контекста, регистры edi и esi хранятся в определенных местах в стеке (когда поток неактивен). Используя edi и esi в качестве регистров общего назначения, edi содержит аргумент void*, а esi содержит адрес функции, вызываемой из нового потока.
_thrstart:
pushl %edi #pushes argument for function func to the stack
call *%esi #indirect call to func
pushl %eax #Expect return value in eax, push to stack
call Thread_exit #Call thread cleanup
В общем; Вы можете разбить "планировщик" на 4 части.
Первая часть - это механика переключения с одного потока на другой. Это в основном включает в себя сохранение состояния предыдущего потока где-то и загрузку состояния следующего потока откуда-то. Здесь "где-то" может быть своего рода блок управления потоком, или это может быть стек потока, или оба, или что-то еще. Состояние потока может включать в себя содержимое регистров общего назначения, это верхняя часть стека (esp
), это указатель инструкции (eip
) и все остальное (регистры MMX/SSE/AVX). Однако для совместного планирования состояние потока может быть намного меньше (например, большая часть состояния потока разрушается переключением потока, и используется совместное планирование, так что сам поток знает, когда его состояние будет уничтожено, и может подготовиться к этому).
Вторая часть решает, когда делать переключение потока и на какой поток переключаться. Это широко варьируется для разных планировщиков.
Третья часть - это начало потока. В основном это включает в себя создание данных, которые будут загружены во время переключения потоков. Однако это можно сделать "ленивым" способом, когда вы создаете минимальное количество состояний только при первом создании потока, а затем завершаете создание оставшейся части состояния потока после того, как ему было выделено время ЦП.
Четвертая часть завершает поток. Это включает в себя уничтожение / освобождение данных, которые будут загружены во время переключения потоков; но это также может означать очистку любых ресурсов, которые потоку не удалось освободить (например, файловые дескрипторы, сетевые подключения, локальное хранилище потока и т. д.), чтобы избежать "утечек ресурсов".
Как правило, в простой RTOessess потоки не запускаются при вызове или переходе к ним - они запускаются при возврате или возвращении прерывания.
Хитрость заключается в том, чтобы собрать данные в верхней части нового стека, чтобы он выглядел так, как если бы поток выполнялся ранее и вызвал планировщик или ввел его через прерывание. Внизу этого "фрейма" должен быть адрес функции потока. Затем вы можете загрузить указатель стека с адресом кадра, разрешить прерывания и выполнить RET или IRET для запуска функции потока.
Также удобно сначала указать параметр, который новый поток может извлечь, и вызвать "TerminateThread" или "Thread_Exit", чтобы, если функция потока вернулась, планировщик мог завершить его.