Как потоки спят с отключенным прерыванием?

Я пытаюсь понять, как работает код ниже. Это прямо из моих слайдов лекций. Эта функция P() и V() является частью реализации семафора в ОС, которую мы используем в классе (OS161). Я думаю, что вам может понадобиться понимание OS161, чтобы ответить на мой вопрос, так как он широко используется, надеюсь, кто-то может ответить на эти вопросы.

Мое понимание этого кода с примечаниями лекции:
X: поток функции P()
1. Когда поток вызывает P(), мы отключаем прерывание
2. проверьте, есть ли у нас ресурсы на sem-> count
3.a) если счетчик равен 0, то мы идем спать
3.b) если count!= 0, то мы уменьшаем счет и позволяем вызывающему потоку перейти к критическому разделу
4. Включить прерывание
Y: поток функции V()
1. Когда поток вызывает V(), мы отключаем прерывание
2. Увеличьте счетчик, подразумевая, что теперь есть еще 1 ресурс, доступный для захвата.
3. Теперь мы идем дальше и пробуждаем весь поток, который мы отправили в спящий режим в P(), потому что не было достаточно ресурсов, когда поток пытался захватить блокировку для критической секции.
4. Включить прерывание

Мои проблемы:
1. Отключает ли секция "отключить прерывание" прерывание в определенном потоке или блокирует все прерывания?
2. При использовании функции V(), когда мы просыпаем все потоки, поток спал внутри цикла while в функции P(), чтобы начать выполнение цикла while. В лекции говорится, что одна нить захватывает замок, а остальные возвращаются ко сну. Мой вопрос, почему условие "sem->count == 0" не оценивается как ложное для других потоков, а только для одного.

Я действительно хочу знать, как работает часть отключения прерывания. это мой первый вопрос. Останавливает ли он планировщик потоков? Останавливает ли он переключение контекста в системе?

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

    P(sem) {
        Disable interrupts;
        while (sem->count == 0) {
        thread_sleep(sem); /* current thread
                               will sleep on this sem */
        }
        sem->count--;
        Enable interrupts;
    }
    V(sem) {
        Disable interrupts;
        sem->count++;
        thread_wakeup (sem); /* this will wake
        up all the threads waiting on this
                   sem. Why wake up all threads? */
        Enable interrupts;
    }

Спасибо.

1 ответ

Решение

Процессор ничего не знает о потоках, это просто логическая / абстрактная концепция, реализованная в программном обеспечении. Но ЦП знает о прерываниях, они реальны, и всякий раз, когда кто-то приходит с какого-либо устройства, ЦПУ прекращает выполнение того, что оно выполняло, и начинает выполнять подпрограмму, предназначенную для обработки этого конкретного прерывания. По завершении подпрограмма сигнализирует о завершении обработки прерываний, и ЦПУ возобновляет выполнение всего, что было прервано подпрограммой обработки прерываний.

Если прерванный код принадлежит потоку, пусть будет так. Если это была другая подпрограмма обработки прерываний, тоже хорошо.

Непосредственно перед запуском процедуры обработки прерываний ЦПУ сохраняет часть контекста выполнения (несколько общих и, возможно, несколько управляющих / системных регистров) либо в стеке, либо где-то еще, поэтому процедура может использовать их для своих собственных целей, и затем в конце процедуры ЦП восстанавливает эти регистры, где бы они ни хранились, как будто ничего не происходит с точки зрения прерванного кода. Если подпрограмма изменяет эти регистры, ЦПУ возобновит выполнение где-то еще, а не там, где оно выполнялось в последний раз перед прерыванием.

Таким образом, вы можете использовать прерывания для переключения выполнения между различными частями кода, потоками или чем-то еще. На самом деле, это именно то, сколько работают планировщики. Они получают периодические прерывания от таймера и в обработчике прерываний сохраняют контекст прерванного кода (например, потока A) в памяти и загружают контекст другого прерванного кода (например, потока B) из памяти и возвращают, тем самым продолжая выполнение в другом потоке.,

Если вы отключите эти прерывания по таймеру, периодическое планирование / переключение потоков также будет отключено. Прерывания влияют на весь процессор и текущий исполняемый поток (или что бы то ни было) и по индукции влияют на все потоки.

Понял?

Теперь, если в системе есть потоки, всегда есть хотя бы один поток, который может выполняться. Это потому, что ЦПУ необходимо что-то выполнить, он не может просто остановиться и ждать, пока поток не придет ниоткуда (в конце концов, это ЦП, который создает потоки, делает их работоспособными и запускает их). Для этой цели в системе существует фиктивный (или не так фиктивный) поток, который имеет низкий приоритет и практически ничего не делает, зацикливаясь навсегда и, возможно, сообщая ЦПУ, что он может переключиться в состояние с более низким энергопотреблением или остановиться до появления прерывания Прерывание завершит режим низкого энергопотребления и приведет к продолжению выполнения кода.

Таким образом, когда поток блокирует семафор или какой-либо другой примитив синхронизации, планировщик просто выбирает другой поток для выполнения. Если все потоки заблокированы, выбирается фиктивная нить.

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

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

spl = splhigh(); // disable interrupts
while (sem->count==0) {
  thread_sleep(sem); // context switch (to another thread and then back) occurs here
}
sem->count--;
splx(spl); // <-- re-enable interrupts here

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

Просто подумай об этом. У вас есть 2 (или более) экземпляров вышеуказанного или аналогичного кода в 2 (или более) потоках. Когда кто-то входит thread_sleep() или аналогичная функция, какой-то другой поток выходит из его thread_sleep() или аналогичная функция и повторно включает прерывания.

Следуйте коду и комментариям по этому пути:

П()
thread_sleep ()
mi_switch ()
md_switch ()
mips_switch ()

Что касается количества семафоров, я не хочу больше анализировать код. Вы должны попытаться понять это самостоятельно, если кто-то еще не вмешивается и не покрывает это.

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