ОбзорPVM.
Основные принципы PVM:
- Гетерогенность вычислительной системы. Аппаратная среда может содержать как однопроцессорные машины, так и многопроцессорные вычислительные системы (как с общей, так и с распределенной памятью).
- Конфигурируемый пользователем набор вычислительных машин. Необходимый для решения задачи пользователя набор машин формируется им из числа любых машин в используемой системе. Набор машин может изменяться добавлением и удалением машин в процессе работы программы пользователя.
- Прозрачность доступа к вычислительным машинам. Прикладные программы могут "видеть" аппаратную среду как группу виртуальных вычислительных элементов без атрибутов.
- Обмен данными меду процессами в соответствии с моделью обмена сообщениями. Допускается обмен между машинами с различным представлением данных
Вычислительная модель PVM предполагает, что приложение состоит из нескольких задач. Задачи могут выполняться параллельно, синхронизуясь и обмениваясь данными.
Коммуникационная модель PVM В коммуникационной модели PVM принято, что любая задача может передавать сообщение любой другой задаче. Ограничения на длину и количество сообщений отсутствуют. Однако имеется "ограничительный" текущий контроль. В тех случаях, когда суммарный объем входящих сообщений превышает размер доступной памяти, PVM выдает сообщение об ошибке.
PVM предоставляет функции
- асинхронной блокирующей передачи;
- асинхронного блокирующего приема;
- не блокирующего приема.
Коммуникационная модель PVM поддерживает коммуникации типа "точка-точка", а также широковещательную передачу данных. Модель гарантирует сохранение порядка сообщений. Если задача 1 посылает сообщение A задаче 2, а затем эта же задача посылает сообщение B той же задаче, то сообщение A поступит задаче 2 раньше, чем сообщение B.
Первая часть – это
программа-демон, которая помещается на все
компьютеры, создающие виртуальную машину. Важно, что пользователи могут конфигурировать перекрывающиеся виртуальные машины.
Вторая часть PVM – это
библиотека PVM подпрограмм. Библиотека PVM содержит вызываемые пользователем подпрограммы для обмена сообщениями, порождения
процессов, координирования задач и модификации виртуальной машины.
Поддерживаемые языки программирования. PVM поддерживает
языки программирования C, C++ и Фортран 77.
Привязка
языков C и C++ к пользовательскому интерфейсу PVM реализована в виде C-функций. Прикладные C и C++ программы получают доступ к функциям
библиотеки PVM путем присоединения к ним библиотеки PVM (libpvm3.a).
Привязка к
языку Фортран 77 реализована в виде подпрограмм. Фортран-приложения требуют присоединения библиотеки (libfpvm3.a) в дополнение к библиотеке (libpvm3.a).
Идентификация задач. Все задачи PVM идентифицируются посредством целочисленного
идентификатора задачи (
TID — Task IDentifier). PVM содержит несколько подпрограмм, которые возвращают значения TID, тем самым, давая возможность пользовательскому приложению идентифицировать другие задачи в системе.
Группы задач. Любая задача PVM может войти в состав любой
группы задач или покинуть ее в произвольный момент времени. Группы могут перекрываться, а задачи могут рассылать широковещательные сообщения, адресованные тем группам, в которых они не состоят. Для использования любой из групповых функций к программе должна быть присоединена библиотека libgpvm3.a.
Схема разработки и запуска приложения с помощью PVM:
- Пользователь пишет одну либо несколько последовательных программ на C, C++ или Фортран 77, в которые встроены вызовы библиотеки PVM (каждая программа затем породит задачу). Одна из программ должна быть "инициирующей" — запускающей все остальные программы приложения.
- Каждая из программ компилируются по правилам той вычислительной машины, на которой она будет выполняться.
- Полученные объектные файлы помещаются на соответствующие вычислительные машины.
- Для выполнения приложения пользователь вручную запускает "инициирующую" задачу, которая запускает остальные задачи PVM.
Сравнение MPI и PVM. MPI и PVM имеют следующие общие характеристики:
- и MPI и PVM призваны решать одну и ту же задачу обеспечения межпроцессорной связи,
- и MPI и PVM ориентированы на одну и туже MPMD парадигму программирования;
- и MPI и PVM представляют собой библиотеку программ;
- и MPI и PVM обеспечивают мобильность создаваемых программ.
MPI и PVM имеют следующие основные отличия:
- интерфейс программиста PVM проще, чем такой же интерфейс MPI;
- PVM имеет средства динамического порождения ветвей (в стандарте MPI-2 такая возможность также имеется);
- в PVM возможно взаимодействие приложений, запущенных одним и тем же пользователем (в стандарте MPI-2 такая возможность также имеется).
Функции контроля процессов.
Как отмечалось выше,
библиотеки PVM содержат функции и подпрограммы для
языков C и Фортран 77. Ограничимся рассмотрением функция для
языка С.
1. Функция
int tid = pvm_mytid(void) возвращает
TID текущего
процесса.
2. Функция
int info = pvm_exit(void) сообщает
программе-демону о том, что
процесс "покинул" среду PVM. Эта подпрограмма не завершает процесс принудительно — он может продолжать выполнять задачу наряду с другими процессами UNIX. Пользователи обычно вызывают функцию
pvm_exit прямо перед выходом.
3. Функция порождения потомков
int numt = pvm_spawn(char *task, char **argv, int flag, char *where, int ntask, int *tids)
запускает до
ntask копий исполняемого
файла task на виртуальной машине. Здесь
argv — указатель на массив аргументов для
task,
flag — используется для указания опций, numt — количество успешно порожденных задач или код ошибки, если ни одна из задач не смогла стартовать. Вызов pvm_spawn() может также запускать задачи и на
мультипроцессорах.
4. Функция
int info = pvm_kill(int tid) принудительно завершает задачу PVM, идентифицированную указанным в вызове
TID.
5. Функция
int info = pvm_catchout(FILE *ff). По умолчанию PVM записывает информацию о порожденных задачах в специальный
файл протокола /tmp/pvml.<uid>. Функция pvm_catchout заставляет посылать эту информацию вызывающей задаче.
Функции сбора информации о процессах.
1. Функция
int tid = pvm_parent(void) возвращает
TID процесса, который порожден данной задачей, или значение
PvmNoParent, если он не создан с помощью функции
pvm_spawn().
2. Функция
int dtid = pvm_tidtohost(int tid) возвращает
TID программы-демона, выполняющейся на той
вычислительной машине, что и задача с указанным TID. Эта подпрограмма применима для определения того, на какой машине выполняется данная задача.
3. Функция
int info = pvm_config(int *nhost, int *narch, struct pvmhostinfo **hostp)
возвращает информацию о виртуальной машине, включая количество
вычислительных машин в ней –
nhost, количество различных форматов данных –
narch и прочую информацию.
4. Функция
int info = pvm_tasks(int which, int *ntask, struct pvmtaskinfo **taskp)
возвращает информацию о задачах PVM, выполняющихся на виртуальной машине.
Функции динамического конфигурирования.
1. Функции
int info = pvm_addhosts(char **hosts, int nhost, int *infos)
int info = pvm_delhosts(char **hosts, int nhost, int *infos)
добавляют к виртуальной машине или удаляют из нее hosts узлов. Аргумент infos возвращает количество успешно добавленных узлов.
Функции посылки извещений.
1. Функция
int info = pvm_sendsig( int tid, int signum) посылает сигнал
signum задаче PVM с указанным
TID.
2. Функция
int info = pvm_notify(int what, int msgtag, int cnt, int tids)
запрашивает PVM об извещении вызывающей задачи о наступлении следующих событий: PvmTaskExit — сообщение о выходе из задачи; PvmHostDelete — сообщение об удалении машины (или сбое); PvmHostAdd — сообщение о добавлении машины.
Схема обмена сообщениями.
Посылка сообщения в PVM осуществляется по следующей схеме.
- Буфер передачи инициализируется вызовом функции pvm_initsend() или функции pvm_mkbuf().
- Сообщение "упаковывается" в этот буфер с помощью подпрограммы pvm_pk().
- Подготовленное сообщение посылается соответствующему процессу вызовом функции pvm_send() или широковещательной передачей с помощью подпрограммы pvm_mcast().
Прием сообщения выполняется по схеме:
- Сообщение принимается вызовом функции блокирующего или не блокирующего приема.
- Каждый из упакованных фрагментов распаковывается в буфер приема.
Функции приема могут быть настроены на прием любого сообщения, любого сообщения от указанного источника, любого сообщения с указанным тегом, только сообщения с данным тегом от данного источника. Существует "пробная" функция, которая проверяет, поступило ли сообщение, но на самом деле не принимает его.
"Буферные" функции.
1. Функция int bufid = pvm_initsend(int encoding) очищает буфер передачи и пересоздает его для упаковки нового сообщения. Схема кодирования, используемая при упаковке, устанавливается с помощью аргумента encoding. Новый идентификатор буфера возвращается в bufid.
В PVM имеется множество других "буферных" функций, которые используются редко:
- функция для создания нового буфера;
- функция, возвращающая в свободное пользование освободившийся буфер;
- функция, возвращающая идентификатор активного буфера передачи;
- функция, возвращающая идентификатор активного буфера приема.Функции упаковки данных.
В PVM имеется большое количество функций упаковки данных, каждая из которых упаковывает массив данных определенного типа в активный буфер передачи. Аргументами для каждой из этих функций являются:
- указатель на первый из элементов для упаковки;
- nitem — суммарное число элементов для упаковки из данного массива;
- stride – "шаг" для использования во время упаковки (так stride=1, означает последовательную упаковку вектора, stride=2 — упаковку через один элемент вектора и т.д.).
Например, функция int info = pvm_pkbyte( char*cp, int nitem, int stride) обеспечивает упаковку в буфер данные типа char.
Функции передачи и приема данных.
1. Функция
int info = pvm_send(int tid, int msgtag) помечает сообщение целочисленным идентификатором
msgtag и передает его
процессу с идентификатором
tid.
2. Функция int info = pvm_mcast(int *tids, int ntask, int msgtag) помечает сообщение целочисленным идентификатором msgtag и широковещательно передает это сообщение всем задачам, указанным в целочисленном массиве tids. Массив tids имеет длину ntask.
3. Функция int info = pvm_psend(int tid, int msgtag, void *vp, int cnt, int type) упаковывает и посылает массив данных указанного типа задаче с идентификатором tid. Аргумент type может иметь любое из следующих значений:
Таблица 1
PVM_STR | PVM_FLOAT |
PVM_BYTE | PVM_CPLX |
PVM_SHORT | PVM_DOUBLE |
PVM_INT | PVM_DCPLX |
PVM_LONG | PVM_UINT |
PVM_USHORT | PVM_ULONG |
4. Функция int bufid = pvm_recv(int tid, int msgtag) реализует блокирующей прием. Функция будет находиться в состоянии ожидания до тех пор, пока от задачи с идентификатором tid не поступит сообщение с меткой msgtag. После поступления сообщения функция помещает сообщение в новый активный буфер приема. Предыдущий активный буфер приема очищается.
5. Функция int bufid = pvm_nrecv(int tid, int msgtag) реализует не блокирующий прием. Если сообщение с меткой msgtag поступило от задачи с идентификатором tid, то функция pvm_nrecv() помещает это сообщение в новый активный буфер (который она создает) и возвращает идентификатор данного буфера. Предыдущий активный буфер приема очищается.
6. Функция int bufid = pvm_probe(int tid, int msgtag). Если запрашиваемое сообщение не прибыло, то функция pvm_probe() возвращает bufid, равный 0. В противном случае она возвращает bufid сообщения, но не принимает это сообщение.
7. Функция int bufid = pvm_trecv(int tid, int msgtag, struct timeval *tmout) реализует приема данных с тайм-аутом. Если ожидаемое сообщение не может прибыть (из-за ошибки или сбоя), то функция pvm_recv() может заблокировать программу. Этой блокировки можно избежать путем использования вместо функции pvm_recv() функции pvm_trecv().
8. Функция
int info = pvm_bufinfo(int bufid, int *bytes, int *msgtag, int *tid) возвращает
msgtag,
TID источника и длину в байтах сообщения, идентифицированного с помощью
bufid.
9. Функция
int info = pvm_precv(int tid, int msgtag, void *vp, int cnt, int type, int *rtid, int *rtag, int *rcnt)
сочетает в себе функции блокирующего приема и распаковки буфера приема.
Функции распаковки данных.
Функции распаковки данных аналогичны рассмотренным выше функция упаковки данных. Каждая из функций распаковки распаковывает данные определенного типа из активного буфера приема. На уровне приложения эти функции должны соответствовать функциям упаковки по типу, числу элементов и шагу.
Пример функции распаковки:
int info = pvm_upkbyte(char *cp, int nitem, int stride)
Функции динамической группировки процессов.
В целом, любая задача PVM может вызвать любую из рассмотренных ниже функций. Исключение составляют функции pvm_lvgroup(), pvm_barrier() и pvm_reduce(), природа которой требует членства вызывающей задачи в указанной группе.
1. Функция
int inum = pvm_joingroup(char *group) создает группу с именем
group и включает в нее вызывающую задачу. Функция возвращает номер экземпляра
процесса (
inum) в указанной группе.
2. Функция int info = pvm_lvgroup(char *group). Чтобы помочь пользователю в управлении последовательностью назначения номеров, независимо от того, происходит присоединение или отсоединение, функция pvm_lvgroup() не завершается до тех пор, пока задача не будет достоверно извещена об этом факте.
3. Функция
int tid = pvm_gettid(char *group, int inum) возвращает
TID процесса с указанными именем группы и номером экземпляра процесса.
4. Функция
int inum = pvm_getinst(char *group, int tid) pvm_gettid() позволяет двум не знающим друг друга задачам получить
TID друг друга посредством присоединения к общей группе. Функция возвращает номер задачи с TID из указанной группы.
5. Функция int size = pvm_gsize(char *group) возвращает количество членов в указанной группе.
6. Функция
int info = pvm_barrier(char *group, int count) блокирует
процесс до тех пор, пока
count членов группы не вызовут
pvm_barrier.
7. Функция int info = pvm_bcast(char *group, int msgtag) помечает сообщение целочисленным идентификатором msgtag и широковещательно передает его всем задачам в указанной группе, за исключением "собственной" задачи.
8. Функция
int info = pvm_reduce(void (*func()), void *data, int nitem,
int datatype, int msgtag, char *group, int root)
выполняет глобальную арифметическую операцию в группе, например нахождение глобальной суммы или глобального максимума. Результат редуцирующей операции помещается в root. PVM поддерживает четыре предопределенных функции, которые пользователь может передать через func: PvmMax, PvmMin, PvmSum, PvmProduct. Редуцирующая операция над входными данными выполняется поэлементно. В дополнение, пользователи могут определить свои собственные функции для глобальных операций и указать их в func.