Рейтинг@Mail.ru

Наши друзья и партнеры

UnixForum





Библиотека сайта rus-linux.net

Ada: язык программирования для оборонных ведомств

Оригинал: Ada: Military-grade programming
Автор: Juliet Kemp
Дата публикации: 13 августа 2016 г.
Перевод: А.Панин
Дата перевода: 4 сентября 2016 г.

Сверхвысокая надежность, многократно подтвержденная совместимость с встраиваемыми системами и отличное имя.

В 1970 годах в министерстве обороны США (US Department of Defence - DoD) развивалось огромное количество программных проектов для встраиваемых систем, для разработки которых использовалось более 2000 языков программирования. Эти языки были либо устаревшими, либо зависящими от аппаратного обеспечения, причем ни один из них не был безопасным и модульным. Разумеется, в это время возникли сомнения относительно безопасности и возможности долговременной поддержки подобных проектов (поиск специалистов, которые будут сопровождать 2000 устаревших языков программирования сам по себе являлся сложной задачей), не говоря уже о потенциальной необходимости списания отлично работающих устройств из-за невозможности модификации используемого программного обеспечения. В 1975 году была создана рабочая группа, цель которой заключалась в решении описанного "кризиса языков программирования" путем поиска или создании языка программирования с его последующей стандартизацией на уровне министерств обороны США и Объединенного королевства.

Из-за особенностей функционирования правительственных проектов, комитетов и проектов по созданию языков программирования, данная рабочая группа просуществовала достаточно долго. Фактически, потребовалось потратить три года и создать множество черновиков требований к языку программирования перед тем, как эти требования были четко сформулированы в рамках документа "Steelman"; в результате по прошествии этих трех лет было дано заключение о том, что соответствующего перечисленным требованиям языка программирования попросту не существует. Участники рабочей группы фокусировали внимание на читаемости кода, простоте поддержки и эффективности языка с учетом того, что он будет применяться для разработки проектов для встраиваемых систем; при этом им нужен был язык программирования, который мог бы применяться для разработки проектов для всех имеющихся в наличии систем. Идеальным решением (которое в итоге так и не было создано) мог бы стать единственный язык программирования, который мог бы использоваться для разработки замен всех существующих программных проектов министерства.

Для разработки языка программирования были заключены контракты с четырьмя компаниями, которые должны были предоставить свои проекты, после чего на основе одного из этих проектов должен был быть разработан компилятор языка программирования. Любопытный факт заключается в том, что все четыре проекта базировались на языке программирования Pascal, являющимся Algol-подобным языком, разработанным в 1960 годы для обучения программированию. В 1979 году был выбран проект "зеленой команды", а также имя нового языка программирования Ada в честь Ады Лавлейс. Данная команда состояла из специалистов французской компании CII Honeywell Bull под руководством Жана Ишбиа.

Ada83 был структурированным и строго типизированным языком программирования со встроенной поддержкой программных потоков, проверок времени исполнения, механизма параллельной обработки, а также механизма обработки исключений. Он также поддерживал концепцию задач для параллельной обработки данных с механизмом их синхронизации. Поддержка систем реального времени была одной из составляющих данной концепции. Так как язык программирования был спроектирован, в том числе, для разработки программных продуктов для высокопроизводительных систем, код был модульным, читаемым и достаточно просто сопровождаемым в случае использования корректных приемов программирования.

Язык Ada был полнофункциональным, но в то же время достаточно сложным, поэтому первые компиляторы были достаточно медленными. Набор тестов, обязательный для каждого из компиляторов Ada, сделал этот язык программирования очень надежным, как и задумывалось изначально; но в то же время он замедлял работу компиляторов или, по крайней мере, замедлял темпы их усовершенствования, так как первый этап процесса разработки компилятора для данного языка чаще всего заключался в достижении выполнения этих тестов, а не повышении скорости компиляции или исполнения программы. С другой стороны, набор этих тестов, а также некоторые тесты времени компиляции, которые обязаны выполнять все компиляторы, гарантировали (или пытались гарантировать) корректность генерации кода и надежность, что очень важно для ответственных систем. При этом медленная компиляция и низкая производительность программ также негативно влияют на возможность поддержки и безопасность систем, замедляя процесс усовершенствования и отладки программных продуктов, следовательно, в данном случае имеет место очевидный компромисс.

Механизмы языка программирования Ada для создания максимально безопасных программ сделали его популярным в области программных проектов для ответственных систем. Хотя на сегодняшний день он не всегда используется для оборонных проектов, он широко распространен в транспортных (на воздушном и железнодорожном транспорте, включая системы контроля перемещения поездов и воздушных судов Объединенного королевства), космических, банковских и медицинских проектах.

Талисман языка программирования Ada, созданный иллюстратором Лией Гудроу под впечатлением от Бэббиджского прозвища Ады Лавлейс

Талисман языка программирования Ada, созданный иллюстратором Лией Гудроу под впечатлением от Бэббиджского прозвища Ады Лавлейс "леди-фея".

Начало работы

Gnat является лучшим компилятором языка Ada для Linux, который должен быть доступен из репозитория пакетов программного обеспечения вашего дистрибутива. Сразу же после его установки следует создать файл hello.adb со следующим содержимым:

with Ada.Text_IO; use Ada.Text_IO;
procedure Hello is
begin
	Put_Line ("Hello World");
end Hello;

Язык Ada проектировался с учетом долговременного использования программных продуктов и простоты поддержки кода теми людьми, которые его не писали, поэтому его разработчики посчитали необходимым сделать синтаксис настолько прозрачным, насколько это возможно. (Любой человек, который когда-либо работал с устаревшим или запутанным кодом поймет ценность данного решения.) Исходя из всего вышесказанного, код должен быть достаточно прост для чтения.

Ada.Text_IO является стандартным пакетом, который позволяет (как и ожидается) осуществлять текстовый ввод/вывод. Оператор with позволяет импортировать пакет, а оператор use представляет собой один из механизмов, позволяющих сделать пакет видимым, после чего вы сможете использовать процедуры из него (такие, как Put_Line). Язык Ada изначально проектировался как модульный язык программирования, который должен был стимулировать использование модулей разработчиками, поэтому концепция пакетов является его неотъемлемой частью.

Основная процедура может быть названа так, как вам захочется, при этом устоявшейся практикой является использование имени, совпадающего с именем файла. Символы точки с запятой используются для в конце выражений, а также в конце процедур. Для компиляции файла исходного кода достаточно выполнить команду gnatmake hello.adb, а для запуска результирующего бинарного файла - команду ./hello.

Сборка и запуск программ с помощью GPS

Если вы ищите интегрированную среду разработки для Ada, вам следует обратить внимание на интегрированную среду разработки GPS (gnat_gps), которая использовалась при создании иллюстраций к данной статье.

Для компиляции файла исходного кода с помощью GPS вы в первую очередь должны добавить этот файл в проект. Для этого следует перейти по меню окна интегрированной среды разработки GPS "Project - Edit Project Properties" и открыть вкладку "Main Files". Теперь нужно нажать на кнопку "Add", выбрать файл (например, hello.adb) и нажать на кнопку "OK". После добавления файла в проект следует перейти по меню окна "Build - Project - hello.adb". После этого вы увидите окно параметров сборки, в котором не стоит ничего менять, ведь, как правило, стандартные параметры сборки являются наиболее оптимальными.

Далее вы можете осуществить переход по меню "Build - Run - hello" для запуска программы либо в окне GPS (как на иллюстрации ниже), либо в отдельном окне терминала.

Сборка и запуск программы средствами GPS

Сборка и запуск программы средствами GPS.

Совет: Если вам нужно сослаться на процедуру A из процедуры B, процедура A должна быть объявлена до процедуры B, иначе компилятор GNAT не сможет найти ее.

Учет времени

В качестве более сложного примера предлагаю разработать программу для учета времени, требующегося на выполнение той или иной задачи. Для начала создайте новый проект в GPS (параметры, установленные по умолчанию, вполне подойдут) и откройте новый файл с именем tracking.adb:

with Ada.Calendar, Ada.Calendar.Formatting, Ada.Text_IO,
Ada.Strings.Unbounded, Ada.Strings.Unbounded.Text_IO;
use Ada.Calendar, Ada.Calendar.Formatting, Ada.Text_IO,
Ada.Strings.Unbounded, Ada.Strings.Unbounded.Text_IO;

procedure Tracking is
	track : Unbounded_String;
	date : Time := Clock;
begin
	Put_Line("What are you tracking?");
	track := Get_Line;
	Put_Line(track);
	Put_Line(Image(Date => date));
	New_Line;
end Tracking;

Вы наверняка обратили внимание на то, что в этот раз мы импортировали множество пакетов главным образом для работы со строками неограниченной длины и календарем.

Обычные строки языка Ada должны иметь ту длину, которая указывается при их объявлении. Таким образом, в том случае, если вы поместите в одну из таких строк слишком много символов, вы потеряете некоторые из них; если же вы поместите в нее слишком мало символов, программа прекратит свое исполнение и будет ожидать заполнения строки. Стандартные пакеты Ada.Strings.Bounded и Ada.Strings.Unbounded решают эту проблему. Строки ограниченной длины имеют максимальную длину и работают немного быстрее, в то время, как строки неограниченной длины не имеют максимальной длины и являются гораздо более гибкими, но из-за динамического резервирования памяти работают немного медленнее. Мы будем использовать строки неограниченной длины из-за их гибкости.

Исходя из этого, в начале процедуры следует объявить две переменных для хранения строки неограниченной длины и времени. Кроме того, мы сохраним текущие системные значения даты и времени во второй переменной в одной строке с ее объявлением (с помощью оператора присваивания :=). В теле процедуры осуществляется вывод строки, а также используется функция Get_Line для сохранения пользовательского ввода в формате строки неограниченной длины. (Эквивалентами данных функций для обычных строк типа String являются Put() и Get().)

Наконец, осуществляется вывод введенной пользователем строки и текущего времени на экран. Image является атрибутом языка Ada, который может использоваться для получения текстового представления пользовательского ввода в случаях, аналогичных описанному. Позднее мы будем использовать и другие атрибуты.

Однако, есть определенный смысл в сохранении наших данных учета времени, а именно, строки, даты и времени в рамках одной структуры данных. Составной тип Record языка Ada, позволяющий пользователю создавать свои записи, может оказаться полезным в данном случае:

procedure Tracking is
	type Track_Record is
		record
			Name : Unbounded_String;
			Date : Time;
			Hours : Integer;
	end record;
	track : Track_Record;

	procedure Output_Track_Record(track : in Track_Record) is
	begin
		Put_Line(track.Name);
		Put_Line(Image(Date => track.Date));
		Put_Line(Item => Integer'Image(track.Hours));
		New_Line;
	end Output_Track_Record;

begin
	Put_Line("Какая задача выполнялась?");
	track.Name := Get_Line;
	track.Date := Clock;
	Put_Line("Сколько времени ушло на выполнение?");
	track.Hours := Integer'Value(Get_Line);
	Output_Track_Record(track);
end Tracking;

В первой секции объявляется запись нового типа с переменными для хранения имени, даты последнего обновления и количества часов, затраченного на выполнение определенной задачи. В следующей секции описана подпроцедура. В языке Ada предусмотрено несколько способов реализации подобных конструкций; обратитесь к соответствующему разделу для получения дополнительной информации. В данном случае процедура имеет одно входное значение (in) с именем track типа Track_Record.

Большая часть остального кода должна быть понятной. Integer'Image и Integer'Value являются специальными механизмами преобразования целочисленных значений в строковые и наоборот с помощью атрибутов. 'Value (вы можете использовать Float'Value и так далее) позволяет получить скалярное значение строки, а 'Image - строковое представление скалярного значения. Атрибуты в общем случае являются сущностями, которые применяются по отношению к различным типам объектов.

Подпрограммы: процедуры и функции

В языке Ada предусмотрено два типа подпрограмм. Процедуры не возвращают значения, а функции - возвращают его. Базовая структура функций и процедур практически полностью схожа со структурой основной процедуры и состоит из описания в начале и кода в рамках блока begin...end;.

Процедуры и функции поддерживают три режима использования входных переменных:

  • in обозначает, что параметр не должен изменяться. В рамках процедуры он рассматривается как константа.
  • in out обозначает, что параметр должен быть изменен. В рамках процедуры он рассматривается как переменная, значение которой изменяется на постоянной основе.
  • out обозначает, что значение параметра должно устанавливаться в рамках процедуры, причем его предыдущее значение не важно. Данный вариант позволяет вам "возвращать" значение из процедуры.

Обращайте внимание на данные режимы при рассмотрении процедур и функций, приведенных в данной статье.

Код программы Hello World, редактируемый с помощью интегрированной среды разработки, а также сама программа, исполняющаяся в терминале

Код программы Hello World, редактируемый с помощью интегрированной среды разработки, а также сама программа, исполняющаяся в терминале.

Вывод в файл

Все отлично, но на данный момент мы не реализовали какого-либо механизма сохранения описанной информации. Для исправления этого упущения мы можем реализовать механизм, сохраняющий содержимое полей записи в текстовом файле и реконструирующий ее после чтения файла. Однако, лучшим (и более оптимальным) решением является сохранение записи в файле в бинарном формате:

with Ada.Directories, Ada.Streams.Stream_IO;
use Ada.Directories, Ada.Streams.Stream_IO;
procedure Tracking is
	-- ... как и раньше ...
	filehandle : Ada.Streams.Stream_IO.File_Type;
	fileaccess : Ada.Streams.Stream_IO.Stream_Access;
	
	procedure Save_Records_To_File(File : in out Ada.Streams.Stream_IO.File_Type; Name : in String) is
	begin
		Create(File, Out_File, Name);
		fileaccess := Stream(File);
		Track_Record'Write(fileaccess, track);
		Close(File);
	end Save_Records_To_File;
begin
	-- как и раньше
	Save_Records_To_File(filehandle, "trackingdb");
end Tracking;

В Ada существует несколько способов записи на диск бинарных данных, при этом самый гибкий из них реализован в рамках пакета Stream_IO и позволяет работать со строками неограниченной длины, поэтому именно его мы и будем использовать.

Для начала нам понадобится переменная filehandle для хранения хэндла файла, предназначенного для доступа к потоку данных, посредством которого мы получим доступ к данным открытого файла. После этого необходимо создать новую подпроцедуру, которая будет принимать два аргумента. Аргумент File является хэндлом файла, аргумент Name - строкой с именем файла. Далее мы создаем новый файл, объявляем переменную fileaccess для доступа к потоку данных этого файла и сохраняем в файле запись с помощью атрибута 'Write типа Track_Record. Таким образом, объект Track_Record будет преобразован в байтовую последовательность, которая может быть записана в поток данных в случае передачи переменных потока данных и объекта упомянутому атрибуту. После этого осуществляется закрытие файла и процесс записи данных в файл завершается. Не стоит забывать о необходимости добавления в основную процедуру строки с вызовом процедуры Save_Records_To_File.

На данный момент мы все так же не можем прочитать сохраненные данные из файла или добавить новые данные в файл; процедура Create каждый раз осуществляет перезапись файла. Поэтому давайте добавим в код нашей программы несколько функций для решения описанных проблем:

with Ada.Directories, Ada.Streams.Stream_IO;
use Ada.Directories, Ada.Streams.Stream_IO;
procedure Tracking is
	type Track_Record is -- как и раньше

	type Record_Array is array(1..100) of Track_Record;
	records : Record_Array := Record_Array'(others =>
						(Hours => -1,
						Name => To_Unbounded_String(""),
						Date => Clock));
	counter : Natural := 1;
	track_file : String := "trackingdb";
	-- другие переменные, как и раньше

В первую очередь нам понадобится структура, в которую будут помещаться прочитанные данные. Массивы языка Ada являются очень гибкими структурами данных, но при этом они не являются динамическими: каждый из создаваемых массивов имеет фиксированную длину. Элементы массивов могут иметь любые типы, при этом типы всех элементов одного массива должны совпадать; индексы массивов могут также иметь любые типы, а не обязательно быть целочисленными. В данном же случае мы будем использовать целочисленные индексы.

При инициализации массива в Ada могут также инициализироваться определенные элементы этого массива. Кроме того, вы можете (начиная с Ada95) использовать ключевое слово others для инициализации всех остальных элементов массива. В нашем случае не осуществляется инициализации отдельных элементов массива, поэтому ключевое слово others относится ко всем элементам массива. Если этого не сделать, нельзя будет с уверенностью сказать о том, какие данные будут находиться в массиве перед записью данных в него.

Далее нам понадобится процедура для чтения записей:

procedure Read_In_Records(File : in out Ada.Streams.Stream_IO.File_Type; Name : in String) is
begin
	Open(File, In_File, Name);
	fileaccess := Ada.Streams.Stream_IO.Stream(File);
	While not End_Of_File(File) loop
		Track_Record'Read(fileaccess, track);
		Output_Track_Record(track);
		records(counter) := track;
		counter := counter +1 ;
	end loop;
	Close(File);
end Read_In_Records;

Данная процедура структурирована практически таким же образом, как и наша предыдущая процедура; она принимает хэндл и имя файла, открывает файл и создает поток данных для доступа к его содержимому. Вы можете использовать синтаксическую конструкцию While...loop...end loop; для итерации в рамках потока данных, а не только в рамках массивов или аналогичных структур и это очень удобно, особенно при условии использования функции End_Of_File. Мы используем атрибут Read для чтения записей, вывода каждой из них на экран, сохранения в массиве и увеличения значения счетчика. Не забудьте закрыть файл! А это последний недостающий фрагмент кода:

	procedure Get_New_Track is
	begin
		Put_Line ("Какая задача выполнялась?");
		-- Получение названия задачи, даты, из количества часов в выполненной ранее основной процедуре
		records(counter) := track;
	end Get_New_Track;
	
	procedure Save_Records_To_File(File: in out Ada.Streams.Stream_IO.File_Type; Name : in String) is
	begin
		Create(File, Out_File, Name);
		fileaccess := Stream(File);
		for T in records'Range loop
			if (records(T).Hours /= -1) then
				Track_Record'Write(fileaccess, records(T));
			end if;
		end loop;
		Close(File);
	end Save_Records_To_File;

begin
	if(Exists(track_file)) then
		Read_In_Records(filehandle, track_file);
	end if;
	Save_Records_To_File(filehandle, track_file);
end Tracking;

Процедура Get_New_Track используется для выделения кода из основной процедуры с добавлением строки кода, выполняющей сохранение новой записи в массиве. Приведенный вариант процедуры Save_Records_To_File также очень похож на ее предыдущий вариант, предназначенный для сохранения одной записи; мы просто используем синтаксическую конструкцию for...loop...end loop; для записи всего массива структур в файл. В данном случае мы также перезаписываем файл, но так как перед этим осуществляется чтение всех данных из него и помещение их в массив, мы перезаписываем его, сохраняя все доступные данные. (Для повышения производительности программы следует рассмотреть возможность реализации механизма добавления в файл лишь новых записей.) Атрибут 'Range в данном случае работает так, как вы могли бы предположить, а именно, позволяет получить диапазон индексов элементов массива (в нашем случае 1 - 100). Вы наверняка заметили то, что повсеместно используемый механизм атрибутов языка Ada является достаточно удобным и мощным. Также обратите внимание на синтаксическую конструкцию if...then...end if;. Наконец, в конце находится код основной процедуры очевидного назначения.

Успешный запуск! Две записи сохранены в файле и ожидается пользовательский ввод

Успешный запуск! Две записи сохранены в файле и ожидается пользовательский ввод.

Ada является мощным полнофункциональным языком программирования и именно поэтому у вас имеется возможность исследования множества его более сложных аспектов, таких, как механизм параллельного исполнения и дополнительные связанные механизмы или полноценная объектная модель. Язык отлично документирован (так как является стандартом ISO) и имеет достаточно активное сообщество пользователей, большая часть из которых использует Ada 2012. Как сказано в подписи к логотипу, это проверенный временем, безопасный и защищенный язык программирования для сложного мира.

Поделиться:


Средняя оценка 3 при 1 голосовавших