Системные концепции программного обеспечения
В этом разделе мы обсудим некоторые общие аспекты программирования для малых компьютеров, имея в виду, что изучение средств связи с компьютером имеет мало смысла, если вы не знакомы с иерархией программ, фактически вызывающих к жизни компьютер. Нам в особенности хотелось бы остановиться на таких важных вопросах, как программирование, операционные системы, файлы и использование памяти. Очень легко унестись в царство мечты, восторгаясь красотой аппаратного обеспечения компьютера и недооценить важность хорошего программного обеспечения. Именно программное обеспечение придает компьютеру крылья, и хорошая операционная система вместе с пакетом «утилит» необходимы ему, как воздух.
Рассмотрев программное обеспечение и системы программирования, мы закончим главу разделом о принципах передачи данных, остановившись на стандартизованном последовательном протоколе ASCII RS-232, параллельном порте Centronics, других возможностях параллельной передачи данных (SCSI, IPI, GPIB) и, наконец, на локальных сетях.
10.17. Программирование
Язык ассемблера. Как уже упоминалось выше, ЦП компьютера распознает определенные комбинации бит, как команды, и действует в соответствии с их значением. Однако этим двоичным машинным языком пользуются крайне редко. Обычно вы составляете программы на мнемоническом языке ассемблера (как это было сделано в приводившихся ранее примерах программирования интерфейсов), а специальная программа, называемая ассемблером, преобразует их в выполнимые машинные коды. Язык ассемблера очень близок к машинному языку; каждая команда языка преобразуется непосредственно в одну или несколько строк машинного кода (из которых первая обычно представляет код операции, а последующие описывают адресацию переменных или содержат константы). Программирование на языке ассемблера позволяет получить максимально эффективную программу и дает возможность обращения к флагам и регистрам, что недоступно из языков высокого уровня. Однако такое программирование утомительно, как это показывают приведенные примеры, и для решения большинства задач (особенно требующих значительного объема вычислений) более целесообразно использовать компилятор или интерпретатор с языка высокого уровня, такого как Си или Фортран, а к процедурам на языке ассемблера обращаться лишь в случае необходимости.
Компиляторы и интерпретаторы. Си, Фортран, Паскаль и Бейсик — примеры популярных языков высокого уровня. При составлении программы вы можете использовать алгебраические выражения вроде
χ = (—Ь + sqrt (b*b — 4*а*с))/(2*а)
и управляющие структуры типа if…elseif… else, for…, while… и do…. Вам не приходится гонять туда и обратно крошечные порции информации объемом 1 байт или заботиться об адресации, сохранении регистров и прочем; вы просто объявляете переменные и массивы с указанием типов и размеров, а затем используете их в арифметических и логических выражениях. Не жизнь, а малина.
Выше шла речь об исходном тексте, который можно преобразовать в выполнимую программу двумя способами. Исходные программы на языках Си или Фортран компилируются, т. е. преобразуются специальной программой (компилятором языка) в строки на языке ассемблера; затем программа ассемблера переводит этот промежуточный язык ассемблера в машинный язык[7]. Программы же на языках Бейсик и АПЛ обычно интерпретируются; вместо преобразования исходной программы в программу на языке ассемблера интерпретатор анализирует предложения программы и выполняет соответствующие машинные команды.
В целом интерпретация занимает гораздо больше времени, чем компиляция. Поскольку, однако, в случае интерпретации отсутствуют процессы компиляции, ассемблирования или компоновки (о компоновке см. ниже), программу можно запустить немедленно после ее ввода в машину. Интерпретирующие программы часто включают простые редакторы, удобные для быстрой модификации программы перед ее повторным пуском в процессе отладки. На заре микрокомпьютерной техники, когда жесткие диски были редкостью, интерпретатор Бейсик приобрел широкую популярность, поскольку он работает целиком в памяти; этому можно противопоставить утомительный многоступенчатый процесс компиляции. Однако сегодня быстрые диски и эффективные компиляторы снимают с нас все заботы. Любопытно отметить, что современные компиляторы часто следуют примеру интерпретирующей системы Turbo Pascal фирмы Borland и предоставляют вам «среду программирования», в которой можно без всяких усилий переходить от редактора к выполняемой программе. Если программа натыкается на ошибку, система возвращает вас в редактор и отмечает при этом неправильное предложение; такого рода системы включают отладчики, библиотекари и другие полезные и приятные средства.
Очевидным любимцем серьезных программистов является язык Си, сочетающий в себе мощность языка высокого уровня с красотой структурированных языков и гибкостью программ на языке ассемблера, обеспечивающих доступ к отдельным битам. Однако в научных приложениях львиная доля программирования все еще выполняется на языке Фортран.
Компоновщики и библиотеки. Ассемблер образует программу на машинном языке (собственно говоря, не совсем; ее часто называют «настраиваемым» или «перемещаемым»[8] модулем) из программы на языке ассемблера, образованной компилятором, или из отдельных подпрограмм, непосредственно написанных на этом языке. Кроме этого, обычно имеются программы, используемые отдельными командами языка высокого уровня. Так, в программе на языке Си может потребоваться обратиться к математической функции вроде sqrt (вычисление квадратного корня), или к сонму функций ввода-вывода, таких как printf или fopen. Весь бюрократический кошмар получения из «библиотеки» необходимых подпрограмм (в объектной форме), а затем настройки переходов и адресаций в программе, чтобы вся эта каша разместилась в памяти должным образом, берет на себя программа, называемая компоновщиком[9]).
В функции компоновщика входит назначение конкретных числовых значений ссылкам на ячейки памяти и адресам переменных в ассемблированной программе, и сделать это компоновщик может, только выяснив, какая программа вызывает какую и сколько места занимает каждая программа в памяти. Именно поэтому программа на машинном языке, получаемая с выхода ассемблера, должна быть настраиваемой, так же, как и ассемблированные подпрограммы, хранящиеся в различных библиотеках [обычно их несколько — библиотека встроенных функций компилятора, библиотека ввода-вывода, математическая библиотека, библиотека системных вызовов и, возможно, созданная дома (или купленная в магазине) библиотека полезных подпрограмм].
Редакторы и форматирующие программы. В доисторическую эпоху (до 1970 г.) вы могли наткнуться на программиста, несущего колоду перфокарт. Действительно, вы писали программу от руки на бланках программирования, затем пробивали, ее (или платили кому-то за пробивку) на «картах IBM» — аккуратных прямоугольниках из тонкого картона пастельных оттенков. В наши дни даже первоклашки умеют работать с текстовыми редакторами, обеспечивающими универсальный метод ввода программ в машину. Ветераны (те, кому за 30) помнят еще первые неуклюжие «интерактивные» редакторы, с помощью которых можно было создать и модифицировать текстовый файл; от этого файла по причинам, ведомым лишь самому редактору, вы всегда могли наблюдать лишь кусочек. Дан Ланкастер искушал нас своей «телевизионной пишущей машинкой», которая позволяла вывести на телевизор строку текста. И больше ничего. Ни редактирования, ни памяти, ни ничего. Поэтому легко поверить, что наш восторг не имел границ, когда мы впервые познакомились с «полноэкранным» редактором.
Хороший редактор (а сейчас они все хорошие) позволяет вам вводить и корректировать текст по мере его ввода, искать требуемые слова, изменять текст, перемещать блоки текста, открывать несколько окон для работы с несколькими файлами и создавать «макроопределения» для выполнения сложных манипуляций. Экран быстро отражает вносимые в текст изменения, даже если вы добавляете строки в начале большого файла. Даже очень большой размер файлов не замедляет процесса редактирования.
Универсальный редактор не знает, да и не интересуется тем, что вы пишите; это может быть программа, или сонет, или книга. Редактор просто создает текстовый файл в соответствии с вашими инструкциями, вводимыми с клавиатуры. Если файл состоит из предложений языка программирования, компилятор, интерпретатор или ассемблер читает его непосредственно. Если, с другой стороны, файл представляет собой текст, который вы хотите напечатать, у вас есть две возможности: прямо послать файл на принтер или дополнить его информацией о форматировании и передать форматирующей программе, которая скажет принтеру, как именно следует печатать текст. Хорошая программа форматирования заботится о полях, выравнивании строк, выделении разного места под буквы разной ширины, смене шрифтов, курсиве, жирном тексте, подчеркивании и т. д. Часто редактор и программа форматирования объединяются, причем иногда вы можете посмотреть на экране, как в действительности будет выглядеть напечатанная страница текста [это средство называется WYSIWYG (произносится «визивиг»), What You See Is What You Get-что вы видите, то вы и имеете]; однако чаще экран лишь частично отображает окончательный вид печатной страницы. Наиболее совершенные редакторы могут вставлять в текст математические и научные формулы. Для получения оригинал-макета вы вводите текст с клавиатуры наборной машины, которая сама выполняет экспонирование фотобумаги или фотопленки; лазерные или электролюминесцентные принтеры обеспечивают весьма приличное качество печати при умеренной цене и высокой скорости; ударные матричные принтеры относятся к самым дешевым, и результат оказывается соответствующим.
Редакторы, выполняющие форматирование, имеют характерные имена, например, MacWrite, Manuscript, Microsoft Word, Sprint, WordPerfect.
К популярным программам форматирования (которые могут работать и с текстом и с формулами) относятся ТЕХ и Troff. Одно предупреждение: в процессе создания текста (в противоположность программам) большинство редакторов с форматированием включают в поток редактируемого текста необычные символы, с помощью которых отмечается, например, курсив или временный конец строки. Эти символы не принимаются компиляторами или ассемблерами. Если вы не хотите, чтобы компилятор подавился вашей программой, вы должны создать текст без всяких упражнений, для чего запустить редактор в самом простом режиме «чистого» текста.
Вот еще пара бесплатных советов: а) найдите хороший редактор и держитесь его; б) не пытайтесь убедить коллег, что ваш редактор лучше какого-нибудь другого.
10.18. Операционные системы, файлы и использование памяти
Операционные системы. Из предыдущих обсуждений можно сделать вывод, что пользователю микрокомпьютера приходится часто запускать разнообразные программы, которые, к тому же, должны обмениваться между собой данными. Если, например, вам надо написать программу, то вы начинаете с запуска текстового редактора и создаете с его помощью текстовый файл, вводя строки программы с клавиатуры (хорошие программисты, насколько мы можем об этом судить, никогда не берут в руки карандаш). Сохранив на время этот файл, вы вызываете программу компилятора и компилируете сохраненный текстовый файл, чтобы получить файл с программой на языке ассемблера. Этот файл вы тоже сохраняете и запускаете ассемблер, который из файла с программой на языке ассемблера создает файл с перемещаемой программой на машинном языке. Наконец, компоновщик объединяет перемещаемую программу с другими ассемблированными подпрограммами и библиотечными программами и выдает выполнимую программу, которую вы (наконец-то!) запускаете. Для выполнения всех этих операций вам нужна сверх-программа, которая жонглировала бы всем этим хозяйством, отыскивая файлы на диске, загружая их в память и передавая управление соответствующим программам. Хотелось бы также избавиться от необходимости включения в каждую программу всех команд, требуемых для записи или чтения диска (включая обработку прерываний, загрузку регистров команд и состояний и проч.) или выполнения других операций по пересылке данных. Все это (и многое другое) входит в функции операционной системы, обширной программы, наблюдающей за загрузкой и запуском пользовательских программ (т. е. тех, которые вы пишите) и утилит (редактора, компилятора, ассемблера, компоновщика, отладчика и др.), а также управляющей вводом-выводом, системой прерываний и различными манипуляциями с файлами. Операционная система включает монитор для связи с пользователем (именно ему вы говорите, что надо вызвать редактор, или компилятор, или запустить программу) и набор «системных запросов», с помощью которых выполняемая программа может прочитать или записать строку текста на некотором устройстве, определить текущее время суток, передать управление другой программе, позволить нескольким «процессам» в многозадачной среде разделять между собой время ЦП или обмениваться данными, загружать программные «оверлеи» и т. д. Хорошая операционная система выполняет всю работу по управлению вводом-выводом, включая «спулинг» (буферизацию входных или выходных данных, позволяющую программе выполняться в то время, когда данные читаются или записываются на некотором устройстве). Выполняясь под управлением операционной системы, программа пользователя может не заботиться о прерываниях; прерывания обслуживаются системой и затрагивают ход программы только если программа сама хочет принять участие в обработке прерываний от конкретного устройства. Вершиной системного программирования является «разделение времени» (использование одного компьютера многими пользователями одновременно), когда диск служит в качестве «виртуальной памяти» для программ неограниченного размера. Примерами популярных микрокомпьютерных операционных систем являются MS-DOS (используемая на машинах IBM PC и их аналогах), OS/2 (предназначенная для машин PS/2, преемников IBM PC), UNIX (разработанная в Bell Labs и широко используемая на машинах VAX, а также на компьютерах с микропроцессором 68000), MacOS и VMS (операционная система машин VAX, предоставляемая компанией).
Файлы. В качестве среды для массовой памяти широчайшее распространение получили магнитные диски, как гибкие («флоппи»), с контактирующими головками чтения-записи, так и жесткие («винчестер»), с плавающими головками. Типичные емкости лежат в пределах 1 Мбайт для гибких дисков и 20-500 Мбайт для небольших винчестеров. Данные организуются в виде файлов. Все машинные материалы — тексты, программы пользователей, утилиты (т. е. редактор, ассемблер, компилятор), библиотеки и проч., хранятся одинаковым образом и составляют файлы. Хотя среда массовой памяти разделяется на физические блоки, или секторы жестко определенного размера (обычно размер сектора составляет 512 байт), сами файлы могут иметь любую длину. Операционная система милосердно берет на себя всю заботу об адресации к дорожкам и секторам; она извлекает требуемые данные, если вы указываете имя файла. Имеется масса любопытнейших деталей файловой организации, которые мы не можем здесь обсудить из-за недостатка места. Важно только понять, что все эти программы (редактор, компилятор и др., так же как и исходный текст, скомпилированная программа и даже данные) хранятся на некотором устройстве массовой памяти как поименованные файлы, и система умеет извлекать их для вас (прочитайте, однако, в следующем разделе об электронных дисках). Осуществляя свои служебные обязанности, система выполняет огромный объем работ по обслуживанию файлов.
Недавние прибавления семейства устройств массовой памяти имеют в своей основе потребительскую электронику и обеспечивают очень высокую плотность хранения в маленьком объеме. Сюда относятся: (а) оптические диски вроде тех, что используются в проигрывателях, с емкостью около гигабайта. Они служат в качестве ПЗУ, WORM-памяти (Write Once, Read Many — записать один раз, прочитать многократно) или как полностью стираемая память для записи/чтения; (б) видеокассеты формата VHS или 8 мм, которые позволяют иметь гигабайты памяти с возможностью записи/чтения на недорогой ленте. Основной недостаток кассет — большое время доступа. Обе упомянутые системы памяти используют изощренные методы коррекции ошибок, возникающих из-за дефектов поверхности и по другим причинам; в обычных аудио/видеоприменениях эти ошибки не имеют особого значения, однако при хранении данных или программ они, не будучи исправлены, носили бы разрушительный характер.
Использование памяти. Файлы хранятся в устройствах массовой памяти, однако программы в процессе их выполнения должны находиться в оперативной памяти. Простую автономную программу вроде той, что будет рассмотрена в следующей главе, можно загрузить почти в любое место памяти. Однако в компьютере с операционной системой всегда имеются специальные области, зарезервированные для выполнения специальных функций. Например, сама операционная система MS-DOS, вместе с ее интерпретатором команд, дисковыми буферами, стеком и прочим, обычно загружается в начало памяти, заполняя при этом векторами прерываний выделенные ячейки, адреса которых известны ЦП, в то время как часть MS-DOS, заключенная в ПЗУ, располагается в конце памяти, за областью, отведенной под видеобуферы дисплея. Если компьютер работает под управлением операционной системы, выделением памяти под программы пользователя ведает система. Понимание этого момента особенно важно при использовании ПДП; в этом случае вы должны предоставить системе возможность определить, где будет располагаться ваш буфер данных, и использовать этот адрес в качестве стартового для блочной передачи по каналу ПДП.
Ситуация еще более усложняется, если программы по ходу выполнения выгружаются и загружаются (так называемый «свопинг») или перемещаются по памяти. В памяти одновременно может находиться много программ, которым в многозадачном режиме выделяются «кванты времени» ЦП. К тому же большинство микрокомпьютеров используют «отображение памяти», при котором физические адреса памяти отображаются на различные логические адреса (т. е. те, по которым программа располагается с ее точки зрения). Если всего сказанного недостаточно, чтобы сбить вас с толку, подумайте о «виртуальной памяти», используемой в более совершенных моделях микрокомпьютеров, когда ваша программа разделяется на небольшие «страницы», каждая из которых в любой момент времени может быть, а может и не быть в памяти; программа «листает» эти страницы, гоняя их между памятью и диском в безумном пароксизме бешеной активности.
Обсуждение памяти нельзя считать законченным, если не упомянут электронный (псевдо) диск, который можно организовать даже на относительно простых машинах, если только хватит оперативной памяти. Идея псевдодиска заключается в такой организации памяти, чтобы с точки зрения операционной системы она выглядела, как диск; в эту псевдодисковую память можно затем загрузить часто используемые программы. Такая процедура может оказаться полезной при разработке программ, когда приходится постоянно обращаться к редактору, компилятору, ассемблеру и компоновщику. При использовании псевдодиска работа идет значительно быстрее, поскольку отпадает необходимость в обращениях к настоящему диску. Правда, если произойдет отказ компьютера, вы потеряете все, что сделали, так как файлы не сохраняются на диске автоматически. Схожая идея лежит в основе кеш-памяти; в этом случае область ОЗУ хранит результаты последних обращений к диску.
Драйверы. Компьютерный мир полон разнообразия — каждый месяц мы сталкиваемся с новинками технологии в области запоминающих устройств (магнитных, оптических), принтеров (лазерные, электролюминесцентные), сетей и проч. Различное электронное оборудование требует и различных управляющих сигналов с разными требованиями к временной синхронизации и т. д. Это могло бы привести к серьезным программным трудностям, так как программное обеспечение, разработанное, например, для матричного принтера, абсолютно не годилось бы для лазерного наборного автомата.
Решение лежит в использовании драйверов, специальных программ, предназначенных для организации единого программного интерфейса с разнообразным оборудованием. Так, например, наборный язык ТЕХ создает файлы в формате dvi (device-independent-не зависящий от устройств); драйвер принтера (специфический для каждого используемого вами принтера) пережевывает файл dvi и выплевывает соответствующие принтерные коды, заставляющие принтер работать должным образом. ТЕХ может работать с любым принтером, если только к этому принтеру у вас есть «dvi-транслирующий» драйвер. Сказанное относится и к устройствам массовой памяти, таким, как дисководы дисков; в результате вы можете взять любой из имеющегося на рынке множества дисков и подключить его к любому компьютеру — типа PC или Macintosh или с системой UNIX.
Драйверы являются частью системного программного обеспечения и средний пользователь компьютера может и не подозревать об их функционировании. Если, однако, вы разрабатываете новое оборудование для компьютера, вы, скорее всего, вскоре станете экспертом по драйверам, так как для того, чтобы заставить вашу аппаратуру играть заодно со всей остальной командой, вам придется написать для нее ваши собственные драйверы.