Функциональная модель учебного эмулятора многопоточных режимов операционных систем (УЭМОС)
Система УЭМОС логически разделена на три подсистемы, выполняющие каждая свои функции. Графическая подсистема отвечает за вывод графической информации на экран монитора, оперирование органами управления (ввиду несовершенства стандартных органов управления ОС MicrosoftTM Windows®, все органы управления были написаны с нуля). Подсистема подключаемых модулей отвечает за работу с динамически подключаемыми библиотеками, обеспечивая работу планировщиков, ведения аудита системы. Исполнительная подсистема отвечает за работу исполнительного ядра системы.
Несмотря на скоростные инструменты разработки, авторы решили отказаться от них из-за недостаточной гибкости их управления, малой скорости работы и отсутствия поддержки современных графических систем, таких как MicrosoftTM DirectX®. Вся система УЭМОС написана на языке Object Pascal с использованием WinAPI, DirectX® API.
Также система УЭМОС имеет многоязыковую поддержку, управляемую из командной строки. Языковой модуль представляет собой динамически подключаемые библиотеки. Таким образом, с системой могут работать люди, говорящие на любом языке.
Графическая подсистема
Для создания графической подсистемы авторами было решено использовать современную систему MicrosoftTM DirectX®. В ходе ее проектирования был создан ряд классов, отвечающих за работу различных органов управления (кнопки, ползунки, надписи, полоски прогресса и окна). Таким образом, авторы добились унификации программирования и оперирования различными элементами управления. Графическая подсистема в автоматическом режиме следит за состоянием своих компонентов и своевременно отвечает на их изменение.
Подсистема подключаемых модулей
Подсистема подключаемых модулей выполняет две функции. Одна отвечает за аудит системы, т.е. ведет подробный лог всех событий и изменений в системе. Вторая отвечает за связывание исполнительной подсистемы УЭМОС с планировщиками. Система при старте автоматически находит и подключает все планировщики, находящиеся в специальной папке. Настоящая подсистема спроектирована таким образом, что программисту не нужно думать о подключении динамических библиотек или очистке памяти после их работы. Все эти трудности подсистема берет на себя. Обращение к функциям также происходит через контейнеры, как если бы это были функции самой вызывающей программы.
Исполнительная подсистема
Исполнительная подсистема отвечает за работу эмулятора. Для обеспечения такой работы авторами был создан набор объектов, отвечающих каждый за свою часть системы - ЦП, КП, процессы, программы, каналы, обмены и VMOS - объект, связывающий и обеспечивающий работу всех остальных.
Объект ЦП отвечает за работу и сбор статистики центрального процессора эмулируемой системы. КП - за канальный процессор. Процессы отвечают за работу и сбор статистики процессов (потоков), программы - за выполнение процессов. Каналы имитируют ресурсы системы, обмены отвечают за работу канального процессора. VMOS - объединяет в себе все описанные объекты и координирует их работу.
Реализация УЭМОС
В ходе проектирования УЭМОС авторами было принято решение об использовании стандартных контейнеров для хранения универсальных объектов. Исходя из этого, базовым классом для всех объектов системы стал класс TPersistent. TPersistent обладает возможностью создания копий объектов, благодаря чему не создается путаницы с указателями на объекты, а также поддерживается стандартными объектами - списками, наследуемыми от класса TStrings.
Основным объектом эмулятора является VMOSSystem класса TVMOS.
Класс TVMOS
TVMOS = class(TPersistent)
private
CurIntTimer: integer;
public
CPUs: TStrings;
CMUs: TStrings;
Devices: TStrings;
Exchanges: TStrings;
Procs: TStrings;
Planners: TStrings;
LastExchange: integer;
ActivePlanner: integer;
ActiveProcess: integer;
Paused: Boolean;
StopOnInt: Boolean;
StopNext: Boolean;
Time: integer;
IntTimer: integer;
PlanTime: integer;
Finished: Boolean;
constructor Create;
destructor Destroy; override;
procedure ClearSystem;
procedure SaveThreadText(FileName: string; Proc: integer);
procedure SaveThreadBin(FileName: string; Proc: integer);
function LoadThreadText(FileName: string): integer;
function LoadThreadBin(FileName: string): integer;
procedure SaveSystem(FileName: string);
procedure LoadSystem(FileName: string);
procedure Tick;
procedure CallPlanner;
procedure ExecuteProc;
end;
Данный класс инкапсулирует работу всей системы, обеспечивая имитацию всех происходящих в эмулируемой ОС процессов, поддерживает сохранение и загрузку процессов, как в текстовом, так и в бинарном виде и целых систем только в бинарном виде.
Основную работу в данном классе выполняет процедура Tick, имитирующая один такт эмулируемой системы.
Процедура TVMOS.Tick
procedure TVMOS.Tick;
var
i, me: integer;
Proc: TProc;
Ex: TExchange;
PE: TProgramEntry;
Pl: TPlanner;
begin
if Finished then Exit;
Pl := Planners.Objects[ActivePlanner] as TPlanner;
if Time = 0 then begin
Log.AddMessage(LangStr(57));
CurIntTimer := PlanTime;
end;
if (ActiveProcess <> -1) or (PlanTime <> 0) then Inc(Time);
if StopNext then begin
Paused := true;
StopNext := false;
end;
Dec(CurIntTimer);
for i := 0 to Procs.Count - 1 do Inc((Procs.Objects[i] as TProc).NOP);
(CMUs.Objects[0] as TCMU).Busy := false;
if Exchanges.Count > 0 then begin
(CMUs.Objects[0] as TCMU).Busy := true;
me := 0;
for i := 0 to Exchanges.Count - 1 do begin
Dec((Exchanges.Objects[i] as TExchange).Time);
if (Exchanges.Objects[i] as TExchange).Time < (Exchanges.Objects[me] as TExchange).Time then me := i;
end;
Ex := Exchanges.Objects[me] as TExchange;
if Ex.Time <= 0 then begin
if StopOnInt then begin
Paused := true;
StopOnInt := false;
end;
if (ActiveProcess >= 0) and (Ex.Proc <> ActiveProcess) then begin
(Procs.Objects[ActiveProcess] as TProc).Busy := false;
(Procs.Objects[ActiveProcess] as TProc).NOP := 0;
ActiveProcess := -1;
CurIntTimer := PlanTime;
end;
Proc := Procs.Objects[(Exchanges.Objects[me] as TExchange).Proc] as TProc;
if Proc.WaitForExchange = (Exchanges.Objects[me] as TExchange).ID then begin
Proc.WaitForExchange := -1;
for i := Proc.CurProg downto 0 do begin
PE := (Proc.Prog.Objects[i] as TProgramEntry);
if (PE.Code = 4) and (PE.Params[0] = Ex.Device) then begin
PE.Params[2] := PE.Params[1];
break;
end;
end;
for i := Proc.CurProg to Proc.Prog.Count - 1 do begin
PE := (Proc.Prog.Objects[i] as TProgramEntry);
if (PE.Code >= 2) and (PE.Code <= 5) and (PE.Params[0] = Ex.Device) then begin
if (PE.Code = 5) then PE.Params[2] := 1;
break;
end;
end;
Log.AddMessage(Format(LangStr(58), [Time, Ex.Proc, Ex.Device]));
Exchanges.Objects[me].Free;
Exchanges.Delete(me);
end;
end;
end;
(CPUs.Objects[0] as TCPU).Busy := false;
if (Exchanges.Count = 0) or (poParallelCPUs in Pl.Options) then begin
if CurIntTimer <= 0 then begin
if ActiveProcess >= 0 then begin
if poIntTimerExists in Pl.Options then begin
if StopOnInt then begin
Paused := true;
StopOnInt := false;
end;
Log.AddMessage(Format(LangStr(59), [Time]));
(Procs.Objects[ActiveProcess] as TProc).Busy := false;
ActiveProcess := -1;
CurIntTimer := PlanTime;
(CPUs.Objects[0] as TCPU).Busy := true;
end;
end else ActiveProcess := -2;
end;
if ActiveProcess = -2 then begin
(CPUs.Objects[0] as TCPU).Busy := true;
if (Exchanges.Count = 0) then CallPlanner
else if ((not (poIgnoreNextProc in Pl.Options)) or (poIntTimerExists in Pl.Options)) then CallPlanner;
if ActiveProcess < 0 then begin
(CPUs.Objects[0] as TCPU).Busy := false;
CurIntTimer := 0;
if Exchanges.Count = 0 then begin
if Procs.Count > 0 then begin
for i := 0 to Procs.Count - 1 do begin
if (Procs.Objects[i] as TProc).Priority <> ppSleep then begin
Log.AddMessage(Format(LangStr(56), [Time]));
break;
end;
Log.AddMessage(Format(LangStr(55), [Time]));
end;
end else Log.AddMessage(Format(LangStr(55), [Time]));
Finished := true;
Exit;
end else Log.AddMessage(Format(LangStr(65), [Time]));
end else Log.AddMessage(Format(LangStr(60), [Time, ActiveProcess]));
end else if ActiveProcess >= 0 then begin
(CPUs.Objects[0] as TCPU).Busy := true;
ExecuteProc;
end;
end;
if (CPUs.Objects[0] as TCPU).Busy then Inc((CPUs.Objects[0] as TCPU).Work);
if (CMUs.Objects[0] as TCMU).Busy then Inc((CMUs.Objects[0] as TCMU).Work);
end;
Как можно увидеть из кода процедуры, универсальность вносит некоторую сложность его восприятия, как и многоязыковая поддержка. Однако, этот способ позволяет не создавать множество очередей с данными разных типов, что существенно упрощает само программирование.
За вызов планировщика отвечает процедура CallPlanner. Она собирает данные для планировщика в зависимости от его настроек и вызывает процедуру Select. Переменная Select принадлежит классу TPlanner и является указателем на внешнюю процедуру, подгружаемую из динамической библиотеки.
Интерфейс УЭМОС представлен ниже:
Набор стандартных планировщиков можно расширить, выбрав новое сочетание из 9 их свойств. УЭМОС позволяет проследить процессы, протекающие в различных операционных системах в динамике, а также получить подробный отчет обо всех событиях, имевших место в процессе имитации, в том числе о тупиках, о средней загрузке процессора и каналов.
Редактор процессов существенно расширен добавлением операторов управления нитями, их синхронизацией, а также оператора ожидания канала. Интерфейс редактора приведен далее:
Разработана методика применения в учебном процессе данной разработки, представлен тематический план проведения лабораторных работ с использованием УЭМОС и составлен список возможных заданий.