В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 46
Текст из файла (страница 46)
11 РегБуф [177566B] : CHAR ; (* буферный регистр телетайпа *)
12 PROCEDURE Печатать (Лит : CHAR) ;
13 BEGIN
14 INC (n) ; (* предопределенная процедура; n := n + 1 *)
15 WHILE n > N DO УменьшитьПриоритет END ;
16 Буф [Класть] := Лит ;
17 Класть := (Класть MOD N) + 1 ; (* MOD - операция
взятия по модулю; Индекс "Класть" циклически пробегает
буфер *)
18 IF n = 0 THEN Переключить (Дай, Возьми) END ;
19 END Печатать ;
20 PROCEDURE Драйвер ;
21 BEGIN
22 LOOP
23 DEC (n) ; (* предопределенная процедура; n := n - 1 ; *)
24 IF n < 0 THEN Переключить (Возьми, Дай) END ;
25 РегБуф := Буф [Брать] ; Брать := (Брать MOD N) + 1 ;
26 РегСост := {6} ; (* шестой разряд инициирует обмен *)
27 ПереключитьСЗаказом (Возьми, Дай, 64B) ;
28 РегСост := { } ; (* обмен завершен *)
29 END ;
30 END Драйвер ;
31 BEGIN n := 0 ; Класть := 1 ; Брать := 1 ; (* Инициализация *)
32 НовыйПроцесс (Драйвер, ADR(РабОбл), SIZE(РабОбл), Возьми) ;
(* Предопределенные функции доставляют соответственно
адрес и размер объекта *)
33 Переключить (Дай, Возьми) ;
34 END Телетайп ;
Подробности о функционировании модуля Телетайп. Представим себе
применение этого модуля по такой схеме:
35 MODULE Печать ;
36 FROM Телетайп IMPORT Печатать ;
37 CONST M = 100 ;
38 VAR Текст : ARRAY [1..N] OF CHAR ;
...
39 FOR J := 1 TO M DO
40 Печатать (Текст [J]) ;
41 END ;
42 END Печать ;
Проследим взаимодействие компонент программы, указывая обрабатываемые
(выполняемые) номера строк.
Инициализация. В самом начале модуля Печать происходит связывание с
модулем Телетайп и выполнение его "инициализирующих" строк 31-33. Создается
процесс с телом Драйвер и присваивается переменной Возьми. С этого момента
Возьми используется для идентификации сопрограммы, непосредственно
работающей с внешним устройством.
[Ее принципиальное отличие от процедуры Драйвер состоит в том, что
переключение на Возьми означает продолжение работы сопрограммы, а не вызов
процедуры Драйвер (с ее начала) ].
Затем (строка 33) эта сопрограмма запускается и одновременно текуший
процесс (т.е. основная программа) присваивается переменной Дай и
приостанавливается (перед выполнением строки 37).
С этого момента основная программа выступает как процесс Дай, а драйвер
- как Возьми. [Названия оправданы тем, что основная программа подает литеры
в буфер Буф, а драйвер забирает их оттуда.]
Итак, запомним, что строка 32 нужна для создания процесса Дай, а строка
33 - для создания процесса Возьми. Взаимодействие начинается.
Начало. Буфер пуст. После строки 33 управление достигает цикла 22 с
условием n = 0, свидетельствующим о пустом буфере. Поэтому после строки 23 в
строке 24 следует переключение на основную программу Дай. [Вернется оно в
драйвер на строку 25!] Так будет всегда, когда драйвер в своем основном
цикле освобождает буфер и при n = -1 переключается на основную программу
Дай.
Эта программа продолжается со строки 37, рано или поздно доходит до
строки 40 и вызывает Печатать с очередной литерой текста. Через строку 14
при условии n = 0 проходим на 16 и помещаем литеру в буфер. Строка 18
отправляет на драйвер (строка 25) при n = 0 (несколько неестественном
условии; ведь в буфере имеется одна литера).
Основное взаимодействие. Буфер не пуст и не полон. Извлекая очередную
литеру из буфера (в строке 25), драйвер запускает обмен с внешним
устройством в строке 26 (присваивая его регистру состояния 1 в шестом
разряде и активизируя тем самым аппаратную задачу).
Принципиально важная для нас строка 27 приостанавливает драйвер,
переключает управление на основную программу (в первый раз - на строку 19,
т.е. сразу же на 39) и заказывает прерывание по концу обмена очередной
литеры. Это прерывание (от телетайпа) в соответствии с семантикой процедуры
ПереключитьСЗаказом приводит к переключению от Дай снова на Возьми в момент
окончания обмена.
Пока идет обмен (работает аппаратная задача асинхронно с исполнением
процессов Дай и Возьми), процесс Дай в цикле 39-41 может наполнять буфер.
После прерывания драйвер в цикле 22-29 очищает буфер по одной литере. Это и
есть основное взаимодействие процессов Дай и Возьми. При этом скорости
заполнения и очистки буфера жестко не связаны.
[Вопрос. За счет чего буфер может очищаться быстрее, чем наполняться, и
наоборот?].
Особые ситуации. Буфер полон и пуст. Основное взаимодействие
прекращается, если буфер оказывается полным (в строке 15 n > N) или пустым
(в строке 24 n < 0).
Когда буфер полон, необходимо дать приоритет процессу Возьми,
очищающему буфер, приостановив заполняющий процесс Дай. Это реализует цикл
уменьшения приоритета (строка 16). Ведь по логике модуля Телетайп заполнение
буфера более чем на одну позицию возможно только одновременно с работой
аппаратной задачи (собственно обменом или ожиданием ей разрешения на
прерывание (убедитесь в этом!).
Поэтому переполнение буфера означает, что нужно обеспечить
беспрепятственное выполнение очищающего цикла драйвера. Для этого процесс
Дай и задерживается на цикле 15, в конечном итоге уступая (единственный!)
процессор драйверу (при достаточном понижении приоритета). И буфер начинает
очищаться.
Когда же буфер пуст, то строка 24 переключает управление на Дай с
n = -1. Это соответствует уже разобранной ситуации "Начало. Буфер пуст".
Еще одно решение. Не видно причин, почему не написать модуль Телетайп
концептуально проще, изъяв строку 33 и (как следствие) попадание в зону
отрицательных n (не соответствующих назначению этой переменной - считать
количество литер в буфере). [Найдите это решение.]
Пишем только "Печатать" и "Драйвер" при условии, что строки 33 нет.
PROCEDURE Печатать (Лит : CHAR);
BEGIN
WHILE n = N DO УменьшитьПриоритет END;
Буф[Класть] := Лит; INC (n); Класть : = (Класть MOD N) + 1;
IF n = 1 THEN Переключить (Дай, Возьми) END;
END Печатать;
PROCEDURE Драйвер;
BEGIN
lOOP
РегБуф := Буф [Брать]; DEC(n); Брать := (Брать MOD N) + 1;
РегСост := {6}; ПереключитьСЗаказом (Возьми, Дай, 64B);
РегСост := {};
IF n = 0 THEN Переключить (Возьми, Дай) END;
END (* цикла *);
END Драйвер;
Упражнение. Докажите эквивалентность первому решению.
12.8. Принцип чайника
Обсуждая методы борьбы со сложностью программирования, полезно обратить
внимание на принцип, интуитивно хорошо знакомый опытным программистам и
выражающий своего рода защитную реакцию на сложность и ненадежность
операционной среды. Суть этого принципа хорошо иллюстрирует старый анекдот :
"Как вскипятить чайник? - Берем чайник, наливаем воду, ставим на огонь,
доводим до кипения. Как вскипятить чайник с водой? - Выливаем воду из
чайника и сводим задачу к предыдущей!".
Почему математики так любят "сводить задачу к известной"? Потому, что
для них главное - ясность ("прозрачность", "надежность") доказательства, а
прямое решение новой задачи рискует оказаться ошибочным.
Но ведь и для программистов главное - надежность и понятность
программы. Поэтому опытный программист без особой нужды не станет
пользоваться элементами операционной среды, которые он лично не проверил.
Это относится, в частности, к использованию отдельных команд, языковых
конструктов, программ, пакетов, а также ЯП. Важными оказываются не столько
их свойства сами по себе, сколько то, что программист эти свойства знает и
этому своему знанию доверяет.
Если окажется возможным "свести задачу к предыдущей", она будет, как
правило, решена традиционными, обкатанными методами. Намекая на упомянутый
анекдот, назовем соответствующий технологический принцип "принципом
чайника".
Очевидное проявление принципа чайника - долгожительство классических
ЯП, в особенности Фортрана. Менее очевидное (указанное впервые Дональдом
Кнутом и затем многократно подтвержденное другими исследователями) -
пристрастие программистов к самым тривиальным оборотам (фразам) при
использовании ЯП. Если шаг цикла, то 1; если прибавить, то 1; если
присвоить, то простейшее выражение; если проверить, то простейшее отношение
и т.п.
Принцип чайника помогает обосновать принцип чемоданчика (на этот раз
уже с точки зрения психологии пользователя) - необязательными, чересчур
изощренными конструктами будут редко пользоваться, они окажутся экономически
неоправданными.
12.9. ЯП Оберон
В феврале 1988 года стало известно о новом языке Н.Вирта - ЯП Оберон.
Вирт не склонен связываь с выбором имени для своего очередного детища каких-
либо глубоких соображений, однако отмечает, что для него "Оберон" скорее
самый крупный спутник Урана, чем король эльфов. Для нас Оберон интересен
прежде всего как очередная попытка достичь идеала ЯП, следуя принципу
чемоданчика с учетом новейших достижений в философии и технологии
программирования.
Не вдаваясь в подробный анализ проектных решений, отметим лишь, что
целью Вирта был минимальный базовый ЯП для персональной рабочей станции.
Более того, правильнее назвать требуемый ЯП не просто базовым, а
монопольным (интегрированным) ЯП [20]. Другими словами, ЯП должен быть
таким, чтобы ни создателю программного обеспечения станции (включая ее
операционную систему), ни пользователю станции просто не нужен был никакой
иной инструмент программирования. Конечно, минимальное ядро реализации
любого ЯП должно быть написано на ассемблере. Но этим и должно
ограничиваться применение иного ЯП.
Идея монопольного ЯП, с одной стороны, очевидным образом перекликается
с идеей единого универсального ЯП, которая, как известно, многократно
терпела фиаско и вновь воскресала на очередном этапе развития
программирования. Ясно, что идея монопольного ЯП жизнеспособнее за счет
отказа от претензий на пригодность для любых классов задач, классов
пользователей, любых компьютеров и программных сред. Более того, она
фактически реализована в таких ЯП, как Си для UNIX-совместимых сред, Эль-76
для отечественной серии "Эльбрус", Том в одноименной интегрированной системе
В.Л.Темова и др. Еще раз подчеркнем, что идеальный монопольный ЯП должен
быть не просто принципиально возможным, а реально наилучшим инструментом
программирования в своей среде. Тем более интересно посмотреть, как
справляется с задачей создания минимального монопольного ЯП такой всемирно
признанный мастер, как Вирт.
12.9.1. От Модулы-2 к Оберону
Укажем отличия Оберона от Модулы-2, следуя [21].
Главная новинка - средства обогащения (extension) комбинированных типов
данных. Этот новейший аспект в ЯП мы подробнее рассмотрим в разделе,
посвященном наследованию. Основная идея обогащения связана с воплощением
"древней" мечты программистов - вводить при необходимости дополнительные
поля в записи таким образом, чтобы сохранялась работоспособность всех ранее
отлаженных программ. Один из известных учебников по структурному
программированию [7] начинается с притчи о злоключениях программистов,
не предусмотревших вовремя нужного поля. Вирт снимает все такого рода
проблемы, предоставляя возможность обогащать комбинированный тип новыми
полями с наследованием всех видимых операций исходного типа.
Например, если задан тип
T = RECORD x. y: INTEGER END
то можно определить обогащенные типы
T1 = RECORD (T) z: REAL END
T2 = RECORD (T) w: LONGREAL END
наследующие все видимые операции, определенные для T. При этом
"обогащенные" объекты типов T1 и T2 можно присваивать "бедным" объектам типа
T, а "бедные" "богатым" - нельзя. Обратите внимание, что не только в Модуле-
2, но и в Аде подобное невозможно.
Вопрос. Почему такое "странное" правило присваивания? Ведь обогащенную
запись несложно разместить в записи с меньшим числом полей, но это как раз
запрещено, в то время как обратное разрешено, хотя вся запись наверняка не
поместится?
Упражнение. Попытайтесь уточнить правило присваивания, предложив
вариант размещения обогащенной записи в бедной.
Подсказка. Основной критерий - надежность программирования и
применимость старых операций к новым объектам.
Конечно, подобное нововведение требует иногда пожертвовать
эффективностью программы ради удобства ее изготовления, надежности и других
преимуществ. Но в этом и состоит истинный прогресс в ЯП - осознается
фундаментальное значение компромиссов, казавшихся ранее немыслимыми.
Вопрос. За счет чего может снижаться эффективность программы?
Подсказка. Не обойтись без указателей там, где ранее обходились.
Еще одно важное нововведение (точнее, коррекция исходного понятия) -
трактовка спецификации как усеченной (без каких-либо добавлений!)
реализации. Другими словами, спецификация полностью состоит из цитат, взятых