ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ ГОСУДАРСТВЕННОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ УФИМСКИЙ ГОСУДАРСТВЕННЫЙ АВИАЦИОННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ Кафедра ВТ и ЗИ РЕФЕРАТ По предмету: «Особенности СПО ВВС» Выполнил: магистрант 2 года ФИРТ ВВС - 609 Евграфов М.Ю. Проверил: профессор Гараев Р.А. УФА 2010 1 Содержание Методы и средства программирования ВВС.....................................................................4 1.1 Синхронизация процессов .............................................................................................4 1.1.1 Средства синхронизации и взаимодействия процессов ...................................4 1.1.2 Проблема синхронизации ...................................................................................4 1.1.3 Семафоры .............................................................................................................5 1.1.4 Почтовые ящики ..................................................................................................6 1.1.5 Монитор Хоара ....................................................................................................6 1.2 Программирование процессов .......................................................................................7 1.3 Состояние процессов ......................................................................................................7 1.3.1 1.4 Алгоритмы планирования процессов ...........................................................................8 1.4.1 1.5 2 Контекст и дескриптор процесса........................................................................8 Вытесняющие и невытесняющие алгоритмы планирования ........................10 Методы структурного программирования .................................................................12 1.5.1 Краткое описание подхода ................................................................................12 1.5.2 Основная структура данных – сеть данных ....................................................13 1.5.3 Потоковая обработка .........................................................................................14 1.5.4 Алгоритмы золотой горы ..................................................................................15 1.5.5 Алгоритм Евклида .............................................................................................16 1.5.6 Метод нисходящего программирования .........................................................18 1.5.7 Методология SADT ...........................................................................................18 Операционные системы ВВС ............................................................................................20 2.1 ОС UNIX ........................................................................................................................20 2.1.1 История ...............................................................................................................21 2.1.2 Предшественники ..............................................................................................21 2.1.3 Первые UNIX .....................................................................................................22 2.1.4 Раскол ..................................................................................................................22 2.1.5 Свободные UNIX-подобные операционные системы ....................................23 2.1.6 Современность ...................................................................................................24 2.1.7 Влияние UNIX на эволюцию операционных систем .....................................25 2.1.8 Некоторые архитектурные особенности ОС UNIX ........................................26 2.1.9 Стандарты ...........................................................................................................27 2.1.10 Стандартные команды ОС UNIX .....................................................................28 2.2 ОС QNX .........................................................................................................................29 2.2.1 Что такое QNX ...................................................................................................29 2.2.2 Архитектура ядра системы QNX ......................................................................29 2.2.3 Ядро системы QNX ............................................................................................30 2.2.4 Системные процессы .........................................................................................30 2.2.5 Системные процессы и процессы пользователя .............................................30 3 2.2.6 Драйверы устройств ..........................................................................................31 2.2.7 Связь между процессами ..................................................................................31 2.2.8 Операционная система с передачей сообщений .............................................31 Языки программирования ВВС ........................................................................................32 3.1 Язык ОККАМ ................................................................................................................32 3.1.1 Процессы ............................................................................................................32 3.1.2 Процессы, не выполняющие действий ............................................................32 3.1.3 Последовательные процессы ............................................................................32 3.1.4 Параллельные процессы....................................................................................32 3.1.5 Процессы времени .............................................................................................33 3.1.6 Описание данных ...............................................................................................33 3.1.7 Циклы ..................................................................................................................33 3.1.8 Приоритеты ........................................................................................................33 3.2 Структура программирования .....................................................................................34 3.3 Язык Forth ......................................................................................................................34 3.3.1 История ...............................................................................................................34 3.3.2 Основные понятия классической Форт-системы............................................35 3.3.3 Типы кода Форта................................................................................................36 3.3.4 Примеры программ ............................................................................................37 3.3.5 Место Forth среди других языков программирования ...................................38 1 Методы и средства программирования ВВС 1.1 Синхронизация процессов Важнейшей частью операционной системы, непосредственно влияющей на функционирование вычислительной машины, является подсистема управления процессами. Процесс (или по-другому, задача) - абстракция, описывающая выполняющуюся программу. Для операционной системы процесс представляет собой единицу работы, заявку на потребление системных ресурсов. Подсистема управления процессами планирует выполнение процессов, то есть распределяет процессорное время между несколькими одновременно существующими в системе процессами, а также занимается созданием и уничтожением процессов, обеспечивает процессы необходимыми системными ресурсами, поддерживает взаимодействие между процессами. 1.1.1 Средства синхронизации и взаимодействия процессов Возможно выделить следующие средства синхронизации: а) Семафоры б) Почтовые ящики в) Монитор Хоара Для того чтобы осуществлять совместный доступ к данным двух процессов одновременно используются критические секции. 1.1.2 Проблема синхронизации Процессы, выполняемые в мультипрограммном режиме, можно рассматривать как набор последовательных слабосвязанных процессов, которые действуют почти независимо друг от друга, лишь изредка используя общие ресурсы. Взаимосвязь между такими процессами устанавливается с помощью различных сообщений и так называемого механизма синхронизации, который позволяет согласовывать и координировать работу процессов. Хотя каждый процесс, выполняемый в мультипрограммном режиме, имеет доступ к общим ресурсам, существует некоторая область, которую в фиксированный момент времени может использовать лишь один процесс. Нарушение этого условия приведет к неизвестному порядку обработки процессов. Назовем такую область критической. При использовании критической области возникают различные проблемы, среди которых можно выделить проблемы состязания (гонок) и тупиков (клинчей). Условие состязания возникает, когда процессы настолько связаны между собой, что порядок их выполнения влияет на результат операции. Условие тупиков появляется, если два взаимосвязанных процесса блокируют друг друга при обращении к критической области. рис 1. - Пример тупиковой ситуации В системах, допускающих перераспределение любых ресурсов в произвольной последовательности, но имеющей и неосвобожденные ресурсы, время от времени должны возникать тупиковые ситуации. Пример. Пусть программе А нужен ресурс R1. Она запрашивает его и получает. Программе В нужен ресурс R2. Она запрашивает его и получает(см. рис 1). Далее, пусть программа А, не отпуская R1, запрашивает R2, а программа В, не отпуская R2, запрашивает R1. Налицо типичный клинч, если только один из ресурсов R1 или R2 не может быть освобожден до момента завершения обработки соответствующего процесса. 1.1.3 Семафоры В 1968 г. Э. Дейкстра предложил удобную форму механизма захвата/освобождения ресурсов, которую он назвал операциями P и V над считающими семафорами. Считающим семафором называют целочисленную переменную, выполняющую те же функции, что и байт блокировки. Однако в отличие от последнего она может принимать кроме "0" и "1" и другие целые положительные значения. Семафоры – это часть абстракции, поддерживающая очередь постоянно ожидающих процессов. Операции P и V над семафором S могут быть определены следующим образом. P(S): 1. Уменьшить значение S на 1, т. е. S : = S – 1. 2. Если S < 0, выполнить ОЖИДАНИЕ (S). V(S): 1. Увеличить значение S на 1, т. е. S : = S + 1. 2. Если S 0, выполнить ОПОВЕЩЕНИЕ (S). Операция ОЖИДАНИЕ (S) (WAIT) блокирует обслуживание процесса, делает соответствующую отметку об этом и связывает процесс со значением переменной S. Операция ОПОВЕЩЕНИЕ (S) (SIGNAL) просматривает связанный с переменной S список блокированных процессов. Если в списке есть процессы, ожидающие освобождения некоторого ресурса, управляемого (сигнализируемого) S, один из них переводится в состояние готовности, а в соответствующей ему записи делается отметка. С этого момента процесс становится опять доступным планировщику. По определению, ожидание, связанное с операцией P(S), не является ожиданием "зависания", так как ожидающие процессы не используют центральный процессор. Так как несколько процессов могут ожидать операцию P(S) над отдельным семафором, во время приращения должен быть осуществлен выбор, какую контрольную точку процесса сделать доступной. Алгоритм выбора не определен, за исключением требования равнодоступности процессов, т. е. никакой процесс не может быть "забыт". Типичным примером использования алгоритма семафоров является задача производитель/потребитель. Задается максимальная емкость хранилища. Производитель не должен переполнять его, а потребитель не должен пытаться брать продукцию из пустого хранилища. Наиболее употребительные операции над семафорами: создать семафор; запросить состояние семафора; изменить состояние семафора; уничтожить семафор. Операции над семафорами в силу своей неделимости позволяют блокировать или активизировать процессы при освобождении или запросах ресурсов любого типа (памяти, процессоров, устройств ввода-вывода и т. п.). Наиболее показательно аппарат семафоров можно применить на следующей задаче. Три курильщика сидят за столом. У одного есть табак, у другого – бумага, у третьего – спички. На столе могут появиться извне два из трех упомянутых предмета. Появившиеся предметы забирает тот курильщик, у которого будет полный набор предметов. Он сворачивает папиросу, раскуривает ее и курит. Новые два предмета могут появиться на столе только после того, как он кончит курить. Другие курильщики в это время не могут начать курение. Задание. Описать с помощью P и V – операций над семафорами – систему процессов, которая моделирует взаимодействия этих курильщиков. Указание. Выделить шесть процессов, три из которых соответствуют трем курильщикам X, Y, Z, а три других имеют следующее назначение: А – поставляет спички и бумагу, В – табак и спички, С – бумагу и табак. Требование. Процессы-поставщики не знают, какие предметы находятся у курильщиков. Проблема взаимоисключения включает в себя нахождение протокола, который может использоваться отдельными процессами в момент входа в критический раздел или выхода из него. Рассмотренные приемы синхронизации просты и сравнительно эффективны, однако их использование приводит порой к сложным и труднопостижимым конструкциям, которые чувствительны к незначительным изменениям. 1.1.4 Почтовые ящики Почтовый ящик – это информационная структура с заданием правил, описывающих его работу. Она состоит из главного элемента, где располагается описание почтового ящика, и нескольких гнезд заданных размеров для размещения сообщений. Если процесс p1 желает послать процессу p2 некоторое сообщение, он записывает его в одно из гнезд почтового ящика, откуда p2 в требуемый момент времени может его извлечь. Иногда оказывается необходимым процессу p1 получить подтверждение, что p2 принял переданное сообщение. Тогда образуются почтовые ящики с двусторонней связью. Известны и другие модификации почтовых ящиков, в частности порты, многовходовые почтовые ящики и т. д. Для установления связей между процессами широко используются почтовые ящики. Примером такого применения может служить операционная система IPMX86 ВС фирмы Intel для вычислительных комплексов на основе микропроцессоров IAPX86 или IAPX88. Для целей синхронизации разрабатываются специальные примитивы создания и уничтожения почтовых ящиков, отправки и запроса сообщений. Можно выделить три типа имеющихся в системе запросов: с ожиданием (ограниченным и неограниченным) или без ожидания. В случае ограниченного ожидания процесс, издавший запрос, если почтовый ящик пуст, ожидает некоторое время поступления сообщения. Если по истечении заданного кванта времени сообщение в почтовый ящик не поступило, процесс активизируется и выдает уведомление, что запрос не обслужен. В случае запроса без ожидания, если почтовый ящик пуст, указанные действия выполняются немедленно. Если время ожидания неограничено, процесс не активизируется до поступления сообщения в почтовый ящик. Очевидно, что почтовые ящики позволяют осуществлять как обмен информацией между процессами, так и их синхронизацию. 1.1.5 Монитор Хоара Монитор – это набор процедур и информационных структур, которыми процессы пользуются в режиме разделения, причем в фиксированный момент времени им может пользоваться только один процесс. Отличительная особенность монитора в том, что в его распоряжении находится некоторая специальная информация, предназначенная для общего пользования, но доступ к ней можно получить только при обращении к этому монитору. Монитор не является процессом, это пассивный объект, который приходит в активное состояние только тогда, когда какой-то объект обращается к нему за услугами. Часто монитор сравнивают с запертой комнатой, от которой имеется только один ключ. Услугами этой комнаты может воспользоваться только тот, у кого есть ключ. При этом процессу запрещается оставаться там сколь угодно долго. Другой процесс должен ждать до тех пор, пока первый не выйдет из нее и передаст ключ. В качестве примера программы-монитора может выступать планировщик ресурсов. Действительно, каждому процессу когда-нибудь понадобятся ресурсы и он будет обращаться к планировщику. Но планировщик одновременно может обслуживать только один ресурс. Иногда монитор задерживает обратившийся к нему процесс. Это происходит, например, в случае обращения за занятым ресурсом. Монитор блокирует процесс с помощью команды "ЖДАТЬ", а в случае освобождения ресурса выдает команду "СИГНАЛ". При этом освободившийся ресурс предоставляется одному из ожидавших его процессов вместе с разрешением на продолжение работы. Управление передается команде монитора, непосредственно следующей за операцией "ЖДАТЬ". Мониторы более гибки, чем семафоры. В форме мониторов сравнительно легко можно реализовать различные синхронизирующие примитивы, в частности семафоры и почтовые ящики. Кроме того, мониторы позволяют нескольким процессам совместно использовать программу, представляющую собой критический участок. 1.2 Программирование процессов 1.2.1 Состояние процессов В многозадачной (многопроцессной) системе процесс может находиться в одном из трех основных состояний: ВЫПОЛНЕНИЕ - активное состояние процесса, во время которого процесс обладает всеми необходимыми ресурсами и непосредственно выполняется процессором; ОЖИДАНИЕ - пассивное состояние процесса, процесс заблокирован, он не может выполняться по своим внутренним причинам, он ждет осуществления некоторого события, например, завершения операции ввода-вывода, получения сообщения от другого процесса, освобождения какого-либо необходимого ему ресурса; ГОТОВНОСТЬ - также пассивное состояние процесса, но в этом случае процесс заблокирован в связи с внешними по отношению к нему обстоятельствами: процесс имеет все требуемые для него ресурсы, он готов выполняться, однако процессор занят выполнением другого процесса. В ходе жизненного цикла каждый процесс переходит из одного состояния в другое в соответствии с алгоритмом планирования процессов, реализуемым в данной операционной системе. Типичный граф состояний процесса показан на рисунке 2. В состоянии ВЫПОЛНЕНИЕ в однопроцессорной системе может находиться только один процесс, а в каждом из состояний ОЖИДАНИЕ и ГОТОВНОСТЬ - несколько процессов, эти процессы образуют очереди соответственно ожидающих и готовых процессов. Жизненный цикл процесса начинается с состояния ГОТОВНОСТЬ, когда процесс готов к выполнению и ждет своей очереди. При активизации процесс переходит в состояние ВЫПОЛНЕНИЕ и находится в нем до тех пор, пока либо он сам освободит процессор, перейдя в состояние ОЖИДАНИЯ какого-нибудь события, либо будет насильно "вытеснен" из процессора, например, вследствие исчерпания отведенного данному процессу кванта процессорного времени. В последнем случае процесс возвращается в состояние ГОТОВНОСТЬ. В это же состояние процесс переходит из состояния ОЖИДАНИЕ, после того, как ожидаемое событие произойдет. рис 2. -Граф состояний процесса в многозадачной среде 1.2.2 Контекст и дескриптор процесса На протяжении существования процесса его выполнение может быть многократно прервано и продолжено. Для того, чтобы возобновить выполнение процесса, необходимо восстановить состояние его операционной среды. Состояние операционной среды отображается состоянием регистров и программного счетчика, режимом работы процессора, указателями на открытые файлы, информацией о незавершенных операциях ввода-вывода, кодами ошибок выполняемых данным процессом системных вызовов и т.д. Эта информация называется контекстом процесса. Кроме этого, операционной системе для реализации планирования процессов требуется дополнительная информация: идентификатор процесса, состояние процесса, данные о степени привилегированности процесса, место нахождения кодового сегмента и другая информация. В некоторых ОС (например, в ОС UNIX) информацию такого рода, используемую ОС для планирования процессов, называют дескриптором процесса. Дескриптор процесса по сравнению с контекстом содержит более оперативную информацию, которая должна быть легко доступна подсистеме планирования процессов. Контекст процесса содержит менее актуальную информацию и используется операционной системой только после того, как принято решение о возобновлении прерванного процесса. Очереди процессов представляют собой дескрипторы отдельных процессов, объединенные в списки. Таким образом, каждый дескриптор, кроме всего прочего, содержит по крайней мере один указатель на другой дескриптор, соседствующий с ним в очереди. Такая организация очередей позволяет легко их переупорядочивать, включать и исключать процессы, переводить процессы из одного состояния в другое. Программный код только тогда начнет выполняться, когда для него операционной системой будет создан процесс. Создать процесс - это значит: 1. создать информационные структуры, описывающие данный процесс, то есть его дескриптор и контекст; 2. включить дескриптор нового процесса в очередь готовых процессов; 3. загрузить кодовый сегмент процесса в оперативную память или в область свопинга. 1.3 Алгоритмы планирования процессов Планирование процессов включает в себя решение следующих задач: 1. определение момента времени для смены выполняемого процесса; 2. выбор процесса на выполнение из очереди готовых процессов; 3. переключение контекстов "старого" и "нового" процессов. Первые две задачи решаются программными средствами, а последняя в значительной степени аппаратно (см. раздел 2.3. "Средства аппаратной поддержки управления памятью и многозадачной среды в микропроцессорах Intel 80386, 80486 и Pentium"). Существует множество различных алгоритмов планирования процессов, по разному решающих вышеперечисленные задачи, преследующих различные цели и обеспечивающих различное качество мультипрограммирования. Среди этого множества алгоритмов рассмотрим подробнее две группы наиболее часто встречающихся алгоритмов: алгоритмы, основанные на квантовании, и алгоритмы, основанные на приоритетах. В соответствии с алгоритмами, основанными на квантовании, смена активного процесса происходит, если: процесс завершился и покинул систему, произошла ошибка, процесс перешел в состояние ОЖИДАНИЕ, исчерпан квант процессорного времени, отведенный данному процессу. Процесс, который исчерпал свой квант, переводится в состояние ГОТОВНОСТЬ и ожидает, когда ему будет предоставлен новый квант процессорного времени, а на выполнение в соответствии с определенным правилом выбирается новый процесс из очереди готовых. Таким образом, ни один процесс не занимает процессор надолго, поэтому квантование широко используется в системах разделения времени. Граф состояний процесса, изображенный на рисунке 2.1, соответствует алгоритму планирования, основанному на квантовании. Кванты, выделяемые процессам, могут быть одинаковыми для всех процессов или различными. Кванты, выделяемые одному процессу, могут быть фиксированной величины или изменяться в разные периоды жизни процесса. Процессы, которые не полностью использовали выделенный им квант (например, из-за ухода на выполнение операций ввода-вывода), могут получить или не получить компенсацию в виде привилегий при последующем обслуживании. По разному может быть организована очередь готовых процессов: циклически, по правилу "первый пришел - первый обслужился" (FIFO) или по правилу "последний пришел - первый обслужился" (LIFO). Другая группа алгоритмов использует понятие "приоритет" процесса. Приоритет - это число, характеризующее степень привилегированности процесса при использовании ресурсов вычислительной машины, в частности, процессорного времени: чем выше приоритет, тем выше привилегии. Приоритет может выражаться целыми или дробными, положительным или отрицательным значением.Чем выше привилегии процесса, тем меньше времени он будет проводить в очередях. Приоритет может назначаться директивно администратором системы в зависимости от важности работы или внесенной платы, либо вычисляться самой ОС по определенным правилам, он может оставаться фиксированным на протяжении всей жизни процесса либо изменяться во времени в соответствии с некоторым законом. В последнем случае приоритеты называются динамическими. Существует две разновидности приоритетных алгоритмов: алгоритмы, использующие относительные приоритеты, и алгоритмы, использующие абсолютные приоритеты. В обоих случаях выбор процесса на выполнение из очереди готовых осуществляется одинаково: выбирается процесс, имеющий наивысший приоритет. По разному решается проблема определения момента смены активного процесса. В системах с относительными приоритетами активный процесс выполняется до тех пор, пока он сам не покинет процессор, перейдя в состояние ОЖИДАНИЕ (или же произойдет ошибка, или процесс завершится). В системах с абсолютными приоритетами выполнение активного процесса прерывается еще при одном условии: если в очереди готовых процессов появился процесс, приоритет которого выше приоритета активного процесса. В этом случае прерванный процесс переходит в состояние готовности. На рисунке 3 показаны графы состояний процесса для алгоритмов с относительными (а) и абсолютными (б) приоритетами. рис 3. -Графы состояний процессов в системах (а) с относительными приоритетами; (б)с абсолютными приоритетами Во многих операционных системах алгоритмы планирования построены с использованием как квантования, так и приоритетов. Например, в основе планирования лежит квантование, но величина кванта и/или порядок выбора процесса из очереди готовых определяется приоритетами процессов. 1.3.1 Вытесняющие и невытесняющие алгоритмы планирования Существует два основных типа процедур планирования процессов - вытесняющие (preemptive) и невытесняющие (non-preemptive). Non-preemptive multitasking - невытесняющая многозадачность - это способ планирования процессов, при котором активный процесс выполняется до тех пор, пока он сам, по собственной инициативе, не отдаст управление планировщику операционной системы для того, чтобы тот выбрал из очереди другой, готовый к выполнению процесс. Preemptive multitasking - вытесняющая многозадачность - это такой способ, при котором решение о переключении процессора с выполнения одного процесса на выполнение другого процесса принимается планировщиком операционной системы, а не самой активной задачей. Понятия preemptive и non-preemptive иногда отождествляются с понятиями приоритетных и бесприоритетных дисциплин, что совершенно неверно, а также с понятиями абсолютных и относительных приоритетов, что неверно отчасти. Вытесняющая и невытесняющая многозадачность - это более широкие понятия, чем типы приоритетности. Приоритеты задач могут как использоваться, так и не использоваться и при вытесняющих, и при невытесняющих способах планирования. Так в случае использования приоритетов дисциплина относительных приоритетов может быть отнесена к классу систем с невытесняющей многозадачностью, а дисциплина абсолютных приоритетов - к классу систем с вытесняющей многозадачностью. А бесприоритетная дисциплина планирования, основанная на выделении равных квантов времени для всех задач, относится к вытесняющим алгоритмам. Основным различием между preemptive и non-preemptive вариантами многозадачности является степень централизации механизма планирования задач. При вытесняющей многозадачности механизм планирования задач целиком сосредоточен в операционной системе, и программист пишет свое приложение, не заботясь о том, что оно будет выполняться параллельно с другими задачами. При этом операционная система выполняет следующие функции: определяет момент снятия с выполнения активной задачи, запоминает ее контекст, выбирает из очереди готовых задач следующую и запускает ее на выполнение, загружая ее контекст. При невытесняющей многозадачности механизм планирования распределен между системой и прикладными программами. Прикладная программа, получив управление от операционной системы, сама определяет момент завершения своей очередной итерации и передает управление ОС с помощью какого-либо системного вызова, а ОС формирует очереди задач и выбирает в соответствии с некоторым алгоритмом (например, с учетом приоритетов) следующую задачу на выполнение. Такой механизм создает проблемы как для пользователей, так и для разработчиков. Для пользователей это означает, что управление системой теряется на произвольный период времени, который определяется приложением (а не пользователем). Если приложение тратит слишком много времени на выполнение какой-либо работы, например, на форматирование диска, пользователь не может переключиться с этой задачи на другую задачу, например, на текстовый редактор, в то время как форматирование продолжалось бы в фоновом режиме. Эта ситуация нежелательна, так как пользователи обычно не хотят долго ждать, когда машина завершит свою задачу. Поэтому разработчики приложений для non-preemptive операционной среды, возлагая на себя функции планировщика, должны создавать приложения так, чтобы они выполняли свои задачи небольшими частями. Например, программа форматирования может отформатировать одну дорожку дискеты и вернуть управление системе. После выполнения других задач система возвратит управление программе форматирования, чтобы та отформатировала следующую дорожку. Подобный метод разделения времени между задачами работает, но он существенно затрудняет разработку программ и предъявляет повышенные требования к квалификации программиста. Программист должен обеспечить "дружественное" отношение своей программы к другим выполняемым одновременно с ней программам, достаточно часто отдавая им управление. Крайним проявлением "недружественности" приложения является его зависание, которое приводит к общему краху системы. В системах с вытесняющей многозадачностью такие ситуации, как правило, исключены, так как центральный планирующий механизм снимет зависшую задачу с выполнения. Однако распределение функций планировщика между системой и приложениями не всегда является недостатком, а при определенных условиях может быть и преимуществом, потому что дает возможность разработчику приложений самому проектировать алгоритм планирования, наиболее подходящий для данного фиксированного набора задач. Так как разработчик сам определяет в программе момент времени отдачи управления, то при этом исключаются нерациональные прерывания программ в "неудобные" для них моменты времени. Кроме того, легко разрешаются проблемы совместного использования данных: задача во время каждой итерации использует их монопольно и уверена, что на протяжении этого периода никто другой не изменит эти данные. Существенным преимуществом non-preemptive систем является более высокая скорость переключения с задачи на задачу. Примером эффективного использования невытесняющей многозадачности является файл-сервер NetWare, в котором, в значительной степени благодаря этому, достигнута высокая скорость выполнения файловых операций. Менее удачным оказалось использование невытесняющей многозадачности в операционной среде Windows 3.х. Однако почти во всех современных операционных системах, ориентированных на высокопроизводительное выполнение приложений (UNIX, Windows NT, OS/2, VAX/VMS), реализована вытесняющая многозадачность. В последнее время дошла очередь и до ОС класса настольных систем, например, OS/2 Warp и Windows 95. Возможно в связи с этим вытесняющую многозадачность часто называют истинной многозадачностью. 1.4 Методы структурного программирования 1.4.1 Краткое описание подхода Структу́рное программи́рование — методология разработки программного обеспечения, в основе которой лежит представление программы в виде иерархической структуры блоков. Предложена в 70-х годах XX века Э. Дейкстрой, разработана и дополнена Н. Виртом. В соответствии с данной методологией 1. Любая программа представляет собой структуру, построенную из трёх типов базовых конструкций: o последовательное исполнение — однократное выполнение операций в том порядке, в котором они записаны в тексте программы; o ветвление — однократное выполнение одной из двух или более операций, в зависимости от выполнения некоторого заданного условия; o цикл — многократное исполнение одной и той же операции до тех пор, пока выполняется некоторое заданное условие (условие продолжения цикла). В программе базовые конструкции могут быть вложены друг в друга произвольным образом, но никаких других средств управления последовательностью выполнения операций не предусматривается. 2. Повторяющиеся фрагменты программы (либо не повторяющиеся, но представляющие собой логически целостные вычислительные блоки) могут оформляться в виде т. н. подпрограмм (процедур или функций). В этом случае в тексте основной программы, вместо помещённого в подпрограмму фрагмента, вставляется инструкция вызова подпрограммы. При выполнении такой инструкции выполняется вызванная подпрограмма, после чего исполнение программы продолжается с инструкции, следующей за командой вызова подпрограммы. 3. Разработка программы ведётся пошагово, методом «сверху вниз». Сначала пишется текст основной программы, в котором, вместо каждого связного логического фрагмента текста, вставляется вызов подпрограммы, которая будет выполнять этот фрагмент. Вместо настоящих, работающих подпрограмм, в программу вставляются «заглушки», которые ничего не делают. Полученная программа проверяется и отлаживается. После того, как программист убедится, что подпрограммы вызываются в правильной последовательности (то есть общая структура программы верна), подпрограммы-заглушки последовательно заменяются на реально работающие, причём разработка каждой подпрограммы ведётся тем же методом, что и основной программы. Разработка заканчивается тогда, когда не останется ни одной «затычки», которая не была бы удалена. Такая последовательность гарантирует, что на каждом этапе разработки программист одновременно имеет дело с обозримым и понятным ему множеством фрагментов, и может быть уверен, что общая структура всех более высоких уровней программы верна. При сопровождении и внесении изменений в программу выясняется, в какие именно процедуры нужно внести изменения, и они вносятся, не затрагивая части программы, непосредственно не связанные с ними. Это позволяет гарантировать, что при внесении изменений и исправлении ошибок не выйдет из строя какая-то часть программы, находящаяся в данный момент вне зоны внимания программиста. 1.4.2 Основная структура данных – сеть данных Рассмотрим основную структуру данных, которая появляется при структурном программировании. Учет этой структуры позволяет преобразовать благие пожелания о согласованности информационных потоков и хода передач управления в достаточно строгую методику. Сеть данных может быть формально описана как ациклический ориентированный граф, в котором все ко-пути (т.е. пути, взятые наоборот) конечны и вершинам которого сопоставлены значения. Рассмотрим пример. Известному стандартному приему программирования в языках без кратных присваиваний - обмену двух значений через промежуточное. z: = second; second: = first; first: = z; соответствует следующая сеть данных: рис 4. -Обмен значений Здесь first, second, z можно считать комментариями, а сами данные опущены, поскольку их конкретные значения не важны. На этом примере видно, что порой для лучшего структурирования сети целесообразно вводить дополнительные вершины, соответствующие сохраняющимся значениям. Ребро, ведущее из одной такой вершины в другую, обозначается при помощи стрелочки, похожей на равенство. Видно так же, как материя воздействует на идею, заставляя вводить дополнительные операторы и дополнительные значения. В данном случае переменная z и включающие ее операторы являются подпорками, и, если их исключить, сеть данных становится проще. Но в общераспространенных языках программирования нет кратных присваиваний типа first,second: =second,first; Даже если бы они были, представьте себе, как неудобно станет читать длинное кратное присваивание и понимать, какое же выражение какой переменной присваивается! В случае программы вычисления факториала1) сеть потенциально бесконечна вниз, поскольку аргументом может быть любое число, но по структуре еще проще: рис 5. – вычисление факториала Перекрестных зависимостей между параметрами нет, следовательно, возможны две известные реализации факториала: циклическая и рекурсивная. Покажем их на разных языках, ибо все равно, на каком традиционном языке их писать. function fact (n: integer): integer; var j,res: integer; begin res: =1; for j: =1 to n do res: =res*j; result: =res; end; int fact (int n) {if (n==0) return (1); else return (n*fact (n-1)); } 1.4.3 Потоковая обработка Схема построения циклической программы называется потоковой обработкой. Значения на следующей итерации цикла зависят от значений на предыдущей. Для чисел Фибоначчи (та же схема (2)) структура уже несколько сложнее предыдущих, поскольку каждое следующее число Фибоначчи зависит от двух предыдущих, но метод потоковой обработки применим и здесь. int fib (int n) {int fib1,fib2; fib1=1; fib2=1; if (n>2) { for (int i=2; i<n; i++) { int j; j=fib1+fib2; fib1=fib2; fib2=j; } }; return (fib2); } Итак, в потоке изменяется структура из двух элементов. Ее можно было бы прямо описать как структуру данных, и это следовало бы сделать, будь программа хоть чуть-чуть посложнее. Тогда вместо подпорки j пришлось бы ввести в качестве подпорки новое значение структуры. В программе имеется еще одна подпорка - параметр цикла i, который нужен лишь для формальной организации цикла. Рекурсивная реализация чисел Фибоначчи пишется еще проще и служит великолепным примером того, как презренная материя убивает красивую, но неглубокую идею. int fib (int n) { if (n<3) return (1); else return (fib (n-1) +fib (n-2)); } Если n достаточно велико, каждое из предыдущих значений функции Фибоначчи будет вычисляться много раз, причем без всякого толку: результат всегда будет один и тот же! Зато все подпорки убраны... 1.4.4 Алгоритм золотой горы В следующем примере неэффективность рекурсии по сравнению с хорошо организованным циклом еще более очевидная. Пусть надо найти путь, на котором можно собрать максимальное количество золота, через сеть значений, подобную показанной на рис 4. рис 6. -Золотая гора При циклической организации вычислений нам придется посчитать значение в каждой точке горы всего один раз (найти добычу на оптимальном пути в эту точку). Здесь используется то свойство оптимальных путей, которое делает возможным так называемые методы волны или динамического программирования: каждый начальный отрезок локально оптимального пути локально оптимален. Это свойство является призраком, стоящим за эффективной циклической реализацией алгоритма, а многочисленные пути, соответственно, призрачными значениями, которые не нужно вычислять. В рекурсивной реализации мы не учитываем данного призрака, и он беспощадно мстит за вопиющее незнание теории. 1.4.5 Алгоритм Евклида Теперь рассмотрим случай, когда рекурсивная реализация намного изящнее циклической, легче обобщается и не хуже по эффективности2) рис 7. -Алгоритм Евклида В данном случае путь для получения результата не разветвляется, и нам остается лишь двигаться по нему в правильном направлении (от цели к исходным данным) и достаточно большими шагами. function Euklides (n,m: integer) integer; { предполагаем m<=n} begin if n=m then resut: =n else result: =Euklides (n mod m, m); end; Если пытаться вычислить наибольший общий делитель методом движения от данных к цели, то нам придется построить громадный массив значений НОД, лишь ничтожная часть значений в котором будет нужна для построения результата. Затраты на вычисление каждого отдельного элемента в данном случае малы, а при обратном направлении движения повторный счет не возникает. Алгоритм Евклида в простейшем случае моделирует ту ситуацию, которая появляется в задачах обработки рекурсивных структур, например списков. То, что в отдельных случаях возникает повторный счет, - небольшое зло, когда эти случаи редки и нерегулярны. Повторный счет возникает и в циклических программах, поскольку найти в большом массиве совпадающие элементы порою труднее, чем заново посчитать нужные нам значения. Именно поэтому рекурсия оказалась столь эффективным методом работы со списками и позволила построить адекватный первопорядковый фундамент для современного функционального программирования. Рассмотрим два крайних случая движения по сети. Когда сеть представлена в виде структуры данных, естественно возникает метод ленивого движения по сети, когда после вычисления значений в очередной точке выбирается одна из точек, для которой все предыдущие значения уже вычислены, и вычисляются значения в данной точке. Как видно, в частности на примере золотой горы, этот метод недетерминирован и в значительной степени может быть распараллелен. Но в конкретном алгоритме нам придется выбрать конкретный способ ленивого движения, и он может быть крайне неудачен: например, он будет провоцировать длительное движение в тупик, когда у нас есть короткий путь к цели. Даже в теоретических исследованиях приходится накладывать условия на метод ленивого движения, чтобы гарантировать достижение результата Другой крайний случай движения по сети, когда сеть делится на одинаковые слои. Например, в сети (4). рис 8. -Полностью заменяемый массив Можно представить слой статическим массивом и вроде бы полностью забыть о самой сети. Забытый призрак мстит за себя, в частности, при необходимости распараллеливания вычислений, и предыдущий случай отнюдь не эквивалентен следующему. рис 9. -Постепенно заменяемый массив Конечно же большая сеть данных становится необозримой. Справиться с нею можно, лишь разбив сеть на подсети. Таким образом, блоки программы соответствуют относительно автономным подсетям. Еще Э. Дейкстра в книге [11] предложил в каждом блоке описывать импортированные и экспортируемые им глобальные значения. Но такая "писанина" раздражала хакеров и в итоге так и не вошла в общепризнанные системы программирования. Сейчас индустриальные технологии требуют таких описаний, но из-за отсутствия поддержки на уровне синтаксического анализа все это остается благими пожеланиями, так что, если хотите, чтобы Ваша программа была понятна хотя бы Вам, описывайте все перекрестные информационные связи! Резюмируя вышеизложенное, можно сделать следующие выводы. Сеть данных сама по себе в программу не переходит, в программу переходят лишь некоторые свойства сети в качестве призраков и некоторые куски сети в качестве реальных значений. Программа определяется не только сетью данных, но и конкретной дисциплиной движения по этой сети. В случае, если очередные слои сети, появляющиеся при движении согласно заданной дисциплине, примерно одинаковы и состоят из многих взаимосвязанных значений, у нас возникает циклическая программа. В случае, если вычисление можно свести к вычислениям для независимых элементов сети, чаще всего удобней рекурсия. Самый общий способ движения по сети - ленивое движение, когда мы имеем право вычислить следующий объект сети, если вычислены все его предшественники. Структурное программирование нейтрально по отношению к тому, каким именно способом будет исполняться полученная программа: последовательно, детерминировано, недетерминировано, совместно, параллельно либо даже на распределенной системе, поскольку сеть лишь частично предписывает порядок действий. 1.4.6 Метод нисходящего программирования Нисходящее программирование - конкретизация нисходящего проектирования для нужд программирования. Оно обладает следующими преимуществами. Нисходящее программирование породило хорошо согласованное с ним нисходящее построение структур данных, а нисходящее построение структур данных является простейшим частным случаем глубокой идеальной концепции - абстракции данных. Согласно парадоксу изобретателя, даже простейшие частные случаи высокоуровневых концепций при удачном применении улучшают все характеристики умственных конструкций (в частности, программ). При таком проектировании на каждом уровне можно ограничиваться одной моделью вычислений, в частности операционной, на которую ориентировано структурное программирование. Возможно раннее программирование, когда прототип программы, работающий хотя бы в условиях грубой эмуляции будущих решений низкого уровня, позволяет продемонстрировать, как будет работать полная программа, и улучшить ее пользовательские характеристики. Ни один конкретный способ анализа либо синтеза не является универсальным. Нисходящее программирование, конечно же, тоже таковым не является. Причины тому, в частности, следующие2) . Невозможность при нисходящем структурном программировании увидеть тождественность процедур, работающих на разных ветвях декомпозиции, а тем более унифицировать несколько формально различных процедур на разных ветвях в одну общую. Недостаточный учет особенностей, которые могут возникнуть после конкретной реализации запланированных блоков программы, что довольно часто вызывает необходимость полной перепланировки системы после реализации ее блоков, а вслед за такой перепланировкой реализованные для других спецификаций блоки часто оказываются отнюдь не лучшими из возможных. Недостаточный перенос опыта и наработок из одного проекта в другой. В нынешних методиках нисходящего проектирования ко всему перечисленному добавляется еще и навязывание последовательного стиля мышления, что мешает воспользоваться преимуществами нетрадиционных машин. Это указывает на необходимость средств, позволяющих поднимать уровень понятий. Поэтому в разработке структурных программ применяется также подход, получивший название восходящего программирования. К примеру, когда строят библиотеку, занимаются обобщением задачи. Части, выделяемые в виде библиотечных средств, выбираются таким образом, чтобы они были применимы в различных контекстах. 1.4.7 Методология SADT Методология SADT разработана Дугласом Россом. На ее основе разработана, в частности, известная методология IDEF0 (Icam DEFinition), которая является основной частью программы ICAM (Интеграция компьютерных и промышленных технологий), проводимой по инициативе ВВС США. Методология SADT представляет собой совокупность методов, правил и процедур, предназначенных для построения функциональной модели объекта какой-либо предметной области. Функциональная модель SADT отображает функциональную структуру объекта, т.е. производимые им действия и связи между этими действиями. Основные элементы этой методологии основываются на следующих концепциях: графическое представление блочного моделирования. Графика блоков и дуг SADTдиаграммы отображает функцию в виде блока, а интерфейсы входа/выхода представляются дугами, соответственно входящими в блок и выходящими из него. Взаимодействие блоков друг с другом описываются посредством интерфейсных дуг, выражающих "ограничения", которые в свою очередь определяют, когда и каким образом функции выполняются и управляются; строгость и точность. Выполнение правил SADT требует достаточной строгости и точности, не накладывая в то же время чрезмерных ограничений на действия аналитика. Правила SADT включают: ограничение количества блоков на каждом уровне декомпозиции (правило 3-6 блоков); связность диаграмм (номера блоков); уникальность меток и наименований (отсутствие повторяющихся имен); синтаксические правила для графики (блоков и дуг); разделение входов и управлений (правило определения роли данных). отделение организации от функции, т.е. исключение влияния организационной структуры на функциональную модель. Методология SADT может использоваться для моделирования широкого круга систем и определения требований и функций, а затем для разработки системы, которая удовлетворяет этим требованиям и реализует эти функции. Для уже существующих систем SADT может быть использована для анализа функций, выполняемых системой, а также для указания механизмов, посредством которых они осуществляются Состав функциональной модели Результатом применения методологии SADT является модель, которая состоит из диаграмм, фрагментов текстов и глоссария, имеющих ссылки друг на друга. Диаграммы - главные компоненты модели, все функции ИС и интерфейсы на них представлены как блоки и дуги. Место соединения дуги с блоком определяет тип интерфейса. Управляющая информация входит в блок сверху, в то время как информация, которая подвергается обработке, показана с левой стороны блока, а результаты выхода показаны с правой стороны. Механизм (человек или автоматизированная система), который осуществляет операцию, представляется дугой, входящей в блок снизу (см. рисунок). Одной из наиболее важных особенностей методологии SADT является постепенное введение все больших уровней детализации по мере создания диаграмм, отображающих модель. рис 10. - Концепт методологии SADT На рисунке, где приведены четыре диаграммы и их взаимосвязи, показана структура SADT-модели. Каждый компонент модели может быть декомпозирован на другой диаграмме. Каждая диаграмма иллюстрирует "внутреннее строение" блока на родительской диаграмме. Иерархия диаграмм. Построение SADT-модели начинается с представления всей системы в виде простейшей компоненты - одного блока и дуг, изображающих интерфейсы с функциями вне системы. Поскольку единственный блок представляет всю систему как единое целое, имя, указанное в блоке, является общим. Это верно и для интерфейсных дуг - они также представляют полный набор внешних интерфейсов системы в целом. Затем блок, который представляет систему в качестве единого модуля, детализируется на другой диаграмме с помощью нескольких блоков, соединенных интерфейсными дугами. Эти блоки представляют основные подфункции исходной функции. Данная декомпозиция выявляет полный набор подфункций, каждая из которых представлена как блок, границы которого определены интерфейсными дугами. Каждая из этих подфункций может быть декомпозирована подобным образом для более детального представления. Во всех случаях каждая подфункция может содержать только те элементы, которые входят в исходную функцию. Кроме того, модель не может опустить какие-либо элементы, т.е., как уже отмечалось, родительский блок и его интерфейсы обеспечивают контекст. К нему нельзя ничего добавить, и из него не может быть ничего удалено. Модель SADT представляет собой серию диаграмм с сопроводительной документацией, разбивающих сложный объект на составные части, которые представлены в виде блоков. Детали каждого из основных блоков показаны в виде блоков на других диаграммах. Каждая детальная диаграмма является декомпозицией блока из более общей диаграммы. На каждом шаге декомпозиции более общая диаграмма называется родительской для более детальной диаграммы. Дуги, входящие в блок и выходящие из него на диаграмме верхнего уровня, являются точно теми же самыми, что и дуги, входящие в диаграмму нижнего уровня и выходящие из нее, потому что блок и диаграмма представляют одну и ту же часть системы. Операционные системы ВВС 2 2.1 ОС UNIX UNIX (читается ю́никс) — группа переносимых, многозадачных и многопользовательских операционных систем. Первая система UNIX была разработана в 1969 г. в подразделении Bell Labs компании AT&T. С тех пор было создано большое количество различных UNIX-систем. Юридически лишь некоторые из них имеют полное право называться «UNIX»; остальные же, хотя и используют сходные концепции и технологии, объединяются термином «UNIXподобные» (англ. Unix-like). Для краткости в данной статье под UNIX-системами подразумеваются как истинные UNIX, так и UNIX-подобные ОС. Некоторые отличительные признаки UNIX-систем включают в себя: использование простых текстовых файлов для настройки и управления системой; широкое применение утилит, запускаемых в командной строке; взаимодействие с пользователем посредством виртуального устройства — терминала; представление физических и виртуальных устройств и некоторых средств межпроцессового взаимодействия как файлов; использование конвейеров из нескольких программ, каждая из которых выполняет одну задачу. В настоящее время UNIX используются в основном на серверах, а также как встроенные системы для различного оборудования. На рынке ОС для рабочих станций и домашнего применения лидером является Microsoft Windows, UNIX занимает только второе (Mac OS X) и третье (GNU/Linux) места. UNIX-системы имеют большую историческую важность, поскольку благодаря им распространились некоторые популярные сегодня концепции и подходы в области ОС и программного обеспечения. Также, в ходе разработки Unix-систем был создан язык Си. рис 11. -Генеалогическое дерево систем UNIX 2.1.1 История 2.1.2 Предшественники В 1957 году в Bell Labs была начата работа по созданию операционной системы для собственных нужд. Под руководством Виктора Высотского (русского по происхождению) была создана система BESYS. Впоследствии он возглавил проект Multics, а затем стал главой информационного подразделения Bell Labs. В 1964 году появились компьютеры третьего поколения, для которых возможности BESYS уже не подходили. Высотский и его коллеги приняли решение не разрабатывать новую собственную операционную систему, а подключиться к совместному проекту General Electric и Массачусетского технологического института Multics. Телекоммуникационный гигант AT&T, в состав которого входили Bell Labs, оказал проекту существенную поддержку, но в 1969 году вышел из проекта, поскольку он не приносил никаких финансовых выгод. 2.1.3 Первые UNIX рис 12. – Создатели UNIX - Кен Томпсон и Денис Ритчи Первоначально UNIX была разработана в конце 1960-х годов сотрудниками Bell Labs, в первую очередь Кеном Томпсоном, Денисом Ритчи и Дугласом МакИлроем. В 1969 году Кен Томпсон, стремясь реализовать идеи, которые были положены в основу MULTICS, но на более скромном аппаратном обеспечении (DEC PDP-7), написал первую версию новой операционной системы, а Брайан Керниган придумал для неё название — UNICS (UNIplexed Information and Computing System) — в противовес MULTICS (MULTIplexed Information and Computing Service). Позже это название сократилось до UNIX. В ноябре 1971 года вышла версия для PDP-11, наиболее успешного семейства миникомпьютеров 1970-х (в СССР его аналоги, выпускавшиеся Министерством Электронной Промышленности были известно как СМ ЭВМ и «Электроника», позже ДВК, производились в Киеве, Воронеже, Зеленограде). Эта версия получила название «первая редакция» (Edition 1) и была первой официальной версией. Системное время все реализации UNIX отсчитывают с 1 января 1970. Первые версии UNIX были написаны на ассемблере и не имели встроенного компилятора с языком высокого уровня. Примерно в 1969 году Кен Томпсон при содействии Дениса Ритчи разработал и реализовал язык Би (B), представлявший собой упрощённый (для реализации на миникомпьютерах) вариант разработанного в 1966 языка BCPL. Би, как и BCPL, был интерпретируемым языком. В 1972 году была выпущена вторая редакция UNIX, переписанная на языке Би. В 1969—1973 годах на основе Би был разработан компилируемый язык, получивший название Си (C). В 1973 году вышла третья редакция UNIX, со встроенным компилятором языка Си. 15 октября того же года появилась четвёртая редакция, с переписанным на Си системным ядром (в духе системы Multics, также написанной на языке высокого уровня (ПЛ/1)), а в 1975 — пятая редакция, полностью переписанная на Си. С 1974 года UNIX стал бесплатно распространяться среди университетов и академических учреждений. С 1975 года началось появление новых версий, разработанных за пределами Bell Labs, и рост популярности системы. В том же 1975 году Bell Labs выпустила шестую редакцию, известную по широко разошедшимся комментариям Джона Лайонса[1]. К 1978 г. система была установлена более чем на 600 машинах, прежде всего, в университетах. Седьмая редакция была последней единой версией UNIX. Именно в ней появился близкий к современному интерпретатор командной строки Bourne shell. 2.1.4 Раскол С 1978 года начинает свою историю BSD UNIX, созданный в университете Беркли. Его первая версия была основана на шестой редакции. В 1979 выпущена новая версия, названная 3BSD, основанная на седьмой редакции. BSD поддерживал такие полезные свойства, как виртуальную память и замещение страниц по требованию. Автором BSD был Билл Джой. В начале 1980-х компания AT&T, которой принадлежали Bell Labs, осознала ценность UNIX и начала создание коммерческой версии UNIX. Эта версия, поступившая в продажу в 1982 году, носила название UNIX System III и была основана на седьмой версии системы. Важной причиной раскола UNIX стала реализация в 1980 году стека протоколов TCP/IP. До этого межмашинное взаимодействие в UNIX пребывало в зачаточном состоянии — наиболее существенным способом связи был UUCP (средство копирования файлов из одной UNIX-системы в другую, изначально работавшее по телефонным сетям с помощью модемов). Было предложено два интерфейса программирования сетевых приложений: Berkley sockets и интерфейс транспортного уровня TLI (англ. Transport Layer Interface). Интерфейс Berkley sockets был разработан в университете Беркли и использовал стек протоколов TCP/IP, разработанный там же. TLI был создан AT&T в соответствии с определением транспортного уровня модели OSI и впервые появился в системе System V версии 3. Хотя эта версия содержала TLI и потоки, первоначально в ней не было реализации TCP/IP или других сетевых протоколов, но подобные реализации предоставлялись сторонними фирмами. Реализация TCP/IP официально и окончательно была включена в базовую поставку System V версии 4. Это, как и другие соображения (по большей части, рыночные), вызвало окончательное размежевание между двумя ветвями UNIX — BSD (университета Беркли) и System V (коммерческая версия от AT&T). Впоследствии, многие компании, лицензировав System V у AT&T, разработали собственные коммерческие разновидности UNIX, такие, как AIX, CLIX, HP-UX, IRIX, Solaris. В середине 1983 года была выпущена версия 4.2BSD, поддерживающая работу в сетях Ethernet и Arpanet. Система стала весьма популярной. Между 1983 и 1990 в BSD было добавлено много новых возможностей, таких как отладчик ядра, сетевая файловая система NFS, виртуальная файловая система VFS, и существенно улучшены возможности работы с файловыми сетями. Тем временем AT&T выпускала новые версии своей системы, названной System V. В 1983 была выпущена версия 1 (SVR1 — System V Release 1), включавшая полноэкранный текстовый редактор vi, библиотеку curses, буферизацию ввода-вывода, кеширование inode. Версия 2 (SVR2), выпущенная в 1984, реализовывала монопольный доступ к файлам (file locking), доступ к страницам по требованию (demand paging), копирование при записи (copy-on-write). Версия 3 вышла в 1987 году и включала, среди прочего, TLI, а также систему поддержки удалённых файловых систем RFS. Версия 4 (SVR4), разработанная в сотрудничестве с фирмой Sun и вышедшая 18 октября 1988, поддерживала многие возможности BSD, в частности TCP/IP, сокеты, новый командный интерпретатор csh. Кроме того, там было много других добавлений, таких как символические ссылки, командный интерпретатор ksh, сетевая файловая система NFS (заимствованная у SunOS) и т. д. Современные реализации UNIX, как правило, не являются системами V или BSD в чистом виде. Они реализуют возможности как System V, так и BSD. 2.1.5 Свободные UNIX-подобные операционные системы В 1983 году Ричард Столлмэн объявил о создании проекта GNU — попытки создания свободной UNIX-подобной операционной системы с нуля, без использования оригинального исходного кода. Большая часть программного обеспечения, разработанного в рамках данного проекта, — такого, как GNU toolchain, Glibc (стандартная библиотека языка Си) и Coreutils — играет ключевую роль в других свободных операционных системах. Однако работы по созданию замены для ядра UNIX, необходимые для полного выполнения задач GNU, продвигались крайне медленно. В настоящее время GNU Hurd — попытка создать современное ядро на основе микроядерной архитектуры Mach — всё ещё далека от завершения. В 1991 году, когда Линус Торвальдс опубликовал ядро Linux и привлёк помощников, использование инструментов, разработанных в рамках проекта GNU, было очевидным выбором. Операционная система GNU и ядро Linux вместе составляют ОС, известную, как GNU/Linux. Дистрибутивы этой системы (такие как Red Hat и Debian), включающие ядро, утилиты GNU и дополнительное программное обеспечение стали популярными как среди любителей, так и среди представителей бизнеса. В начале 1992 года вышел дистрибутив 386/BSD, основанный на дистрибутиве Networking Release 2, распространяемый компанией BSDI за $995 с «ужасающей скидкой» в 99 % по сравнению с ценой System V. UNIX Systems Laboratories подала иск против BSDI, а затем против университета Беркли, допустившей распространение файлов UNIX в исходных и двоичных форматах фактически за бесценок, что подрывало бизнес самой USL. Весь 1992 год никаких значительных успехов в судебной тяжбе для USL не принёс, зато появился встречный иск от Калифорнийского университета. К началу 1993 года дистрибутив 386/BSD поменял своё название на NetBSD. В декабре 1993 года появился другой дистрибутив — FreeBSD, нацеленный на простых пользователей. После приобретения USL компанией Novell к лету 1993 года начались переговоры по урегулированию статуса кодов BSD. К январю 1994 CSRG и Novell договорились удалить три файла из 18000 Networking Release 2, часть файлов должна быть подвергнута правке, а к примерно 70 файлам университет должен был добавить информацию о копирайте USL. В июне 1994 года вышел «чистый» выпуск 4.4BSD-Lite. Вот с этого момента группы BSDI, NetBSD и FreeBSD должны были повторно синхронизировать свои версии систем с «чистой» системой 4.4BSD-Lite. Таким образом, все наработки, сделанные за три года с момента подачи иска USL, пришлось пересматривать на предмет нарушения авторских прав и использования стороннего кода. Переписывать важные составляющие ядра и операционного окружения. Значительно позднее выделились в самостоятельные проекты OpenBSD, TrustedBSD и DragonFlyBSD. 14 июня 2005 был открыт исходный код операционной системы Solaris. Этот проект, как и созданная на его основе операционная система, получили название OpenSolaris. 17 июня, через три дня после открытия кода, был создан дистрибутив SchilliX. В мае 2008 появился первый официальный дистрибутив OpenSolaris 2008.05. Существует более десяти дистрибутивов на основе OpenSolaris, наиболее известные из которых BeleniX и Nexenta OS. В настоящий момент GNU/Linux и представители семейства BSD быстро отвоёвывают рынок у коммерческих UNIX-систем и одновременно проникают как на настольные компьютеры конечных пользователей, так и на мобильные и встраиваемые системы. Одним из свидетельств данного успеха служит тот факт, что, когда фирма Apple искала основу для своей новой операционной системы, она выбрала NEXTSTEP — операционную систему со свободно распространяемым ядром, разработанную фирмой NeXT и переименованную в Darwin после приобретения фирмой Apple. Данная система относится к семейству BSD и основана на ядре Mach. Применение Darwin BSD UNIX в Mac OS X делает его одной из наиболее широко используемых версий UNIX. 2.1.6 Современность После разделения компании AT&T, товарный знак UNIX и права на оригинальный исходный код неоднократно меняли владельцев, в частности, длительное время принадлежали компании Novell. В 1993 году Novell передала права на товарный знак и на сертификацию программного обеспечения на соответствие этому знаку консорциуму X/Open, который затем объединился с Open Software Foundation, образовав консорциум The Open Group. Он объединяет ведущие компьютерные корпорации и государственные организации, в том числе IBM, Hewlett-Packard, Sun, NASA и многие другие. Консорциум занимается разработкой открытых стандартов в области операционных систем, самым важным из которых является Single UNIX Specification, ранее известный как POSIX. С точки зрения The Open Group, название UNIX могут носить только системы, прошедшие сертификацию на соответствие Single UNIX Specification. В 1995 году Novell продала права на существующие лицензии и дальнейшую разработку System V компании Santa Cruz Operation. В 2000 году Santa Cruz Operation продала свой UNIX-бизнес компании Caldera, которая затем была переименована в SCO Group. Хотя это название похоже на аббревиатуру SCO, используемую Santa Cruz Operation, это две разные компании. SCO Group заявила, что она также обладает правами на исходный код UNIX и развернула кампанию против различных пользователей и поставщиков UNIX-подобных систем, требуя выплаты лицензионных отчислений. Однако Novell утверждает, что права на исходный код не были переданы Santa Cruz Operation и, таким образом, не перешли к SCO Group, а остаются у Novell, что и подтвердил вердикт суда. Несмотря на это в августе 2009 года апелляционный суд США отменил вынесенное ранее решение, назначив дополнительное расследование с целью установления законного владельца авторских прав на исходные тексты операционной системы[2]. 2.1.7 Влияние UNIX на эволюцию операционных систем Идеи, заложенные в основу UNIX, оказали огромное влияние на развитие компьютерных операционных систем. В настоящее время UNIX-системы признаны одними из самых исторически важных ОС. Как и Multics, UNIX была написана на языке высокого уровня, а не на ассемблере (доминировавшем в то время). Она содержала значительно упрощённую, по сравнению с современными ей операционными системами, файловую модель. Файловая система включала как службы, так и устройства (такие как принтеры, терминалы и жёсткие диски) и предоставляла внешне единообразный интерфейс к ним, но дополнительные механизмы работы с устройствами (такие как IOCTL и биты доступа) не вписывались в простую модель «поток байтов». UNIX популяризовала предложенную в Multics идею иерархической файловой системы с произвольной глубиной вложенности. Другие операционные системы того времени позволяли разбивать дисковое пространство на каталоги или разделы, но число уровней вложенности было фиксировано и, зачастую, уровень вложенности был только один. Позднее все основные фирменные операционные системы обрели возможность создания рекурсивных подкаталогов, также заимствованную из Multics. То, что интерпретатор команд стал просто одной из пользовательских программ, а в качестве дополнительных команд выступают отдельные программы, является ещё одной инновацией Multics, популяризированной UNIX. Язык командной оболочки UNIX используется пользователем как для интерактивной работы, так и для написания скриптов, то есть не существует отдельного языка описания заданий, как, например, в системе JCL фирмы IBM. Так как оболочка и команды операционной системы являются обычными программами, пользователь может выбирать их в соответствии со своими предпочтениями, или даже написать собственную оболочку. Наконец, новые команды можно добавлять к системе без перекомпиляции ядра. Новый, предложенный в командной строке UNIX, способ создания цепочек программ, последовательно обрабатывающих данные, способствовал использованию параллельной обработки данных. Существенными особенностями UNIX были полная ориентация на текстовый вводвывод и предположение, что размер машинного слова кратен восьми битам. Первоначально в UNIX не было даже редакторов двоичных файлов — система полностью конфигурировалась с помощью текстовых команд. Наибольшей и наименьшей единицей ввода-вывода служил текстовый байт, что полностью отличало ввод-вывод UNIX от ввода-вывода других операционных систем, ориентированного на работу с записями. Ориентация на использование текста для представления всего, что только можно, сделала полезными т. н. конвейеры (англ. pipelines). Ориентация на текстовый восьмибитный байт сделала UNIX более масштабируемой и переносимой, чем другие операционные системы. Со временем текстовые приложения одержали победу и в других областях, например, на уровне сетевых протоколов, таких как Telnet, FTP, SMTP, HTTP и других. UNIX способствовала широкому распространению регулярных выражений, которые были впервые реализованы в текстовом редакторе ed для UNIX. Возможности, предоставляемые UNIX-программам, стали основой стандартных интерфейсов операционных систем (POSIX). Широко используемый в системном программировании язык Си, созданный изначально для разработки UNIX, превзошёл UNIX по популярности. Си был первым «веротерпимым» языком, который не пытался навязать программисту тот или иной стиль программирования. Си был первым высокоуровневым языком, предоставляющим доступ ко всем возможностям процессора, таким как ссылки, таблицы, битовые сдвиги, приращения и т. п. С другой стороны, свобода Си приводила к ошибкам переполнения буфера в таких функциях стандартной библиотеки Си, как gets и scanf. Результатом стали многие печально известные уязвимости, например, та, что эксплуатировалась в знаменитом черве Морриса. Первые разработчики UNIX способствовали внедрению принципов модульного программирования и повторного использования в инженерную практику. UNIX предоставлял возможность использования протоколов TCP/IP на сравнительно недорогих компьютерах, что привело к быстрому росту Интернета. Это, в свою очередь, способствовало быстрому обнаружению нескольких крупных уязвимостей в системе безопасности, архитектуре и системных утилитах UNIX. Со временем ведущие разработчики UNIX разработали культурные нормы разработки программного обеспечения, которые стали столь же важны, как и сам UNIX (см. Философия UNIX). 2.1.8 Некоторые архитектурные особенности ОС UNIX Особенности UNIX, отличающие данное семейство от других ОС: Файловая система древовидная, чувствительная к регистру символов в именах, очень слабые ограничения на длину имён. Нет поддержки структурированных файлов ядром ОС, на уровне системных вызовов файл есть поток байт. Командная строка находится в адресном пространстве запускаемого процесса, а не извлекается системным вызовом из процесса интерпретатора команд (как это происходит, например, в RSX-11). Понятие «переменных окружения». Запуск процессов вызовом fork(), то есть возможность клонирования текущего процесса со всем состоянием. Понятия stdin/stdout/stderr. Ввод/вывод только через дескрипторы файлов. Традиционно крайне слабая поддержка асинхронного ввода/вывода, по сравнению с VMS и Windows NT. Интерпретатор команд есть обыкновенное приложение, общающееся с ядром обыкновенными системными вызовами (в RSX-11 и VMS интерпретатор команд выполнялся как специальное приложение, специальным образом размещенное в памяти, пользующееся специальными системными вызовами, поддерживались также системные вызовы, дающие возможность приложению обращаться к своему родительскому интерпретатору команд). Команда командной строки есть не более чем имя файла программы, не требуется специальная регистрация и специальная разработка программ как команд (что являлось обычной практикой в RSX-11, RT-11). Не принят подход с программой, задающей пользователю вопросы о режимах своей работы, вместо этого используются параметры командной строки (в VMS, RSX-11, RT-11 программы работали также с командной строкой, но при её отсутствии выдавали запрос на ввод команд). Пространство имён устройств на диске в каталоге /dev, поддающееся управлению администратором, в отличие от подхода Windows, где это пространство имен размещается в памяти ядра, и администрирование этого пространства (например, задание прав доступа) крайне затруднено из-за отсутствия его постоянного хранения на дисках (строится каждый раз при загрузке). Широкое использование текстовых файлов для хранения настроек, в отличие от двоичной базы данных настроек, как, например, в Windows. Широкое использование утилит обработки текста для выполнения повседневных задач под управлением скриптов. «Раскрутка» ОС после загрузки ядра путём исполнения скриптов стандартным интерпретатором команд. Широкое использование конвейеров (pipe). Все процессы, кроме init, равны между собой, не бывает «специальных процессов». Адресное пространство делится на глобальное для всех процессов ядро и на локальную для процесса части, нет «групповой» части адресного пространства, как в VMS и Windows NT, как и возможности загрузки туда кода и его исполнения там. Использование двух уровней привилегий процессора вместо четырёх в VMS. Отказ от использования оверлеев в пользу деления программы на несколько программ поменьше, общающихся через конвейеры или временные файлы. Отсутствие APC и аналогов, то есть произвольных (а не жестко перечисленных в стандартном множестве) сигналов, не доставляемых до явного пожелания процесса их получить (Windows, VMS). Концепция сигнала уникальна для UNIX, и крайне сложна в переносе на другие ОС, такие, как Windows. 2.1.9 Стандарты Большое количество разных вариантов системы UNIX привело к необходимости стандартизовать её средства, чтобы упростить переносимость приложений и избавить пользователя от необходимости изучать особенности каждой разновидности UNIX. С этой целью ещё в 1980 была создана пользовательская группа /usr/group. Самые первые стандарты были разработаны в 1984—1985 гг. Одним из самых первых стандартов стала спецификация System V Interface Definition (SVID), выпущенная UNIX System Laboratories (USL) одновременно с UNIX System V Release 4. Этот документ, однако, не стал официальным. Наряду с версиями UNIX System V существовало направление UNIX BSD. Для того, чтобы обеспечить совместимость System V и BSD, были созданы рабочие группы POSIX (Portable Operating System Interface for UNIX). Существует много стандартов POSIX, однако наиболее известным является стандарт POSIX 1003.1-1988, определяющий программный интерфейс приложений (API, Application Programming Interface). Он используется не только в UNIX, но и в других операционных системах. В 1990 он был принят институтом IEEE как IEEE 1003.1-1990, а позднее — ISO/IEC 9945. В настоящее время наиболее важными являются следующие стандарты: POSIX 1003.2-1992, определяющий поведение утилит, в том числе командного интерпретатора. POSIX 1003.1b-1993, дополняющий POSIX 1003.1-1988. Определяет поддержку систем реального времени. POSIX 1003.1c-1995, дополняющий POSIX 1003.1-1988. Определяет нити (threads), известные также как pthreads. Все стандарты POSIX объединены в документе IEEE 1003. В начале 1990-х годов The Open Group предложила другой, похожий на POSIX стандарт — Common API Specification, или Spec 1170. Стандарт приобрёл большую популярность, чем POSIX, поскольку был доступен бесплатно, в то время как IEEE требовало немалую плату за доступ к своему стандарту. В 1998 году были начаты работы по объединению данных стандартов. Благодаря этому в настоящее время данные стандарты почти идентичны. Совместный стандарт называется Single UNIX Specification Version 3 и доступен бесплатно в интернете [4]. В целях совместимости несколько создателей UNIX-систем предложили использовать ELF-формат систем SVR4 для двоичных и объектных файлов. Единый формат полностью обеспечивает соответствие двоичных файлов в рамках одной компьютерной архитектуры. Структура каталогов некоторых систем, в частности, GNU/Linux, определена в стандарте Filesystem Hierarchy Standard. Однако во многих отношениях этот тип стандарта является спорным, и он, даже внутри сообщества GNU/Linux, далеко не универсален. 2.1.10 Стандартные команды ОС UNIX Создание и навигация по файлам и каталогам: ls, mv, rm, cp, ln, cd, pwd, mkdir, rmdir, find, du, df; Просмотр и редактирование файлов: touch, more, less, ed, ex, vi, emacs; Обработка текста: echo, cat, grep, sort, uniq, sed, awk, tee, head, tail, cut, tr, split, printf; Сравнение файлов: comm, cmp, diff, patch; Разнообразные утилиты командного интерпретатора: yes, test, xargs, expr; Системное администрирование: chmod, chown, ps, su, w, who, df, mount, umount; Коммуникации: mail, telnet, ftp, finger, ssh; Командные оболочки: sh, bash, csh, ksh, tcsh, zsh; Работа с исходным кодом и объектным кодом: cc, gcc, ld, nm, yacc, bison, lex, flex, ar, ranlib, make; Сжатие и архивация: compress, uncompress, gzip, gunzip, tar Работа с двоичными файлами: od, strings Ниже приведён список 60 команд из раздела 1 первой версии UNIX: ar, as, b, bas, bcd, boot, cat, chdir, check, chmod, chown, cmp, cp, date, db, dbppt, dc, df, dsw, dtf, du, ed, find, for, form, hup, lbppt, ld, ln, ls, mail, mesg, mkdir, mkfs, mount, mv, nm, od, pr, rew, rkd, rkf, rkl, rm, rmdir, roff, sdate, sh, stat, strip, su, sum, tap, tm, tty, type, un, wc, who, write. 2.2 ОС QNX 2.2.1 Что такое QNX Основным назначением любой операционной системы (ОС) является управление ресурсами компьютера. Все процессы в системе: планирование выполнения прикладных программ, запись файлов на диск, пересылка данных по сети и т.д., - должны выполняться как можно более единообразно и бесконфликтно. Некоторые прикладные системы могут предъявлять повышенные требования к управлению ресурсами и планированию процессов. Например, работа приложений реального времени зависит от того, как операционная система управляет большим количеством событий, возникающих за конечные интервалы времени. Чем больше функций берет на себя ОС, тем более свободно "чувствуют" себя эти приложения при возникновении конфликтных ситуаций. Для приложений, работающих в режиме реального времени, QNX является идеальной операционной системой. Она удовлетворяет всем основным требованиям, предъявляемым к системам реального времени: в ней реализован многозадачный режим, приоритетно-управляемое планирование и быстрое переключение контекста. Кроме того, система QNX обладает большой гибкостью. Разработчики могут легко адаптировать ее под требования своих приложений. Настройка системы QNX может быть выполнена от минимальной (ядро и несколько небольших модулей) до полной сетевой конфигурации (обслуживание сотен пользователей), позволяя использовать в каждом конкретном случае только те ресурсы, которые необходимы. Уникальная эффективность, модульность и простота системы QNX определяется: архитектурой ядра; взаимодействием между процессами посредством сообщений. 2.2.2 Архитектура ядра системы QNX Система QNX состоит из небольшого ядра (микроядра) и набора взаимодействующих процессов. Как показано на рисунке 13, система не имеет иерархической структуры, ее организация скорее напоминает "спортивную команду", в которой игроки (процессы), имеющие равную значимость, взаимодействуют друг с другом и со своим "ведущим игроком" (ядром). рис 13. - Микроядро системы QNX координирует работу системных администраторов 2.2.3 Ядро системы QNX Ядро является "сердцем" любой операционной системы. В некоторых системах на ядро возложено такое количество функций, что, по сути дела, оно само является полной операционной системой. В системе QNX ядро является действительно ядром. Прежде всего, как и подобает ядру операционной системы реального времени, оно имеет небольшой размер_-_менее 8 Кбайт. На ядро системы QNX возложено выполнение только двух основных функций: передача сообщений (ядро реализует передачу всех сообщений между всеми процессами во всей системе); планирование (планировщик является частью ядра и подключается каждый раз, когда процесс меняет свое состояние в результате появления сообщения или прерывания). В отличие от процессов само ядро никогда не планируется к выполнению. Управление передается ядру только в результате прямого вызова ядра либо из процесса, либо по аппаратному прерыванию. 2.2.4 Системные процессы Все функции, выполняемые операционной системой QNX, за исключением функций ядра, реализуются стандартными процессами. В типичной конфигурации системы QNX имеются следующие системные процессы: Администратор процессов (Proc); Администратор файловой системы (Fsys); Администратор устройств (Dev); Сетевой администратор (Net). 2.2.5 Системные процессы и процессы пользователя Системные процессы практически ничем не отличаются от любого процесса пользователя: у них нет специального или скрытого интерфейса, недоступного процессу пользователя. Именно такая архитектура обеспечивает системе QNX неограниченную расширяемость. Поскольку большинство функций QNX выполняется стандартными системными процессами, то расширить операционную систему совсем не сложно: достаточно написать и включить в систему программу, реализующую новую функцию ОС. Действительно, грань между операционной системой и прикладными программами весьма условна. Единственным принципиальным отличием системных процессов от прикладных является то, что системные процессы управляют ресурсами системы, предоставляя их прикладным процессам. Сервер базы данных должен выполнять функции, аналогичные функциям Администратора файловой системы, который получает запросы (сообщения) на открытие файлов и чтение или запись данных. Несмотря на то, что запросы к серверу базы данных могут быть более сложными, и в том и в другом случае формируется набор примитивов (посредством сообщений), в результате чего обеспечивается доступ к системному ресурсу. В обоих случаях речь идет о процессах, которые могут быть написаны конечным пользователем и выполняться по необходимости. Таким образом, сервер базы данных можно рассматривать как системный процесс в одном случае и как прикладной в другом. Фактически нет никакой разницы. Важно отметить, что в системе QNX подобные процессы включаются без каких бы то ни было модификаций других компонентов операционной системы. 2.2.6 Драйверы устройств Драйверы устройств - это процессы, которые избавляют операционную систему от необходимости иметь дело со всеми особенностями работы аппаратного обеспечения. Поскольку драйверы выполняются как стандартные процессы, то добавление нового драйвера в систему QNX не влияет на работу других компонентов операционной системы. Единственное изменение, которое происходит в среде QNX - это запуск нового драйвера. Драйвер может быть оформлен либо как дополнение к системному процессу, либо, для сохранения его "индивидуальности", в качестве стандартного процесса. 2.2.7 Связь между процессами В типичной многозадачной среде, при одновременном выполнении нескольких процессов реального времени, операционная система должна обеспечивать возможность взаимодействия процессов друг с другом. Связь между процессами (interprocess communication - IPC) является ключом к разработке приложений, представляющих собой набор взаимосвязанных процессов, в котором каждый процесс выполняетет одну строго определенную функцию. В QNX реализован простой, но мощный набор IPC-возможностей, благодаря которому значительно упрощается разработка приложений, представляющих набор взаимодействующих процессов. 2.2.8 Операционная система с передачей сообщений QNX стала первой коммерческой операционной системой данного класса, в которой IPC основан на принципе передачи сообщений. Именно благодаря глобальному использованию передачи сообщений во всей системе, ОС QNX обладает присущей ей мощностью, простотой и элегантностью. В системе QNX под сообщением понимается пакет байтов, передаваемый от одного процесса к другому. QNX не предъявляет никаких требований к содержимому сообщения: данные в сообщении имеют значение только для его отправителя и получателя. Посредством сообщений происходит не только передача данных между процессами, но также и синхронизация выполнения нескольких процессов. При передаче, получении и выдаче ответа на сообщения процессы изменяют свое состояние, что определяет время и продолжительность их выполнения. Располагая информацией о состоянии и приоритетах процессов, ядро может максимально эффективно планировать их выполнение, используя все доступные ресурсы центрального процессора. Этот метод передачи сообщений используется глобально во всей системе. Для приложений реального времени требуется зависимая форма IPC, т.к. процессы, входящие в состав подобных приложений, сильно взаимосвязаны. Механизм передачи сообщений, реализованный в системе QNX, позволяет обеспечить высокую надежность работы приложений. 3 Языки программирования ВВС 3.1 Язык ОККАМ Язык Оккам считается “языком ассемблера” для транспьютерных систем. Основой для языка Оккам является разработанный Т. Хоаром язык CSP. Основным понятием языка является процесс: примитивный процесс, составной процесс, именованный процесс. Процессы могут выполняться последовательно и параллельно. Параллельные процессы взаимодействуют с помощью каналов. Программа представляет простой процесс, использующий идентификаторы, точное значение которых зависит от конкретной машины. Процесс описывает действие, подлежащие выполнению. Процесс может быть примитивным или составным, содержащим определения и более простые составляющие процессы, объединенные «конструкторами» процессов. Структура составных задается посредствам фиксированного расположения текстов, в котором каждая компонента размещается на новой строке с небольшим отступом вправо от начала ключевого слова, вводящего определенную конструкцию. 3.1.1 Процессы 3.1.2 Процессы, не выполняющие действий SKIP - простейший примитивный процесс, который не выполняет никаких действий. STOP - процесс не выполняющий никаких действий, в отличие от процесса SKIP не завершается, но параллельно с остановленным процессом могут выполняться другие процессы. 3.1.3 Последовательные процессы Выполнение последовательных программ заключается в присваивании переменным определенных значений, на основании которых в дальнейшем принимаются некоторые решения. Присваивание в языке Оккам имеет следующую форму. переменная: = выражение Каждое выражение имеет значение в виде комбинации битов, длина которой равна значению слова, используемого компьютером, каждая переменная способна сохранить битовую комбинацию размера слова. SEQ – последовательный процесс конструируемый посредством записи последовательности его компонент, располагаемых одна за другой с небольшим отступом от ключевого слова SEQ. Последовательный процесс реализуется посредством выполнения каждой его компоненты в порядке записи. IF - условный процесс с помощью которого производится выбор для выполнения одного из нескольких процессов, основанный на анализе значений переменных, Конструкция состоит из ключевого слова IF, ниже которого с небольшим отступом в право записываются компоненты. IF процессы выполняются путем просмотра сверху вниз списка компонент до тех пор пока не встретится условие со значением TRUE. 3.1.4 Параллельные процессы. PAR – процесс, состоящий в выполнение каждой его компоненты до полного его завершения. Конструкция состоит из ключевого слова PAR, после которого с небольшим отступом вправо один под другим записываются процессы. Связь между процессами осуществляется посредством ввода и вывода по каналам. Процесс вывода канал! Выражение - передает значение выражения по каналу. Процесс ввода канал? Переменная - принимает значение из канала и запоминает его как значение переменной. Выбор процесса для выполнения в зависимости от других процессов ввода может производится с использованием ALT- процесса. 3.1.5 Процессы времени В языке Оккам существует два примитивных процесса, с помощью которых программа получает доступ к текущему значению времени. Процесс считывания показаний часов TIME? Переменная - в результате переменной присваивается значение, равное текущему показанию часов. Процесс временной задержки TIME? AFTER выражение - может приостановить выполнение программы. 3.1.6 Описание данных Имя в Оккам - программе, перед использованием, должно быть описано. Описание позволяет именовать значение констант, переменных и каналов. Для записи констант используют ключевое слово DEF, за которым следуют определения вида имя = константное выражение Переменные описываются перечислением их имен после ключевого слова VAR выше процесса, в котором будут использоваться. Каналы, описываются также, как переменные, но с ключевым словом CHAN. С помощью ключевого слова PROC, можно давать имена процессам. Описание процесса вводится строкой вида: PROC имя = текст именованного процесса В языке существует один вид структурированных данных – одномерный массив. Можно использовать массивы констант, переменных и каналов. Описание массива состоит из имени массива, за которым в квадратных скобках записывается константное выражение. имя [количество] описывает ряд переменных, на которые можно ссылаться, индексируя имя массива выражениями, значение которых принадлежат диапазону от 0 до количество – 1. Массив констант называется таблицей и обозначается следующим образом TABLE [выражение. 0, выражение. 1, выражение. 2, …, выражение. n] 3.1.7 Циклы В языке Оккам существует два вида циклов: неограниченные циклы WHILE и ограниченные индексируемы циклы FOR. Выполнение WHILE заключается в проверке значений условий и затем, если это значение равно TRUE, выполнении тела цикла. 3.1.8 Приоритеты Приоритеты в разменных операций не определены, поэтому в выражениях более чем с одной операцией, порядок действий обычно необходимо определять с помощью скобок. С помощью операций сравнения <, ≤, =, ≥, >, <> производится сравнение операндов. 3.2 Структура программирования Прием, который может заставить работать процесс, когда другой процесс занят какой – либо работой – это организовать передачу данных из одного канала в другой. Для этого надо неоднократно выполнять ввод из одного канала присваивания значения локальной переменной и последующую передачу значения переменной по другому каналу. Структурные конфликты происходят тогда, когда программа должна выполнять операции над данными, различными на перекрывающиеся компоненты. Естественный подход по нормированию подобных задач состоит в написании программ, структура которых отражает структуру документа. 3.3 Язык Forth Форт (Forth) — язык программирования, в котором программы записываются в постфиксной записи и в стековой нотации. Поддерживает механизмы метарасширения для изменения семантики и синтаксиса языка при настройке на предметную область. Синтаксис базового уровня в Форте прост и состоит из единственного правила: «все определения разделяются пробелами». Определения Форта могут иметь любое сочетание символов. Ряд свойств, а именно интерактивность, гибкость и простота разработки делают Форт эффективным языком в прикладных исследованиях и при создании инструментальных средств. Очевидными областями применения этого языка являются встраиваемые системы управления. За счёт простоты транслятор, а зачастую и компилятор Форта легко реализуется для подавляющего числа микроконтроллеров. Также находит применение при программировании компьютеров под управлением различных операционных систем. 3.3.1 История Язык Форт был создан Чарльзом X. Муром в конце 1960-х — начале 1970-х годов. Мур назвал свой язык Fourth, считая, что это будет язык для ЭВМ четвёртого (англ. fourth) поколения, однако ему приходилось работать на ЭВМ, которая допускала лишь пятибуквенные имена, составленные из прописных букв, поэтому название было преобразовано в FORTH, что символизировало его революционность (англ. forth — вперёд). В 1971 году Мура пригласили на работу в Национальную Радиоастрономическую обсерваторию для разработки программ сбора и обработки данных, получаемых с радиотелескопа. В процессе этой работы и появилась первая реализация языка Форт (а вторым в мире программистом на этом языке стала сотрудница Мура Элизабет Ратер (E.Rather)), который был принят в качестве основного языка программирования в Американском астрономическом обществе. Базовая реализация язык Форт общедоступен и бесплатно распространяется заинтересованной группой FORTH Interest Group (FIG). FIG также опубликовала первоначальный стандарт языка — FIG-FORTH. Существует множество реализаций разработанных как отдельными лицами, так и группами. Часть фирм поставляет различные по своим возможностям коммерческие версии языка. В составе ACM была организована группа SIGFORTH; была и соответствующая Российская группа ACM (председатель — проф. С. Н. Баранов (Санкт-Петербург, СПИИРАН)); сейчас — в составе более общей группы SIGPLAN. В феврале 1978 года в Утрехте (Голландия) был принят стандарт 1977 года (FORTH-77), адресованный прежде всего пользователям микроЭВМ. В октябре 1979 года встреча на острове Каталина закончилась разработкой стандарта FORTH-79, который распространяется на ЭВМ всех типов. Осенью 1983 года состоялась встреча по разработке стандарта 1983 года, утверждённого в 1984 году как FORTH-83. Стандарт Форт-83 отличается от стандарта Форт-79 некоторыми деталями, но не отличается от него по существу. В 1994 году после продолжительных обсуждений многими фирмами был принят ANSI стандарт.[3] Одним из успехов применения языка можно отметить использование его в программном обеспечении глубоководного спускаемого аппарата, при поисках «Титаника» в 1985 году. Существуют процессоры и контроллеры, поддерживающие вычислительную модель языка. Диалект языка Форт используется в OpenBoot — базовом программном обеспечении ЭВМ на базе процессоров SPARC, PowerPC. На основе языка Форт Джоном Уорноком и Чаком Гешке из Adobe Systems в начале 1980x годов был создан язык PostScript[4], широко используемый для управления устройствами печати и послуживший основой для создания формата документов PDF. Ежегодно проводятся конференции ЕвроФорт (EuroForth), в том числе в СанктПетербурге (Россия), Англии, Австрии, Германии, Испании, Чехии (и Чехословакии)[5]. 3.3.2 Основные понятия классической Форт-системы Основная часть Форт-системы — это связный список слов, или словарь, из которого слово вызывается по имени для выполнения специфических функций. Программирование на Форте состоит в определении новых слов на основе слов, определённых в словаре ранее. Как только новые слова скомпилированы в словарь, они не отличаются по форме от слов, которые в нём уже имелись. Описание слова в словаре называется статьёй. Структура «типичной» статьи словаря Форта: поле имени — содержит имя статьи (идентификатор слова) в виде строки со счётчиком, а также несколько флагов. поле связи — указатель на предыдущую статью. поле кода — указатель на код для интерпретации статьи. поле параметров — семантика слова (в зависимости от поля кода). Условно статьи Форта можно разделить на две категории: низкоуровневые статьи и фортстатьи. Статьи первого типа содержат в поле кода указатель на процедуру в кодах целевого процессора, непосредственно выполняющую семантику слова. В поле параметров таких статей располагаются передаваемые процедуре параметры, либо сам её код. Форт-статьи содержат в поле параметров указатели на другие статьи, а поле кода указывает на специальную процедуру, называемую интерпретатором ссылок. На практике структура статьи зависит от реализации, но, как правило, похожа на рассмотренную выше. Принцип, используемый внутри форт-статьи, называется шитый код (англ. threaded code), а интерпретатор ссылок — виртуальной Форт-машиной. Грамматически текст, обрабатываемый транслятором Форта, представляет собой последовательность лексем (англ. token), разделённых пробелами и символами конца строки. Транслятор входной строки выбирает очередной токен и производит его поиск в текущем словаре, причём поиск ведётся от более новых слов к старым. Если слово не найдено, предпринимается попытка интерпретировать токен в качестве записи числа, которое, в случае успеха, помещается на вершину стека. Если же токен соответствует слову Форта, анализируется текущее состояния флага compile Форт-системы. Если флаг сброшен, то слово исполняется — управление передаётся по указателю поля кода найденной статьи. Если флаг установлен, слово компилируется, то есть указатель на его поле кода дописывается в текущую создаваемую статью. Если было оттранслировано число, оно снимается со стека и компилируется в литеральный код, исполнение которого внутри словарной статьи помещает число на вершину стека. Кроме того, слова могут содержать флаг immediate («немедленный»), в этом случае они всегда исполняются. Механизм передачи параметров между словами: через стек данных; через ячейки памяти; через именованные локальные переменные (стандарт 1994 года). Язык предоставляет способ работы с памятью системы, как с линейной областью. Обязательным компонентом системы является также стек возвратов. Доступен программно для изменения потока управления программы. Всё вышесказанное относится к понятию Форт только в первом приближении. Форт — это не совсем язык программирования; вернее, он перекрывает понятие языка программирования. Форт в большей степени является виртуальной машиной и операционной системой ForthOS.[6] Синтаксис и семантику Форта можно расширить до любого другого языка программирования прямо во время интерпретации (компиляции) форт-программы. Использовать Форт в качестве метаязыка удобно благодаря доступности средств Форта, поддерживающих те языки, которые уже есть в Форт-системе. Все ресурсы Форт-системы доступны пользователю и представлены в виде словарных статей. Как правило, словарные статьи, определённые пользователем, имеют точно такое же представление в Фортсистеме, как и все остальные словарные статьи, из которых и состоит вся Форт-система. 3.3.3 Типы кода Форта В качестве машинного представления скомпилированной форт-программы используется тот или иной вид шитого кода. При использовании подпрограммного кода получается машинный код, в котором, по сравнению с кодом, сгенерированном компилятором обычного языка программирования, где на единственный стек ложатся и переменные, и адреса возвратов из подпрограмм, отсутствуют операции по «перетаскиванию» параметров подпрограмм. В качестве стека возвратов используется основной стек процессора, стек данных организуется программно. При использовании шитого кода, отличающегося от подпрограммного, определения Форта, состоящие только из машинного кода, называются примитивы. В таком шитом коде часто стараются использовать основной стек процессора в качестве стека данных, а обращения к данным, лежащим на нём, в виде машинных команд pop и push. Одно из не совсем очевидных преимуществ использования косвенного шитого кода в том, что весь машинный код, то есть примитивы, вызовы интерпретатора кода и переменных, могут размещаться в одном сегменте кода, который будет недоступен для изменения. Весь остальной код Форта размещается в сегменте данных. Этих сегментов может быть много, а работать с единственным номером сегмента легче, чем с двумя. Форт системы могут так же использовать байт-код, как логическое завершение развития косвенного шитого кода и свёрнутого шитого кода с адресной таблицей. В этом случае код программы (Форта) представляет собой последовательность байтов, или код некоторого придуманного виртуального процессора. Для исполнения этого кода должна существовать таблица на 256 адресов (2-байтовых, 4- или 8-байтовых), по которым расположены примитивы Форта или сложные определения. Этот вариант сильно отличается от других видов кода и заслуживает особого внимания. Как и в косвенном шитом коде, примитивы Форта могут быть расположены в едином сегменте кода, защищённом от вмешательства, прошитом в ПЗУ. Примитивы зависят от конкретной платформы и могут быть выполнены в виде отдельного блока. Вся остальная часть Форта является платформонезависимой и переносимой на любую машину. Вокруг таблицы на 256 определений группируется отдельный словарь, лексикон, предназначенный для конкретной задачи или группы задач. Эти 256 определений занимают места не более 64К, то есть таблица может содержать 2-байтовые адреса (смещения относительно начала словаря). Байтовый код позволяет расширить количество определений за счет древовидной структуры словарей до любой величины, сохраняя минимальные размеры программы. Байтовый код может быть стандартизован. Как и для Java, такой код может быстро пересылаться по сети и исполняться на машинах с любой платформой. 3.3.4 Примеры программ ." Привет Мир" Пример определения слова SIGN, печатающего соответствующую фразу в зависимости от знака числа на вершине стека: : SIGN ( n -- ) DUP 0> IF ." ПОЛОЖИТЕЛЬНОЕ ЧИСЛО" DROP ELSE 0= IF ." НОЛЬ" ELSE ." ОТРИЦАТЕЛЬНОЕ ЧИСЛО" ENDIF ENDIF ; С учётом принятых в языке Форт норм оформления и написания, это должно быть, скорее, слово .SIGN и определяться так: \ Напечатать знак числа : .SIGN ( n -- ) ?DUP 0= IF ." НОЛЬ" ELSE 0> IF ." ПОЛОЖИТЕЛЬНОЕ ЧИСЛО" ." ОТРИЦАТЕЛЬНОЕ ЧИСЛО" ENDIF ; ELSE ENDIF Пример реального кода, создающего строчную константу в принятом в Форт виде (со счётчиком): \ Создать "константу" из строки : S-CONSTANT ( c-addr u "&lt;spaces&gt;name" -- ) CREATE DUP , 0 ?DO DUP C@ C, CHAR+ LOOP DROP 0 C, DOES> DUP CELL+ SWAP @ ; В этом примере создаётся определение слова name с помощью слова CREATE. При исполнении слова name на стек будет ложиться адрес указателя области памяти, который был во время компиляции слова. Для того, чтобы его можно было как-то использовать, туда записывается («компилируется» строка). При выполнении слова выполняются слова, указанные после слова DOES>. Таким образом, в этом примере была создана новая синтаксическая конструкция. Подобные возможности редко представлены в других языках программирования. Кроме создания новых синтаксических конструкций, одной из самых сильных возможностей Форта является возможность вмешиваться в процесс компиляции с помощью слов немедленного исполнения (immediate-слов). Примеры таких стандартных слов: — Временное переключение в режим исполнения (фактически, часто просто записывает 0 в переменную STATE). [ ] — Переключиться обратно в режим компиляции. — Компилировать число, в данный момент лежащее на вершине стека, как константу. Также является словом немедленного исполнения. LITERAL Пример кода, где используются эти слова: \ Некоторый размер данных в килобайтах 16 CONSTANT size \ Напечатать отчёт о пересчёте килобайтов в байты : report ( -- ) size . ." килобайт эквивалентны " [ size 1024 * ] LITERAL . ." байтам" ; 3.3.5 Место Forth среди других языков программирования Одна из постоянных тем споров вокруг языка Форт — это место, которое он занимает среди «классических» императивных языков. Программы на Форте имеют крайне непривычный вид: Программа состоит из необычной последовательности слов, среди которых отсутствуют так называемые «ключевые» слова, которые распознаются и обрабатываются, в других языках программирования, специальным образом. Приведённый пример заодно указывает на уникальную особенность Форта: отсутствие списка параметров в скобках и возможность программировать на родном языке. Использование словарных конструкций родного языка позволяет сделать программу понятной, что повышает её надёжность. «Обратная польская запись» арифметических выражений и наличие нескольких стеков. Двойственная природа компилятора Форта. Нельзя утверждать однозначно, является ли Форт компилятором или интерпретатором. Практически всегда его можно использовать в двух режимах, за исключением редких случаев вроде «целевой компиляции» (трансляции в машинный код программы для системы с иной архитектурой). Отсутствие системы типов. Подобно многим «скриптовым» языкам, в Форте нет встроенной системы типов. Нет возможности узнать, что лежит на вершине стека — число со знаком, число без знака, указатель на строку, символ, или два числа, рассматриваемых как одно длинное число. Эта проблема разрешается двумя основными путями — использованием специальных наборов слов (например, запись и чтение ячеек памяти производят словами ! и @, а символов — словами C! и C@) и вынесением некоторых сущностей в специальные стеки (например, стек чисел с плавающей запятой, согласно стандарту ANSI FORTH 94; он может быть, а может и не быть, реализован с помощью основного стека). Эти особенности и определяют преимущества и недостатки языка Форт: Простота идеи, заложенной в Форт, позволяют написать ядро Форт-системы за день. Свобода, предоставляемая программисту, требует сильного самоконтроля. Входной порог для программирования на Форте ниже, чем у языков типа C++, но требует привыкания и понимания не только возможностей и особенностей синтаксиса Форта, но, также, понимания философии, лежащей в его основе. Форт не поддерживает никакую парадигму программирования и поддерживает их все одновременно. Написать набор слов для организации ООП в программе на Форте (а их может быть одновременно несколько и они будут отлично уживаться вместе) гораздо проще, чем решить, какие возможности от этого набора слов требуются. Разбиение программы на множество мелких слов позволяет легко и быстро проверять их по отдельности, передавая им нужные наборы входных параметров и контролируя то, что остаётся на стеке. Фактически, это означает, что для тестирования какого-то компонента программы можно не загружать все зависимые компоненты целиком. Форт не скрывает ошибки. Этот факт установлен опытным путём. «Отложенные» ошибки в программе на Форте — большая редкость. Ошибки, которые, в обычных языках программирования, скрываются стандартным преобразованием типов (например, int в char в C++ (хотя большинство современных компиляторов выдаст, конечно, предупреждение) или строки в число в каком-нибудь скриптовом языке), практически мгновенно, при следующем же тестовом запуске, «обрушивают» программу. Форт позволяет сделать декомпиляцию программы. Полученный текст мало отличается от исходного. Форт позволяет реализовать любую возможность и технологию, которой овладели другие языки и системы. В то же время, он может сделать то, что не под силу другим системам. В нём допустимы приёмы, запрещённые в других языках (например — самомодификация кода). Грамотная методика использования и правильный лексикон могут устранить негативные последствия этих приёмов. В интерпретаторе легко реализовать все проверки на границы диапазона адресов, а это при создании ОС позволяет отказаться от защищенного режима процессора. Получается существенный выигрыш в скорости работы. Размер кода Форта для 16-разрядных систем, при грамотном написании программы, иногда в 10-20 раз меньше кода, скомпилированного из программы на Си. Для 32-разрядных систем этот разрыв еще больше. В операционных системах общий выигрыш может составлять уже сотни, а то и тысячи крат. Причина очень простая — готовая задача на Форте имеет размер несколько байт, все вспомогательные подпрограммы реализованы в виде определений, доступных всем. Система на Форте вместится в процессор, в который другие системы влезть в принципе не способны. Синхронизация процессов и потоков в многозадачных системах, переключение контекста, реализация доступа к ограниченным ресурсам — сложнейшие проблемы при написании ОС. Для поддержки этих возможностей даже создаются специальные команды в микропроцессорах. Для интерпретатора это вообще не проблема, поскольку он эмулирует любой процессор и любую необходимую команду. Возможно, что на самом деле больше всего развитию Форта препятствует «тяжёлое наследство», пришедшее от машин с низкими возможностями, для которых он изначально создавался. В последнем стандарте (ANSI FORTH 94), существуют, например, следующие особенности: Переносимая программа должна предполагать, что стек чисел с плавающей запятой может быть реализован с использованием основного стека. К счастью, для большинства современных компиляторов это не так. Но сам факт наличия такого пункта в стандарте создаёт определённые неудобства. При программировании с активным использованием арифметики с плавающей точкой, эту норму стандарта традиционно игнорируют. Аналогичная норма существует относительно стека потока управления. Здесь всё не так просто, так как часто это именно так и есть — в процессе компиляции стек используется самим компилятором. В абсолютном большинстве случаев никакого влияния на программу это не оказывает, но про саму особенность надо помнить. Например, если вы хотите в процессе компиляции вычислить какое-то число, за пределами начала определения, а потом вставить его в слово как константу, то для этого придётся использовать какой-либо обходной путь. Определения многих слов в стандарте слишком низкоуровневые. Например, слово 2* производит не умножение на два, как следует из его названия, а «смещает число на один бит к старшему двоичному разряду, заполняя младший бит нулём». Конечно, на большинстве современных машин — это одно и то же, но сам факт использования особенностей конкретной архитектуры настораживает. (Существуют также более очевидные стандартные слова для сдвига битов — LSHIFT и RSHIFT.) Многие из этих особенностей — следствие того, что на момент принятия стандарта существовало множество плохо совместимых Форт-систем, которые базировались на двух частично различающихся стандартах 79 и 83 года. В настоящий происходит обсуждение принятия европейского стандарта Форта. Перспективы этого начинания пока не ясны.