- Общие методы программирования операций ввода-вывода
- 5. Программирование ввода и вывода
- 5.1. Аппаратная реализация взаимодействия процессора с внешним устройством
- Раздел 5.1 необходим, если студент претендует на высшую оценку при защите курсового проекта. Содержание раздела излагается на лекции.
- Работа с потоками ввода-вывода в C++
- Работа с файлами
- Строковые потоки
- Резюме
- Документация
Общие методы программирования операций ввода-вывода
Один из самых важных методов программирования ввода-вывода — это метод, который следует избегать: принудительное ожидание устройства операционной системой. Почти у всех был опыт просмотра Microsoft Windows «заморозить». Иногда зависание происходит из-за сбоя, но в других случаях система просто ожидает ответа устройства.
Существует два основных метода программирования для работы с ожиданием устройства: синхронный и асинхронный. Синхронное программирование ожидает устройства, и его следует избегать. Асинхронное программирование использует другие методы (например, ожидание запросов прерывания). Дополнительные сведения об синхронном и асинхронном программировании см. в следующих разделах:
Microsoft Vista имеет новую политику для решения проблем с синхронным программированием. Дополнительные сведения об этой новой политике см. в разделе Ограничение ожиданий в Windows Vista .
В более ранних версиях программирования драйверов устройств драйвер должен был многократно запрашивать сведения от драйвера, пока не будет предоставлен ответ. Этот метод называется опросом и почти никогда не должен использоваться. Лучший способ решения проблемы опроса — использовать аппаратные прерывания. Дополнительные сведения об аппаратных прерываниях см. в разделе Прерывания обслуживания. Дополнительные сведения о опросе и о том, почему его не следует использовать, см. в разделе Предотвращение опроса устройств.
5. Программирование ввода и вывода
5.1. Аппаратная реализация взаимодействия процессора с внешним устройством
Раздел 5.1 необходим, если студент претендует на высшую оценку при защите курсового проекта. Содержание раздела излагается на лекции.
Внешнее устройство (ВУ) представляется для процессора, либо в виде набора адресуемых ячеек памяти, либо набора адресуемых портов (регистров). Совокупность допустимых адресов образует адресное пространство.
При выполнении команды взаимодействия с ячейкой памяти или с портом ВУ (чтение/запись) используются шины (A,D,Control) соединяющие процессор с памятью и ВУ. Для обмена данными процессор сначала формирует адрес порта или ячейки, который с помощью специальной схемы дешифрируется, выявляя адресуемый порт или ячейку.
Допустимое адресное пространство (совокупность допустимых адресов) может использоваться, как общее для памяти и ВУ. Портам назначаются уникальные адреса памяти, которые нельзя использовать для ячеек памяти. Такой метод адресации портов ВУ называют методом отображения адресации ввода/вывода на память. Все операции, допустимые над содержимым ячеек памяти, пригодны для обработки содержимого портов.
При другом способе адресации портов используют различные адресные пространства для памяти и для ВУ.
Идентификация используемого в процессе записи/чтения данных адресного пространства осуществляется с помощью сигнала IO/M. Нулевое значение этого сигнала идентифицирует режим работы с ячейками памяти, а единичное значение – указывает на режим взаимодействия с портами ВУ. При таком способе адресации портов, для взаимодействия с портами используются только специальные команды: для чтения данных IN Port, для записи данных OUT Port.
Признак IO/Mиспользуемый для идентификации типа адресного пространства формируется при дешифрации команды, если команда IN или OUT, то устанавливается единичное значение признака IO/M.
Для подачи данных на шину Dи приема данных с шиныDиспользуется двунаправленный шинный формирователь.
Два буферных регистра (один для приема, а другой для передачи данных), объединенных в единый функциональный блок, представляются в курсовом проекте, как буферный регистр с именем RD.
Функциональный блок RD размещается в процессоре и через управляемую двунаправленную шину Dподсоединяется к портам ВУ и к ячейкам памяти. Выбор соединения осуществляется управляющим сигналом IO/M.
Кроме буферного регистра RD, в процессоре размещается буферный регистр RA, содержимое, которого подается на шину А, соединенную со схемами дешифрации, размещенными во внешнем устройстве и в устройстве памяти. Нужная схема дешифрации активируется с помощью сигнала IO/M, подаваемого на вход CSмикросхемы дешифратора.
Для управления обменом данными используются также сигналы : Read, Writeдля памяти и сигналы: IOR, IOW для ВУ.
Работа ЭВМ заключается в выполнении командного цикла, состоящего из машинных циклов, таких как выборка команды (IFETCH), дешифрации кода операции (DECODE), формирование исполнительного адреса (EADDR), выборка операндов (OFETCH) и тому подобные. Машинный цикл в свою очередь делится на нужное количество тактов в зависимости от типа машинного цикла. На каждом такте выполняется микрооперация, напримерRA=RI[A], гдеRI[A] обозначает адресную информацию, размещенную в регистре командRI.
Отметим, что в анализируемой модели ЭВМ машинные циклы команд READ(WRITE) иIN(OUT) могут быть идентичными по описанию, но количество тактов (и микроопераций), требуемое для выполнения этих команд будет различное.
Так как в машинном цикле OFETCHописаны две последовательности микроопераций, то для идентификации последовательностей внутри машинного цикла используется признакIO/M.
Ниже представлены функциональные схемы реализации интерфейса ввода/вывода, предусматривающего использование протокола обмена с квитированием.
Организация ввода данных с проверкой готовности ВУ к обмену.
Данные поступают от ВУ вместе с сигналом стробирования (STB), который служит для загрузки данных в буферный регистр RGI, и для установки признака готовности IBF (InputBufferFull) к обмену. Для хранения признака используется триггер TIBF , который соответствует седьмому биту регистра состояния процессора. Седьмой бит служит для оповещения о готовности порта внешнего устройства к обмену. КомандаINпрограммыCPUвводит данные с регистра состояния в аккумулятор и проверяет наличие единичного значения признака готовности. При наличии признака готовностиCPUосуществляет ввод данных с порта (RGI), хранящего данные. Сигналы дешифратораCSD и CSS служат для управления передачей данных между портом ВУ и регистромRD(аккумулятор) данных процессора.
Далее необходимо представить рисунок функциональной схемы (см. лекции) и фрагмент программы на языке ассемблера, описывающей процесс ввода данных.
Организация вывода данных с проверкой готовности ВУ к обмену.
Необходимо представить функциональную схему, фрагмент программы на языке ассемблера и соответствующие пояснения.
Работа с потоками ввода-вывода в C++
Программа, выводящая в консоль сообщение Hello, world! , с которой традиционно начинают изучение языка программирования, на языке C++ выглядит следующим образом:
#include using namespace std; int main() cout <"Hello, world!" <endl; return 0; >
Директива #include и функция main() знакомы читателю по языку C. Способ вывода строки в консоль отличается от стандартной функции printf языка C и даёт нам повод начать разговор про C++.
Чтобы считать данные из стандартного потока ввода stdin , необходимо воспользоваться объектом cin и оператором извлечения >> .
Здесь мы снова построили цепочку вызовов и получили значения сразу для двух переменных. При обращении к потоку ввода мы не указывали тип данных, которые необходимо прочитать. Оператор >> сам определяет типы объектов и заполняет их из потока ввода.
Объекты cout , cin , а также операторы вставки и извлечения определены в заголовочном файле
Работа с файлами
Все операции ввода-вывода в C++ организованы через потоки и операторы > . Мы уже рассмотрели операции ввода-вывода в потоки stdout и stdin . Операции ввода-вывода с файлами устроены схожим образом. Для работы с файловыми потоками необходимо подключить заголовочный файл . Следующая программа создает файл test.txt и записывает в него строку Hello, world!
#include using namespace std; int main() ofstream ofile("test.txt", ios::out); if (ofile.is_open()) ofile <"Hello, world!"; > return 0; >
Сначала мы создали объект типа ofstream . В его конструктор мы передали имя файла test.txt и флаг ios::out , указывающий на то, что мы собираемся осуществлять операции вывода. Всегда необходимо проверять, что операция открытия/создания файла прошла успешно. Если не выполнить эту проверку, то, если по какой-либо причине файл открыть не удалось, дальнейшие шаги приведут к аварийному завершению программы. Метод is_open() позволяет выполнить такою проверку. Дальше идет уже знакомый нам вызов оператора
Чтение данных из файла производится следующим образом:
#include #include #include using namespace std; int main() ifstream ifile("test.txt", ios::in); if (ifile.is_open()) string line; while (ifile >> line) cout <line <' '; > > return 0; >
Здесь мы воспользовались файловым потоком ввода ifstream и флагом ios::in . В этой программе мы создали переменную line типа string , чтобы хранить считанные из файла данные. Неочевидным моментом здесь является использование цикла while . Дело в том, что оператор >> считывает символы до тех пор, пока не встретит разделитель (пробел, табуляция или перенос строки). Если бы мы вызвали этот оператор один раз, то в переменную line было бы записано Hello, , а это не то, чего мы хотели. Цикл позволяет прочитать файл до конца.
Если же мы хотим прочитать только одну строчку из файла, то можно воспользоваться функцией getline , определенной в . С этой функцией наша программа примет следующий вид:
#include #include #include using namespace std; int main() ifstream ifile("test.txt", ios::in); if (ifile.is_open()) string line; getline(ifile, line); cout <line <end; > return 0; >
Наконец, для чтения символов из потока по одному можно использовать метод get()
char c; while (ifile.get(c)) cout <c; >
Аналогичный метод есть и у объекта cin .
По умолчанию файловые потоки работают в текстовом режиме, т.е. передают и принимают строковые символы. В некоторых случаях необходимо работать непосредственно с последовательностью байт, которые не должны быть интерпретированы как строковые символы. Для таких случаев существует флаг ios::binary . Детали работы с бинарными файлами смотрите в документации.
Строковые потоки
Часто бывает удобно работать со строковыми потоками. Инструменты для работы со строковыми потоками подключаются с помощью заголовочного файла . Строковые потоки позволяют удобно инициализировать объекты различных типов из их текстового представления. Представим себе, что мы получили географические координаты НГУ в виде строки «(54.847830, 83.094392)» . Наша задача извлечь из строки две величины типа double . Сделать это можно следующим образом:
#include #include #include using namespace std; int main() string nsucoor("(54.847830, 83.094392)"); stringstream ss(nsucoor); double lat, lon; ss.ignore(1); // skip '(' ss >> lat; ss.ignore(2); // skip ", " ss >> lon; cout <lat <", " <lon <endl; return 0; >
Резюме
Мы обсудили, что все операции ввода-вывода в С++ реализованы единообразно с помощью потоков. Вывод в поток осуществляется с помощью оператора вставки > . Мы рассмотрели три типа потоков: стандартные, файловые и строковые. Этого достаточно для уверенного начала работы с потоками ввода-вывода в C++.