Реферат на тему Interprocess Communication
Работа добавлена на сайт bukvasha.net: 2015-06-29Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Лекция №17
Interprocess Communication
Мы с вами говорили, что далее речь пойдет о разделяемых ресурсах, доступ к которым может осуществляться со стороны произвольных процессов, в общем случае, в произвольном порядке. Эти ресурсы доступны любому процессу, а процессы не обязательно должны быть родственными. При наличии такой схемы возникают две принципиальные проблемы:
Именование;
Синхронизация;
Проблемы именования связаны с тем, что родственных связей нет и по наследству передать ничего нельзя.
Если проблема именования решена, то возникает проблема синхронизации доступа - как организовать обмен с ресурсами, чтобы этот обмен был корректным. Если у нас есть, например, ресурс “оперативная память”, то когда один процесс еще не дописал информацию, а другой процесс уже прочитал весь блок, то возникает некорректная ситуация.
Решения этих проблем мы и будем рассматривать.
Проблема именования решается за счет ассоциирования с каждым ресурсом некоторого ключа. В общем случае это целочисленное значение. То есть при создании разделяемого ресурса его автор приписывает ему номер и определяет права доступа к этому ресурсу. После этого любой процесс, который укажет системе, что он хочет общаться с разделяемым ресурсом с ключом N, и обладает необходимыми правами доступа, будет допущен для работы с этим ресурсом.
Однако такое решение не является идеальным, так как вполне возможна коллизия номеров - когда совпадают номера разделяемых ресурсов. В этом случае процессы будут путаться, что неизбежно приведет к ошибкам. Поэтому в системе предусмотрено стандартное средство генерации уникальных ключей. Для генерации уникального ключа используется функция ftok
#include
#include
key_t ftok(char *s, char c);
Суть ее действия - по текстовой строке и символу генерируется уникальное для каждой такой пары значение ключа. После этого сгенерированным ключом можно пользоваться как для создания ресурса, так и для подтверждения использования ресурса. Более того, для исключения коллизий, рекомендуется указывать в качестве параметра "указателя на строку" путь к некоторому своему файлу. Второй аргумент - символьный, который позволяет создавать некоторые варианты ключа, связанного с этим именем, этот аргумент называется проектом (project). При таком подходе можно добиться отсутствия коллизий.
Давайте посмотрим конкретные средства работы с разделяемыми ресурсами.
Разделяемая память.
Общая схема работы с разделяемыми ресурсами такова - есть некоторый процесс-автор, создающий ресурс с какими-либо параметрами. При создании ресурса разделяемой памяти задаются три параметра - ключ, права доступа и размер области памяти. После создания ресурса к нему могут быть подключены процессы, желающие работать с этой памятью. Соответственно, имеется действие подключения к ресурсу с помощью ключа, который генерируется по тем же правилам, что и ключ для создания ресурса. Понятно, что здесь имеется момент некоторой рассинхронизации, который связан с тем, что потребитель разделяемого ресурса (процесс, который будет работать с ресурсом, но не является его автором) может быть запущен и начать подключаться до запуска автора ресурса. В этой ситуации особого криминала нету, так как имеются функции управления доступом к разделяемому ресурсу, с использованием которых можно установить некоторые опции, определяющие правила работы функций, взаимодействующих с разделяемыми ресурсами. В частности, существует опция, заставляющая процесс дождаться появления ресурса. Это также, может быть, не очень хорошо, например, автор может так и не появиться, но другого выхода нету, это есть некоторые накладные расходы. Вот в общих словах - что есть что.
Давайте рассмотрим те функции, которые предоставляются нам для работы с разделяемыми ресурсами.
Первая функция - создание общей памяти.
int shmget (key_t key, int size, int shmemflg);
key - ключ разделяемой памяти
size - размер раздела памяти, который должен быть создан
shmemflg - флаги
Данная функция возвращает идентификатор ресурса, который ассоциируется с созданным по данному запросу разделяемым ресурсом. То есть в рамках процесса по аналогии с файловыми дескрипторами каждому разделяемому ресурсу определяется его идентификатор. Надо разделять ключ - это общесистемный атрибут, и идентификатор, используя который мы работаем с конкретным разделяемым ресурсом в рамках процесса.
С помощью этой функции можно как создать новый разделяемый ресурс “память” (в этом случае во флагах должен быть указан IPC_CREAT)?, а также можно подключиться к существующему разделяемому ресурсу. Кроме того, в возможных флагах может быть указан флаг IPC_EXECL, он позволяет проверить и подключиться к существующему ресурсу - если ресурс существует, то функция подключает к нему процесс и возвращает код идентификатора, если же ресурс не существует, то функция возвращает -1 и соответствующий код в errno.
Следующая функция - доступ к разделяемой памяти:
char *shmat(int shmid, char *shmaddr, int shmflg);
shmid - идентификатор разделяемого ресурса
shmaddr - адрес, с которого мы хотели бы разместить разделяемую память
При этом, если значение shmaddr - адрес, то память будет подключена, начиная с этого адреса, если его значение - нуль, то система сама подберет адрес начала. Также в качестве значений этого аргумента могут быть некоторые предопределенные константы, которые позволяют организовать, в частности выравнивание адреса по странице или началу сегмента памяти.
shmflg - флаги. Они определяют разные режимы доступа, в частности, есть флаг SHM_RDONLY.
Эта функция возвращает указатель на адрес, начиная с которого будет начинаться запрашиваемая разделяемая память. Если происходит ошибка, то возвращается -1.
Хотелось бы немного поговорить о правах доступа. Они реально могут использоваться и корректно работать не всегда. Так как, если аппаратно не поддерживается закрытие области данных на чтение или на запись, то в этом случае могут возникнуть проблемы с реализацией такого рода флагов. Во-первых, они не будут работать, так как мы получаем указатель и начинаем работать с указателем, как с указателем, и общая схема здесь не предусматривает защиты. Второе, можно программно сделать так, чтобы работали флаги, но тогда мы не сможем указывать произвольный адрес, в этом случае система будет подставлять и возвращать в качестве адрес разделенной памяти некоторые свои адреса, обращение к которым будет создавать заведомо ошибочную ситуацию, возникнет прерывание процесса, во время которого система посмотрит - кто и почему был инициатором некорректного обращения к памяти, и если тот процесс имеет нужные права доступа - система подставит нужные адреса, иначе доступ для процесса будет заблокирован. Это похоже на установку контрольной точки в программе при отладке, когда создавалась заведомо ошибочная ситуация для того, чтобы можно было прервать процесс и оценить его состояние.
Третья функция - открепление разделяемой памяти:
int shmdt(char *shmaddr);
shmaddr - адрес прикрепленной к процессу памяти, который был получен при подключении памяти в начале работы.
Четвертая функция - управление разделяемой памятью:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid - идентификатор разделяемой памяти
cmd - команда управления.
В частности, могут быть команды: IPC_SET (сменить права доступа и владельца ресурса - для этого надо иметь идентификатор автора данного ресурса или суперпользователя), IPC_STAT (запросить состояние ресурса - в этом случае заполняется информация в структуру, указатель на которую передается третьим параметром, IPC_RMID (уничтожение ресурса - после того, как автор создал процесс - с ним работают процессы, которые подключаются и отключаются, но не уничтожают ресурс, а с помощью данной команды мы уничтожаем ресурс в системе).
Это все, что касается функций управления разделяемой памятью.
Передача сообщений.
Следующим средством взаимодействия процессов в системе IPC - это передача сообщений. Ее суть в следующем: в системе имеется так называемая очередь сообщений, в которой каждое сообщение представляет из себя структуру данных, с которой ассоциирован буфер, содержащий тело сообщения и признак, который называется типом сообщения. Очередь сообщений может быть рассмотрена двояко:
очередь рассматривается, как одна единственная сквозная очередь, порядок сообщений в которой определяется хронологией их попадания в эту очередь.
кроме того, так как каждое сообщение имеет тип (на схеме - буква рядом с номером сообщения), то эту очередь можно рассматривать, как суперпозицию очередей, связанную с сообщениями одного типа.
Система IPC позволяет создавать разделяемый ресурс, называемый “очередь сообщений” - таких очередей может быть произвольное количество. По аналогии с разделяемой памятью - мы можем создать очередь, подключиться к ней, послать сообщение, принять сообщение, уничтожить очередь и т.д. Рассмотрим функции работы с очередями сообщений:
Создание очереди сообщений:
int msgget(key_t key, int flags);
В зависимости от флагов при обращении к данной функции либо создается разделяемый ресурс, либо осуществляется подключение к уже существующему.
Отправка сообщения:
int msgsnd( int id, struct msgbuf *buf, int size, int flags);
id - идентификатор очереди сообщения;
struct msgbuf {
long type; /* тип сообщения */
char mtext[s] /* указатель на тело сообщения */
}
size - размер сообщения, здесь указывается размер сообщения, размещенного по указателю buf;
flags - флаги, в частности, флагом может быть константа IPC_NOWAIT. При наличии такого флага будут следующие действия - возможна ситуация, когда буфера, предусмотренные системой под очередь сообщений, переполнены. В этом случае возможны два варианта - процесс будет ожидать освобождения пространства, если не указано IPC_NOWAIT, либо функция вернет -1 (с соответствующим кодом в errno), если было указано IPC_NOWAIT.
Прием сообщения:
int msgrcv( int id, struct msgbuf *buf, int size, long type, int flags);
id - идентификатор очереди;
buf - указатель на буфер, куда будет принято сообщение;
size - размер буфера, в котором будет размещено тело сообщения;
type - если тип равен нулю, то будет принято первое сообщение из сквозной очереди, если тип больше нуля, то в этом случае будет принято первое сообщение из очереди сообщений, связанной с типом, равным значению этого параметра.
flags - флаги, в частности, IPC_NOWAIT, он обеспечит работу запроса без ожидания прихода сообщения, если такого сообщения в момент обращения функции к ресурсу не было, иначе процесс будет ждать.
Управление очередью:
int msgctl( int id, int cmd, struct msgid_dl *buf);
id - идентификатор очереди;
cmd - команда управления, для нас интерес представляет IPC_RMID, которая уничтожит ресурс.
buf - этот параметр будет оставлен без комментария.
Мы описали два средства взаимодействия между процессами. Что же мы увидели? Понятно, что названия и описания интерфейсов мало понятны. Прежде всего следует заметить то, что как только мы переходим к вопросу взаимодействия процессов, у нас возникает проблема синхронизации. И здесь мы уже видим проблемы, связанные с тем, что после того, как мы поработали с разделяемой памятью или очередью сообщений, в системе может оставаться “хлам”, например, процессы, которые ожидают сообщений, которые в свою очередь не были посланы. Так, если мы обратились к функции получения сообщений с типом, которое вообще не пришло, и если не стоит ключ IPC_NOWAIT, то процесс будет ждать его появления, пока не исчезнет ресурс. Или мы можем забыть уничтожить ресурс (и система никого не поправит) - этот ресурс останется в виде загрязняющего элемента системы.
Когда человек начинает работать с подобными средствами, то он берет на себя ответственность за все последствия, которые могут возникнуть. Это первый набор проблем - системная синхронизация и аккуратность. Вторая проблема - синхронизация данных, когда приемник и передатчик работают синхронно. Заметим, что самый плохой по синхронизации ресурс из рассмотренных нами - разделяемая память. Это означает, что корректная работа с разделяемой памятью не может осуществляться без использования средств синхронизации, и, в частности, некоторым элементом синхронизации может быть очередь сообщений. Например, мы можем записать в память данные и послать сообщение приемнику, что информация поступила в ресурс, после чего приемник, получив сообщение, начинает считывать данные. Также в качестве синхронизирующего средства могут применяться сигналы.
И это главное - не язык интерфейсов, а проблемы, которые могут возникнуть при взаимодействии параллельных процессов.
Лекция №18
К сегодняшнему дню мы разобрали два механизма взаимодействия процессов в системе IPC - это механизм общей (или разделяемой) памяти и механизм сообщений. Мы с вами выяснили, что одной из основных проблем, возникающей при взаимодействии процессов, является проблема синхронизации. Ярким примером механизма, для которого эта проблема является наиболее острой, является механизм взаимодействия процессов с использованием разделяемой памяти.
Вы помните, что механизм разделяемой памяти позволяет создавать объект, который становится доступным всем процессам, подтвердившим ключ доступа к этому объекту, а также имеют соответствующие права. После этого общая память становится, с точки зрения каждого из этих процессов, как бы фрагментом адресного пространства каждого из них, к которому этот процесс может добираться через указатель этого адресного пространства. С другой стороны нет никаких средств, которые позволили бы синхронизовать чтение и запись в эту область данных. Так как в эту область данных одновременно имеет доступ произвольное количество процессов, то проблема синхронизации здесь имеет место быть.
Возможна ситуация, когда один из процессов начал запись в разделяемую память, но еще не закончил, но другой процесс не дождался завершения записи, считал и начал пользоваться этой информацией. В этом случае возможны коллизии. Т.е. без синхронизации использовать механизм разделяемой памяти невозможно.
Следующий механизм, который мы с вами рассмотрели - очередь сообщений. Имеется возможность совместной работы с разделяемым объектом, который называется очередь сообщений. Имеется сообщение, которое состоит из некоторого спецификатора типа, и некоторого набора данных. Процесс, подтвердив ключ и имея права доступа к этому разделяемому ресурсу, может осуществлять действия по записи сообщений в очередь, и по чтению сообщений из очереди.
Порядок чтения и записи сообщений из очереди соответствует названию этой структуры - очередь. Кроме того, за счет того, что каждое сообщение типизировано, есть возможность рассмотрения этой очереди с нескольких точек зрения. Первая точка зрения - это одна очередь и порядок в ней хронологический. Вторая точка зрения - это возможность представление этой очереди в виде нескольких очередей, каждая из которых содержит элементы определенного типа.
Понятно, что механизм сообщений может выступать в двух ролях: как средство передача данных, и как средство синхронизации (понятно каким образом).
Итак, к сегодняшнему дню мы познакомились с двумя этими механизмами. Напомню, как только мы переходим к работе от однопроцессной задачи к задаче многопроцессной, у нас сразу же возникают проблемы, связанные с тем, что любой параллелизм накладывает определенную ответственность на программу. Это ответственность по синхронизации доступа к разделяемой памяти, ответственность за правильность подпрограммы, занимающейся приемом и передачей сообщений и т.д. Можно, например, ошибиться в механизме передачи и приема сообщений за счет того, что какой-то процесс будет бесконечно долго ожидать несуществующее сообщение, то, которое никогда в очереди не появится, и система вам никогда такую ошибку не зафиксирует. Т.е. возможны зависания процессов, могут образоваться неосвобожденные ресурсы ("мусор"), и это приводит к деградации системы.
Сейчас мы напишем следующую программу: первый процесс будет читать некоторую текстовую строку из стандартного ввода и в случае, если строка начинается с буквы 'a', то эта строка в качестве сообщения будет передана процессу А, если 'b' - процессу В, если 'q' - то процессам А и В и затем будет осуществлен выход. Процессы А и В распечатывают полученные строки на стандартный вывод.
Основной процесс
#include
#include
#include
#include
struct { long mtype; /* тип сообщения */
char Data[256]; /* сообщение */
} Message;
int main()
{ key_t key; int msgid; char str[256];
key=ftok("/usr/mash",'s'); /*получаем уникальный ключ, однозначно определяющий доступ к ресурсу данного типа */
msgid=msgget(key, 0666 | IPC_CREAT); /*создаем очередь сообщений , 0666 определяет права доступа */
for(;;) { /* запускаем вечный цикл */
gets(str); /* читаем из стандартного ввода строку */
strcpy(Message.Data, str); /* и копируем ее в буфер сообщения */
switch(str[0]){
case 'a':
case 'A': Message.mtype=1; /* устанавливаем тип и посылаем сообщение в очередь*/
msgsnd(msgid, (struct msgbuf*) (&Message), strlen(str)+1, 0);
break;
case 'b':
case 'B': Message.mtype=2;
msgsnd(msgid, (struct msgbuf*) (&Message), strlen(str)+1, 0);
break;
case q':
case 'Q': Message.mtype=1;
msgsnd(msgid, (struct msgbuf*) (&Message), strlen(str)+1, 0);
Message.mtype=2;
msgsnd(msgid, (struct msgbuf*) (&Message), strlen(str)+1, 0);
sleep(10); /* ждем получения сообщений процессами А и В */
msgctl(msgid, IPC_RMID, NULL); /* уничтожаем очередь*/
exit(0);
default: break;
}
}
}
Процесс-приемник А /* процесс В аналогичен с точностью до четвертого параметра в msgrcv */
#include
#include
#include
#include
struct { long mtype;
char Data[256];
} Message;
int main()
{ key_t key; int msgid;
key=ftok("/usr/mash",'s'); /* получаем ключ по тем же параметрам */
msgid=msgget(key, 0666 | IPC_CREAT); /*создаем очередь сообщений */
for(;;) { /* запускаем вечный цикл */
msgrcv(msgid, (struct msgbuf*) (&Message), 256, 1, 0); /* читаем сообщение с типом 1*/
if (Message.Data[0]='q' || Message.Data[0]='Q') break;
printf("%s",Message.Data);
}
exit(0);
}
Семафоры
С точки зрения тех проблем, с которыми мы знакомимся, семафоры - это есть вполне законное и существующее понятие. Впервые ввел это понятие достаточно известный ученый Дейкстра. Семафор - это некоторый объект, который имеет целочисленное значение S, и с которым связаны две операции: V(S) и P(S).
Операция P(S) уменьшает значение семафора на 1, и если S0 процесс продолжает работу. Если S<0, то процесс будет приостановлен и встанет в очередь ожидания, связанную с семафором S, до тех пор, пока его не освободит другой процесс.
Операция V(S) увеличивает семафор на 1. Если S>0, то процесс продолжает выполнение. Если S0, то разблокируется один из процессов, ожидающий в очереди процессов, связанной с семафором S, и текущий процесс продолжит выполнение.
Считается, что операции P(S) и V(S) неделимы. Это означает, что выполнение этих операций не может прерваться до их завершения. Т.е. если семафор реализован в системе, то это должна быть одна команда.
Частным случаем продекларированного семафора является двоичный семафор, максимальное значение которого равно единичке. При этом значение S может быть равно 1, это означает, что ни один из процессов (связанных с этим семафором) не находится в критическом участке. При S=0 один из процессов находится в критическом участке {вот-вот попадет в очередь}, а другой нормально функционирует. При S= -1 один из процессов находится в критическом участке, а другой заблокирован и находится в очереди.
На самом деле двоичные семафоры наиболее часто находили применение в аппаратных реализациях, например, в многомашинных комплексах с общей оперативной памятью.
Одним из разделяемых ресурсов, который поддерживает система IPC является т.н. массив семафоров. Система позволяет процессам, участвующим во взаимодействии с данным разделяемым ресурсом, увеличивать или уменьшать один или несколько семафоров из данного массива на произвольное значение (в пределах разрядной сетки). Система обеспечивает ожидание процессами обнуления одного или нескольких семафоров.
Давайте рассмотрим средства, предоставляемые системой IPC, для создания, управления и взаимодействия с семафорами.
int semget(key_t key, int n, int flags);
int semop(int semid, struct sembuf * sops, int n);
struct sembuf{
short sem_num; /* номер семафора в массиве семафоров */
short sem_op; /* код операции, которую надо выполнить */
short sem_flg; /* флаги */
}
Первый параметр функции semget - ключ, второй - количество семафоров (длина массива семафоров) и третий параметр - флаги. Через флаги можно определить права доступа и те операции, которые должны выполняться (открытие семафора, проверка, и т.д.). Функция semget возвращает целочисленный идентификатор созданного разделяемого ресурса, либо -1, если ресурс не удалось создать (причина - в errno).
Первый параметр функции semop - идентификатор семафора, второй - указатель на структуру sembuf и третий параметр - количество указателей на эту структуру, которые передаются функцией semop. В структуре содержится вся информация о необходимом действии.
Поле операции интерпретируется следующим образом. Пусть значение семафора с номером sem_num равно sem_val. В этом случае, если значение операции не равно нулю, то оценивается значение суммы (sem_val + sem_op). Если эта сумма больше либо равна нулю, то значение данного семафора устанавливается равным сумме предыдущего значения и кода операции. Если эта сумма меньше нуля, то действие процесса будет приостановлено до наступления одного из следующих событий:
Значение суммы (sem_val + sem_op) станет больше либо равно нулю.
Пришел какой-то сигнал. (Значение semop в этом случае будет равно -1).
Если код операции semop равен нулю, то процесс будет ожидать обнуления семафора. Если мы обратились к функции semop с нулевым кодом операции, а к этому моменту значение семафора стало равным нулю, то никакого ожидания не происходит.
Рассмотрим третий параметр - флаги. Если третий параметр равен нулю, то это означает, что флаги не используются. Флагов имеется большое количество в т.ч. IPC_NOWAIT (при этом флаге во всех тех случаях, когда мы говорили, что процесс будет ожидать, он не будет ожидать).
Обращаю ваше внимание, что за одно обращение к функции semop можно передать n структур и соответственно выполнить действия с n семафорами из этого массива. Если в этой последовательности будут присутствовать две разных операции с одни семафором, то скорее всего, выполнится последняя.
Функция управления массивом семафоров.
int semctl(int id, int sem_num, int cmd, union sem_buf arg);
Первый параметр - идентификатор, второй - номер семафора в массиве, с которым мы будем выполнять команду cmd из третьего параметра. Последний параметр - некоторое объединение типа sembuf.
Команды могут быть традиционные (IPC_RMID), и кроме них есть другие команды, и среди них IPC_SET, которая устанавливает значение семафора, при этом значение передается через объединение arg. При установке семафора этой функцией, задержек, которые определяют основной смысл работы семафора, не будет.
Вот те функции, которые предназначены для работы с семафорами. Пример работы с семафорами рассмотрим на следующей лекции.
Лекция №19
Мы остановились на средствах синхронизации доступа к разделяемым ресурсам - на семафорах. Мы говорили о том, что семафоры - это тот формализм, который изначально был предложен ученым в области компьютерных наук Дейкстрой, поэтому часто в литературе их называют семафорами Дейкстры. Семафор - это есть некоторая переменная и над ней определены операции P и V. Одна позволяет увеличивать значение семафора, другая - уменьшать. Причем с этими изменениями связаны возможности блокировки процесса и разблокировки процесса. Обратим внимание, что речь идет о неразделяемых операциях, то есть тех операциях, которые не могут быть прерваны, если начались. То есть не может быть так, чтобы во время выполнения P или V пришло прерывание, и система передала управление другому процессу. Это принципиально. Поэтому семафоры можно реализовывать программно, но при этом мы должны понимать, что эта реализация не совсем корректна, так как
программа пишется человеком, а прерывается аппаратурой, отсюда возможно нарушение неразделяемости;
в развитых вычислительных системах, которые поддерживают многопроцессорную обработку или обработку разделяемых ресурсов в рамках одного процесса, предусмотрены семафорные команды, которые фактически реализовывают операции P и V. Это важно.
Мы говорили о реализации семафоров в Unix в системе IPC и о том, что эта система позволяет создать разделяемый ресурс “массив семафоров”, соответственно, как и к любому разделяемому ресурсу, к этому массиву может быть обеспечен доступ со стороны различных процессов, обладающих нужными правами и ключом к данному ресурсу.
Каждый элемент массива - семафор. Для управления работой семафора есть функции:
semop, которая позволяет реализовывать операции P и V над одним или несколькими семафорами;
segctl - управление ресурсом. Под управлением здесь понимается три вещи:
- получение информации о состоянии семафора;
- возможность создания некоторого режима работы семафора, уничтожение семафора;
- изменение значения семафора (под изменением значения здесь понимается установление начальных значений, чтобы использовать в дальнейшем семафоры, как семафоры, а не ящички для передачи значений, другие изменения - только с помощью semop);
Давайте приведем пример, которым попытаемся проиллюстрировать использование семафоров на практике.
Наша программа будет оперировать с разделяемой памятью.
1 процесс - создает ресурсы “разделяемая память” и “семафоры”, далее он начинает принимать строки со стандартного ввода и записывает их в разделяемую память.
2 процесс - читает строки из разделяемой памяти.
Таким образом мы имеем критический участок в момент, когда один процесс еще не дописал строку, а другой ее уже читает. Поэтому следует установить некоторые синхронизации и задержки.
Следует отметить, что, как и все программы, которые мы приводим, эта программа не совершенна. Но не потому, что мы не можем ее написать (в крайнем случае можно попросить своих аспирантов или студентов), а потому, что совершенная программа будет занимать слишком много места, и мы сознательно делаем некоторые упрощения. Об этих упрощениях мы постараемся упоминать.
1й процесс:
#include
#include
#include
#include
int main(void)
{ key_t key;
int semid, shmid;
struct sembuf sops;
char *shmaddr;
char str[256];
key = ftok(“/usr/mash/exmpl”,’S’); /* создаем уникальный ключ */
semid = semget(key,1,0666 | IPC_CREAT); /* создаем один семафор с определенными правами доступа */
shmid = shmget(key,256, 0666 | IPC_CREAT); /*создаем разделяемую память на 256 элементов */
shmaddr = shmat(shmid, NULL, 0); /* подключаемся к разделу памяти, в shaddr - указатель на буфер с разделяемой памятью*/
semctl(semid,0,IPC_SET, (union semun) 0); /*инициализируем семафор со значением 0 */
sops.sem_num = 0; sops.sem_flg = 0;
/* запуск бесконечного цикла */
while(1) { printf(“Введите строку:”);
if ((str = gets(str)) == NULL) break;
sops.sem_op=0; /* ожидание обнуления семафора */
semop(semid, &sops, 1);
strcpy(shmaddr, str); /* копируем строку в разд. память */
sops.sem_op=3; /* увеличение семафора на 3 */
semop(semid, &sops, 1);
}
shmaddr[0]=’Q’; /* укажем 2ому процессу на то, */
sops.sem_op=3; /* что пора завершаться */
semop(semid, &sops, 1);
sops.sem_op = 0; /* ждем, пока обнулится семафор */
semop(semid, &sops, 1);
shmdt(shmaddr); /* отключаемся от разд. памяти */
semctl(semid, 0, IPC_RMID, (union semun) 0); /* убиваем семафор */
shmctl(shmid, IPC_RMID, NULL); /* уничтожаем разделяемую память */
exit(0);
}
2й процесс:
/* здесь нам надо корректно определить существование ресурса, если он есть - подключиться, если нет - сделать что-то еще, но как раз этого мы делать не будем */
#include
#include
#include
#include
int main(void)
{ key_t key; int semid;
struct sembuf sops;
char *shmaddr;
char st=0;
/* далее аналогично предыдущему процессу - инициализации ресурсов */
semid = semget(key,1,0666 | IPC_CREAT);
shmid = shmget(key,256, 0666 | IPC_CREAT);
shmaddr = shmat(shmid, NULL, 0);
sops.sem_num = 0; sops.sem_flg = 0;
/* запускаем цикл */
while(st!=’Q’) {
printf(“Ждем открытия семафора \n”);
/* ожидание положительного значения семафора */
sops.sem_op=-2;
semop(semid, &sops, 1);
/* будем ожидать, пока “значение семафора”+”значение sem_op” не перевалит за 0, то есть если придет “3”, то “3-2=1” */
/* теперь значение семафора равно 1 */
st = shmaddr[0];
{ /*критическая секция - работа с разделяемой памятью - в этот момент первый процесс к разделяемой памяти доступа не имеет*/}
/*после работы - закроем семафор*/
sem.sem_op=-1;
semop(semid, &sops, 1);
/* вернувшись в начало цикла мы опять будем ждать, пока значение семафора не станет больше нуля */
}
shmdt(shmaddr); /* освобождаем разделяемую память и выходим */
exit(0);
}
Это программа, состоящая из двух процессов, синхронно работающих с разделяемой памятью. Понятно, что при наличии интереса можно работать и с сообщениями.
На этом мы заканчиваем большую и достаточно важную тему организации взаимодействия процессов в системе.
Наша самоцель - не изучение тех средств, которые предоставляет Unix, а изучение принципов, которые предоставляет система для решения тех или иных задач, так как другие ОС предоставляют аналогичные или похожие средства управления процессами.
Системы программирования.
Система программирования - это комплекс программных средств, обеспечивающих поддержку технологий проектирования, кодирования, тестирования и отладки, называется системой программирования
Этап проектирования
Было сказано, что на сегодняшний день достаточно сложно, а практически невозможно создавать программное обеспечение без этапа проектирования, такого же долгого, нудного и детального периода, который проходит во время проектирования любого технического объекта. Следует понять, что те программы, которые пишутся студентами в качестве практических и дипломных задач не являются по сути дела программами - это игрушки, так как их сложность невелика, объемы незначительны и такого рода программы можно писать слегка. Реальные же программы так не создаются, так же, как и не создаются сложные технические объекты. Никто никогда не может себе представить, чтобы какая-нибудь авиационная компания продекларировала создание нового самолета и дала команду своим заводам слепить лайнер с такими-то параметрами. Так не бывает. Каждый из элементов такого объекта, как самолет, проходит сложный этап проектирования.
Например, фирма Боинг подняла в воздух самолет “Боинг-777”, замечательность этого факта заключается в том, что самолет взлетел без предварительной продувки в аэродинамической трубе. Это означает, что весь самолет был спроектирован и промоделирован на программных моделях, и это проектирование и моделирование было настолько четким и правильным, что позволило сразу же поднять самолет в воздух. Для справки - продувка самолета в аэродинамической трубе стоит сумасшедшие деньги.
Примерно та же ситуация происходит при создании сложных современных программных систем. В начале 80х гг была начата СОИ (стратегическая оборонная инициатива), ее идея была в том, чтобы создать сложной технической системы, которая бы в автоматическом режиме установила контроль за пусковыми установками СССР и стран Варшавского блока, и в случае фиксации старта с наших позиций какого-то непродекларированного объекта автоматически начиналась война. То есть запускались бы средства уничтожения данного объекта и средства для ответных действий. Реально тот департамент вооруженных сил, который занимался этим проектом, испытал ряд кризисов в связи с тем, что ведущие специалисты в области программного обеспечения отказывались участвовать в реализации этого проекта из-за невозможности корректно его спроектировать, потому что система обладала гигантским потоком входных данных, на основе которых должны были быть приняты однозначные решения, ответственность за которые оценить весьма сложно. На самом деле эта проблема подтолкнула к развитию с одной стороны - языков программирования, которые обладали надежностью, в частности, язык Ада, одной из целью которого было создание безошибочного ПО. В таких языках накладывались ограничения на места, где наиболее вероятно возникновение ошибки (межмодульные интерфейсы; выражения, где присутствуют разные типы данных и т.п.) Заметим, что язык C не удовлетворяет требованиям безопасности. С другой стороны - к детальному проектированию, которое бы позволяло некоторым формальным образом описывать создаваемый проект и работать с проектом в части его детализации. Причем, переход от детализации к кодированию не имел бы четкой границы. Понятно, что это есть некоторая задача не сегодняшнего, а завтрашнего дня, но реально разработчики программ находятся на пути создания таких средств, которые позволили бы совместить проектирование и кодирование. Сегодняшние системы программирования, которые строятся на объектно-ориентированном подходе, частично решают эту проблему.
Следующая проблема проектирования. Мы продекларировали модули, объявили их взаимосвязи, как-то описали семантику модулей (это тоже проблема). Но никто не даст гарантии, что этот проект правилен. Для решения этой проблемы используется моделирование программных систем. То есть, когда вместе с построением проекта, который декларирует все интерфейсы, функциональность и прочее, мы можем каким-то образом промоделировать работу всей или частей создаваемой системы. Реально при создании больших программных систем на сегодняшний день нет единых инструментариев для таких действий. Каждые из существующих систем имеют разные подходы. Иногда эти подходы (как и у нас, так и за рубежом) достаточно архаичны.
Но тем не менее следует понимать, что период проектирования есть очень важный момент.
Кодирование
Если составлен нормальный проект, то с кодированием проблем нет. Но следует обратить внимание на то, что специалист в программировании это не тот, кто быстро пишет на С, а тот, кто хорошо и подробно сможет спроектировать задачу. При современном развитии инструментальных средств закодировать сможет любой школьник, а спроектировать систему - это и есть профессиональная задача людей, занимающихся программированием - выбрать инструментальные средства, составить проект, промоделировать решение.
Основной компонент системы кодирования - язык программирования. В голове каждого программиста лежит иерархия языков программирования - от машинного кода и ассемблера до универсальных языков программирования (FORTRAN, Algol, Pascal, C и т.д.), специализированных языков (SQL, HTML, Java и т.д.)
Мы имеем ЯП и программу, которая написана в нотации этого языка. Система программирования обеспечивает перевод исходной программы в объектный язык. Этот процесс перевода называется трансляцией. Объектный язык может быть как некоторым языком программирования высокого уровня (трансляция), так и машинный язык (компиляция). Мы можем говорить о трансляторах-компиляторах и трансляторах-интерпретаторах.
Компилятор - это транслятор, переводящий текст программы в машинный код.
Интерпретатор - это транслятор, который обычно совмещает процесс перевода и выполнения программы (компилятор сначала переводит программу, а только затем ее можно выполнить). Он, грубо говоря, выполняет каждую строчку, при этом машинный код не генерируется, а происходит обращение к некоторой стандартной библиотеке программ интерпретатора. Если результат работы компилятора - код программы на машинном языке, то результат работы транслятора - последовательность обращений к функциям интерпретации. При этом, также как и при компиляции, когда создается оттранслированная программа, у нас тоже может быть создана программа, но в этом интепретируемом коде (последовательности обращений к функциям интерпретации).
Понятна разница - компиляторы более эффективны, так как в интерпретаторах невозможна оптимизация и постоянные вызовы функций также не эффективны. Но интерпретаторы более удобны за счет того, что при интерпретации возможно включать в функции интерпретации множество сервисных средств: отладки, возможность интеграции интерпретатора и языкового редактора (компиляция это делать не позволяет).
На сегодняшний день каждый из методов - и компиляция и интерпретация занимают свои определенные ниши.
Лекция №20
Тип лексемы | № лексемы |
Тип лексемы - это код, который говорит о том, что данная лексема принадлежит одной из обозначенных нами групп. к примеру лексема может быть ключевым словом, тогда в поле типа будет стоять соответствующий номер. Номер лексемы уточняет конкретное значение этой лексемы. Если, к примеру, было ключевое слово begin, то номер лексемы будет содержать число, соответствующее ключевому слову begin. Если тип лексемы - идентификатор, то номер лексемы будет номером идентификатора в таблице имен которую создаст лексический анализатор. Если тип лексемы - константа, то номер лексемы тоже будет ссылкой на таблицу с константами.
После лексического анализатора мы получаем компактную программу, в которой нет уже ничего лишнего (пробелов, комментариев, и т.д.). Вся программа составлена в виде таких лексем, и поэтому она более компактна и проста.
Синтаксический анализ. Программа в виде лексем поступает на вход синтаксическому анализатору, который осуществляет проверку программы на предмет правильности с точки зрения синтаксических правил. Результатом работы синтаксического анализатора является либо информация о том, что в программе имеются синтаксические ошибки и указание координат этих ошибок и их диагностика, либо представление программы в некотором промежуточном виде. Этим промежуточным видом может быть, предположим, бесскобочная запись, либо запись в виде деревьев (хотя одно однозначно сводится к другому). Это промежуточное представление, которое является синтаксически и лексически правильной программой, поступает на вход семантическому анализатору.
Семантический анализ. Семантика - это все то, что не описывается синтаксисом и лексикой языка. К примеру, лексикой и синтаксисом языка сложно описать то, что нехорошо передавать управление в тело цикла не через начало цикла. Выявление таких ошибок - одна из функций семантического анализа. при этом семантический анализатор ставит в соответствие синтаксически и семантически правильным конструкциям объектный код, т.е. происходит генерация кода.
Система программирования и трансляции - очень наукоемкая область программного обеспечения. Организация трансляторов - это было первое применение теоретических достижений науки, которые заключались в следующем. За счет возможности использования тех или иных грамматик (наборов формальных правил построения лексических конструкций и синтаксических правил), можно разделить программную реализацию лексических и синтаксических анализаторов на два компонента. Первый компонент - это программа, которая в общем случае ничего не знает о том языке, который она будет анализировать. Второй компонент - это набор данных, представляющий из себя формальное описание свойств языка, который мы анализируем. Совмещение этих двух компонентов, позволяет автоматизировать процесс построения лексических и синтаксических анализаторов, а также генераторов кода, для различных языков программирования. Современные системы программирования в своем составе имеют средства автоматизации построения компиляторов. Для ОС UNIX есть пакет LEX - пакет генерации лексических анализаторов, и есть пакет YACC - для генерации синтаксических анализаторов. Это все достигается за счет возможности формализации свойств языка, и использования этого формального описания, как параметров для тех или иных инструментальных средств.
Проходы трансляторов. Мы с вами посмотрели на транслятор с точки зрения функциональных этапов. Но очень часто мы слышим об однопроходных трансляторах, двухпроходных, трехпроходных, и т.д. С проблемой трансляции связано понятие "проход". Проход - это полный просмотр некоторого представления исходного текста программы.
Есть трансляторы однопроходные. Это означает, что транслятор просматривает исходный текст от начала и до конца, и к концу просмотра (в случае правильности программы) он получает объектный модуль.
Если мы посмотрим Си-компилятор, с которым вы работаете, то скорее всего он двухпроходный. Первый проход - это работа препроцессора. После первого прохода появляется чистая Си-программа без всяких препроцессорных команд. На Втором проходе происходит лексический, синтаксический и семантический анализ, и в итоге вы получаете объектную программу в виде ассемблера.
Количество проходов в некоторых трансляторах связано с количеством этапов, т.е. бывают реализации, для которых удобно сделать отдельный проход для лексического анализа, отдельный проход для синтаксического анализа и отдельный проход для семантического анализа. Если транслятор многопроходный, то возникает проблема сохранения промежуточной нотации программы между проходами.
Make-файл. К этой же проблеме кодирования относится средство поддержки разработки программных проектов. Одним из популярных средств, ориентированных на работу одного или нескольких программистов, является т.н. make-средство. Название происходит от соответствующей команды ОС UNIX. C make-командой связан т.н. make-файл, в котором построчно указываются взаимосвязи всевозможных файлов, получаемых при трансляции, редактировании связей, и т.д., и те действия, которые надо выполнить, если эти взаимосвязи нарушаются. В частности можно сказать, что некоторый исполняемый файл зависит от группы объектных файлов, и если эта связь нарушена, то надо выполнить команду редактирования связей (link ...). Что значит нарушение зависимости и что значит связь? Make-команда проверяет существование этих объектных файлов. Если они существуют, то времена их создания должны быть более ранние, чем время создания исполняемого файла. В том случае, если это правило будет нарушено (а это проверяет make-команда), то будет запущен редактор связей (link), который заново создаст исполняемый файл. Тем самым такое средство позволяет нам работать с программой, состоящей из большого количества модулей, и не заботиться о том, соответствует ли в данный момент времени исполняемый файл набору объектных файлов или не соответствует (можно просто запустить make-файл).
Make-файлы могут содержать большое количество такого рода строчек, которые таким образом свяжут не только объектные и исполняемые файлы, но и каждый из исходных файлов с соответствующим объектным файлом, и т.д. Т.е. суть такова, что после работы не надо каждый раз для каждого файла запускать компилятор, редактор связей, а можно просто запустить make-файл, а он уже сам определит и выберет те файлы, которые нужно корректировать, и выполнит необходимые действия. На самом деле такими средствами сейчас обладают почти все системы программирования.
Система контроля версий. Если make-файл - это система, предназначенная для одного программиста, в лучшем случае, для нескольких программистов, то если у нас существует большой коллектив, который делает большой программный проект, то используется т.н. система контроля версий, которая позволяет организовывать корректную работу больших коллективов людей над одним и тем же проектом, которая основана на возможности декларации версий и осуществлении контроля за этими версиями.
Этапы тестирования и отладки
Тестирование - это поиск ситуации, в которой программный продукт не работает. При этом используются наборы тестов, определяющих внешнюю нагрузку на программный продукт. Можно сказать, что программа оттестирована на определенном наборе тестов. Утверждение, что программа оттестирована вообще в общем случае некорректно.
Отладка - это процесс поиска, локализации и исправления ошибки. Отладка осуществляется, когда мы имеем программную систему, и знаем, что она не работает на каком-то из тестов.
Проблемы тестирования и отладки - это есть проблемы крайней важности. По оценкам, на тестирование и отладку затрачивается порядка 30% времени разработки проекта. Сложность тестирования и отладки зависит от качества проектирования и кодирования. Тестирования зачастую выполнить сложно и часто для тестирования используются модельные нагрузки, например мы тестируем бортовую сеть самолета, что-то мы сможем сделать на земле, а что-то так или иначе делается уже на реальном полете, когда собираются данные и фиксируется работает система или не работает.
Лекция №21
Тема, которую мы с вами начнем рассматривать будет короткой и простой, и мы обозначим основные болевые точки. Детали и подробности вы должны изучить сами.
Командный язык ОС UNIX CSHELL (CSH)
Для многих пользователей программного обеспечения основным и единственным свойством, на которое обращает внимание пользователь, является не внутреннее устройство системы, а тот интерфейс, который предоставляется системой пользователю. Почти каждая система имеет средства интерактивного взаимодействия с пользователем, т.е. средства, которые позволяют в той или иной форме вводить запросы на выполнение действий. С этой точки зрения UNIX поддерживает возможность работы с произвольным количеством интерпретаторов команд. В файле /etc/passwd/ одно из полей, относящихся к данному пользователю, содержит полное имя интерпретатора команд, который должен быть запущен при входе пользователя в систему. В общем случае, при входе пользователя в систему может быть запущена абсолютно любая программа.
Традиционными интерпретаторами команд в системе UNIX являются SH, CSH и BASH. Давайте рассмотрим на концептуальном уровне что такое СSH (в принципе все интерпретаторы команд похожи друг на друга и являются некоторым расширением SH).
Интерпретатор команд определяет структуру вводимой команды. Команда (для CSH) - это последовательность символов, заканчивающаяся некоторым кодом, и которая состоит из слов. Слова - это последовательности символов, не содержащих разделители. Разделители - это набор фиксированных символов, в частности привычным для нас разделителем является пробел. Кроме пробела разделителями служат запятые, знаки < >, и т.д. Каждый из этих разделителей имеет свою интерпретацию. В частности, символ "|" означает создание конвейера. Например команда ls|more позволит избежать быстрый вывод текста на экран и за экран, и позволит пролистать его.
Система UNIX поддерживает набор специальных символов, которые называются метасимволами. Метасимволы обычно встречаются в словах команды и интерпретируются по заранее определенным правилам. Метасимволов существует много, в частности среди них есть знакомые нам * и ?. Команда rm * удалит все файлы не начинающиеся с точки в текущем каталоге. ? означает, что на месте этого символа может быть один любой знак. Метасимволы могут быть парными. Например внутри квадратных скобок указывается альтернативная группа, предположим, [abc] означает, что вместо этой квадратной скобки может быть один из перечисленных в ней символов (любую цифру можно задать так [0-9]).
CSH позволяет объединять команды. Для этого также используются метасимволы. Если внутри круглых скобок перечислены некоторые команды, то запустится еще один интерпретатор, который выполнит эту последовательность команд. Например команда (cd /etc; ls -la|grep pas) сменит каталог и осуществит поиск в этом каталоге строки pas.
В чем разнится между тем, выполнилась ли эта команда в интерпретаторе с которым мы работаем, или если был запущен еще один интерпретатор. Разница в том что, в этом случае не изменится текущий каталог, несмотря на то, что выполнилась команда смены каталога.
Имеется возможность объединять команды с использованием {}. Все команды, перечисленные в фигурных скобках будут запущены слева направо, но при этом на стандартный вывод будет положена объединенная последовательность стандартных выводов всех команд. {more t.b; more t.c}>tt.b - в файле tt.b окажется стандартный вывод одной команды, а затем стандартный вывод другой, эта команда без фигурных скобок поместила бы туда стандартный вывод только второй команды.
Интерпретатор команд имеет набор встроенных команд. Все команды подразделяются на два типа:
Команды, которые реализованы в виде отдельных файлов. Это те команды, которые можно модифицировать или добавлять новые.
Команды, которые встроены в интерпретатор команд, т.е. те команды, которые выполняет сам интерпретатор. К таким командам относится команда kill, по которой осуществляется передача соответствующего сигнала от имени интерпретатора. Есть также полезная команда alias, которая используется для переименования существующих команд.
Интерпретатор команд CSH позволяет осуществлять работу с предысторией. Он может организовать буферизацию N последних команд и организует доступ к списку последних команд. В частности, можно выполнять и редактировать командные строки из списка предыстории и снова их выполнять. CSH имеет возможность именовать строки из списка предыстории. Ссылка на соответствующую строку осуществляется с помощью команды, которая начинается с символа ! , за которым следует некоторая суффиксная часть . Ссылка !! выполняет последнюю команду. Ссылка вида !!N , где N - некоторое число, выполняет строку из с писка предыстории с номером N. Если N отрицательно, то номер строки отсчитывается с конца, к примеру, !!-1 означает выполнение последней команды. Кроме того, могут быть некоторые контекстные ссылки вида !<...>.
Переменные CSH
Интерпретатор команд предоставляет возможность программирования на уровне CSH. Для этого предусмотрена декларация переменных и возможность присвоения им значения, а также набор высокоуровневых операторов, которые по своей семантике похожи на операторы языка Си (отсюда и название интерпретатора CSH). CSH фактически есть высокоуровневый язык с операторами языка Си. Оперируя с переменными CSH можно составлять программы, которые выполняют некоторые действия.
Кроме всего прочего, имеются предопределенные имена, которые отвечают за настройку системы, в частности, о том, сколько строк предыстории сохраняются. Сохранение происходит в двух ипостасях: первое - это оперативное сохранение, есть параметр (переменная history), который определяет, сколько строк должно быть сохранено в течении последнего сеанса работы. Второе - есть возможность сохранения предыстории между сеансами, т.е. при очередном входе систему уже будет определен некоторый список предыстории (размер списка - в переменной savehistory).
Кроме переменных, с помощью которых осуществляется настройка, и имена которых определены, есть еще один класс переменных CSH - это т.н. внутренние переменные, которые также являются зарезервированными. Это переменные, которые имеют предопределенные имена и определяют свое значение через внутренние функции интерпретатора команд. В частности, есть переменная path, это есть текстовый массив, в котором находятся текстовые строки, содержащие полные имена некоторых каталогов. В соответствии с содержимым переменной path, CSH осуществляет поиск файлов, которые являются командами, введенными пользователями. Мы с вами говорили, что в UNIX (кроме встроенных команд) специальных команд нет, командой является любой исполняемый файл. Если пользователь ввел некоторое имя NAME, поиск исполняемого файла с именем NAME будет осуществляться, во-первых, в текущем каталоге, а во вторых, в каталогах, указанных в переменной path, в соответствующем порядке.
Переменная home - содержит имя домашнего каталога.
Переменная ignoreeof - это переменная, установка которой блокирует завершение сеанса работы по вводу символа ^D (Ctrl-D).
В системе можно варьировать вид приглашения (к вводу) с помощью переменной prompt, причем это приглашение может быть достаточно интеллектуальным. В нем, например, может присутствовать дата и т.п.
Мы с вами рассмотрели переменную path, она может определять много директорий. Представьте себе, что я ввожу некоторую командную строку NAME и в текущем каталоге соответствующего файла нет. Это означает, что будет осуществлен поиск файла с именем NAME по всем каталогам, записанным в переменной path (со всеми вытекающими последствиями). Это может быть долго. А если еще учесть, что вы работаете в многопользовательской системе, и каждый постоянно вводит какие-то команды, то накладные расходы получаются сумасшедшими. Система UNIX разумная система, и одним из правил, которых придерживались ее разработчики было то, что если где-то что-то можно оптимизировать, то надо это сделать. При входе пользователя в систему на основании значения переменной path формируется hash-таблица имен исполняемых файлов, находящихся во всех перечисленных каталогах (естественно, учитывается порядок каталогов). Поиск команды (если она не найдена в текущем каталоге) будет заключаться в просмотре этой быстрой hash-таблицы. За счет этого достигается ускорение доступа к команде, хотя при входе в систему возможна значительная задержка, которая вызвана формированием этой hash-таблицы.
Предположим, пользователь вошел в систему, а потом администратор добавил команду, которая этому пользователю необходима. Но за счет того, что hash-таблица уже сформировалась, доступ к ней (без набора полного имени) закрыт, хотя она есть в каталоге, указанном в path. В этом случае можно переформировать hash-таблицу с помощью команды rehash.
Кроме всех этих переменных, CSH имеет еще одну категорию переменных, которые называются переменными окружения. Если вы вспомните, то при запуске процесса функции main передаются несколько параметров, среди которых есть массив значений переменных окружения. В процесс можно передать те параметры, которые характеризуют сеанс работы пользователя в данный момент времени. В частности можно передать имя домашнего каталога, имя текущего каталога, имя терминала, с которого вызван данный процесс и т.д.
CSH имеет возможность работы с переменными окружения (можно их просматривать, устанавливать и т.п.).
С помощью средств CSH можно составлять программы. В этих программах могут фигурировать имена переменных CSH, которые мы можем интерпретировать как имена файлов. Средствами CSH можно определять ряд свойств, связанных с именем, которое мы интерпретируем как имя файла. В частности, есть возможности проверки, существует ли такой-то файл. Есть возможность проверки, является ли такой-то файл каталогом или не является. Есть возможность проверки всех прав доступа. Есть возможность определения размера файла. И, наконец, есть возможность запуска файла как команды. Кстати, с помощью этих средств реализовано множество команд системы. Программы на CSH можно вводить построчно, в этом случае CSH играет роль транслятора-интерпретатора, а можно программу на CSH записать в некоторый файл и исполнять его как командный файл (Но все равно будет происходить процесс интерпретации).
Специальные файлы
Любой командный язык имеет набор т.н. профайлов, или стартовых файлов. CSH имеет две разновидности этих файлов: это файлы, которые могут выполняться при старте CSH, и файлы, которые выполняются при завершении работы.
При старте CSH работает с двумя файлами - .cshrc и .login. Файл .cshrc - это командный файл, в котором пользователь по своему усмотрению может размещать произвольное количество команд на CSH, которые будут выполняться сразу после запуска CSH (например, в этом файле может находиться команда тестирования файловой системы и т.д.). Файл .login запускается при входе пользователя в систему. В этом файле также может находиться любая последовательность команд, например там может быть переопределение имен команд, наиболее приемлемое для данного пользователя (потому что мнемоника команд в UNIX достаточно тяжелая, да и параметров у каждой команды много).
При завершении работы с CSH запускается файл с именем .logout в котором также может находиться некоторый набор команд.
Есть стандартный файл, который может образоваться в процессе работы - это файл .history. Если у вас определена возможность сохранения истории, то как раз в этом файле буферизуется предыстория вашей работы.
Теперь подведем итог, и я напомню в чем вы должны разобраться сами:
CSH - как язык программирования. Типы переменных CSH. Программирование на CSH.
Соглашения, которые определяет CSH при работе со строками. Разбиение командной строки на слова. Интерпретация метасимволов. Возможность ссылки на командные строки предыстории.
Встроенные команды CSH.
Специальные переменные CSH: внутренние переменные и переменные окружения.
Специальные командные файлы CSH.
Лекция №22
Многомашинные ассоциации
Терминальные комплексы
Первые многомашинные ассоциации появились в начале 60-х годов и это было связано с двумя проблемами. Первая проблема - проблема обеспечения массового доступа к вычислительным ресурсам некоторой вычислительной системы. Второе - появление задач, требовавших возможности привлечения для их решения более чем одной вычислительной системы. С точки зрения интеллектуализации многомашинных ассоциаций, первым типом многомашинных комплексов был терминальный комплекс. Терминальный комплекс можно определить как набор программных и аппаратных средств, предназначенных для взаимодействия пользователей с вычислительной установкой, через телефонную или телеграфную сеть (или через любую другую среду, через которую можно обеспечить связь).
Структуру терминального комплекса можно изобразить следующим образом:
Есть вычислительная система, имеется канал взаимодействия с внешним миром, к которому подключено устройство, называемое мультиплексор. Это устройство, которое обеспечивает взаимодействие группы внешних устройств с вычислительной системой (ВС) через один канал ввода/вывода. К каналам мультиплексора могут быть подключены локальные терминалы.
Кроме того может быть подключено устройство, называемое модемом. Модем позволяет выйти в телефонную или телеграфную сеть, работающую в аналоговом режиме, и передавать информацию (модему, в другой части сети). К модему подключается удаленный терминал. Модем преобразует дискретный сигнал, поступающий от ВС (или терминала), в аналоговый сигнал, который уже можно передавать по сети; и обратно - аналоговый в дискретный.
Телефонная сеть состоит из какого-то набора телефонных станций, и она предоставляет т.н. коммутируемый канал. Суть его заключается в том, что при нескольких звонках к одному и тому же абоненту, раз от раза маршруты коммутации (т.е. набор проводов, по которым идет сообщение) отличаются друг от друга, за счет того, что каждый раз выбираются свободные каналы.
Может быть также многоуровневое мультиплексирование. Вместо удаленного терминала, подключается удаленный мультиплексор, и начиная от него может быть продолжение этого же уровня рисунков. Это означает, что мы, работая за терминалом второго (или третьего, четвертого, и т.д.) уровня существенно загружаем коммутируемую линию.
Линия связи, которая связывает один удаленный терминал с компьютером, называется линией связи типа точка-точка. Эта линия может быть либо арендуемой (мы договариваемся с телефонными станциями и фиксируем коммутацию), либо коммутируемой.
Канал может быть многоточечным. При этом на входе находится удаленный мультиплексор. Многоточечные каналы также могут быть либо арендуемыми, либо коммутируемыми.
Типы каналов связи:
Симплексные каналы - каналы, по которым передача информации ведется в одном направлении.
Дуплексные каналы - каналы, которые обеспечивают одновременную передачу информации в двух направлениях.
Полудуплексные каналы - каналы, которые обеспечивают передачу информации в двух направлениях, но в каждый момент времени только в одну сторону (подобно рации).
ВС имеет комплекс программ, которые обеспечивают взаимодействие пользователей через всю коммуникационную среду, с ВС. В качестве локальных и удаленных терминалов могут присутствовать как реальные терминальные устройства, так и компьютеры, которые эмулируют работу терминала.
Многомашинные вычислительные комплексы
Многомашинные вычислительные комплексы (ММВК) - это программно аппаратное объединение группы вычислительных машин, в которых:
На каждой из машин работает своя операционная система (этот признак отличает ММВК от многопроцессорного вычислительного комплекса).
В ММВК имеются общие физические ресурсы (а следовательно имеются проблемы синхронизации доступа).
ММВК использовались в качестве систем сбора и обработки больших наборов данных, и для организации глобальных терминальных комплексов. ММВК появились в начале 60-х и сейчас продолжают успешно существовать. Одно из основных применений ММВК - это дублирование вычислительной мощи, примером таких систем может служить любая система управления важными технологическими процессами.
Вычислительные сети
И терминальные комплексы, и ММВК можно считать частным случаем вычислительных сетей, однако хронология развития многомашинных ассоциаций была именно такой - сначала появились терминальные комплексы, потом ММВК, потом вычислительные сети.
Предположим у нас есть некоторая группа вычислительных машин, которые мы будем называть абонентскими машинами (АМ). Имеется некоторое образование, которое называется коммутационной средой. Коммутационная среда включает каналы передачи данных, обеспечивающие взаимодействие между машинами, специальные вычислительные машины, которые мы будем называть коммутационными машинами. Абонентские машины могут осуществлять взаимодействие друг с другом через коммутационную среду, в рамках которой используются каналы передачи данных и коммутационные машины.
Существует ряд классических разновидностей сетей.
Сеть коммутации каналов. Суть ее заключается в том, что если надо связать АМ2 с АМ3, то происходит соединение каналов и коммутационных машин между этими АМ. Это соединение будет существовать до конца взаимодействия АМ2 и АМ3. Достоинство этой сети в том, что скорость взаимодействия между машинами равна скорости самого медленного компонента сети, участвующего в связи (это максимально возможная скорость). Недостаток в том, что такая связь может блокировать другие соединения (в данном случае АМ1 и АМ4 не свяжутся до конца связи между АМ2 и АМ3). Уйти от этой проблемы можно потребовав от коммутационной среды большой избыточности, т.е. организовать дополнительные (дублирующие) каналы.
Сеть коммутации сообщений. Если коммутация каналов - это коммутация на время всего сеанса связи, то коммутация сообщений - это связь, при которой весь сеанс разделяется на передачу сообщений (сообщение - некоторая, логически завершенная, порция данных), и коммутация происходит на период передачи сообщения. В такой сети на коммуникационные машины ложатся большие нагрузки, они должны обладать возможностью буферизации сообщений в связи с неравномерной скоростью передачи на разных участках сети. Достоинства - простота логическая и физическая, недостатки - снижение скорости работы в сети, и потери, связанные с буферизацией.
Сеть коммутации пакетов. Сеанс разбивается на сообщения, сообщения разбиваются на порции данных одинакового объема - пакеты. По сети перемещаются не сообщения, а пакеты. Здесь действует принцип горячей картошки: основное действие коммутационной машины - как можно быстрее избавиться от пакета, определив кому его дальше можно перекинуть.. Т.к. все пакеты одинакового объема, не возникает проблем с буферизацией, потому что мы всегда можем рассчитать необходимую буферную способность коммутационных машин. Логически происходит достаточно быстрое соединение, потому что сеть коммутации пакетов практически не имеет ситуаций, когда какие-то каналы заблокированы. За счет того, что происходит дробление сеанса на пакеты, имеется возможность оптимизации обработки ошибок при передаче данных. Если мы получаем ошибку в режиме коммутации каналов, то надо повторять весь сеанс, если в режиме коммутации сообщений, то надо повторять сообщение, здесь же достаточно повторить передачу пакета, в котором обнаружена ошибка.
>
|