ГЛАВА 11. СИСТЕМНЫЙ ВВОД И ВЫВОД

11.1. ВВЕДЕНИЕ

В гл. 8 была описана упрощённая форма ввода-вывода, которая позволяла программистам вводить и выводить символьные строки на свой терминал. В настоящей главе будет более детально рассмотрено использование системных сервисных средств ввода-вывода операционной системы VAX/VMS, позволяющих осуществлять полное управление форматами данных, обеспечивать работу с различными периферийными, а также с логическими устройствами. Кроме того, читатель познакомится со структурой файлов, операциями работы с файлами, методами обработки ошибок, которые могут встретиться в процессе ввода-вывода, а также с другими системными операциями.

Существует ряд средств, с помощью которых такая развитая операционная система, как VAX/VMS, предоставляет пользователям возможность выполнения ввода-вывода. Эти средства предназначены для работы на различных уровнях в зависимости от потребностей пользователя. На верхнем уровне располагаются средства ввода-вывода, применяемые в языках высокого уровня, таких как Фортран с его операторами READ, WRITE, FORMAT и др. Предполагается, что читатель в той или иной степени уже знаком с такой формой ввода-вывода. На нижнем уровне находятся операции ввода-вывода, реализованные на машинном языке, что связано с установкой битов и полей в специальных регистрах, являющихся составной частью каждого периферийного устройства. Фактически выполнение ввода и вывода на любом уровне в конце концов сводится к выполнению операций на самом нижнем уровне. Однако в многопользовательских системах, таких как VAX/VMS, имеются механизмы защиты, предотвращающие непосредственное обращение пользователей к нижнему уровню ввода-вывода, поскольку оно могло бы помешать выполнению операций ввода-вывода других пользователей. В результате только сама операционная система может иметь доступ к регистрам устройств. Как работают некоторые из этих функций, описано в гл. 15.

Если программисты, работающие с языком ассемблера ЭВМ семейства VAX, не имеют Системных привилегий, они могут выполнять операции ввода-вывода на двух уровнях, занимающих промежуточное положение между операциями, реализуемыми на машинном языке и языках высокого уровня. Первый из этих методов основан на использовании подсистемы ввода-вывода с очередями, называемой QIO, второй метод - на использовании службы управления записями, называемой RMS. Подсистема QIO работает на более низком из этих двух уровней и позволяет осуществлять ввод неструктурированной информации с устройства или вывод на него. Подсистема RMS работает на более высоком уровне и обеспечивает выполнение операций с файлами. В данной главе будут рассмотрены некоторые основные операции обеих систем, однако читателю следует помнить что эти темы весьма сложны и наше рассмотрение ни в коей мере не является исчерпывающим. За более подробной информацией следует обратиться к соответствующим руководствам ("VAX-11 Record Manager Services Reference Manual", "VAX/VMS System Services Reference Manual", "VAX/VMS I/O Users Guide".)

11.2. ИСПОЛЬЗОВАНИЕ СИСТЕМНОГО СЕРВИСА

Обычный способ использования большинства системных функций, таких как операции ввода-вывода, заключается в вызове системных сервисных подпрограмм. В операционной системе VAX/VMS системные сервисные подпрограммы могут вызываться как из программ, написанных на языках высокого уровня, так и из программ на языке ассемблера. Поскольку достаточно часто вызывающие последовательности бывают весьма сложными, для упрощения работы с этими подпрограммами предусмотрены системные макроинструкции. Макроопределения этих макроинструкций находятся в библиотечном файле, в котором осуществляется поиск всякий раз, когда в программе встречается макроинструкция, не определённая внутри самой этой программы. Чтобы отличить системные макроинструкции от макроинструкций, определённых пользователем, имена системных макроинструкций начинаются со знака доллара. Примером такой системной макроинструкции, уже применяемой ранее, является $EXIT_S. В данном случае сочетание _S показывает, что её макрорасширение будет содержать инструкцию CALLS. Существует аналогичная макроинструкция $EXIT_G, в которой используется инструкция CALLG. Большинство описанных в данной главе макроинструкций имеют формы _S и _G. Макроинструкции формы _S имеют обычно большие списки аргументов, которые описывают то, что должно быть занесено в стек перед выполнением инструкции CALLS. Макроинструкции формы _G обычно имеют только один аргумент, который является указателем на список аргументов, но при этом подразумевается, что список аргументов должен быть сформирован отдельно. Для простоты в этой главе будет использоваться более лёгкая в обращении, хотя иногда и менее эффективная, форма _S макроинструкций.

Напомним об употреблении знака подчёркивания. В программах на языке ассемблера и знак доллара, и знак подчёркивания могут присутствовать в метках, символических именах и именах макроинструкций. Эти знаки входят в алфавит языка ассемблера. Программисты могут пользоваться ими как знаками псевдопунктуации, разделяя тем самым длинные символические имена на несколько частей, несущих смысловую нагрузку. Знак подчёркивания можно применять не только в именах системных программ. Его применение для разбиения длинных имён даже поощряется, так как повышается их удобочитаемость. Например, символическое имя INPUT_OUTPUT_ROUTINE предпочтительнее имени INPUTOUTPUTROUTINE. С другой стороны, знак доллара зарезервирован для системного использования и не должен применяться в именах, определённых пользователем, так как может возникнуть путаница.

Большинство системных макроинструкций при их расширении образуют вызывающие последовательности для системных подпрограмм. Конечно, вызывающие последовательности могут быть сформированы и без применения макроинструкций, но всё-таки макроинструкции существенно упрощают работу программиста. В вызывающих последовательностях используется стандартная форма списков аргументов, описанная в гл. 9, и, следовательно, эти подпрограммы могут вызываться программами, написанными на языках высокого уровня, таких как Фортран или Паскаль. Список аргументов может быть помещён либо в стек, либо в массив в фиксированной области памяти.

Хотя большинство макроинструкций используется для генерации вызывающих последовательностей для системных подпрограмм, некоторые системные макроинструкции предназначены для генерации таблиц данных. Двумя из таких макроинструкций являются $FAB и $RAB, применяемые для определения атрибутов файлов и записей при работе с подсистемой RMS, описанной далее в этой главе. Другие макроинструкции позволяют сформировать списки аргументов, когда применяются макроинструкции формы _G.

Кроме формирования таблиц данных и вызывающих последовательностей для подпрограмм, многие системные макроинструкции представляют собой длинные списки определений символических имён. Эти символические имена программисты могут использовать вместо числовых кодов для обозначения операций или указания относительного расположения полей данных в таблицах. Например, символическое имя IO$_READVBLK может использоваться в макровызове $QIOW_S для обозначения того, что должна быть выполнена функция QIO - чтение виртуального блока.

11.3. ЗАПРОСЫ ВВОДА-ВЫВОДА С ОЧЕРЕДЯМИ

Запросы ввода-вывода с очередями используются для выполнения простых функций ввода-вывода неструктурированной информации, поступающей с устройства ввода или на устройство вывода. Термин устройство применяется для обозначения источника ввода или приёмника вывода, подключённого к ЭВМ, например телетайпа, устройства печати, магнитного диска или ленты. В данном разделе работа с устройствами ввода-вывода будет рассмотрена только на примере работы с терминалом пользователя.

НАЗНАЧЕНИЕ КАНАЛА ВВОДА-ВЫВОДА

Конкретные устройства идентифицируются определёнными в системе именами. Имена SYS$INPUT и SYS$OUTPUT обычно применяются для обозначения терминала пользователя. Имя SYS$INPUT используется для обозначения входного потока данных, а имя SYS$OUTPUT - выходного потока данных. Когда системе передаётся имя устройства, она должна найти информацию, описывающую параметры, необходимые для доступа к указанному устройству. Ясно, что было бы очень неэффективно осуществлять поиск этой информации при каждом обращении к устройству. Поэтому система разработана так, что поиск выполняется только один раз, а информация помещается во внутренний массив и становится доступной в дальнейшем. Каждая запись в этом массиве содержит информацию, необходимую для работы с устройством, или указатель на место, где эта информация может быть найдена. Такие записи в массиве называются каналами, а их относительное месторасположение в массиве определяет номер канала. Для того чтобы система осуществила поиск информации об устройстве и назначила номер канала, применяется системная макроинструкция $ASSIGN_S. Эта макроинструкция имеет два обязательных ключевых параметра DEVNAM и CHAN. Параметр DEVNAM - это адрес дескриптора символьной строки, которая задаёт имя устройства (см. гл. 8 и 9 по поводу описания директивы .ASCID - дескриптора символьной строки). Параметр CHAN - это адрес длинного слова, в которое будет помещён номер канала, назначенный системой. На рис. 11.1 показано, как в программе может быть выполнено назначение канала для устройства SYS$INPUT, в данном случае система поместит номер канала в длинное слово CH1. В дополнение к двум обязательным параметрам, показанным в макровызове $ASSIGN_S на рис. 11.1, имеются два необязательных. Они служат для задания режима доступа и имени почтового ящика.

CH1:        .BLKL   1
CHNAME:     .ASCID  "SYS$INPUT"
            .
            .
            .
            $ASSIGN_S   DEVNAM=CHNAME,CHAN=CH1

Рис. 11.1. Назначение номера канала

ЭВМ семейства VAX имеют четыре режима доступа: пользователя, супервизора, управления и ядра. Параметр "режим доступа"позволяет программе изменять привилегии операции для назначенного канала в сторону уменьшения привилегий режима доступа. Таким образом обеспечиваются средства защиты от системных программ, вызываемых для выполнения операций программами пользователей, имеющих меньшие привилегии доступа. Но поскольку наиболее вероятно, что читатели книги имеют привилегии самого нижнего уровня, т.е. доступ в режиме пользователя, то отпадает необходимость в указании режима доступа.

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

ФУНКЦИИ ВВОДА-ВЫВОДА С ОЧЕРЕДЯМИ

Операции ввода-вывода осуществляются с помощью инструкций $QIO_S и $QIOW_S. Эти макроинструкции помещают запросы на выполнение требуемых операций ввода-вывода в очередь, которая используется системой для определения того, какие операции предстоит выполнять. В большинстве случаев операции ввода-вывода выполняются существенно дольше, чем внутренние машинные операции. Например, ЭВМ семейства VAX может выполнить миллионы инструкций за то время, которое требуется пользователю для ввода команды с терминала. Поэтому операционная система ЭВМ семейства VAX, как правило, только инициирует операции ввода-вывода, а затем продолжает выполнять другую обработку до тех пор, пока не будет завершён ввод-вывод.

Действие макроинструкций $QIO_S и $QIOW_S основано на этих же принципах. Подпрограмма, вызываемая при расширении макроинструкции $QIO_S, возвращает управление вызывающей программе сразу же после инициации запроса. После этого программа может свободно выполнять вычисления, не связанные с запрошенными операциями ввода-вывода. Существует ряд способов, позволяющих определить, завершился ли запрошенный ввод-вывод. И программа должна воспользоваться одним из них, чтобы удостовериться, что уже может выполнять операции, связанные с этим вводом или выводом.

Макроинструкция $QIOW_S используется, если в программе ничего не может быть выполнено до тех пор, пока не станут доступными запрашиваемые данные. В таком случае она должна ждать завершения операций ввода или вывода. Обратите, однако, внимание, что в отношении ЭВМ это не есть реальное ожидание. В любой многопользовательской системе, такой как VAX/VMS, пока программа ожидает завершения запроса ввода-вывода, могут обычно выполняться программы других пользователей.

Обе макроинструкции имеют один и тот же набор параметров. Два из них являются обязательными. Это:

  1. CHAN - номер канала;
  2. FUNC - код функции ввода-вывода.

Номер канала обычно получают из области, которая была загружена с помощью макроинструкции $ASSIGN_S. Функция ввода-вывода задаётся числовым кодом, который идентифицирует функцию. Независимо от того, вызывается ли макроинструкция $QIO_S или $QIOW_S, для идентификации всех этих функций определяется набор символических имён. Ссылка на функции по их символическим именам вместо указания их фактического числового кода имеет два преимущества. Во-первых, символические имена, используемые для обозначения функции, обычно содержат информацию о сути операции и делают программу самодокументируемой. Во-вторых, при доработке ОС VAX/VMS, возможно, могут применяться другие числовые коды для функций. При использовании символических имён повторное ассемблирование программы автоматически скорректирует коды функций. Некоторые коды функций:

1.

IO$_READVBLK

Читать виртуальный блок

2.

IO$_READLBLK

Читать логический блок

3.

IO$_READPBLK

Читать физический блок

4.

IO$_WRITEVBLK

Записать виртуальный блок

5.

IO$_WRITELBLK

Записать логический блок

6.

IO$_WRITEPBLK

Записать физический блок

7.

IO$_SETMODE

Установить режим

8.

IO$_SETCHAR

Установить характеристики

 Более подробно коды функций и их возможные модификации будут описаны в этом разделе далее. В зависимости от того, какой код функции используется в макроинструкции, может быть до шести параметров, которые обозначаются именами P1, P2, ..., P6. Например, при чтении и записи Р1 - это адрес массива или буфера, куда будут помещены данные или откуда они будут извлекаться. Параметр P2 задаёт размер буфера в байтах. На рис. 11.2 показано, как можно вывести сообщение на устройство SYS$OUTPUT. Макровызов $ASSIGN_S - тот же самый, что использовался на рис. 11.1.

Обратите внимание, что перед кодом функции и длиной сообщения стоит знак номера. Это сделано потому, что макрорасширение включает инструкции PUSHL FUNC и PUSHL P2. Это позволяет подставлять на место параметров или число с предшествующим ему знаком номера #, или адрес ячейки, в которой содержится значение, или даже имя некоторого регистра или указать режим адресации, определяющий местоположение значения.

Обратите также внимание на то, что строка сообщения содержит символы перевода строки и возврата каретки. Это необходимо потому, что при такой вызывающей последовательности все заданные в строке сообщения символы выводятся без какого-либо управления форматом. Обычно же операционная система VAX/VMS осуществляет ввод и вывод информации записями, которые соответствуют строчкам распечатки и не требуют управляющих символов. В большинстве случаев предпочтительно форматирование записей, поскольку оно более эффективно. Однако пока наше обсуждение ограничивается рассмотрением форматирующих символов, чтобы сконцентрировать внимание на особенностях работы терминала. Макроинструкции $QIO имеют параметр P4, который может использоваться для выполнения автоматического форматирования; более детально мы обсудим это позднее.

CR=^X0D
LF=^X0A
CH1:        .BLKL       1
CHNAME:     .ASCID      "SYS$OUTPUT"
MESSAGE:    .ASCII      <LF>"ЗДЕСЬ ПОМЕЩЕНО СООБЩЕНИЕ"<CR>
MESSAGE_END:
MESSAGE_SIZE=MESSAGE_END-MESSAGE
            .
            .
            .
            $ASSIGN_S   DEVNAM=CHNAME,CHAN=CH1
            .
            .
            .
            $QIOW_S     CHAN=CH1,FUNC=#IO$_WRITEVBLK,-
                        P1=MESSAGE,P2=MESSAGE_SIZE
            .
            .
            .

Рис. 11.2. Вывод сообщения

В рассматриваемой программе для выполнения операции вывода используется также функция IO$_WRITEVBLK. Эта операция осуществляет запись виртуального блока информации. Если устройством вывода является диск, расположение виртуальных блоков определяется относительно начала файла, тогда как расположение логических блоков определяется относительно самого устройства. Физические блоки определяются как физические блоки на магнитной ленте или диске и могут отличаться по размеру от логических блоков. Для совместимости длина логических блоков на устройствах с файловой структурой единообразно устанавливается равной 512 байтам, даже если на физическом носителе применяется другое блокирование информации. Вообще запись физических и логических блоков необходима только для реализации системных функций и безопасность системы может быть нарушена, если обычные пользователи смогут осуществлять доступ к физическим или логическим блокам на системном диске. Вследствие этого функции IO$_READLBLK, IO$_WRITELBLK, IO$_READPBLK и IO$_WRITEPBLK могут быть использованы только привилегированными пользователями.

11.4. ВВОД И ВЫВОД С ТЕРМИНАЛА

ОСОБЕННОСТИ ОРГАНИЗАЦИИ ВВОДА С ОЧЕРЕДЯМИ

Ввод с терминала имеет одну сложность, которая обычно не встречается при выводе. Во время распечатки выводимой информации программа в точности определяет, сколько символов находится в буфере вывода. Это справедливо для вывода одиночной строки, для многострочного вывода или даже если должна быть выведена лишь часть строки. Однако при вводе число символов в строке определяется обычно не в программе, а работающим за терминалом человеком. Поэтому программа должна быть способна определить, сколько символов было напечатано оператором терминала. Существует ряд способов для определения этого, тем не менее их можно свести всего лишь к трём.

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

Если при вводе происходит считывание одновременно всей строки, то для хранения строки должен быть предусмотрен буфер длиной не меньше самой длинной допустимой при вводе строки. Если терминал пользователя имеет экран шириной 80 символов, то разумно будет ограничить длину вводимых строк длиной строки, отображаемой на экране терминала. Тогда подходящим окажется буфер длиной 81 байт. Заметим, что дополнительный байт необходим для того, чтобы в него помещался символ возврата каретки при 80-символьной строке. В двух первых способах ввода используется такой буфер. Реально же память обычно не так уж ограничена, поэтому, чтобы не рисковать, предпочтительно иметь буфер несколько большего размера. По первому способу сначала считывается строка целиком, а затем посимвольно просматривается содержимое буфера для нахождения признака конца строки. Признаком конца строки служит обычно символ возврата каретки (код ASCII ^X0D). Однако при вводе к завершению строки обычно приводит любой управляющий символ, который может быть введён (за исключением символов табуляции, перевода строки, перевода страницы, вертикальной табуляции и возврата на шаг назад)[1]. Для изменения списка символов, которые интерпретируются как признаки конца строки, может использоваться параметр P4. Может быть выбран любой набор символов алфавита, однако потребность в использовании символов, отличающихся от стандартного набора, возникает редко; для более детального изучения этих функций читатель может обратиться к руководству "VAX/VMS Input/Output User’s Guide".

Поскольку существует ряд возможных символов - признаков конца строки, проверка каждого символа на его принадлежность к множеству всех допустимых признаков конца строки потребовала бы очень много времени. И это приводит ко второму способу определения длины вводимой строки. По мере ввода символов операционная система должна контролировать каждый символ, проверяя, не является ли он завершающим, и подсчитывать число занятых байтов в буфере; поэтому существует счётчик вводимых символов, доступный для программиста через квадраслово, называемое блоком состояния ввода-вывода (IOSB). Второе слово в этом квадраслове является счётчиком числа переданных байтов без учёта завершающего символа. На рис. 11.3 показаны строчки программы, необходимые для ввода строки с терминала и для того, чтобы сделать счётчик байтов строки доступным по адресу BCOUNT. Остальные три слова в блоке IOSB содержат информацию о состоянии, требуемую для более сложной обработки данных. Вновь следует читателю обратиться за более подробной информацией к руководствам фирмы DEC.

Будет ли показанная на рис. 11.3 программа работать по первому или второму способу, зависит от того, что программа делает с информацией, получаемой с помощью операции $QIOW_S. Если она просматривает буфер в поисках признака конца строки, то это будет первый способ; если же используется счётчик BCOUNT для получения числа байтов, то это - второй способ. Поскольку для первого способа не нужны BCOUNT и SBLOK, то определяющие их строчки текста могут быть исключены из программы на рис. 11.3.

CH1:        .BLKL       1
CHNAME:     .ASCID      "SYS$INPUT"
BUFFER:     .BLKB       100
SBLOK:      .BLKQ       1
BCOUNT=SBLOK+2          ;ВТОРОЕ СЛОВО IOSB
            .
            .
            .
            $ASSIGN_S   DEVNAM=CHNAME,CHAN=CH1
            .
            .
            .
            $QIOW_S     CHAN=CH1,FUNC=#IO$_READVBLK,-
                        P1=BUFFER,P2=#100,IOSB=SBLOK
            .
            .
            .

Рис. 11.3. Пример ввода с терминала

Посимвольный ввод. Третий способ ввода заключается в считывании за один раз по одному символу. Наипростейший путь выполнить такое считывание - сделать длину буфера равной одному байту. Тогда, как только человек за терминалом напечатает один символ, буфер заполнится и ввод будет завершён, как если бы был введён завершающий строку символ. После завершения операции, заданной в макровызове, $QIOW_S, программе будет передан этот одиночный символ и программа сможет обработать его соответствующим образом. Это даёт программе возможность обрабатывать каждый раз по одному символу и немедленно ответить, основываясь на посимвольной обработке, как это делает редактор EDT в экранном режиме. Однако в этом режиме возникает проблема - клавиша удаления DEL и комбинация CTRL/U не могут быть использованы как обычно. Чаще всего по мере поступления вводимая информация сохраняется в массиве операционной системы, который называется буфером клавиатуры. В какой-то момент символ удаления и CTRL/U предписывают операционной системе удалить символ или строку из буфера. При активизировании операции ввода, информация будет пересылаться из буфера клавиатуры в буфер пользователя всякий раз, когда буфер заполнится полностью или когда встретится завершающий символ. Если буфер пользователя имеет длину 1 байт, то, как только буфер клавиатуры принимает одиночный символ, этот символ сразу же пересылается программе пользователя. Следовательно, в буфере клавиатуры не будет каких-либо данных, которые могли бы удаляться при вводе символа удаления или CTRL/U, поэтому эти управляющие символы никакого действия не оказывают.

CR=^X0D
BS=^X08
CTLU=^X15
DEL=^X7F
BSB:        .ASCII  <BS>" "<BS>
BUFFER:     .BLKB   100
CHBUF:      .BLKB   1
            .
            .
            .
READ:       MOVL    R5,-(SP)
            CLRL    R5              ;ИНИЦИАЛИЗАЦИЯ УКАЗАТЕЛЯ БУФЕРА
10$:        $QIOW_S CHAN=CH1,FUNC=#IO$_READVBLK!IO$M_NOFILTR,-
                    P1=CHBUF,P2=#1
            MOVB    CHBUF,BUFFER[R5] ; ВЗЯТЬ СИМВОЛ
            INCL    R5              ; И ПЕРЕМЕСТИТЬ УКАЗАТЕЛЬ
            CMPB    CHBUF,#CR       ; ВОЗВРАТ КАРЕТКИ?
            BEQL    20$
            CMPB    CHBUF,#DEL      ; УДАЛЕНИЕ СИМВОЛА?
            BEQL    30$
            CMPB    CHBUF,#CTLU     ; CTRL/U?
            BEQL    40$
            CMPL    R5,#100         ; В БУФЕРЕ ЕСТЬ ЕЩЁ МЕСТО?
            BLSSU   10$             ; ДА
            JMP     ERROR           ; НЕТ
20$:        MOVL    R5,R0           ; ВОЗВРАТИТЬ ДЛИНУ СТРОКИ В R0
            MOVL   (SP)+,R5         ; ВОССТАНОВИТЬ СОДЕРЖИМОЕ R5
            RSB                     ; ВОЗВРАТ

30$:        DECL    R5              ; ПЕРЕМЕСТИТЬ УКАЗАТЕЛЬ НАЗАД
            BEQL    10$             ; ЗАКОНЧИТЬ,ЕСЛИ БУФЕР ПУСТ
            BSBB    60$             ; УДАЛИТЬ СИМВОЛ
            BRB     10$             ; И ЗАКОНЧИТЬ ОБРАБОТКУ УДАЛЕНИЯ
40$:        DECL    R5
50$:        TSTL    R5              ; БУФЕР ПУСТ?
            BEQL    10$             ; ДА,ОБРАБОТКА ЗАВЕРШЕНА
            BSBB    60$             ; НЕТ,УДАЛИТЬ СИМВОЛ
            BRB     50$             ; ПОВТОРИТЬ ЕЩЁ РАЗ
60$:        DECL    R5              ; УДАЛИТЬ СИМВОЛ
            $QIOW_S CHAN=CH2,FUNC=#IO$_WRITEVBLK,-
                    P1=BSB,P2=#3    ; СТЕРЕТЬ СИМВОЛ С ЭКРАНА
            RSB                     ; ВОЗВРАТ

Рис. 11.4. Посимвольный ввод данных

В результате в режиме посимвольного ввода обычно необходимо, чтобы в самой программе выполнялось всё редактирование входной информации, включая обработку символов удаления и CTRL/U. Здесь кроется одна проблема, так как при нормальной работе символы удаления и CTRL/U программе пользователя не передаются. Следовательно, программа не знает о том, что пользователь нажал клавишу DEL для удаления символа или комбинацию клавиш CTRL/U для удаления целой строки. Это может вводить в заблуждение, поскольку изображение на экране терминала может быть таким, как если бы на самом деле отрабатывались эти управляющие символы. Одно решение этой проблемы состоит в том, чтобы перейти к модифицированному режиму работы, когда символы удаления и CTRL/U не обрабатываются системой, а передаются программе, как обычные символы. Это можно сделать, воспользовавшись кодом модификации IO$M_NOFILTR. Биты этого кода комбинируются с битами кода IO$_READVBLK с помощью логической операции ИЛИ (!), так что функция записывается следующим образом:

FUNC=#IO$_READVBLK!IO$M_NOFILTR

На рис. 11.4 показан фрагмент программы, в котором выполняется посимвольный ввод, введённые символы помещаются в буфер. Символ возврата каретки является символом завершения строки. Если нажата клавиша удаления, то один символ (если он есть) удаляется из буфера, а для стирания символа с экрана терминала выдаётся последовательность "возврат на шаг - пробел - возврат на шаг". Если нажата комбинация клавиш CTRL/U, то функция удаления повторяется, пока буфер не будет пуст (соответствующие символы стираются с экрана). В программе подразумевается, что каналы CH1 и CH2 были назначены устройствам SYS$INPUT и SYS$OUTPUT, как в более ранних примерах.

ИНТЕРАКТИВНЫЙ РЕЖИМ РАБОТЫ С ТЕРМИНАЛОМ

Когда программа поддерживает интерактивный режим работы с терминалом, важно, чтобы программа и терминал могли обеспечить некоторый вид диалога, в котором сохранялась бы постоянная синхронизация пользователя и программы. Нередко попытка написать программу, предназначенную для интерактивного режима работы, терпит неудачу. Программист вводит команду, запуская программу на выполнение, но ничего не происходит. Кажется, что ЭВМ ничего не делает. Вскоре программист убеждается, что программа всё-таки работает, но ожидает ввода. Решение, конечно, состоит в том, чтобы сопроводить каждую операцию ввода в программе выдачей какого-то наводящего сообщения, которое предупреждало бы пользователя о том, что ожидается ввод с терминала.

Наводящие сообщения могут быть простыми или сложными в зависимости от того, насколько много информации необходимо знать пользователю, чтобы правильно ответить на сообщение. Например, операционная система VAX/VMS перед началом сеанса работы предлагает пользователю сообщения User: и Password:, на которые ожидаются очень конкретные ответы. После начала сеанса работы обычно используется более простое сообщение $, которое следует понимать как приглашение к вводу любой команды операционной системы. В пользовательских программах применяются свои собственные наводящие сообщения, отличающие одну программу от других. Многие редакторы текста, например TECO и EDIT, для запроса команды используют приглашение "*". Приглашение отладчика имеет вид DBG>.

Для облегчения использования наводящих сообщений при вводе, макроинструкции $QIO поддерживают дополнительную функцию чтения IO$_READPROMPT. Эта функция имеет два дополнительных параметра P5 и P6. Параметр P5 определяет адрес строки сообщения, а параметр P6 задаёт длину этой строки. Строкой наводящего сообщения может быть любая символьная строка, но, как правило, она должна начинаться с символов возврата каретки и перевода строки, чтобы гарантировать появление наводящего сообщения на новой строке. На рис. 11.5 показан фрагмент программы, в котором выполняется ввод в 100-байтовый буфер после вывода наводящего сообщения "Введите данные -". За исключением вывода наводящего сообщения, функция IO$_READPROMPT работает точно так же, как и функция IO$_READVBLK. В программе на рис. 11.5 предполагается, что, как и в предыдущих примерах, по адресу CH1 будет находиться некоторый номер канала. Отметим также использование параметра IOSB, описанного выше.

CR=^X0D
LF=^X0A
PROMPT:     .ASCII      <CR><LF>"ВВЕДИТЕ ДАННЫЕ - "
PROMPT_END:
PROMPT_LOC:
            .ADDRESS    PROMPT
BUFFER:     .BLKB       100
SBLOK:      .BLKQ       1
BCOUNT=SBLOK+2
            .
            .
            .
            $QIOW_S     CHAN=CH1,FUNC=#IO$_READPROMPT,-
                        P1=BUFFER,P2=#100,IOSB=SBLOK,-
                        P5=PROMPT_LOC,P6=PROMPT_END-PROMPT
            .
            .
            .

Рис. 11.5. Ввод с наводящим сообщением

Следует заметить, что выдача наводящего сообщения может быть осуществлена и без использования функции IO$_READPROMPT, поскольку операция ввода всегда может сопровождаться в программе обычной операцией вывода, выполняемой функцией IO$_WRITEVBLK, Однако использование функции ввода с наводящим сообщением даёт несколько преимуществ. Во-первых, здесь несколько выше эффективность, так как требуется только одно обращение к $QIO. Во-вторых, поскольку наводящее сообщение связывается со вводом, то операционная система знает, что необходимо преодолеть действие управляющего символа CTRL/О и даже повторить сообщение, если пользователь нажал комбинацию клавиш CTRL/U.

11.5. ДОПОЛНИТЕЛЬНЫЕ СООБРАЖЕНИЯ ПО ОРГАНИЗАЦИИ
ВВОДА-ВЫВОДА С ОЧЕРЕДЯМИ

ОШИБКИ

До сих пор во всех примерах предполагалось, что всё шло прекрасно и не было необходимости беспокоиться об ошибках. Но любому человеку, имеющему опыт работы с ЭВМ, известно, что это плохая посылка. Ошибки могут возникать по самым разным причинам. В действительности даже в случае простых программ крайне редко бывает так, чтобы программа заработала при первом же запуске. Поэтому тот, кто использует системные сервисные средства ввода-вывода, должен быть готов иметь дело с ошибками.

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

Все такие системные подпрограммы используют для возвращения информации об ошибке регистр R0. Если самый младший бит (бит 0) регистра R0 равен 1, это означает успешное завершение подпрограммы. Если он равен 0, то была обнаружена ошибка. Состояние самого младшего бита регистра R0 легко можно проверить с помощью инструкции BLBC или BLBS. Следовательно, чтобы проверить, успешно ли осуществлено назначение устройства, можно использовать следующую последовательность инструкций:

        $ASSIGN_S   DEVNAM=NAME,CHAN=CH1
        BLBS        R0,10$  ; ОШИБКА?
        JMP         ERROR   ; ДА, ЗАВЕРШИТЬ ОБРАБОТКУ
10$:                        ; НЕТ, ПРОДОЛЖИТЬ

Если младший бит регистра R0 не равен 1, то три младших бита показывают степень серьёзности ошибки, а остальные биты содержат код, идентифицирующий ошибку. Каждой ошибке соответствует определённое символическое имя. Например, код ошибки, обозначенный как SS$NOPRIV, сигнализирует о том, что пользователь не имеет достаточных привилегий для операции, которую он пытается выполнить. Но в большинстве случаев единственной причиной, по которой необходимо знать фактический код ошибки, является желание распечатывать сообщение об ошибке, информирующее пользователя о причине ошибки. Поскольку существуют сотни различных кодов ошибок, написание программы для распечатки всех возможных сообщений об ошибках потребовало бы значительных усилий. Однако сообщения об ошибках можно запросить у операционной системы, указав аргумент в макровызове $EXIT_S. Если это сделано, операционная система будет проверять значение аргумента и, если младший бит нулевой, распечатывает сообщение об ошибке, поясняющее причину ошибки. Таким образом, вместе с тремя предыдущими строчками программы для обработки ошибок может быть использована следующая:

ERROR:  $EXIT_S R0

Если в регистр R0 помещено значение SS$NOPRIV, то на терминале пользователя будет распечатано или занесено в файл регистрации при пакетной обработке сообщение вроде следующего:

SYSTEM-F-NOPRIV, No privilege for attempted operation, reason ...

ЛОГИЧЕСКИЕ ИМЕНА УСТРОЙСТВ

При написании программы не очень хорошо помещать конкретное имя какого-то физического устройства в код программы по той причине, что в зависимости от разных пользователей фактически используемое устройство может меняться. Например, с помощью команды SHOW TERMINAL пользователь может определить, что конкретным физическим портом связи, через который он начал сеанс работы, является TTC3. Это означает, что при назначении канала для ввода-вывода с терминала должно использоваться имя устройства TTC3.

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

.ASCID  "ТТС3"

Проблема заключается в том, что после окончания сеанса работы сегодня, при новом сеансе работы завтра (сегодня же, но позднее) порт, назначенный вашему терминалу, может быть уже не TTC3, a TTD4, поскольку порт TTC3, вероятно, был уже выделен какому-то другому пользователю. И эта программа не сможет больше работать и вызовет ошибку нарушения привилегий.

Одно возможное решение состоит в том, чтобы вместо включения имени устройства в программу в виде константы пользователь вводил имя устройства как входные данные. С таким решением сопряжены две проблемы. Первая - это неудобство, что пользователи вынуждены вводить эти данные, так как пользователь может и не знать, с какого именно устройства он начал сеанс работы, и ему придётся воспользоваться командой SHOW, чтобы узнать это. Вторая же проблема представляет логическую дилемму. Как может программа взаимодействовать с терминалом, чтобы определить имя устройства, если она не знает имени терминала? Решение этой проблемы состоит в использовании логических имён вместо имён физических устройств.

Логическое имя устройства - это символьная строка, которая назначается вместо имени физического устройства. Такие имена могут изменяться пользователем и назначаться устройством с помощью команды ASSIGN операционной системы VAX/VMS. Например, если пользователь хочет назначить для терминала имя SCOPE, может быть введена следующая команда:

ASSIGN TTC3: SCOPE

Это команда операционной системы, которая вводится с терминала в ответ на приглашение $.

Если такое назначение было сделано, программа может воспользоваться именем SCOPE для назначения канала для устройства TTC3. Программа может быть написана так:

NAME:   .ASCID  "SCOPE"
CH:     .BLKL   1
        .
        .
        .
        $ASSIGN_S DEVNAM=NAME,CHAN=CH

Таким образом решается вторая проблема, связанная с невозможностью в программе назначить канал, не зная физического имени терминала; но всё же необходимо знать это имя, иначе пользователь не сможет ввести команду назначения. Одно решение состоит в автоматическом назначении нескольких логических имён устройствам всякий раз, когда пользователь начинает сеанс работы. В одном примере были использованы логические имена SYS$INPUT и SYS$OUTPUT. Данные имена автоматически назначаются соответствующим устройствам ввода и вывода для выполнения обычного ввода и вывода. Для пользователей, работающих в интерактивном режиме, эти имена назначаются для терминала пользователя. В режиме пакетной обработки они назначаются для входного и выходного потоков пакета.

Поэтому улучшенная реализация вышеприведённого примера, может быть такой:

NAME:   .ASCID  "SYS$INPUT"
CH:     .BLKL   1
        .
        .
        .
        $ASSIGN_S DEVNAM=NAME,CHAN=CH

Канал CH будет автоматически назначен для терминала пользователя.

ФОРМАТНЫЙ ВЫВОД

Одной из более трудных задач программирования является подготовка выходной информации в подходящем формате, облегчающем чтение. Сюда входят формирование сообщений и преобразование чисел из внутренней формы представления в какую-либо другую подходящую форму представления - обычно десятичную, иногда - шестнадцатеричную. Большинство языков высокого уровня включают некоторые средства управления форматом вывода. В Фортране для этих целей используется оператор FORMAT, в Коболе - оператор PICTURE, а в Паскале - информация о формате содержится в списках вывода.

В языке ассемблера форматирование ввода и вывода обычно требует, чтобы программист писал пространные программы символьной обработки. Чтобы упростить эту задачу, в операционную систему VAX/VMS включены некоторые подпрограммы для форматирования выходной информации, которые могут вызываться с помощью системной макроинструкции $FAO_S. Действие этой макроинструкции во многом такое же, как и оператора Фортрана FORMAT; здесь также имеется управляющая строка, которая описывает формат вывода. Однако при этом используется совершенно другой набор кодов. В оставшейся части этого раздела описаны некоторые наиболее полезные управляющие коды для макроинструкции $FAO_S. Полное описание читатель может найти в руководстве "VAX/ VMS System Services Reference Manual".

Макроинструкция $FAO_S может быть вызвана несколькими способами. Простейший способ включает три обязательных параметра и множество необязательных параметров, обязательными параметрами являются: CTRSTR, OUTBUF и OUTLEN. Параметр CTRSTR - это адрес дескриптора символьной строки, содержащей управляющую строку. Вероятно, что эта строка определяется директивой .ASCID. Параметр OUTBUF - это адрес дескриптора байтового массива, куда помещается преобразованная выходная информация. При ассемблировании дескриптора образуется два длинных слова. Одно содержит длину байтового массива, а другое - адрес байтового массива, в который будет помещаться выходная информация. Параметр OUTLEN - это адрес слова, в которое будет помещена длина преобразованной символьной строки. Обратите внимание, что поле длины дескриптора OUTBUF определяет максимальный размер выводимой строки. Реальная строка, сформированная с помощью макроинструкции $FAO_S, может быть меньше. Значение фактической длины строки будет находиться в слове OUTLEN. Заметим, что если подпрограммы, вызываемые при расширении макроинструкции $FAO_S, завершаются успешно, то это значение всегда будет меньше или равно длине, заданной в дескрипторе буфера. При попытке сформировать строку более длинную, чем вмещает буфер, будет возвращена усечённая строка вместе с соответствующим значением ошибки, помещённым в регистр R0.

Управляющая строка содержит последовательность символьных строк и спецификаторов формата, определяющих формат вывода. Любая строка, которая не распознаётся как директива управления форматом, обрабатывается, как символьная строка, которая должна быть выведена. Такие строки не заключаются, как в операторе Фортрана FORMAT, в кавычки. Все директивы управления форматом начинаются с восклицательного знака. Поэтому любая символьная строка, которая не начинается с восклицательного знака, помещается в буфер вывода без какой-либо обработки.

После восклицательного знака следуют значение длины поля вывода, которое может быть опущено, и код спецификатора формата, состоящий из одного или двух символов. Например, код SL задаёт преобразование содержимого длинного слова в десятичное число со знаком. Следовательно, следующей управляющей строкой можно воспользоваться для распечатки ответа:

.ASCID "ПОЛУЧЕН ОТВЕТ !10SL(ДЕСЯТИЧНОЕ)."

Это эквивалентно такому оператору формата Фортрана:

100 FORMAT('ПОЛУЧЕН ОТВЕТ',I10,'(ДЕСЯТИЧНОЕ).')

На рис. 11.6 представлен неполный список управляющих кодов, используемых с макроинструкцией $FAO_S, который включает наиболее важные коды управления форматом. Полную информацию об использовании макроинструкции $FAO_S читатель может найти в руководстве "VAX/VMS System Services Reference Manual".

Каждая директива управления форматом может включать длину поля вывода, помещённую между восклицательным знаком и двухсимвольным кодом спецификатора формата. Длина поля вывода - это десятичное число, определяющее число символов, которые могут в нём разместиться. Если указана, например, длина !6SL, то преобразованное значение будет расширено или усечено так, чтобы оно занимало точно 6 позиций. Если длина поля не указана, то она будет равна минимальной длине поля, необходимого для представления числа. Заметим, однако, что если длина буфера недостаточно велика, то произойдёт усечение строки.

Следует также отметить, что каждому спецификатору формата должно быть поставлено в соответствие подлежащее преобразованию значение, как, например, в Фортране, где каждой спецификации формата I6 должна соответствовать переменная. Это же делается и при работе с макроинструкцией $FAO_S с помощью параметров P1, P2, P3, ... Может быть задано до двенадцати параметров. Каждому параметру соответствует адрес ячейки, содержащей подлежащее преобразованию значение. Следовательно, если ячейки A, B и C содержат числа в формате байта, слова и длинного слова, то для их преобразования в десятичное представление может использоваться фрагмент программы, показанный на рис. 11.7.

Код

Функции

!XB

Преобразовать байт в шестнадцатеричное

!XW

Преобразовать слово в шестнадцатеричное

!XL

Преобразовать длинное слово в шестнадцатеричное

!SB

Преобразовать байт в десятичное со знаком

!SW

Преобразовать слово в десятичное со знаком

!SL

Преобразовать длинное слово в десятичное со знаком

!%Т

Занести в буфер значение времени

!%D

Занести в буфер дату и время

Рис. 11.6. Коды управления форматом для макроинструкции $FAO_S

CR=^X0D
LF=^X0A
CSTR:   .ASCID  <LF>"БАЙТ !SB СЛОВО !SW ДЛИННОЕ СЛОВО !SL"<CR>
BDES:   .LONG   80,BUF
BUF:    .BLKB   80
BSIZ:   .BLKL   1
A:      .BLKB   1
B:      .BLKW   1
C:      .BLKL   1
        .
        .
        .
        $FAO_S  CTRSTK=CSTR,OUTBUF=BDES,OUTLEN=BSIZ,-
                P1=A, P2=B, P3=C

Рис. 11.7. Пример форматного вывода с помощью макроинструкции $FAO_S

Очевидно, что не только для таких спецификаторов формата, как !SW, требуется наличие соответствующих им параметров, но и для спецификаторов, задающих преобразование значений даты и времени суток. Это связано с тем, что эти спецификаторы могут применяться не только для преобразования текущей даты и времени, но также и для преобразования предварительно сохранённых значений даты и времени, которые могут потребоваться для идентификации версий файлов или систем. Простейшее применение таких преобразований - распечатка текущей даты и времени. В подобных случаях параметр должен заменяться значением, равным #0. Поэтому для распечатки текущей даты и времени может применяться следующая последовательность операторов:

TSTR:   .ASCID  <LF>"ВРЕМЯ=!%T."<CR>
        .
        .
        .
        $FAO_S  CTRSTR=TSTR,OUTBUF=BDES,OUTLEN=BSIZ,-
                P1=#0
        $QIO_S  CHAN=CH1,FUNC=#IO$_WRITEVBLK,-
                P1=BUF,P2=BSIZ

Предполагается, что на этот фрагмент программы распространяется действие определений имён, приведённых на рис. 11.3 и 11.7.

УПРАЖНЕНИЯ 11.1

  1. Напишите программу, используя макроинструкцию $QIOW_S для считывания строки, вводимой с клавиатуры терминала, и для последующей распечатки этой строки на терминале 5 раз.
  2. Напишите программу, которая бы считывала символьную строку длиной до 30 символов, а затем пыталась открыть канал ввода-вывода, используя эту строку как имя устройства. Используйте подпрограмму PNUM для печати назначенного номера канала, если удалось сделать назначение, или в противном случае - кода ошибки, помещаемого в регистр R0.
  3. Используйте команду ASSIGN операционной системы VAX/VMS, чтобы назначить логическое имя XXX для вашего текущего терминала. Например, если ваш терминал имеет имя TTA3:, введите команду
    ASSIGN  TTA3: XXX

    Затем назначьте имя YYY для имени XXX и имя ZZZ для имени YYY с помощью команд

    ASSIGN  XXX YYY
    ASSIGN  YYY ZZZ

    Используйте программу, которую вы написали для п. 2 упр. 11.1, и попытайтесь назначить номера канала для вашего терминала и для устройств с логическими именами XXX, YYY и ZZZ. Все ли назначения выполняются? Если нет, то сколько уровней логических имён допустимо?

  4. Перепишите подпрограммы IOINIT, RNUM и PNUM, описанные в приложении Б, так, чтобы использовалась макроинструкция $QIOW_S и связанные с ней подпрограммы вместо $PUT и $GET, как это сделано в указанных подпрограммах.
  5. Повторите п. 4, но модифицируйте алгоритмы так, чтобы отсутствовал массив для хранения вводимой строки, а ввод осуществляется по одному символу. Это потребует также, чтобы программа включала обработку символов DEL и CTRL/U.

11.6. ФАЙЛЫ И ЗАПИСИ

ВНЕШНИЕ ЗАПОМИНАЮЩИЕ УСТРОЙСТВА БОЛЬШОЙ ЁМКОСТИ

Чтобы ЭВМ была практически полезной, она должна обладать способностью хранить большие объёмы информации на каких-либо внешних запоминающих устройствах большой ёмкости. В больших вычислительных системах применяются разнообразные устройства подобного рода. Наиболее распространёнными внешними запоминающими устройствами большой ёмкости, применяемыми для ЭВМ семейства VAX, являются магнитные диски и магнитные ленты. Информация на таких устройствах записывается в виде последовательно организованной совокупности блоков. Привилегированные пользователи могут осуществлять доступ к дискам и лентам по назначенным для диска или ленты каналам поблочно, используя операции ввода-вывода с очередями (макроинструкция $QIO). Но непривилегированные пользователи для доступа к информации, расположенной на этих устройствах, будут пользоваться другими методами. В оставшейся части главы будут описаны эти последние методы ввода-вывода.

Хотя и диски, и ленты имеют последовательную блочно-организованную структуру данных, физические характеристики этих двух типов устройств совершенно различны, что обусловливает радикально разные способы их использования. Ленты представляют собой длинные узкие полосы магнитного материала, на который производится запись, намотанного на катушки. Длина 2400 футов при ширине ленты половина дюйма - это стандартные параметры для промышленно совместимых полномерных катушек. Блоки информации размешаются один за другим по длине ленты. На рис. 11.8 показано, как располагается информация на ленте.

Чтение или запись на ленту производятся при её перемещении мимо закреплённой неподвижно магнитной головки, подобно тому как это делается в бытовых магнитофонах. Чтобы прочитать информацию, расположенную в конце ленты, приходится перематывать вперёд 2400 футов ленты. Если затем потребуется информация, расположенная в начале ленты, то снова придётся перемотать назад те же самые 2400 футов. Понятно, что это занимает значительное время (возможно, несколько минут). По этой причине доступ к данным на магнитной ленте обычно осуществляется не в произвольном порядке, а во вполне определённой последовательности.

Рис. 11.8. Структура магнитной ленты

Диски собираются из одной или нескольких круглых пластин, поверхности которых покрыты магнитным материалом, нанесённым на пластины в виде кольца. Эти кольца образуют поверхности записи диска. Информация записывается на концентрических дорожках, на которые разбивается кольцо магнитного материала на каждой поверхности. На каждой поверхности может быть более чем 1000 дорожек, а на каждой дорожке записываются несколько блоков фиксированной длины, называемых секторами. На магнитных лентах, отвечающих промышленному стандарту, расположение блоков не фиксировано, при записи они занимают изменяющееся по объёму пространство. Положение секторов дисков на дорожках фиксировано. Это достигается с помощью физических маркеров, таких как прорези или отверстия на диске, или с помощью меток, записываемых на самих дорожках. Первый метод называют жёсткой разметкой секторов, второй - программной разметкой секторов. Прежде чем диск с программной разметкой секторов может быть использован, он должен быть сформатирован посредством записи на каждой дорожке маркеров, отмечающих местоположение секторов. Типичное устройство пакетного диска, состоящего из нескольких пластин, показано на рис. 11.9.

Благодаря рассмотренной выше организации каждый блок информации на диске может быть найден при указании специального адреса, имеющего следующие части:

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

Магнитные головки перемещаются очень быстро, так что только доли секунды занимает перемещение с самых внутренних на самые внешние дорожки. Кроме того, и диски вращаются весьма быстро, совершая 5-100 полных оборотов в секунду. В результате этого дисковая система может осуществлять доступ к любому блоку информации на диске за доли секунды. Сравните это с магнитной лентой, где могут потребоваться минуты для перехода с одного конца ленты на другой. Поэтому магнитные ленты имеет смысл использовать только для последовательного доступа, продвигаясь от одного конца ленты к другому. Для дисков может использоваться произвольный доступ, т.е. доступ к блокам осуществляется прямо, независимо от их положения на диске.

Рис. 11.9. Устройство магнитного диска

Как ещё более важный момент, касающийся специфики организации информации на лентах и дисках, рассмотрим запись информации. Как уже упоминалось, на дисках имеются специальные маркеры, которые точно определяют местоположение блоков информации на диске. Обычные магнитные ленты таких меток не имеют. Следовательно, информацию на ленту можно записывать только последовательно. Более того, как правило, невозможно перемотать назад и перезаписать блок информации в середине ленты без того, чтобы не испортить всю следующую далее информацию. С другой стороны, любой сектор диска может быть перезаписан, не затрагивая информацию в любых других секторах.

ФАЙЛЫ

Обычно объём информации, который способно вместить внешнее запоминающее устройство большой ёмкости, намного превосходит потребности отдельного приложения. Поэтому общепринято разделять информацию на таких устройствах на совокупности данных, называемых файлами[2].

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

Диски же позволяют выполнять операции ввода и вывода с произвольной выборкой информации. Поэтому файл на диске может состоять из произвольно расположенных блоков информации, разбросанных по разным секторам диска. При записи файла система ввода-вывода должна для блоков файла отыскать неиспользованные секторы. Дисковые файлы могут модифицироваться или удаляться. Если файл удаляется с диска, его блоки становятся доступными, как неиспользованные секторы диска. Чтобы отслеживать все эти операции, требуется система каталогов. Каталог - это некоторая область диска, которая содержит информацию о расположении файлов на диске, расположении блоков файлов и т.п. Кроме того, диски большой ёмкости используются для обслуживания большого числа пользователей. Поскольку каждый пользователь может иметь свой собственный каталог файлов, могут даже существовать каталоги каталогов.

Сказанное оставляет впечатление нечто сложного, но это сделано преднамеренно. Написание программ для обслуживания файлов на диске - это не только крайне сложное дело, но и опасное. Диск, содержащий 100 тыс. блоков информации, может быть приведён в полную непригодность при потере одного блока информации в каталоге. Поскольку информация рассеяна по диску, каталог следует вести очень тщательно, обеспечивая его максимальную защиту, чтобы можно было отыскать какую-либо информацию. Как следствие этого, пользователям системы редко приходится программировать операции такого рода. В действительности существуют механизмы защиты, препятствующие любым программам, кроме самых привилегированных, выполнять любые операции, которые могли бы испортить справочную информацию на диске. По этой причине доступ пользователей к дисковым файлам обычно осуществляется с помощью системных программ, работающих на более высоком уровне, чем операции подсистемы $QIO, описанные в начале этой главы. В частности. в операционной системе VAX/VMS файловый ввод-вывод выполняется с помощью службы управления записями (подсистема RMS), описание которой даётся в разд. 11.7.

ЗАПИСИ

Так же как диски и ленты подразделяются на файлы, обычно необходимо файлы разделять на более мелкие порции информации, которые называют записями. Исторически сложилось так, что записи служили синонимами блоков, так как блок - это наименьшая единица информации, которая может быть считана за одну физическую операцию устройства. Однако быстро пришло понимание того, что единицы информации, с которыми удобно иметь дело в программных приложениях, не совпадают с размерами физических блоков. В результате этого или очень неэффективно работали программы, так как небольшие порции информации запоминались в блоках, способных вместить больший объём информации, или программист вынужден был изменять структуру программ, чтобы достичь эффективного использования внешних запоминающих устройств большой ёмкости. В большинстве случаев программист хотел бы переложить эту работу на кого-нибудь другого. Поэтому во многих развитых системах ввода-вывода файлы разбивают на записи, которые могут быть приспособлены под потребности конкретного приложения. Затем эти записи упаковывают так, чтобы они эффективно укладывались в физическую блочную структуру конкретного устройства ввода-вывода.

Следовательно, запись представляет собой такую порцию информации, содержащейся в файле, которую удобно обработать за один приём. Наиболее общим типом записи является вводимая или выводимая строчка текста. Чаще всего записи считываются и записываются одна за другой. Это называется последовательным доступом. Однако для некоторых приложений требуется существование возможности считывать записи из любого места файла. Это называется произвольным или прямым доступом. Большинство операционных систем обеспечивает прямой доступ к записям в файлах, хранимым на устройствах прямого доступа.

Для того чтобы использовать стандартное системное программное обеспечение для выполнения операций чтения и записи, программист должен определить структуру файла. Некоторые характеристики файла, которые должны быть определены:

В операционной системе VAX/VMS эти операции выполняются подсистемой RMS, описание которой даётся в следующем разделе.

11.7. ПОДСИСТЕМА УПРАВЛЕНИЯ ЗАПИСЯМИ (RMS)

Служба управления записями (RMS) операционной системы VAX/VMS предназначена для выполнения операции ввода-вывода при работе с файлами. Это совокупность системных подпрограмм, которые необходимы большинству программистов для работы с файлами. Как и в случае ввода-вывода с очередями, операции подсистемы RMS выполняются с помощью ряда системных подпрограмм, обращение к которым осуществляется с помощью макроинструкций. По существу, имеется два типа макроинструкций, применяемых для RMS. Макроинструкции одного типа порождают блоки данных, которые содержат всю необходимую информацию о файле или о конкретных записях файла. Макроинструкции другого типа порождают исполняемый программный код, предназначенный для вызова подпрограммы, необходимой для выполнения операции подсистемы RMS.

БЛОКИ ДОСТУПА К ФАЙЛУ

Блоки доступа к файлу - это блоки данных, содержащие всю информацию о некотором файле, которую должен обеспечить пользователь для выполнения операции ввода-вывода при работе с файлом. Среди прочего блок содержит имя файла и информацию об организации файла, методах доступа и типах записей. Блок доступа к файлу формируется с помощью макроинструкции $FAB. Если файл относится к наиболее распространённому типу файлов последовательного доступа с записями переменной длины, то большая часть блока будет определена по умолчанию, так что необходимо указать только имя файла.

Имена файлов представляют собой символьные строки, которые однозначно идентифицируют файлы. Полное имя файла имеет следующие части:

  1. Имя узла - определяет конкретную ЭВМ VAX в сети вычислительных машинах семейства VAX.
  2. Имя устройства - определяет, какой используется диск, какая лента и т.п.
  3. Имя каталога - определяет используемый каталог или подкаталог, поскольку на диске может быть много каталогов или подкаталогов.
  4. Имя файла - определяет конкретный файл в совокупности с типом файла (см. п. 5) и номером версии (см. п. 6).
  5. Тип файла - определяет предназначение файла, сообщая, является ли файл программой на макроассемблере, программой на Фортране, объектным файлом или чем-то другим.
  6. Номер версии - определяет версию файла, изменяющуюся при модификации или перезаписи файла.

Эти части имени файла отделяются друг от друга различными знаками пунктуации, такими как : , [ ] и ; . Поэтому полное имя может выглядеть так:

TEMPVAX::DPA2:[KANDS PROGS]EXAMPLE.MAR;4

Части этого описания:

Поскольку полное имя файла может быть очень длинным, большинство частей, если они не указаны, подразумеваются по умолчанию. Обычная спецификация файла, используемая по умолчанию, такова:

  1. Имя узла - узел сети, в котором выполняется программа.
  2. Имя устройства - диск, назначаемый по умолчанию для пользователя в начале сеанса работы.
  3. Каталог или подкаталог - каталог, устанавливаемый по умолчанию для пользователя в начале сеанса работы.
  4. Имя файла - нет значения по умолчанию.
  5. Тип файла - нет значения по умолчанию.
  6. Номер версии - самый старший для этого файла.

Большинство этих умолчаний управляются посредством логических имён, автоматически назначаемых для пользователя в начале сеанса работы. Эти умолчания могут быть изменены с помощью команды DEFINE. Кроме того, специальные значения по умолчанию могут быть заданы в блоке доступа к файлу. В результате, если местонахождение вышеупомянутого файла удовлетворяет всем условиям умолчания, то он может быть назван просто как

EXAMPLE.MAR

Аналогично можно сформировать блок доступа к файлу, который позволит осуществлять чтение и запись файла, используя последовательный доступ, с помощью следующего макровызова:

F1:     $FAB    FNM=<EXAMPLE.MAR>

Обратите внимание! Угловые скобки являются необходимой частью синтаксиса!

Этот макровызов генерирует две структуры. Первая - это сам блок доступа к файлу. Однако в действительности блок доступа к файлу не содержит имени файла, поскольку этот блок имеет фиксированную длину и может содержать только данные фиксированной длины. Поэтому в блоке доступа к файлу содержатся адрес и длина строки имени файла, помещённые соответственно в длинном слове и слове. Реальное имя файла хранится, как строка в коде ASCII, в отдельной программной секции с именем $RMSNAM. Заметим, что подсистема RMS требует, чтобы блоки доступа к файлу и блоки доступа к записи (которые будут обсуждаться в следующем разделе) были выровнены по границе длинного слова. То есть адрес начала блока должен быть кратен четырём. Чтобы гарантировать, что блоки доступа к файлу и блоки доступа к записи выровнены по границе длинного слова, лучше всего организовать их как отдельные программные секции. Например, можно использовать следующую последовательность операторов:

        .PSECT  IOBLOCKS,LONG
F2:     $FAB    FNM=<FILE1.TYP>
F3:     $FAB    FNM=<FILE2.TYP>
        .
        .
        .
        .PSECT

Заметим, что программа может иметь любое число блоков доступа к файлам. Однако в любой конкретный момент времени для каждого файла должен использоваться только один блок.

Заметим также, что имена файлов, применяемые с блоком доступа к файлу, могут содержать как логические, так и реальные имена для частей или для всей спецификации файла. Например, вызов макроинструкции $FAB может быть сделан по-разному:

F4:     $FAB    FNM=<SYS$INPUT>
F5:     $FAB    FNM=<USER:FILE.TYP>

Использование логических имён SYS$INPUT и SYS$OUTPUT позволяет направлять ввод-вывод на терминал пользователя. Если программа выполняется в пакетном режиме, то эти логические имена относятся к входному и выходному потокам пакета.

БЛОКИ ДОСТУПА К ЗАПИСИ

Блоки доступа к записи используются для того, чтобы обеспечить всю информацию, необходимую для чтения или записи какой-либо отдельной записи файла. С блоком доступа к записи связана следующая информация:

  1. Ссылка на блок доступа к файлу для файла, с которым выполняются операции ввода-вывода.
  2. Местоположение в памяти буфера записи.
  3. Длина записи.
  4. Длина буфера.
  5. Информация, необходимая для обнаружения записи, для файла прямого доступа.
  6. Наводящие сообщения для терминального ввода.
  7. Информация для синхронизации, позволяющая организовать одновременную работу с несколькими файлами.

Часть этой информации должна передаваться подсистеме RMS программой пользователя. Другая часть информации поступает в программу пользователя от подсистемы RMS. Какая и куда информация передаётся, зависит от того, выполняется ввод или вывод. Например, при записи записей переменной длины фактическая длина записи определяется программой пользователя. Однако при чтении записей переменной длины длина определяется тем, что реально имеется в файле.

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

Используя эту информацию, можно применить макроинструкцию $RAB для формирования блока доступа к записи. Чтобы вывести запись, можно воспользоваться следующим макровызовом:

RI:      $RAB    FAB=OUTFILE,RBF=OUTBUF,RSZ=OBS

Указанные аргументы имеют следующий смысл:

Аргумент

Значение

OUTFILE

Метка блока доступа к файлу, в который производится запись

OUTBUF

Метка строчки программы с директивой .BLKB или директивой .ASCII, определяющая место, где хранятся данные, подлежащие записи

OBS

Символическое имя, или число, или выражение, определяющее длину записываемой строки (записи)

Для того чтобы считать запись, необходим макровызов следующего типа:

RI:     $RAB    FAB=INFILE,UBF=INBUF,USZ=IBS

В этом случае аргументы имеют следующий смысл:

Аргумент

Значение

INFILE

Метка блока доступа к файлу, из которого производится чтение

INBUF

Метка блока памяти, куда помещается считываемая запись

IBS

Длина буфера ввода

Обратите внимание, что IBS задаёт не длину записи, которая может быть больше или меньше, а максимальный объём памяти, доступной для размещения записи. Поэтому, если делается попытка чтения записи большей длины, информация будет теряться. Если такое произошло, в регистр R0 будет возвращён код предупреждения, которому соответствует символическое имя RMS$_RTB. (см. в предыдущих разделах о возврате кода ошибки и в последующем разделе о работе подсистемы RMS).

        .PSECT  IOBLOK,LONG
F1:     $FAB    FNM=<SYS$INPUT>
F2:     $FAB    FNM=<SYS$OUTPUT>
R1:     $RAB    FAB=F1,UBF=IBUF,USZ=80
R2A:    $RAB    FAB=F2,RBF=MSGA,RSZ=MSGAE-MSGA
R2B:    $RAB    FAB=F2,RBF=MSGB,RSZ=MSGBE-MSGB
R2C:    $RAB    FAB=F2,RBF=OBUF
        .PSECT
MSGA:   .ASCII  "СООБЩЕНИЕ A"
MSGAE:
MSGB:   .ASCII  "СООБЩЕНИЕ В"
MSGBE:
IBUF:   .BLKB   80
OBUF:   .BLKB   132

Рис. 11.10. Типовое определение блоков доступа к файлу и записям

Заметим, что для вывода используются параметры RBF и RSZ, а для ввода - параметры UBF и USZ. Почему это так, станет ясно позднее.

Как и блоки доступа к файлу, блоки доступа к записи должны быть выровнены по границе длинного слова. Легче всего это сделать, если поместить их все в одну программную секцию, выровненную по границе длинного слова. На рис. 11.10 показан фрагмент программы, в котором определяются блоки доступа к файлу и записи для вывода на терминал.

Обратите внимание, что блок доступа к записи с меткой R2C: не содержит параметра RSZ. Это сделано потому, что, как случается в большинстве программ, длина выводимой строки не известна до тех пор, пока выполнение программы не дойдёт до того места, когда должна быть осуществлена операция записи. Имеются средства для получения отсутствующей информации во время выполнения программы. Это относится как к блокам доступа к файлу, так и к блокам доступа к записи и позволяет учесть изменения любой информации, включая имена файлов, адреса буферов, длину записей и т.п.

11.8. ИСПОЛЬЗОВАНИЕ ПОДСИСТЕМЫ RMS

ОПЕРАЦИИ ПОДСИСТЕМЫ RMS

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

1.

$OPEN

Выполняет поиск по каталогу и другие операции, необходимые для того, чтобы дать возможность подсистеме RMS прочитать записи из уже существующего файла[3].

2.

$CREATE

Подготавливает пространство на диске и в каталоге для нового файла, который будет записан на диск[3]. Используется вместо $OPEN для создания новых выходных файлов.

3.

$CONNECT

Подключает файл к потоку записей, что завершает процесс открытия файла для операций над записями и связывает блок доступа к записи с блоком доступа к файлу и с потоком записей. Эта операция должна выполняться для каждого файла после того, как он был открыт или создан, но перед тем, как могут быть выполнены любые другие операции, использующие блок доступа к записи. Если для файла используется более одного блока доступа к записи, то единственным активным блоком будет подключенный самым последним. Поэтому, прежде чем новый блок доступа к записи может быть использован, он должен быть подключён. Предыдущий блок будет отключён и не может быть повторно использован до тех пор, пока не будет подключён повторно. Макроинструкция $CONNECT заставляет систему установить внутренние буферы, соответствующие структурам физических блоков и логических записей файла.

4.

$GET

Осуществляет считывание записи в буфер ввода.

5.

$PUT

Осуществляет вывод записи из буфера вывода.

6.

$CLOSE

Завершает работу с файлом и позволяет использовать блок доступа к файлу для ввода и вывода из другого файла. Макроинструкция $EXIT_S обеспечивает автоматическое закрытие всех открытых файлов. Однако лучшая программистская практика рекомендует закрывать открытые файлы явным образом. Хотя операционная система VAX/VMS очень хороша для пользователя в смысле закрытия файлов, иногда информация, записанная в выходной файл, может быть потеряна, если программа прекращает работу прежде, чем файл будет закрыт. Именно закрытие файла завершает формирование записи о файле в каталоге.

В простейших случаях необходимо, чтобы в макровызовах указывались только блок доступа к файлу и блок доступа к записи, поскольку в блоках доступа содержится вся необходимая информация. Так как в макроинструкциях $OPEN, $CREATE и $CLOSE выполняются операции с файлами, в их макровызовах должен быть указан блок доступа к файлу. Аналогично в макровызовах $CONNECT, $GET и $PUT указывается блок доступа к записи.

Если предположить, что блоки доступа к файлу и блоки доступа к записи были определены так, как показано на рис. 11.10, то операции ввода с устройства SYS$INPUT могут быть выполнены с помощью следующих шагов программы:

$OPEN       FAB=F1      ;ОТКРЫТЬ ФАЙЛ
$CONNECT    RAB=R1      ;ПОДКЛЮЧИТЬ R1 К СВОЕМУ FAB
.
.
.
$GET        RAB=R1      ;ЭТОТ ОПЕРАТОР МОЖЕТ БЫТЬ
.                       ;ПОВТОРЕН, ИЛИ ВЫПОЛНЯТЬСЯ В ЦИКЛЕ
.
.
$CLOSE      FAB=F1      ;ЗАКРЫТИЕ ВЫПОЛНЯЕТСЯ
$EXIT_S                 ;В КОНЦЕ ПРОГРАММЫ

Аналогично вывод на устройство SYS$OUTPUT можно выполнить следующим образом:

$CREATE     FAB=F2      ;ОТКРЫТЬ ФАЙЛ ДЛЯ ВЫВОДА
$CONNECT    RAB=R2A     ;ПОДКЛЮЧИТЬ R2A
.                       ;ЗАМЕТЬТЕ, ЧТО R2B ИЛИ R2C НЕ МОГУТ
.                       ;ИСПОЛЬЗОВАТЬСЯ ДО ТЕХ ПОР ПОКА
.                       ;НЕ ВЫПОЛНЕНО ПОДКЛЮЧЕНИЕ
$PUT        RAB=R2A     ;ЭТИ ОПЕРАТОРЫ МОГУТ
.                       ;ВЫПОЛНЯТЬСЯ НЕОДНОКРАТНО
.
.
$PUT        RAB=R2B     ;В ЛЮБОМ ПОРЯДКЕ
.                       ;ИЛИ В ЦИКЛАХ
.
.
$PUT        RAB=R2C
.
.
.
$CLOSE      FAB=F2      ;ЗАКРЫТИЕ ВЫПОЛНЯЕТСЯ
$EXIT_S                 ;В КОНЦЕ ПРОГРАММЫ

ИСПОЛЬЗОВАНИЕ БЛОКОВ ДОСТУПА В ПРОГРАММЕ

Как упоминалось ранее, для формирования постоянной части блоков доступа к файлу и записи используются макроинструкции $FAB и $RAB. Однако эти блоки могут иметь переменные части, такие как адреса буферов, длина записей и строки имён файлов, которые не могут быть определены во время ассемблирования и с которыми поэтому приходится иметь дело во время выполнения. Чтобы осуществить это, необходимо просто определить местонахождение соответствующих длинных слов, слов, байтов или битов в блоках доступа и изменить или проверить их значения. Помня основные положения о том, как это делается в операционной системе VAX/VMS, все поля в блоках доступа идентифицируются символически, вместо того чтобы указывать их действительное местонахождение.

При вызове макроинструкций $FAB и $RAB происходит определение ряда символических имён, задающих расположение различных частей блока доступа относительно начала блока. Символические имена, соответствующие уже обсуждавшимся параметрам, таковы:

Символическое имя

Значение

FAB$L_FNA

Длинное слово, в котором содержится адрес строки имени файла в коде ASCII

FAB$B_FNS

Байт, в котором содержится длина строки имени файла.

FAB$L_FAB

Длинное слово, в котором содержится адрес блока доступа к файлу в блоке доступа к записи

FAB$L_RBF

Длинное слово, в котором содержится адрес буфера записи

FAB$W_RSZ

Слово, в котором содержится длина буфера записи в байтах

FAB$L_UBF

Длинное слово, в котором содержится адрес буфера пользователя

FAB$W_USZ

Слово, в котором содержится длина буфера пользователя в байтах

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

Заметим, что определённые выше символические имена дают адреса относительно начала блока доступа. Поэтому, чтобы адресовать в блоке доступа поле, в которое заносится значение параметра RSZ, необходимо вычислить исполнительный адрес RBLOCK + FAB$W_RSZ.

        .TITLE      ПРОГРАММА ВЫВОДА СТРОКИ
        .PSECT      SWIOBLOCK,LONG
RBLOCK: $RAB                                ;ПУСТОЙ RAB ДОЛЖЕН БЫТЬ ЗАПОЛНЕН
        .PSECT
        .ENTRY      STRINGWRITE,0
        MOVL        8(AP),RBLOCK+FAB$L_FAB  ;ЗАГРУЗИТЬ АДРЕС RAB
        MOVL        4(AP),R0                ;ПОЛУЧИТЬ УКАЗАТЕЛЬ ДЕСКРИПТОРА
        MOVL        4(R0),RBLOCK+FAB$L_RBF  ;ЗАГРУЗИТЬ АДРЕС СТРОКИ
        MOVW        (R0),RBLOCK+FAB$W_RSZ   ;ЗАГРУЗИТЬ ДЛИНУ СТРОКИ
        $CONNECT    RAB=RBLOCK              ;ПОДКЛЮЧИТЬ RBLOCK
        $PUT        RAB=RBLOCK              ;ВЫВЕСТИ СТРОКУ
        RET
        .END

Рис. 11.11. Подпрограмма вывода строки

Вообще для распечатки сообщений предпочтительно использовать такой приём, как приведённый выше. Если для каждого сообщения используется отдельный блок доступа к записи, то теряется полезная память, причём в значительных объёмах.

УПРАВЛЕНИЕ ТЕРМИНАЛАМИ И УСТРОЙСТВАМИ ПЕЧАТИ

На терминале или устройстве печати запись обычно имеет вид строчки текста. Накопленный нами опыт показывает, что для ограничения концов строк используются управляющие символы, такие как возврат каретки и перевод строки. И хотя во многих операционных системах эти управляющие символы также применяются для ограничения конца записей, в подсистеме RMS обычно это не делается. Причина заключается в том, что подсистема RMS предназначена для ввода-вывода информации более общего вида. Если информацию в общем случае рассматривать как произвольные строки байтов, то числовые значения, такие как ^X0D, являются простым элементом данных в строке. Однако, поскольку ^X0D является также и кодом возврата каретки, будет возникать путаница, если это значение будет интерпретироваться и как управляющий символ конца записи. Чтобы избежать этого, во внутреннем формате записей, записанных подсистемой RMS, содержится дополнительное слово-счётчик байтов, которое даёт число байтов информации в записи. Пользователь обычно не видит этого счётчика байтов как части записи. Напротив, при выполнении операции ввода $GET счётчик байтов помещается по относительному адресу FAB$W_RSZ в блоке доступа к записи.

Одно исключение из этого правила относится к вводу с терминала. Поскольку ввод с терминала производится человеком, печатающим на клавиатуре, использование счётчиков байтов было бы весьма неудобным. Поэтому обычно для завершения строк применяют символ возврата каретки. Однако, чтобы сделать ввод с терминала совместимым с вводом с любого другого устройства, подсистема RMS удаляет символ возврата каретки и возвращает число введённых байтов, помещая его по относительному адресу FAB$W_RSZ, как и при вводе с других устройств. Поскольку для завершения ввода записей с терминала могут применяться многие управляющие символы, пользователю может потребоваться узнать, какой из них был введён в действительности[4], и в самом ли деле оператор нажал клавишу возврата каретки, или же была нажата комбинация клавиш CTRL/W, или CTRL/G, или что-либо иное? Это можно определить, так как система RMS сохраняет фактический символ завершения строки в младшем байте слова, расположенного по относительному адресу RAB$W_STV0.

При создании файла, подлежащего распечатке, допускается включение управляющих символов в символьные строки. Эти управляющие символы, такие как "Возврат каретки", "Перевод строки"и "Перевод формата", будут выполнять свои обычные функции. Однако включение символов завершения строки в выводимые записи ведёт к несовместимости вывода с вводом, при котором эти символы удаляются. Существует способ избежать такой несовместимости. В действительности имеется три варианта.

Чтобы избежать присутствия символов завершения строки в явном виде в выводимой записи, в блок доступа к файлу помещается параметр, показывающий, что этот файл в конечном счёте предназначается для распечатки. Тогда при распечатке файла или выводе его на терминал на устройство будут передаваться соответствующие управляющие символы. Три способа осуществления этого имеют разную степень сложности, зависящую от сложности требуемого вывода. Первый способ состоит в том, что между строками помещаются символы возврата каретки и перевода строки. При втором способе используются символы управления кареткой, как это принято в Фортране. При третьем способе применяются более сложные управляющие коды, которые выходят за рамки этой главы. Чтобы выбрать один из этих способов, следует в макровызове $FAB для файла указать один из следующих параметров типа RAT (определяющих атрибуты записи):

  1. $FAB FNM=<SYS$OUTPUT>,RAT=<CR> - для вставки символов возврата каретки и перевода строки между строками. При распечатке этого файла каждая строка будет начинаться с символа перевода строки, а завершать строку будет символ возврата каретки.
  2. $FAB FNM=<SYS$OUTPUT>,RAT=<FTN> - для символов управления кареткой, используемых в Фортране. Первый символ каждой строки удаляется из распечатки и используется как управляющий для принтера. Нормально воспринимаемые на ЭВМ семейства VAX символы управления кареткой:
    •   пробел - перевод на следующую строчку перед печатью;
    •   0 - пропуск строки перед печатью;
    •   1 - перевод на новую страницу перед печатью;
    •   + - печать без продвижения на следующую строку (надпечатка строки);
    •   $ - в конце строки не выполняется возврат каретки (используется для наводящих сообщений при выводе с терминала).
  3. $FAB FNM=<SYS$OUTPUT>,RAT=<PRN> - для сложного управления устройством печати. Дальнейшую информацию об управлении этого типа вы можете найти в руководстве "VAX-11 Record Management Services Reference Manual".

НАВОДЯЩИЕ СООБЩЕНИЯ ПРИ ВВОДЕ С ТЕРМИНАЛА

Как было сказано в первой половине этой главы, при вводе с терминала обычно требуются некоторые наводящие сообщения. Без таких сообщений пользователь за терминалом может оказаться в неведении и сомнении по поводу того, ожидает ли программа ввода или нет. Такие сообщения могут, конечно, выводиться и обычным образом на устройство SYS$OUTPUT. Однако выдаче наводящих сообщений таким способом свойственно несколько недостатков:

  1. Если сделано переназначение устройства SYS$OUTPUT, чтобы сохранить то, что обычно выводилось на терминал, то сообщение появится в несоответствующем месте.
  2. Если программа выполняется в пакетном режиме, наводящие сообщения не нужны и будут лишь загромождать вывод.
  3. Поскольку каждый ввод становится двойной операцией, это усложняет программу.

Чтобы избежать этих проблем, наводящие сообщения можно выдавать автоматически с помощью макровызова $GET. Для выполнения таких действий нужно внести три дополнения в блок доступа к записи. Первое - это установить флаг для указания того, что желателен вывод наводящего сообщения. Второе и третье дополнения определяют строку наводящего сообщения и её длину. Эти три элемента могут быть помещены в блок доступа к записи с помощью следующих параметров:

1.

ROP = <PMT>

Флаг запроса наводящего сообщения.

2.

PBF = адрес строки

Идентифицирует строку наводящего сообщения.

3.

PSZ = длина строки

Задаёт длину строки сообщения.

Для примера следующая последовательность операторов может использоваться в программе для выдачи наводящего сообщения "Введите данные -" всякий раз, когда данные должны считываться с терминала:

RSIZE=8
        .PSECT  IOBLOCK,LONG
INFILE: $FAB    FNM=<SYS$INPUT>
INREC:  $RAB    FAB=INFILE,UBF=IBLK,USZ=RSIZE,-
                ROP=<PMT>,PBF=PRMSG,PSZ=PRMSGE-PRMSG
        .PSECT
PRMSG:  .ASCII  "ВВЕДИТЕ ДАННЫЕ - "
PRMSGE:
IBLK:   .BLKB   RSIZE

Тогда каждый раз при выполнении макровызова $GET RAB=INREC будет выдаваться наводящее сообщение и программа будет ожидать ввод данных с клавиатуры.

Как и в случае других параметров, значения параметров PBF и PSZ могут быть загружены в блок доступа к записи во время выполнения. Как и прежде, соответствующие этим параметрам поля в блоке доступа определяются с помощью символических имён, задающих адреса относительно начала блока доступа к записи. Символические имена, определяющие поля для значений параметров PBF и PSZ, таковы:

Символическое имя

Значение

RAB$L_PBF

Длинное слово, содержит адрес наводящего сообщения

RAB$B_PSZ

Байт, содержит длину сообщения

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

ОШИБКИ ВВОДА-ВЫВОДА

Как и многие другие системные подпрограммы, подпрограммы, вызываемые подсистемой RMS, возвращают информацию об ошибках через регистр R0. Код ошибки может быть обнаружен по тому факту, что младший бит регистра R0 не равен 1. Как и прежде, самый простой способ проверки этого бита - с помощью инструкции BLBC. В большинстве случаев при обнаружении ошибки программа должна просто передать управление операционной системе. Макровызов $EXIT_S R0 позволяет вывести сообщение об ошибке, если код ошибки будет находиться в регистре R0.

Но один из кодов ошибок может быть полезным при работе с файлами. Его символическое имя RMS$_EOF. Значение помещается в регистр R0 при попытке осуществить чтение после того, как обнаружен конец файла. Этим можно воспользоваться при чтении файла, длина которого неизвестна. Программа продолжает чтение до тех пор, пока в регистре R0 не появится значение RMS$_EOF, после чего переходит к другой обработке. Для примера того, как это работает, можно воспользоваться следующей последовательностью операторов:

$GET    RAB=INFILE
CMPL    R0,#RMS$_EOF
BEQL    DONE
BLBC    R0,ERROR

Выполнение программы будет продолжено с метки DONE, если будет обнаружен конец файла; в случае же любой другой ошибки будет сделан переход на метку ERROR. При отсутствии ошибок выполнение программы будет продолжено со следующей инструкции.

ОБРАЗЕЦ ПРОГРАММЫ

В качестве примера работы подсистемы RMS на рис. 11.12 показана программа, которая копирует один файл в другой подобно тому, как это осуществляется по команде операционной системы COPY FILEA FILEB. Программа выдаёт сообщения пользователю о том, что необходимо ввести имена для входного и выходного файлов.

            .TITLE      ПРОГРАММА КОПИРОВАНИЯ ФАЙЛА
            NSIZE=40                            ; МАХ ДЛИНА ИМЕНИ
            RSIZE=80                            ; МАХ ДЛИНА ЗАПИСИ
            .PSECT      IOBLOCK,LONG
TRMFAB:     $FAB        FNM=<SYS$INPUT>                 ; ДЛЯ ВВОДА ИМЁН ФАЙЛОВ
INFAB:      $FAB                                        ; ИМЕНА ФАЙЛОВ БУДУТ
OUTFAB:     $FAB        RAT=<CR>                        ; ПОЛУЧЕНЫ ПОЗДНЕЕ
TRMRAB:     $RAB        FAB=TRMFAB,ROP=PMT,USZ=NSIZE    ; ДРУГИЕ ПАРАМЕТРЫ
INRAB:      $RAB        FAB=INFAB,UBF=RBUFFER,USZ=RSIZE ; БУДУТ ПОЛУЧЕНЫ
OUTRAB:     $RAB        FAB=OUTFAB,RBF=RBUFFER          ; ДЛИНА БУДЕТ ПОЛУЧЕНА
            .PSECT
PMT1:       .ASCII      "ВВЕДИТЕ ИМЯ ФАЙЛА ВВОДА - "
PMT1S=.-PMT1                                    ; ВЫЧИСЛЕНИЕ ДЛИНЫ СООБЩЕНИЯ
PMT2:       .ASCII      "ВВЕДИТЕ ИМЯ ФАЙЛА ВЫВОДА - "
PMT2S=.-PMT2                                    ; ВЫЧИСЛЕНИЕ ДЛИНЫ СООБЩЕНИЯ
NAME1:      .BLKB       NSIZE
NAME2:      .BLKB       NSIZE
RBUFFER:    .BLKB       NSIZE
;
            .ENTRY      START,0
            $OPEN       FAB=TRMFAB                      ; ОТКРЫТЬ И ПОДКЛЮЧИТЬСЯ
            $CONNECT    RAB=TRMRAB                      ; K ТЕРМИНАЛУ
            MOVAB       NAME1,TRMRAB+FAB$L_UBF          ; ПОДГОТОВКА БУФЕРА ИМЁН
            MOVAB       PMT1,TRMRAB+RAB$L_PBF           ; СТРОКИ ПОДСКАЗКИ
            MOVB        @PMT1S,TRMRAB+RAB$B_PSZ         ; И ДЛИНЫ ПОДСКАЗКИ
            $GET        RAB=TRMRAB                      ; ПОЛУЧИТЬ ИМЯ ФАЙЛА ВВОДА
            MOVAB       NAME1,INFAB+FAB$L_FNA           ; ПОМЕСТИТЬ ИМЯ В FAB
            MOVB        TRMRAB+RAB$W_RSZ,INFAB+FAB$B_FNS ; УСТАНОВИТЬ ДЛИНУ
            $OPEN       FAB=INFAB                       ; ОТКРЫТЬ ФАЙЛ ВВОДА
            BLBS        R0,10$                          ; ПРОВЕРКА НАЛИЧИЯ ОШИБКИ
            JMP         ERROR
10$:        $CONNECT    RAB=INRAB                       ; И ПОДКЛЮЧИТЬ ФАЙЛ ВВОДА
            BLBS        R0,20$                          ; ПРОВЕРКА НАЛИЧИЯ ОШИБКИ
            JMP         ERROR
20$:        MOVAB       NAME2,TRMRAB+RAB$L_UBF          ; УСТАНОВКИ ДЛЯ
            MOVAB       PMT2,TRMRAB+RAB$L_PBF           ; ВВОДА ИМЕНИ
            MOVB        @PMT2S,TRMRAB+RAB$B_PSZ         ; ФАЙЛА ВЫВОДА
            $GET        RAB=TRMRAB
            MOVAB       NAME2,OUTFAB+FAB$L_FNA          ; И ОТКРЫТИЯ
            MOVB        TRMRAB+RAB$W_RSZ,OUTFAB+FAB$B_FNS ; ФАЙЛА
            $CREATE     FAB=OUTFAB                      ; ВЫВОДА
            BLBS        R0,30$                          ; ПРОВЕРКА НАЛИЧИЯ ОШИБКИ
            JMP         ERROR

30$:        $CONNECT    RAB=OUTRAB
            BLBS        R0,40$                          ; ПРОВЕРКА НАЛИЧИЯ ОШИБКИ
            JMP         ERROR
;
;В ЭТОМ МЕСТЕ В ЦИКЛЕ ВЫПОЛНЯЕТСЯ КОПИРОВАНИЕ СОДЕРЖИМОГО ФАЙЛА
;
40$:        $GET        RAB=INRAB                       ; СЧИТАТЬ ЗАПИСЬ
            CMPL        R0,#RMS$_EOF                    ; КОНЕЦ ФАЙЛА?
            BEQL        50$                             ; ДА
            BLBC        R0,ERROR                        ; НЕТ, ДРУГИЕ ОШИБКИ?
            MOVW        INRAB+RAB$W_RSZ,OUTRAB+RAB$W_RSZ ; ПОЛУЧИТЬ ДЛИНУ ЗАПИСИ
            $PUT        RAB=OUTRAB                      ; ВЫВЕСТИ ЗАПИСЬ
            BLBS        R0,40$                          ; ПОВТОРЯТЬ В ЦИКЛЕ,
                                                        ; ЕСЛИ НЕТ ОШИБОК
50$:        $CLOSE      FAB=INFAB                       ; ДО КОНЦА ФАЙЛА
            $CLOSE      FAB=OUTFAB                      ; ЗАКРЫТЬ ФАЙЛЫ
            $EXIT_S                                     ; ЗАВЕРШИТЬ ВЫПОЛНЕНИЕ
;
ERROR:      $EXIT_S     R0                              ; ЗАВЕРШЕНИЕ ПО ОШИБКЕ
;
            .END        START

Рис. 11.12. Программа ввода-вывода

УПРАЖНЕНИЯ 11.2

  1. Ниже даны относительные адреса полей в блоках доступа к файлу и записи; для каждого из этих полей ответьте на следующие вопросы:
    • Для чего оно используется?
    • Появляется ли оно в блоке доступа к файлу или в блоке доступа к записи?
    • Длина этого поля байт, слово или длинное слово?
    • Значение является обязательным или нет?
    • Значение задаётся в программе? Если да, когда оно используется?
    • Значение возвращается программе системой? Если да, то когда?

    а.

    FAB$L_FNA

    б.

    FAB$B_FNS

    в.

    RAB$L_PBF

    г.

    RAB$B_PSZ

    д.

    RAB$L_RBF

    е.

    RAB$B_RSZ

    ж.

    RAB$L_UBF

    з.

    RAB$B_USZ

  2. Напишите программу, которая считывает с терминала некоторое число записей, используя макровызовы подсистемы RMS, и распечатывает их на терминале в обратном порядке. Например, если вы вводите с клавиатуры "HELLO, HOW ARE YOU?", программа распечатает "?UOY ERA WOH, OLLEH". Программа будет циклически осуществлять чтение и распечатку до тех пор, пока не будет считана запись нулевой длины, после чего программа передаёт управление операционной системе. Используйте форматный вывод, добавив в выводимые записи символы возврата каретки и перевода строки.
  3. Напишите программу, которая будет выполнять чтение файла на диске, записи в котором имеют в начале каждой строки символы управления кареткой, используемые в Фортране. Содержимое файла будет выводиться на системное устройство с логическим именем SYS$PRINT. Выведенная информация будет иметь обычную страничную организацию, присущую всем распечаткам (если устройство SYS$PRINT недоступно для начинающих пользователей, ваш наставник может переназначить вывод на устройство SYS$OUTPUT или в какой-либо другой файл вывода). Подсказка: используйте параметр атрибутов записи (RAT).
  4. Повторите п. 3, но не используйте параметр RAT. Вместо этого ваша программа должна удалять и интерпретировать символы управления кареткой и вставлять управляющие символы кода ASCII CR, LF и FF в соответствующие места для получения распечатки желаемого вида.
  5. Рассмотрим базу данных служащих предприятия, в которой имеются сведения о 20 служащих, хранящиеся в одном файле. Запись о каждом сотруднике состоит из пяти частей:
    • а)  номер служащего - 2 цифры;
    • б)  имя служащего - 25 символов;
    • в)  адрес служащего - 100 символов (может включать символы возврата каретки и перевода строки);
    • г)  номер страхового полиса служащего - 9 цифр, 2 тире;
    • д)  оклад служащего - 8 цифр и десятичная точка.

    Нарисуйте структурную схему системы, которая принимает и выполняет следующие однобуквенные команды:

    • А - создать новый файл с 20 пустыми записями;
    • В - найти и распечатать запись по её номеру;
    • С - позволяет пользователю ввести или изменить поле имени;
    • D - позволяет пользователю ввести или изменить поле адреса;
    • Е - позволяет пользователю ввести или изменить поле номера страхового полиса;
    • F - позволяет пользователю ввести или изменить поле оклада;
    • G - перезаписать старую запись новой информацией;
    • Н - закончить работу и закрыть файл(ы).
  6. * Используя дополнительную информацию, которой нет в этой главе, но которая имеется в руководстве "VAX-11 Record Manager Reference Manual", напишите программу на языке ассемблера, реализующую систему, которая описана в п. 5. В программе следует использовать файл прямого доступа, содержащий 20 записей, которые могут считываться и записываться в любом порядке.

 

< НАЗАД ОГЛАВЛЕНИЕ ВПЕРЁД >


[1] Благодаря своему специальному назначению управляющие символы CTRL/Q, CTRL/S, CTRL/Y и некоторые другие не являются обычными в том смысле, что они не попадают в символьную строку, которая будет считываться в нормальном режиме.

[2] В некоторых приложениях нужно так много информации, что она не помещается на отдельном диске или ленте. Тогда информация может быть распространена на несколько таких устройств. При этом говорят о многотомных файлах.

[3] Аналогичные операции имеют место, если файл располагается на магнитной ленте, терминала или осуществляется ввод-вывод с некоторых других устройств.

[4] Существует возможность выбора программистом своего собственного набора символов завершения строки. Однако обычный набор завершающих символов включает все управляющие символы, исключая те, которые используются в текстах, такие как "Табуляция", "Перевод строки", "Возврат на шаг назад", "Вертикальная табуляция" и специальные управляющие символы, такие как ^С, ^О,^Q, ^R, ^S, ^U, ^Y и символ удаления.