Распределенная синхронизация процессов
Курсовая работа
по дисциплине:
Операционные
системы
Распределенная
синхронизация процессов
Введение
программа сетевой интерфейс
При работе с данными на современных операционных системах остро стоит
проблема в их синхронизации. Проблемы могут происходить, когда к одним и тем же
данным получают доступ различные процессы. И даже не столько в случае
одновременного изменения. Чтение данных в одном потоке после изменения их
другим тоже может давать неверный результат в целом. Так же одной из проблем
может являться уменьшение производительности работы в целом. Синхронизация -
это некоторые дополнительные издержки.
Цель работы является разработка программы, создающая и запускающая
несколько отдельных процессов в определённой последовательности. Информация о
каждом запущенном процессе передаётся и принимается через сокеты UDP и записывается в файл.
В целом, задачей разработки являлось создание такого приложения, которое
будет удалённо запускать в последовательности отдельные задачи.
В главе «Описание программ» подробно описаны теоретические особенности
построения сетевого стека.
В главе «Тексты программ» находятся исходные коды на языке C# и файлы конфигурации процессов в
формате INI файлов.
В главе «Выводы» даются результаты о проделанной работе.
Наиболее важными источниками знаний послужил сервис от Microsoft - MSDN, а также книга Гордеева - «Операционные системы».
Задание
Создать процессы (отдельные задачи), которые выполняются в
последовательности, определяемой графом запуска в соответствии с вариантом
задания. Каждая дуга графа соответствует процессу, имя дуги - имя процесса.
Вершины графа - точки синхронизации процессов.
Процессы после запуска находятся в ожидании завершения предшествующих им
процессов. При выполнении выводят информацию (имя процесса, время запуска и
окончания работы), передают управление другим процессам и завершаются. В
процессах вводится небольшая временная задержка (несколько секунд) для того,
чтобы было легче отследить соответствие запусков и окончаний процессов графу
запуска. Для первоначального запуска набора процессов рекомендуется
использовать командный файл.
Приём и передача информации о завершении процесса осуществляется через
сокеты TCP или UDP. Фактически приём-передача этой информации выступает
в роли средства распределённой синхронизации процессов в IP-сети.
Необходимо предусмотреть создание необходимого числа сокетов в каждой
задаче и определить корректную нумерацию портов, чтобы обмен был возможен как
на локальном компьютере (local host.localdomain) так и при запуске задач на
компьютерах с разными IP-адресами
в пределах локальной или глобальной TCP/IP-сети. Должна быть предусмотрена
возможность изменения IP-адреса
компьютера при запуске процесса (ввод из командной строки, файла и т.п.). При
демонстрации курсовой работы часть процессов запускается на одном, а остальные
- на другом (других) компьютере (компьютерах) ЛВС.
Операционная система - Windows
или Linux. Система программирования - Visual C или GNU C. Возможен
“гибридный” вариант реализации, когда часть процессов работает под управление
ОС Linux, а оставшиеся - под ОС MS Windows. В последнем случае для отладки вне вычислительной
сети имеет смысл использовать эмуляторы другой ОС (CYGWIN, WINE)
или виртуальные машины.
Граф синхронизации процессов. Вариант #2
Рис. 1. Граф синхронизации процессов
На графе отчётливо видно, что существует 11 дуг. Каждая дуга графа
соответствует процессу. Вершины графа - точки синхронизации процессов. Имена
процессов: a, b, c, d, e, f ,g, h, i, m, n.
1.
Описание программ
Программа работает под управлением Windows 32 бит. Написана на C# .NET
2.0. Данные в программу поступают по сетевому стеку UDP. Исходные данные лежат в отдельных INI файлах.
Программа состоит из модуля сервера и клиента. Клиент-сервер работает
одновременно на одном компьютере. Во время включения программы, проверяется -
есть ли у программы родитель - в случае нахождения происходит отправка
сообщения и последующая работа. В остальном случае - программа слушает и в
зависимости от принятых данных отправляет сообщения. Функции локального
управления используются, главным образом, для выполнения подготовительных
действий, необходимых для организации взаимодействия двух программ-партнёров.
1.1 Сокеты
В программе широко используются сокеты. В C# сокеты реализованы с помощью класса Socket. Класс Socket обеспечивает широкий набор методов и свойств для
сетевых взаимодействий. Socket
придерживается шаблона имён платформы .NET Framework для синхронный методов. Для
протокола UDP, нет необходимости в отслеживании
подключений. Метод Accept
обрабатывает любые входящие запросы на подключение и возвращает объект Socket, который может использоваться для
передачи данных с удалённого узла. Объект Socket используется для вызова метода Send или Receive. Метод Bind,
обращается к Listen, к которому необходимо указать
локальный IP-адрес или номер порта. Если
требуется произвести подключение к прослушивающему узлу, используется метод Connect. Для обмена данными метод Send или Receive. Когда приём и отправка данных завершены,
используется метод Shutdown для того,
чтобы отключить объект Socket.
После вызова метода Shutdown
происходит обращение к методу Close,
чтобы освободить все связанные с объектом Socket ресурсы.
Процесс в Windows состоит из следующих компонентов:
· Структура данных, содержащая всю информацию о процессе, в том
числе список открытых дескрипторов различных системных ресурсов, уникальный
идентификатор процесса, различную статистическую информацию и т.д.;
· Адресное пространство - диапазон адресов виртуальной памяти,
которым может пользоваться процесс;
· Исполняемая программа и данные, проецируемые на виртуальное
адресное пространство процесса.
Создание Win32 процесса
осуществляется вызовом одной из таких функций, как CreateProcess, CreateProcessAsUser (для Win NT/2000) и CreateProcessWithLogonW (начиная с Win2000)
и происходит в несколько этапов:
· Открывается файл образа (EXE), который будет выполняться в
процессе.
· Если исполняемый файл не является Win32 приложением, то
ищется образ поддержки (support image) для запуска этой программы. Например,
если запускается cmd.exe.
Процесс завершается если:
· Входная функция первичного потока возвратила управление.
· Один из потоков процесса вызвал функцию ExitProcess.
· Поток другого процесса вызвал функцию TerminateProcess.
· Когда процесс завершается, все User- и GDI-объекты, созданные
процессом, уничтожаются, объекты ядра закрываются (если их не использует другой
процесс), адресное пространство процесса уничтожается.
1.2
Процессы
Процесс - это объединение нескольких потоков. А объединяет эти потоки
единое виртуальное адресное пространство. В этом пространстве размещаются код и
данные приложения (обычно это один exe и несколько dll-модулей). Именно
единство этого пространства и делает обмен данными между потоками приложения
предельно простым. Наоборот, адресные пространства различных процессов
независимы и отделены друг от друга (хотя, используя проекции файла в память
(memory mapped file), можно создать область памяти, которая будет доступна
совместно нескольким процессам). Таким образом, процесс - это несколько потоков
(как минимум один) плюс единое виртуальное адресное пространство. Поскольку все
потоки процесса работают в едином адресном пространстве, обмен данными между
ними крайне прост, однако при этом требуется согласовывать их работу над
совместными данными. Собственно, под термином «синхронизация», как правило,
имеют в виду именно согласование работы потоков, принадлежащих одному процессу.
Этому и будут посвящены следующие части данной статьи. Хотя некоторые из
описанных далее приёмов можно использовать и для синхронизации потоков
принадлежащих разным процессам, в основном согласование их работы связано с
«механизмами взаимосвязи процессов» (inter-process communications, IPC).
Действительно, трудно представить ситуацию, когда нам потребовалось бы
согласовывать движение потоков без необходимости обмена данными между ними. А
для этого, если потоки работают в разных адресных пространствах, требуются
специальные механизмы, носящие обобщённое название IPC (проекции файлов в
память - один из них). Процесс - это набор потоков, работающих в едином
адресном пространстве. Само по себе, адресное пространство без потоков смысла
не имеет. Поэтому процесс считается завершённым, как только завершатся все его
потоки.
2.
Тексты программ
Интерфейс программы разработан с помощью Windows Forms. Интерфейс состоит из главного окна, на котором
отображены данные о пользовательских настройках запуска процессов. При загрузке
программы происходит заполнение окна конфигурационными данными. После загрузки,
программа ищет «потомков» по сети и подключается к ним. Когда подключение
установлено - программа проверяет, какой процесс следует запустить, в случае
нахождения данных о процессе - запускает его. После завершения процесса - выполнившая
программа останавливается. А «родителем» становится программа, которая приняла
все данные. «Родитель» начинает процесс поиска своих «потомков». Интерфейс
достаточно прост. И не требует от пользователя каких-либо действий, кроме
правки конфигурационного файла.
Рис. 2. Скриншот работы 4 процессов на локальном компьютере
2.1 Исходный
код
class WriteLog {static bool Write(string message) {{string
url = @"\\fs\Study\_\ПО08\temp.txt";text
= System.IO.File.AppendText(url);.WriteLine(message);.Close();
}{false;
} return true;
}
}class Client {readonly string Ip;readonly int Port;readonly
string Processname;TcpClient _client;Client(string ip, int port, string
processname) {= ip;= port;= processname;
if (SendData()) {.Msg("Данные успешно отправлены!");
} // Теперь подключаться к потомкам
}
private bool SendData() {
_client = new TcpClient();flag = true;{
_client.Connect(Ip, Port);[] mas =
Encoding.UTF8.GetBytes(Processname);
_client.Client.Send(mas);
}{= false;
} return flag;
}
}partial class FormClient : Form {static readonly string Path
= Environment.CurrentDirectory; // текущая
директория
#region СпискиList<string>
_childProcessname;List<string> _childsIp;List<string>
_childsPort;List<string> _parentProcessname;List<string> _serverPort;List<string>
_serverProcessname;
#endregionFormClient() {();();cr = new Thread(OpenPort);
cr.Start();
//Если родителей нет - подключаюсь к потомкам
if (_parentProcessname.Count == 0 &&
_serverProcessname.Count > 0) {processname = _serverProcessname[0];string ip
= "127.0.0.1";port = int.Parse(_serverPort[0]);
new Client(ip, port, processname); //а также выполняем главные процесс А
ConnectToChild();
}
}void OpenPort() {(string t in _serverPort) {(t ==
"") return;{port = Convert.ToInt16(t);
//Если родители есть то открываем порт(_parentProcessname.Count != 0)
{("Открываю порт " + port + " для подключения
родителей");Servak(port);
}
}{("Проблема при открытие порта");
}
}void ConnectToChild() {("ищем потомков...");
//Если потомки
есть(_childProcessname.Count
!= 0) {(int i = 0; i < _childsPort.Count; i++) {port =
Convert.ToInt16(_childsPort[i]);ip = _childsIp[i];processname =
_childProcessname[i];
Msg("подключаюсь к потомку " + processname); // Подключаемся к
серверу по serverPort
try {Client(ip, port, processname); //подключаемся к потомку
}{(string.Format("Подключиться к потомку {0} не удалось", processname));
}
}
}{("Потомков нет.");
}
}static void Msg(string mesg) {(!WriteLog.Write(mesg))
{.Show("Произошла проблема с записью");
}
}void GetInfoFromINIFile() {[] directorypath = new
DirectoryInfo(Path).GetFiles();
//Заполнение списков
_serverProcessname = new List<string>();
_serverPort = new List<string>();
_parentProcessname = new List<string>();
_childProcessname = new List<string>();
_childsIp = new List<string>();
_childsPort = new List<string>();
//инфо об файлах(FileInfo info in directorypath)
{fullname = info.FullName;temp = new IniFile(fullname);
string[] readText = File.ReadAllLines(fullname); //временный, зранит все
записи
//Читаем все строчки
foreach (string s in readText) {(s ==
"");(Regex.IsMatch(s, "Server")) {
_serverProcessname.Add(temp.IniReadValue("Server",
"Processname"));
_serverPort.Add(temp.IniReadValue("Server", "Port"));
}
//Если найден Parent - добавляем (учитывая что есть 1, 2, ...)
else if (Regex.IsMatch(s, "Parent")){parentS =
s.Replace("[", "").Replace("]", "");
_parentProcessname.Add(temp.IniReadValue(parentS,
"Processname"));
}
//то же самое только с Child
else if (Regex.IsMatch(s, "Child")){childS =
s.Replace("[", "").Replace("]", "");
//Проверка на UTF-8. Глюк возникает, в "s" вводится какая-то
очень длинная
if (s.Length > 50);
_childProcessname.Add(temp.IniReadValue(childS, "Processname"));
_childsIp.Add(temp.IniReadValue(childS, "Ip"));
_childsPort.Add(temp.IniReadValue(childS, "Port"));
}
}
}();
}void AddText(string text) {(text != "").Text =
tbxText.Text + Environment.NewLine + text;
}void GetText() {(Environment.NewLine + "Серверы");(string server in
_serverProcessname) {(server);
} (Environment.NewLine + "Айпи Серверов ");(string servPort
in _serverPort) {(servPort);
}(Environment.NewLine + "Родители");(string parent in _parentProcessname) {(parent);
}(Environment.NewLine + "Потомки");(string child in _childProcessname) {(child);
}(Environment.NewLine + "Айпи детей");(string s in
_childsIp) {(s);
}(Environment.NewLine + "Порты детей");(string s in
_childsPort) {(s);
}
}
}class IniFile{string path;
/// <summary>
/// Конструктор класса
/// </summary>
/// <PARAM name="INIPath">Путь к INI-файлу</PARAM>IniFile(string INIPath)
{= INIPath;
}
[DllImport("kernel32")]static extern long
WritePrivateProfileString(string section, string key, string val, string
filePath);
[DllImport("kernel32")]static extern int
GetPrivateProfileString(string section, string key, string def, StringBuilder
retVal, int size, string filePath);void IniWriteValue(string Section, string
Key, string Value) {(Section, Key, Value, path);
}string IniReadValue(string section, string key) {temp = new
StringBuilder(255);(section, key, "", temp,
, path);temp.ToString();
}
}class Processes {static void SendData(object data) {
FormClient.Msg("выполняется процесс " + data);
var fp = new FormPotok {Text = data.ToString()};.Show();(var
millisecondsTimeout = 1; millisecondsTimeout < 100; millisecondsTimeout++ )
{.progBar.Value = millisecondsTimeout;.Sleep(millisecondsTimeout);
}.Msg("Процесс
" + data + " выполнен");FormClient().ConnectToChild();//Теперь передачу на потомков
}static void StartPotoks(string name)
{Thread(SendData).Start(name);
}
}Servak {Servak(int port) {
FormClient.Msg("Ожидаем запрос к подключению");
IPEndPoint ipep = new IPEndPoint(IPAddress.Any, port);newsock
= new Socket(AddressFamily.InterNetwork, .Stream,
ProtocolType.IP);.Bind(ipep);.Listen(10);.Msg("родитель подключен...");client = newsock.Accept();data = new
byte[1024];recv = client.Receive(data);RecvString =
Encoding.ASCII.GetString(data, 0, recv);
Processes.StartPotoks(RecvString);
}
}
[Server]= 8881= a
[Child1]= 127.0.0.1= 8883= c
[Child2]= 127.0.0.1= 8884= d
[Child3]= 127.0.0.1= 8885= e
Процесс
b
[Server]= 8882= b
[Child1]= 127.0.0.1= 8889= i
[Child2]= 127.0.0.1= 8888= h
[Child3]= 127.0.0.1= 8887= g
Процесс
c
[Server]= 8883= c
[Parent1]= a
[Child1]= 127.0.0.1= 8889= i
[Child2]= 127.0.0.1= 8888= h
[Child3]= 8887= 127.0.0.1= g
Процесс
n
[Server]= 8892= n
[Parent1]= i
[Parent2]= k
[Parent3]= m
Процесс
d
[Server]= 8884= d
[Parent1]= a
[Child1]= 127.0.0.1= 8886= f
Процесс
e
[Server]= 8885= e
[Parent1]= a
[Child1]= 127.0.0.1= 8890= k
[Child2]= 127.0.0.1= 8891= m
Процесс
f
[Server]= 8886= f
[Parent1]= d
[Child1]= 127.0.0.1= 8890= k
[Child2]= 127.0.0.1= 8891= m
Процесс
h
[Server]= 8888= h
[Parent1]= c
[Parent2]= b
[Child1]= 127.0.0.1= 8890= k
[Child2]= 127.0.0.1= 8891= m
Процесс
g
[Server]= 8887= g
[Parent1]= b
[Parent2]= c
[Child1]= 127.0.0.1= 8890= k
[Child2]= 127.0.0.1= 8891= m
Процесс
k
[Server]= 8890= k
[Parent1]= h
[Parent2]= g
[Parent3]= f
[Parent4]= e
[Child1]= 127.0.0.1= 8892= n
Процесс
i
[Server]= 8889= i
[Parent1]= b
[Parent2]= c
[Child1]= 127.0.0.1= 8892= n
Процесс
m
[Server]= 8891= m
[Parent1]= h
[Parent2]= g
[Parent3]= f
[Parent4]= e
[Child1]= 127.0.0.1
Port = 8892= n
2.3 Результаты
тестирования
Открываю порт 8883 для подключения родителей
Ожидаем запрос к подключению
родитель подключен...
Открываю порт 8889 для подключения родителей
родитель подключен...
ищем потомков...
подключаюсь к потомку c
Данные успешно отправлены!
выполняется процесс c
Процесс c выполнен
ищем потомков...
подключаюсь к потомку i
Данные успешно отправлены!
подключаюсь к потомку h
Данные успешно отправлены!
выполняется процесс i
выполняется процесс h
Проблема при открытие порта
Процесс i выполнен
Открываю порт 8889 для подключения родителей
Ожидаем запрос к подключению
Проблема при открытие порта
ищем потомков...
потомков нет.
Процесс h выполнен
Открываю порт 8888 для подключения родителей
Ожидаем запрос к подключению
Проблема при открытие порта
ищем потомков...
потомков нет.
Заключение
Благодаря использованию встроенных средств языка C# удалось создать проект, использующий сетевой стек и сетевые
функции Windows. Что в конечном итоге сделало
возможным создание программы, которая способна пересылать данные на удалённый
компьютер. Следующим этапом стало создание удобного интерфейса. Интерфейс
построен с использованием Windows Forms
В программе предусмотрен обмен как на локальном компьютере, так и при
запуске на компьютерах с разными IP-адресами
в пределах локальной или глобальной TCP/IP-сети. Предусмотрена возможность
изменения IP-адреса компьютера при запуске
процесса, посредством правки файла конфигурации.
“Гибридный” вариант реализации, когда часть процессов работает под
управление ОС Linux, а оставшиеся - под ОС MS Windows не предусмотрена в силу ограничения работы .net framework на первом. Отладка производилась без
применения виртуальных машин.
Список литературы
1. Гордеев А.В. Операционные системы СПб. Питер, 2004.
2. Рихтер Д.Ж. CLR via C#. Программирование на
платформе.NET Framework 2.0 на языке С#. Мастер-класс. СПб. Питер, 2007
3. MSDN
Process
(http://msdn.microsoft.com/ru-ru/library/system.diagnostics.process.aspx)(http://msdn.microsoft.com/en-us/library/system.net.sockets.udpclient.beginreceive%28VS.80%29.aspx)
. Wikipedia
UDP (https://ru.wikipedia.org/wiki/UDP)/IP
(https://ru.wikipedia.org/wiki/TCP/IP)
5. Форум stackoverflow.com