В многозадачной системе нельзя исключить возможность того, что переключение процессов произойдет во время выполнения вытесняемым процессом какой-либо из системных функций. При этом выполнение функции не будет завершено, оно будет прервано на середине. Предполагается, что выполнение функции будет завершено позднее, когда прерванный процесс снова будет выбран на выполнение.
Проблема заключается в том, что новый текущий процесс может вызвать ту же самую системную функцию. Возникает вопрос: возможно ли корректное выполнение второго вызова функции, если к этому моменту не закончено выполнение первого вызова той же функции?
Простой пример подобной ситуации – запуск в ОС нескольких программ, каждая из которых блокируется на ожидании клавиатурного ввода, вызвав для этого одну и ту же системную функцию ввода.
Функция или процедура, для которой возможно корректное выполнение ее повторного вызова до завершения первого вызова, называется реентерабельной или повторно-входимой.
Проблема реентерабельности функций возникает в программировании и по совершенно другому поводу, не связанному с реализацией ОС. Речь идет о рекурсивных функциях, т.е. функциях, которые могут прямо или косвенно вызывать сами себя. Давно известна и основная причина нереентерабельности функций. Она заключается в использовании одних и тех же ячеек памяти для формальных параметров и локальных переменных при разных вызовах функции. Действительно, если при первом вызове функции в ячейку локальной переменной X будет занесено, например, число 10, а при втором – число 20, то после возобновления выполнения первого вызова значение X будет неверным.
Лекарство от этой формы нереентерабельности также давно известно и встроено во все уважающие себя языки программирования, начиная с C и Pascal. Оно заключается в том, что память для формальных параметров и локальных переменных должна выделяться в стеке программы, при этом каждый новый вложенный вызов получает свой набор ячеек для переменных.
Для случая многозадачной системы использование единого стека неприемлемо, поскольку вызовы функций не являются вложенными, т.е. первый вызов может завершиться раньше, чем второй, что привело бы к неверному использованию стека. Каждый процесс получает свой собственный стек, являющийся частью контекста процесса.
Еще одна причина нереентерабельности касается тех функций ввода/вывода, которые запускают операцию и затем дожидаются ее завершения. Повторное обращение к тому же устройству до завершения первого вызова может привести к ошибке. В данном случае система должна отслеживать состояние устройства и блокировать второй вызов до освобождения устройства.
Проблема реентерабельности системных функций значительно острее стоит для ОС с вытесняющей диспетчеризацией, поскольку переключение процесса может случиться при выполнении любой функции. При невытесняющей диспетчеризации достаточно обеспечить реентерабельную реализацию лишь небольшого числа блокирующих функций.
При переходе от невытесняющей Windows 3.x к вытесняющей Windows 95 одна из серьезных проблем состояла в сохранении кода большого количества нереентерабельных системных функций. Проблему «решили» путем введения семафора, блокирующего повторный вызов для большого числа функций. Неприятным следствием этого стало взаимное тормозящее влияние процессов. В Windows NT этой проблемы нет, все функции реализованы реентерабельно.