Таблица векторов связи
13.1. Таблица векторов связи
Таблицы, используемые DOS, организованы в древовидную структуру. В корне этого дерева находится таблица, которая содержит адреса других таблиц и списков, используемых в DOS. Поскольку эта таблица является недокументированной, в различных источниках она называется по-разному: Список Списков (List of List), Блок Переменных DOS (DOS Variables Block) и т.п. Мы будем использовать название Таблица Векторов Связи и абревиатуру CVT (Connection Vectors Table) - по аналогии с подобной структурой данных в операционной системе OS/360. Через эту таблицу открывается возможность доступа ко всем внутренним данным системы. Ниже приведено описание формата CVT. Размеры полей таблицы и их назначения должны быть ясны из описания и комментариев к нему. Подробнее списки, начальные адреса которых сведены в CVT рассматриваются в следующих разделах.
Драйверы устройств
13.2. Драйверы устройств
Управление данными в DOS происходит при посредстве драйверов устройств - системных программ, которые обеспечивают обмен данными между программами и внешними устройствами. Здесь мы, говоря о драйверах, имеем в виду не любые программы ввода-вывода, а именно программы либо входящие в состав системы, либо разработанные пользователем, но полностью отвечающие системным требованиям, поддерживаемые системой, записываемые в оперативную память при загрузке системы по команде DEVICE файла CONFIG.SYS. Последние программы называются устанавливаемыми драйверами устройств. Системные и устанавливаемые драйверы позволяют унифицировать операции, выполняемые на различных устройствах и файлах, делают прикладные программы независимыми от аппаратной части внешних устройств.
Драйвер устройства создается в виде COM-программы, отвечающей весьма жестким требованиям. Мы намереваемся посвятить разработке и применению драйверов отдельное пособие, здесь же рассмотрим только размещение драйверов в системной памяти. Драйвер обязательно должен начинаться с заголовка - области памяти, имеющей длину 18 байт и следующий формат:
Блоки параметров дисков и массив текущих каталогов
13.3. Блоки параметров дисков и массив текущих каталогов
Блок Параметров Диска (DPB - Disk Parameter Block) строится DOS для каждого дискового устройства и содержит (в основном) информацию, полученную из загрузочного сектора диска. Структура блока для версий DOS 3.x следующая:
Системные таблицы файлов
13.4. Системные таблицы файлов
Один из элементов CVT указывает на первую Системную Таблицу Файлов (SFT - System File Table). Таблиц SFT может быть в системе несколько. Каждая таблица начинается с заголовка, который имеет следующий формат:
Буферизация дискового ввода-вывода
13.5. Буферизация дискового ввода-вывода
Дисковый ввод-вывод в DOS выполняется с буферизацией. Необходимость буферизации объясняется прежде всего следующими соображениями. Поскольку единицей информации при обмене с дисками является сектор, то при поступлении запроса на чтение данных с диска (пусть даже одного байта), DOS читает весь сектор - 512 байт, а затем передает затребованное число байт в программу. При следующем запросе данных из того же сектора у DOS уже нет необходимости обращаться к диску. Она передает программе данные из буфера. Аналогичным образом при записи данные накапливаются в буфере, пока весь буфер не будет запомнен, и лишь затем выводятся на диск. Кроме того, при наличии большого числа буферов DOS использует их в основном для сохранения содержимого FAT, а также каталогов, что может существенно повысить скорость поиска файлов на диске.
Буферизация (число буферов) задается командой BUFFERS файла CONFIG.SYS. В оперативной памяти можно разместить от 2 до 99 буферов. При задании слишком большого числа буферов помимо того, что расходуется оперативная память проявляется также и снижение эффективности, связанное с затратами времени на обслуживание буферного пула. Справочники рекомендуют такие значения для числа буферов:
AT с 20- или 30-мегабайтным твердым диском: 32; XT с 10-мегабайтным твердым диском: 16; PC (только гибкие диски): 4.
Для AT число буферов в основной памяти может быть существенно уменьшено, если используется утилита FASTOPEN, при помощи которой можно хранить информацию о файлах и каталогах в дополнительной (expanded) памяти, или драйвер SMARTDRV.SYS, создающей в дополнительной или расширенной (extended) памяти кэш-память для дисков.
Следует иметь в виду, что буферизация применяется только при выполнении файловых операций ввода-вывода. Секторы читаемые/ записываемые по прямым обращениям из программы к прерываниям DOS 0x25/0x26 через систему буферизации не проходят.
При закрытии файла все связанные с ним буфера сбрасываются на диск. Но в DOS (начиная с 3.30) имеется функция 0x68, позволяющая принудительно записать на диск все буфера, связанные с заданным файлом - ее целесообразно применять, если есть риск потери накопленной в буферах информации при выключении или сбое питания. Кроме того, функция 0x6C (расширенное открытие файлов - начиная с DOS 4.0) позволяет открыть файл без буферизации.
Буферизация обеспечивается следующими внутренними структурами данных DOS. Каждый буфер в оперативной памяти предваряется Блоком Управления Буфера (BCB - Buffer Control Block). Нам не удалось найти в источниках удовлетворительных описаний формата и назначений полей BCB, следующие описания базируются в основном на наших собственных экспериментах в DOS 3.30 и DOS 5.0. Источники утверждают, что во всех вариантах DOS 3.x буферизация построена одинаково, и также одинакова она в DOS 4.x и DOS 5.x, однако, мы не можем ручаться за истинность этого утверждения.
Для DOS 3.30 структура BCB следующая:
Связи системных блоков
13.6. Связи системных блоков
Итоговая схема связи системных управляющих блоков представлена на Рисунок 13.1, на котором таблицы изображены в виде вертикальных последовательностей ячеек, списки - в виде последовательностей, "уходящих в глубину" рисунка. Большинство связей, показанных на рисунке, задается в виде прямых адресных ссылок, исключение составляют: список MCB, в котором следующий адрес вычисляется, и ссылка из JFT в SFT - по номеру элемента. Пунктиром на рисунке показана ссылка от DFCB на драйвер символьного устройства, альтернативная ссылке на DPB блочного устройства.
При возникновении критической ошибки DOS14.1. Обработчик прерывания 24 При возникновении критической ошибки DOS вызывает прерывание 0x24, вектор которого указывает на обработчик критической ошибки. Стандартный обработчик критической ошибки находится в резидентной части COMMAND.COM. Эта программа выдает сообщения типа: "Device not ready/Abort, Retry, Ignore, Fail?". Но пользователь может (и это, по-видимому, предусмотрено в системе) установить свой обработчик критической ошибки, перехватив вектор его прерывания. Адрес прежнего обработчика (как мы уже видели) сохраняется в PSP программы и автоматически восстанавливается системой при завершении программы. Когда обработчик критической ошибки получает управление, регистры содержат такую информацию: AH - байт кода ошибки; разряды регистра интерпретируются следующим образом: Расширенная информация об ошибке.14.2. Расширенная информация об ошибке. Начиная с DOS 2.0, те функции DOS, при выполнении которых могут возникать ошибки, индицируют наличие ошибки установкой флага CY, при этом регистр AX содержит код ошибки. Следующие коды ошибок могут возвращаться в регистре AX: | |||
1 | -ошибка номера функции DOS | 10 | -ошибка в окружении |
2 | -файл не найден | 11 | -ошибка формата |
3 | -путь не найден | 12 | -ошибка кода доступа |
4 | -слишком много открытых файлов | 13 | -ошибка даты |
5 | -доступ отвергнут | 14 | -(не используется) |
6 | -ошибка дескриптора файла | 15 | -задан неверный диск |
7 | -ошибка в MCB | 16 | -удаление текущего оглавления |
8 | -нехватка памяти | 17 | -не то же самое устройство |
9 | -ошибка адреса блока памяти | 18 | -больше нет искомых файлов |
Начиная с версии DOS 3.0 имеется функция 0x59, выдающая расширенную информацию об ошибке. При обращении к этой функции регистр BX должен содержать номер версии DOS (0 - для версии 3). На выходе функции регистр AX содержит расширенный код ошибки, BH - класс ошибки, BL - код предполагаемых действий, CH - локализацию ошибки.
Расширенные коды ошибок:
19 - 36 | -соответствуют кодам 0 - 17, передаваемым обработчику критической ошибки в регистре DI; |
36 - 49 | -зарезервировано; |
50 - 60 | -ошибки, связанные с работой в сети; |
61 - 63 | -ошибки спуллинга печати; |
55 - 72 | -ошибки, связанные с работой в сети; |
73 - 79 | -зарезервировано; |
80 | -файл уже существует; |
81 | -зарезервировано; |
82 | -невозможно создать элемент каталога; |
83 | -сбой при обработке прерывания по критической ошибке; |
84 | -слишком много перенаправлений; |
85 | -повторяющееся перенаправление; |
86 | -неправильный пароль; |
87 | -неправильный параметр; |
88 | -ошибка в данных сети. |
Классы ошибок:
1 | -нехватка ресурса (памяти, описателей файлов и т.п.); |
2 | -временная ситуация; |
3 | -превышение пользователем своих полномочий; |
4 | -внутренняя ошибка DOS; |
5 | -ошибка оборудования; |
6 | -системная ошибка DOS; |
7 | -ошибка применения (некорректный запрос, неверные параметры и т.п.); |
8 | -запрошенный файл или другой объект найден; |
9 | -неверный формат файла, диска и т.п.; |
10 | -файл или другой объект захвачен другим пользователем; |
11 | -ошибка носителя; |
12 | -файл или другой объект уже существует; |
13 | -неивестный класс. |
Предполагаемые действия:
1 | -повторить операцию несколько раз; |
2 | -повторить операцию после задержки; |
3 | - повторить ввод данных для DOS пользователем; |
4 | -снять задачу, выполнив операции завершения (закрытие файлов, обновление индексов, освобождение памяти и т.п.); |
5 | -снять задачу, не выполняя операции завершения; |
6 | -игнорировать ошибку; |
7 | -повторить операцию после выполнения пользователем каких-то действий. |
Локализация ошибки:
1 | -место ошибки не определено; |
2 | -ошибка на блочном устройстве; |
3 | -зарезервировано; |
4 | -ошибка на символьном устройстве; |
5 | -ошибка в памяти. |
Обработчик критической ошибки в примере 14.1 обращается к функции 0x59 и запоминает также расширенную информацию об ошибке. Чтобы не перегружать пример строковыми константами на печать выводятся только коды ошибки, класса и т.д., а не смысловые сообщения.
Проблемы разработки резидентных программ
15.1. Проблемы разработки резидентных программ
Резидентными называются программы, постоянно находящиеся в оперативной памяти ЭВМ. В специальной литературе за ними закрепилось также название TSR-программы - по наименованию функции DOS "Завершить и оставить резидентной" (Terminate and Stay Resident). Находясь постоянно в оперативной памяти, TSR-программа, однако, большую часть времени является неактивной, компьютер выполняет другие - системные и пользовательские - программы. Активизация TSR-программы происходит по прерыванию (аппаратному или программному), и после выполнения TSR-программой требуемых от нее действий управление вновь возвращается к прерванной (фоновой) программе, а резидентная становится неактивной до следующего ее вызова. Очень часто активизация TSR-программы иницируется нажатием закрепленной за ней ("горячей") клавиши или комбинации клавиш, примером таких программ могут быть программы копирования экрана или резидентные словари и справочники. Из всего класса TSR-программ иногда выделяют подкласс, называемый резидентными обработчиками прерываний (ISR - Interrupt Service Resident) - программы, применяющиеся обычно для обслуживания внешних устройств и активизирующиеся по программному прерыванию подобно программам BIOS (например, драйвер мыши MOUSE. COM). ISR-программы, как правило, проще программ "горячей клавиши", так как моменты обращения к ним более предсказуемы.
Наш подход к рассмотрению TSR-программ будет несколько отличаться от принятого в большинстве справочных и учебных пособий. Поскольку TSR-программа постоянно находится в памяти, она должна занимать в ней как можно меньше места, поэтому средством ее реализации бывает, как правило, язык Ассемблера, и всегда значительное внимание уделяется вопросам минимизации объема. Нас же TSR-программы будут интересовать в несколько ином аспекте. Известно, что MS DOS - система однопрограммная. Но возможность функционирования в системе резидентных программ уже является шагом к организации работы DOS в многопрограммном режиме: в памяти одновременно сосуществуют фоновая программа и TSR-программа, которые активизируются поочередно. Нас будут интересовать в первую очередь проблемы корректного взаимодействия TSR-программы с фоновой программой и с DOS. Большинство TSR-программ, приводимых в литературе, "паразитируют" на фоновой программе, используя в той или иной степени ее ресурсы. В этом нет ничего предосудительного, особенно, если в каждом таком случае использование ресурса фоновой программы обосновано и гарантируется его сохранность. Мы же рассмотрим самый общий случай, когда фоновая и TSR-программа имеют раздельные ресурсы. При таком подходе проблемы построения TSR-программы можно разбить на следующие группы:
инициализация программы; условия активизации и обработка прерываний; переключения контекстов; программные коммуникации; выгрузка программы из памяти (завершение).
Решение всех этих проблем иллюстрируется программным примером 15.1.
Инициализация программы
15.2. Инициализация программы
TSR-программа запускается так же, как и любая другая программа. Но те действия, для которых она предназначена, программа должна выполнять не при запуске, а по прерыванию или по нажатию "горячей клавиши". Таким образом, действия, выполняемые при запуске программы должны только обеспечить условия для ее оставления резидентной и ее последующего функционирования в качестве резидентной. В нашем примере эти действия организуются функцией main, которая получает управление при запуске программы. Прежде всего программа должна проверить, не присутствует ли уже ее копия в памяти. Такую проверку выполняет функция check_tsr, которая будет нами подробно рассмотрена при обсуждении программных коммуникаций. В нашей программе предусмотрены два варианта запуска: запуск без параметров - для инициализации программы и запуск с параметром "/Q" - для выгрузки из памяти ранее загруженной копии программы. Функция main анализирует параметр и сопоставляет способ запуска с результатом проверки, произведенной функцией check_tsr. Если при запуске без параметров обнаруживается, что копия программы уже есть в памяти, то инициализация не производится. Если программа запущена с параметром, то для выгрузки TSR-программы происходит обращение к прерыванию 0x2F, которое также будет рассмотрено при обсуждении программных коммуникаций.
Если нет препятствий для инициализации программы, происходит обращение к функции get_context (рессматривается при обсуждении переключения контекстов), затем программа освобождает свой блок окружения, устанавливает свои векторы обрабатываемых прерываний (функция set_vectors - рассматривается при обсуждении обработки прерываний) и получает адрес флажка занятости DOS. Функция DOS 0x34 возвращает в регистрах ES:BX адрес переменной DOS, которая называется флажком занятости (ее использование рассматривается при обсуждении условий активизации).
Для завершения программы используется функция DOS 0x31 - "Завершить и оставить резидентной". Этой функции в регистре AL передается код завершения (как при завершении по функции 0x4C), а в DX - размер в параграфах той части программы, которая остается резидентной в памяти. При программировании на языке Ассемблера для программиста не представляет труда определение этого размера. При программировании на языке высокого уровня точно определить требуемый минимальный размер практически невозможно. Мы взяли размер, равный размеру EXE-файла, этот размер мог бы быть уменьшен, это уменьшение обя- зательно необходимо учитывать при формировании указателя стека (см.функцию get_context), окончательное значение размера подбирается методом проб и ошибок.
Другой способ оставить программу резидентной - прерывание 0x27, но функция DOS 0x31 предпочтительнее, она - более позднее средство, полностью перекрывающее возможности прерывания 0x27.
Условия активизации и обработка прерываний
15.3. Условия активизации и обработка прерываний
Два аспекта, вынесенные в заголовок этого раздела, настолько связаны между собой, что не могут рассматриваться порознь.
Мы определили, что TSR-программа должна активизироваться по нажатию "горячей клавиши". Следовательно, первым прерыванием, которое должна обрабатывать программа является прерывание 9 - от клавиатуры. Обработчик прерывания 9 в нашей программе - функция new_9. По этому прерыванию программа должна читать скан-код нажатой клавиши и состояние клавиш-переключателей и сравнивать со своей "горячей комбинацией" (в нашей программе "горячей" является комбинация Alt+1, определяемая начальными значениями переменных hot_scan и hot_status). Если опознана "горячая комбинация" то код клавиши удаляется из буфера клавиатуры, в противном случае вызывается системный обработчик прерывания 9. Но может оказаться, что немедленную активизацию TSR-программы производить нельзя. Это обусловлено тем, что функции DOS нереентерабельны. Если мы прервали выполнение функции DOS и активизируем TSR-программу, которая, в свою очередь, будет обра-щаться к функциям DOS, то это может привести к катастрофическим последствиям. Поэтому лучше всего не активизировать TSR-программу сразу же после распознавания "горячей комбинации", а отложить активизацию до тех пор, пока не будут выполнены условия, делающие активизацию возможной, а в обработчике прерывания от клавиатуры только установить признак (hot_key) того, что "горячая клавиша" была нажата. Флаг tsr_run, также анализируемый в обработчике этого прерывания, блокирует реентерабельный вызов TSR-программы: его значение 1 сигнализирует о том, что TSR-программа уже активизирована.
Проверку этих условий удобно производить по таймеру. Следовательно, второе прерывание, которое должна обрабатывать наша программа - 8 (или 0x1C) - от таймера. Обработчик прерывания 8 в нашей программе - функция new_8. При каждом прерывании от таймера проверяется прежде всего флаг hot_key.
Если он взведен, т.е. " горячая клавиша" была нажата, то проверяется флажок занятости DOS, который устанавливается DOS в 1 при выполнении любой функции DOS (адрес флажка занятости мы определили при инициализации программы). Еще одно условие инициализации - нулевое значение флага disk_op, о котором мы расскажем ниже. Если выполнены все условия инициализации, то из функции 8 вызывается функция act_tsr, а флаг "горячей клавиши" сбрасывается.
Но полностью отказываясь от активизации TSR-программы в моменты выполнения любых функций DOS, мы можем отложить эту активизацию на весьма неопределенный срок. Например, при отсутствии выполняемых программ командный процессор находится в состоянии ожидания ввода с клавиатуры, при этом выполняется функция DOS 0x0A, следовательно, флажок занятости взведен. Имеется, однако, способ избежать такой блокировки активизации. Основным фактором нереентерабельности функций DOS является то, что разные функции DOS используют для своей работы один и тот же стек, поэтому вложенный вызов функции DOS портит стек вызывающей функции. Оказывается однако, что в DOS имеется два стека - один используется функциями с номерами до 0x0C включительно, а второй - функциями с большими номерами. Если DOS выполняет функцию с номером не выше 0x0C, то TSR-программа может активизироваться, но при своем выполнении она не должна обращаться к функциям с меньшими номерами (что, впрочем, не составляет больших проблем). Индикатором такого состояния DOS является прерывание 0x28, выдаваемое DOS, когда DOS находится в состоянии ожидания ввода. Следовательно, еще одно обрабатываемое нами прерывание - 0x28 (обработчик - new_28). Обработчик этого прерывания вызывает функцию act_tsr при установленном флаге "горячей клавиши" даже при установленном флажке занятости DOS.
Еще одно прерывание, которое необходимо перехватывать нашей программе - 0x13 (обработчик new_13) - прерывание BIOS, обеспечивающее дисковые операции. Дисковые операции не следует прерывать хотя бы потому, что некоторые из них требуют временной синхронизации.
Поэтому основная задача нашего обработчика new_13 - установить флаг disk_op, который блокирует активизацию TSR-программы на время выполнения дисковых операций. Для выполнения операции наш обработчик обращается к прежнему (системному) обработчику этого прерывания и обеспечивает возврат тех значений регистров AX, CX, DX и флагов, которые устанавливает системный обработчик (для формирования флагов приходится обращаться к функции new_2F).
И еще одно прерывание, которое обрабатывает наша программа - 0x2F, будет рассмотрено при обсуждении программных коммуникаций.
Эти пять векторов прерываний перехватываются TSR-программой при ее инициализации. При активизации TSR-программы она также должна перехватить вектор 0x24 - обработчика критических ошибок, в нашей программе он обеспечивает игнорирование ошибок во всех случаях.
Для получения и установки векторов прерываний служат у нас функции get_vector и set_vector, использующие функции DOS 0x35 и 0x25 соответственно. Представляет, однако, интерес организация передачи управления нашим обработчикам прерываний (функция set_vectors). Выделяется отдельный блок оперативной памяти размером 2 параграфа, в котором размещаются 5 команд "дальнего" перехода на наши функции обработки пяти перехватываемых при инициализации прерываний. В программе команда перехода описывается структурой struct far_jmp, в поле jmp которой заносится код команды JMP - 0xEA, а в поле int_h - адрес функции, на которую производится передача управления. В таблицу векторов записываются адреса сформированных команд перехода. Адреса прежних обработчиков прерываний запоминаются в массиве old_v. Для прерывания 0x2F сегментная часть и смещение адреса прежнего обработчика также запоминаются в дополнительной переменной a_2F. Почему же мы сразу не записали в таблицу векторов адреса наших обработчиков? Это сделано для предупреждения возможной аварии при выгрузке нашей программы (эта проблема была поставлена еще в главе 3). Наша программа включила свои обработчики прерываний в цепочки.
Если наша программа будет удаляться из памяти и восстановит запомненные прежние векторы, то она этим исключит из цепочки обработчики тех программ, которые, возможно, подключились к цепочкам после нашей программы.
Восстановление векторов выполняется функцией restore_vectors. Эта функция читает из таблицы векторов вектора всех перехватываемых нашей программой прерываний и сравнивает их с адресами наших обработчиков. Если для какого-либо прерывания вектор содержит адрес нашего обработчика, то это значит, что наш обработчик является последним в цепочке обработки этого прерывания, в этом случае мы можем спокойно восстанавливать сохраненный при инициализации прежний вектор в таблице. Если же вектор содержит не наш адрес, это означает, что после нас к цепочке подключилась другая программа (возможно даже не одна), в этом случае мы не меняем таблицу векторов, но в команде перехода меняем адрес перехода на адрес прежнего обработчика. Если хоть один вектор прерывания не удалось восстановить в таблице векторов, то блок памяти, содержащий команды перехода, должен быть оставлен в памяти после удаления из нее нашей программы. Для этого в поле "владельца" 'этого блока, которое до сих пор содержало PID нашей программы, мы записываем код 8 - признак принадлежности этого блока DOS, что защитит его от удаления.
Переключение контекста
15.4. Переключение контекста
Под контекстом мы понимаем все системные переменные, указатели и т.п., значения которых определяют выполняемую (активную) в данный момент программу. Фактически контекст содержит описание ресурсов, выделенных данной программе, так как в его составе - стек программы, таблица файлов задачи, дисковая передаточная область.
При инициализации функция main обращается к функции get_context, которая запоминает переменные контекста.
Запоминается содержимое стековых регистров программы (содержимое регистра SP запоминается с уменьшением на 200, так как часть стека используется при инициализации TSR-программы; в случае, если размер оставляемой в памяти части TSR-программы уменьшается, следует перевычислить значение SP в соответствии с устанавливаемым размером). Если TSR-программа не будет при активизации переключаться на свой стек, она будет использовать стек фоновой программы; даже при аккуратной работе с этим стеком (TSR-программа не должна в нем ничего "забывать" или наоборот - выбирать лишнее) это может привести к аварии, если размер стека фоновой программы будет невелик.
Запоминается адрес дисковой передаточной области, назначенной программе. Это, как правило, не обязательно и необходимо только в том случае, если TSR-программа использует функции, работающие с DTA.
Запоминается адрес PSP программы. Не все TSR-программы это делают, но это совершенно необходимо, если TSR-программа использует функции файлового ввода-вывода метода дескрипторов DOS. Поскольку PSP содержит ссылку на таблицу файлов задания, переключение на свой PSP дает TSR-программе возможность работать с собственной таблицей файлов. Использование таблицы файлов фоновой программы в большинстве случаев рисковано, и оно совершенно невозможно в тех случаях, когда TSR -программе необходимо сохранять свои файлы открытыми между свими активизациями.
Когда выполнены условия активизации TSR-программы, происходит обращение к функции act_tsr. Эта функция устанавливает флаг активности tsr_run и обеспечивает переключение контекста с фоновой программы на TSR-программу.
В операции по переключению контекста входят: сохранение значений регистров SS, SP, указывающих на стек фоновой программы, и установка собственных значений этих регистров, запомненных в get_context (на время изменения значений в стековых регистрах запрещаются прерывания); перехват вектора прерывания 0x24 - обработки критических ситуаций (прежний вектор сохраняется); получение и сохранение статуса обработки Ctrl+Break, установленного в фоновой программе, и отключение этой обработки (можно также перехватывать вектор прерывания 0x23, устанавливая собственный обработчик Ctrl+Break); сохранение адреса DTA фоновой программы и установка адреса своей DTA; запоминание PID текущей программы и установка собственного PID (с этого момента система "знает", что активной стала наша программа).
После переключения контекстов вызывается функция tsr_exec, выполняющая "прикладные" действия - то, для чего и разрабатывалась TSR-программа. После выполнения прикладной части происходит обратное переключение контекстов - восстановление запомненных адресов, переменных и т.д. фоновой программы, заканчивается функция act_tsr сбросом флага активности TSR-программы.
Прикладная часть нашей TSR-программы (функция tsr_exec) имеет только демонстрационное назначение: она выводит в левый верхний угол экрана сообщение о работе TSR-программы, которое снимается по нажатию любой клавиши. Однако, даже в такой простой функции следует обратить внимание на два важ- ных момента. Во-первых, переключение контекстов как бы продолжается в прикладной части. Мы имеем в виду сохранение образа той части экрана, которая будет перекрыта сообщением TSR-программы и восстановление этого образа перед возвратом из функции. В более общем случае следует, возможно, сохранять и восстанавливать и номер видеорежима, позицию курсора, номер текущей видеостраницы и т.д. Во-вторых, обратите внимание на то, что для вывода информации мы используем здесь прямую запись в видеопамять, а для ввода (ожидание нажатия клавиши) - прерывание BIOS.Это неслучайно: ведь по условиям применения прерывания 0x28 нам запрещено использование функций консольного ввода-вывода DOS.
Программные коммуникации
15.5. Программные коммуникации
Под программными коммуникациями мы понимаем обращения к TSR-программе из других программ. Для чего может понадобиться такое обращение? Во-первых, если речь идет об ISR-программах, то их активизация может происходить только по программному обращению. Во-вторых, при инициализации TSR-программы необходимо проверять, нет ли уже копии этой программы в памяти. Следовательно, запускаемая программа должна обратиться к копии программы уже резидентной в памяти, а резидентная копия должна ответить на это обращение, подтвердив свое присутствие. Наконец, в-третьих, при запуске, например, нашей программы с параметром "/Q", она должна передать резидентной копии команду на самоуничтожение. Список соображений, по которым требуется обеспечение программных коммуникаций можно было бы продолжить.
Для программных коммуникаций можно использовать прерывания, закрепив для этих целей один из свободных векторов. В DOS 3.0 и далее для этих целей введено специальное прерывание 0x2F, именуемое мультиплексным. Каждый процесс (т.е. каждая TSR-программа) закрепляет за собой какую-либо функцию этого прерывания. Функции с номерами от 0 до 0xBF закреплены за DOS (известно, что функция 1 используется программой фоновой печати PRINT, 0xB7 - резидентной утилитой APPEND, 0x11 - внутренними вызовами DOS и т.д.), функции с номерами от 0xC0 до oxFF - для пользовательских программ. Поскольку одновременно несколько пользовательских резидентных программ могут находиться в памяти и использовать мультиплекское прерывание для своих коммуникаций, необходимо обеспечить разделение между ними функций, оставленных пользователям. По спецификациям прерывания 0x2F подфункция 0 (AL=0) любой функции должна обеспечивать индикацию занятости функции. При обращении к подфункции 0 любой функции прерывания 0x2F на выходе мы должны получать в регистре AL код 0 - если функция свободна и может быть захвачена пользовательской программой, или код 0xFF - если функция занята. Каждая программа, подключающаяся к прерыванию 0x2F должна обеспечить такое выполнение подфункции 0 для занимаемой ею функции, остальные подфункции можно назначать и использовать по своему выбору.
В нашем примере обработчиком прерывания 0x2F является функция new_2F. Номер функции прерывания 0x2F, которую занимает наша TSR-программа является переменной, значение которой устанавливается в процессе инициализации. Обработчик прерывания прежде всего анализирует содержимое регистра AH, сравнивая его с номером занятой нашей программой функции. Если они совпадают, то далее выбирается номер подфункции из регистра AL, и выполняются действия, определенные для данной подфункции. Для нашего обработчика определены 4 подфункции.
Подфункция 0. В соответствии с требованиями к обработке прерывания 0x2F наш обработчик по этой подфункции возвращает код 0xFF в регистре AL. Кроме того, чтобы показать, что данная функция занята именно нашей программой, в регистрах ES:BX он возвращает адрес символьной строки "TSR-программа 15_1 загружена". Подфункция 0x10 предназначена для активизации TSR-программы из другой программы. По этой подфункции вызывается функция act_tsr. Подфункция 0x20 - внутренний вызов TSR-программы, она вызывается из обработчика прерывания 0x13 для получения содержимого флагов микропроцессора. По этой подфункции содержимое флагов копируется из параметров new_2F в глобальную переменную cflag. Подфункция 0x30 - команда на самоуничтожение TSR-программы, по ней вызывается функция self_kill.
Если функция, с которой вызвано прерывание 0x2F, нами не опознана, необходимо обратиться к прежнему обработчику этого прерывания. Обратите внимание на то, как оформлено это обращение. Трудности, возникающие при выполнении такого обращения, характерны для перехвата всех программных прерываний. Заключаются они в том, что программные прерывания получают входные параметры в регистрах, в регистрах же возвращают и выходные параметры. Поэтому перед обращением к прежнему обработчику необходимо позаботиться о восстановлении всех регистров, а при возврате из нашего обработчика - обеспечить передачу содержимого регистров, сформированного прежним обработчиком. Применительно к прерыванию 0x2F трудности усугубляются тем, что выполняемым через него внутренним вызовам DOS (функция 0x11) параметры передаются не только через регистры, но и через стек, так что надо позаботиться о том, чтобы и содержимое стека при вызове старого обработчика было идентично тому, которое имелось при выдаче команды INT 2Fh.При программировании на языка Ассемблера это легко обеспечивается передачей управления на старый обработчик командой JMP, а не CALL. Здесь же нам приходится производить некоторые манипуляции со стеком, иллюстрируемые Рисунок 15.1.
Завершение программы15.6. Завершение программы Не все TSR-программы предусматривают свою выгрузку, то есть удаление из памяти, в этом случае удалить программу можно только перезагрузив систему. Но если такая возможность предусмотрена, то самоуничтожение TSR-программы сводится к восстановлению перехваченных векторов прерываний и освобождению занимаемой памяти. В нашем примере это обеспечивается функцией self_kill. Для восстановления векторов выполняется обращение к функции restore_vectors, уже нами рассмотренной, а освобождение памяти сводится к просмотру цепочки MCB. В тех блоках памяти, в которых идентификатор "владельца" совпадает с PID TSR-программы, этот идентификатор заменяется на нулевой, т.е. свободный. Заметим, что блок памяти, содержащий команды перехода на обработчики прерываний, может не быть освобожден, если в функции restore_vectors идентификатор его "владельца" был изменен. Функции таймера - звук и время
Микросхема таймера генерирует импульсы частоты 1193180 гц. Эта последовательность импульсов поступает на три канала таймера. В каждом канале есть свой счетчик, работающий как делитель частоты, максимальное число которое может быть записано в счетчике (коэффициент деления) - 655357. Счетчики каналов таймера независимы друг от друга и доступны для программиста. Назначение каналов таймера следующее: канал 0 используется для системной службы времени. При инициализации системы BIOS записывает в счетчик этого канала максимально возможное число, таким образом, импульсы на выходе делителя выдаются с частотой около 18.2 гц (период этой частоты - около 55 мсек - программисты иногда называют "тиком" таймера). Выход делителя этого канала поступает на контроллер прерываний и вызывает прерывание с номером 8; выход канала 1 используется схемами регенерации памяти, поэтому программисты с этим каналом не работают; выход канала 2 поступает на динамик ПЭВМ, этот канал используется для генерации звука. Каждый канал может работать в одном из 6 режимов, но программисты, как правило, используют его в режиме 3 (генератор меандра). Программирование канала таймера представляет собой запись числа в счетчик канала. Имеется один управляющий порт - 0x43 для всех каналов и по одному порту данных для каждого канала - 0x40, 0x41, 0x42. При программировании следует записать в порт 0x43 управляющий байт, который обычно имеет вид: struct WORDREGS x; struct BYTEREGSПример 1 union REGS { struct WORDREGS x; struct BYTEREGS h; }; struct WORDREGS { unsigned int ax, bx, cx, dx; unsigned int si, di, cflag, flags; }; struct BYTEREGS { unsigned char al, ah, bl, bh; unsigned char cl, ch, cl, dh; }; Поле flags структуры WORDREGS отражает состояние флагов микропроцессора, а поле cflag - состояние системного флага переноса CY, в котором обычно при обращениях к DOS и к BIOS индицируется ошибка. Использование объединения REGS позволяет программисту обращаться к регистру общего назначения как к целому двухбайтному слову или к каждому байту этого слова, выбирая описатель второго уровня x или h соответственно. Так, если в программе имеется определение: Пример 1 int int86(int int_num, union REGS *inregs, union REGS *outregs); Функция выполняет прерывание с номером int_num, причем, перед выдачей команды INT содержимое полей объединения inregs копируется в регистры микропроцессора, а после возврата из прерывания - содержимое регистров - в поля объединения outregs. Функция обращения к DOS: Пример 1 /*= ПРИМЕР 4.2 =*/ /*=============== Поиск расширений ПЗУ ===============*/ #include <dos.h> main() { unsigned int segm,off; /* Части адреса */ unsigned int byte=0xAA55; /* Маркер ПЗУ */ clrscr(); for (segm=0xc000;segm Во всех проверенных нами машинах по адресу C000:0000 размещается ПЗУ дисплейного адаптера, а в машинах типа XT также по адресу C800:0000 - ПЗУ жесткого диска. Адресное пространство с F600:0000 по FD00:0FFF предназначено для ПЗУ интерпретатора Бэйсика, имеющегося только на ПЭВМ производства фирмы IBM. Наконец, от FE00:0000 и до конца адресного пространства расположено ПЗУ BIOS. BIOS в ПЗУ содержит программы, выполняющие следующие функции: тест самопроверки; начальный загрузчик; обслуживание клавиатуры; обслуживание дисплеев (CGA и MDA); обслуживание последовательных портов; служба времени; печать экрана. Пример 1 /*== ПРИМЕР 5.1 ==*/ /*======== Чтение типа ПЭВМ и даты издания BIOS =========*/ #include <dos.h> main() { unsigned char pc; /* Код типа PC */ char *PT[]= { "AT", "PCjr", "XT", "IBM PC", "???" }; unsigned int t; /* Текущее смещение */ printf("\nТип ПЭВМ = %x = ",pc=peekb(0xf000,0xfffe)); if ((pc-=0xfc)>4) pc=4; printf("%s\n",PT[pc]); printf("Дата издания BIOS = "); for (t=0xfff5;t Пример 1 x x 1 1 0 1 1 0 где xx - номер канала таймера, а затем послать в порт данных выбранного канала сначала младший, а затем старший байт счетчика. Пример 1 /*== ПРИМЕР 7.1 ==*/ /*======== Прерывание от клавиатуры и scan-коды =========*/ #include <dos.h> # define byte unsigned char void interrupt (*old9)(); /* Для сохр. старого вектора */ void interrupt new9(); /* Описание нового обработчика */ byte SC[100]; /* Массив - накопитель скан-кодов */ byte Nsc=0; /* Счетчик скан-кодов */ byte eoj_flag; /* Флаг окончания */ void *readvect(int in); void writevect(int in, void *h); union REGS rr; struct SREGS sr; main() { int i; /* Перехват вектора 9 */ old9=readvect(9); writevect(9,new9); printf("\n\nНажимайте на калавиши \n"); printf("Esc - конец работы\n"); /* В этом цикле происходит обработка нажатых клавиш */ for (eoj_flag=0; eoj_flag==0;); /* Восстановление вектора 9 */ writevect(9,old9); /* Вывод на экран введенных скан-кодов */ for (i=0; i<Nsc; printf("%x ",SC[i++]) ); } /*==== Обработчик прерывания 9 ====*/ void interrupt new9() { byte scan; /* Скан-код */ byte c; /* Состояние порта 61 */ /* Чтение scan-кода. */ scan=inportb(0x60); /* По Esc (скан - 1) устанавливается признак окончания */ if (scan==1) eoj_flag=1; /* Запоминается скан-код */ SC[Nsc++]=scan; /* Устанавл.признак окончания при заполнении массива */ if (Nsc>99) eoj_flag=1; /* Подтверждение приема. В порт 61 на короткое время выставляется "1" по шине старшего разряда. */ c=inportb(0x61); outportb(0x61,c|0x80); outportb(0x61,c); /* Сброс контроллера прерываний. */ outportb(0x20,0x20); } /*==== Получение старого вектора ====*/ void *readvect(int in) { rr.h.ah=0x35; rr.h.al=in; intdosx(&rr,&rr,&sr); return(MK_FP(sr.es,rr.x.bx)); } /*==== Запись нового вектора ====*/ void writevect(int in, void *h) { rr.h.ah=0x25; rr.h.al=in; sr.ds=FP_SEG(h); rr.x.dx=FP_OFF(h); intdosx(&rr,&rr,&sr); } Программный пример 7.2 иллюстрирует технику обработки "горячей клавиши", часто применяемую в резидентных программах. Такие программы перехватывают прерывание 9 и распознают код некоторой закрепленной за ними клавиши. При распознавании "горячей клавиши" программа выполняет какие-то свои действия, обработку всех остальных клавиш программа "отдает" системе. Для нашей программы "горячей" является клавиша "a", действия нашей программы по клавише "a" - пустые, что приведет к исчезновению буквы "a" из вводимого потока. Эта программа, как и предыдущая, также перехватывает прерывание 9. Обработчик прерывания читает скан-код, но не спешит посылать в клавиатуру подтверждение, а анализирует код. Если это скан-код клавиши "a", то обработчик удаляет его из клавиатуры и сбрасывает контроллер прерываний. В противном случае вызывается системный обработчик, который повторно прочитает тот же код из порта 0x60 и распорядится им по-своему. Вы можете убедиться в том, что в символьную строку, вводимую функцией scanf основной программы, не будут включаться буквы "a". Пример 1 /*== ПРИМЕР 8.1 ==*/ /*============== Получение статуса принтера ==============*/ #include <dos.h> main() { union REGS rr; int dataport,statusport,ctrlport; /* Номера портов */ unsigned char stat; /* Байт статуса */ int i; /* Определение адресов портов принтера */ dataport=peek(0x40,8); statusport=dataport+1; ctrlport=statusport+1; printf("Порты LPT1 = %03X, %03X, %03X\n", dataport,statusport,ctrlport); /* Проверка состояний */ printf("\nУстановите: принтер выключен. "); printf("Нажмите любую клавишу\n"); getch(); stat=inportb(statusport); printf("Состояние принтера - "); for (i=7; i>=0; i--) if ((stat>>i)&1) printf("1"); else printf("0"); printf("\nEpson состояние - 11110111\n"); printf("\nУстановите: принтер offline. "); printf("Нажмите любую клавишу\n"); getch(); stat=inportb(statusport); printf("Состояние принтера - "); for (i=7; i>=0; i--) if ((stat>>i)&1) printf("1"); else printf("0"); printf("\nEpson состояние - 01010111\n"); printf("\nУстановите: нет бумаги. "); printf("Нажмите любую клавишу\n"); getch(); stat=inportb(statusport); printf("Состояние принтера - "); for (i=7; i>=0; i--) if ((stat>>i)&1) printf("1"); else printf("0"); printf("\nEpson состояние - 01110111\n"); printf("\nУстановите: принтер готов. "); printf("Нажмите любую клавишу\n"); getch(); stat=inportb(statusport); printf("Состояние принтера - "); for (i=7; i>=0; i--) if ((stat>>i)&1) printf("1"); else printf("0"); printf("\nEpson состояние - 11011111\n"); } Разряды байта, передаваемого в порт управления, интерпретируются следующим образом:
Пример 1 /*== ПРИМЕР 9.1 ==*/ /*============ Определение активного адаптера ============*/ #include <dos.h> unsigned char types1A[] = { 0,1,2,4,5,6,7,8,10,11,12,0xff }; char *stypes1A[] = { "нет дисплея","MDA,моно","CGA,цв.", "EGA,цв.","EGA,моно","PGA,цв.","VGA,моно,анал.", "VGA,цв.,анал.","MCGA,цв.,цифр.","MCGA,моно,анал.", "MCGA,цв.,анал.","неизвестный тип", "непредусмотренный код" }; unsigned char b[64]; /* буфер данных VGA */ struct SREGS sr; union REGS rr; int i; main() { /* Предположим, VGA */ rr.h.ah=0x1a; rr.h.al=0; int86(0x10,&rr,&rr); if (rr.h.al==0x1a) { printf("Поддерживается ф-ция 1Ah прерывания 10h\n"); for (i=0; i0 && i Функция 0x1A доступна только при наличии расширения BIOS, ориентированного на обслуживание VGA. В этом случае функция возвращает в регистре AL код 0x1A - свою "визитную карточку", а в BL - код активного видеоадаптера. Мы в нашем примере в случае, когда функция 0x1A поддерживается, обращаемся еще и к функции 0x1B - эта последняя заполняет 70-байтный блок информации о состоянии, из которого мы выбираем объем видеопамяти. Если 0x1A не поддерживается, значит, VGA у нас нет, в этом случае можно обратиться к функции 0x12 - получение информации о EGA. При наличии расширения, ориентированного на EGA, эта функция изменяет содержимое BL (перед обращением оно должно быть 0x10) на 0 (цветной режим) или 1 (монохромный режим) а в BH возвращает объем видеопамяти. Если же ни 0x1A, ни 0x12 не поддерживаются, то список оборудования BIOS содержит достаточную информацию о видеоадаптере. Пример 1 ttttttttTTssssss, где t...t - младшие 8 бит номера дорожки; TT - старшие 2 бита номера дорожки; s...s - номер сектора на дорожке (6 бит). В DOS для чтения/записи секторов служат прерывания (прерывания, а не функции DOS!) 0x25 (чтение) и 0x26 (запись). Обычный формат обращения к этим прерываниям следующий:
На выходе, как и для прерывания 0x13, устанавливается флаг переноса, а регистр AH содержит код ошибки при наличии таковой. Заметим, что дисковый адрес задается здесь не физический, а логический - номер сектора относительно начала логического диска (о логических дисках - см. ниже). Нумерация секторов внутри логического диска начинается с 0, и номер логического сектора может быть вычислен как: logs = ( (t * ns) + h) * nh + s-1; (10.1) где t, h, s - физический адрес (дорожка, головка, сектор); ns - количество секторов на дорожке, nh - количество головок чтения/записи. Для получения абсолютного номера сектора надо к вычисленному значению прибавить еще некоторую величину s0 - абсолютный номер сектора начала логического диска. Как мы увидим ниже, величины ns, nh, s0 могут быть получены из системной информации, находящейся на самом носителе. В программе примера 10.1, иллюстрирующей применение средств чтения секторов средствами BIOS и DOS наибольший интерес (кроме вызывающих последовательностей для прерываний) представляют функции Daddr_to_Sect и Sect_to_Daddr, осуществляющие перевод физического адреса в логический и наоборот соответственно. Программа запрашивает способ задания адреса, формирует по заданному физическому адресу логический или наоборот, вводит один и тот же сектор дважды (сначала используя прерывание BIOS, а затем - DOS) и выводит на экран содержимое начального участка сектора, при двух вариантах ввода - результаты должны быть одинаковыми. Физический адрес в программе описывается структурой daddr, поля t, h, s которой содержат компоненты физического адреса, а ts - заготовку для регистра CX при обращении к прерыванию 0x13. Предупредим читателя, что приведенная программа будет правильно работать только с дискетой, отформатированной на 360 Кбайт, так как величины nh, ns, s0 (параметры формулы 10.1) и другие (nt, nls) имеют константные значения, свойственные именно этому формату. Пример 1 /*== ПРИМЕР 11.1 ==*/ /*=============== Распечатка собственного PSP ============*/ #include <dos.h> #define byte unsigned char # define word unsigned int struct psp { byte ret_op[2]; /* команда INT 20h */ word end_of_mem; /* вершина доступной памяти */ byte reserved1; byte old_call_dos[5]; /* старый вызов DOS */ void *term_ptr; /* адрес завершения */ void *ctrlbrk_ptr; /* адрес обработчика Ctrl+Break */ void *criterr_ptr; /* адрес обработчика крит.ошибок */ word father_psp; /* PID родителя */ byte JFT[20]; /* таблица файлов программы */ word env_seg; /* адрес окружения */ void *stack_ptr; /* адрес стека */ word JFT_size; /* размер таблицы файлов */ byte *JFT_ptr; /* адрес таблицы файлов */ byte reserved2[24]; byte new_call_dos[3]; /* новый вызов DOS */ } *mypsp; word mypid; /* сегм.адрес PSP */ int dos, i, l; char *s; union REGS rr; main() { clrscr(); /* определение версии DOS */ rr.h.ah=0x30; intdos(&rr,&rr); dos=rr.h.al; /* получение адреса своего PSP */ rr.h.ah=0x62; intdos(&rr,&rr); mypid=rr.x.bx; /* распечатка PSP */ printf("***** PID=%04X *****\n",mypid); mypsp=(struct psp *)MK_FP(mypid,0); printf ("Команды: завершение - int 20h ---> %02X %02X\n", mypsp->ret_op[0],mypsp->ret_op[1]); printf (" старый вызов DOS -------> "); for (i=0;i<5;printf("%02X ",mypsp->old_call_dos[i++])); printf ("\n новый вызов DOS --------> "); for(i=0;i<3;printf("%02X ",mypsp->new_call_dos[i++])); printf ("\n\nАдреса: конец памяти -------------> %04X:0000\n", mypsp->end_of_mem); printf(" обработчик завершения ----> %Fp\n", mypsp->term_ptr); printf(" обработчик Ctrl+Break ----> %Fp\n", mypsp->ctrlbrk_ptr); printf(" обработчик критич.ошибки -> %Fp\n", mypsp->criterr_ptr); printf(" стек ---------------------> %Fp\n", mypsp->stack_ptr); printf("\nРодитель: ------------------------> %04X ", mypsp->father_psp); pr_file_tab(); /* таблица файлов */ printf("\nОкружение DOS --------------------> %04X\n", mypsp->env_seg); s=(char *)MK_FP(mypsp->env_seg,0); while(l=strlen(s)) { printf(" %s\n",s); s+=l+1; } if (dos>2) { /* для DOS 3.0 и дальше можно получить строку вызова */ s++; l=*((int *)s); printf("Строки вызова ----------------------> %d\n",l); s+=2; for(i=0; i<l; i++) { printf("%s\n",s); s+=strlen(s)+1; } } getch(); /* увеличение размера таблицы файлов */ rr.h.ah=0x67; /* функция 67 */ rr.x.bx=30; /* новый размер - 30 */ intdos(&rr,&rr); if (rr.x.cflag) printf("Ошибка функции 67h\n"); else pr_file_tab(); getch(); } /*==== распечатка таблицы файлов ====*/ pr_file_tab() { s=mypsp->JFT_ptr; printf ("\n\nТаблица файлов: -------------------> %Fp (%d) ", s,mypsp->JFT_size); if (s==(byte *)mypsp+0x18) printf(" - в этом же PSP"); printf("\n"); for (i=0; ++i<=mypsp->JFT_size; printf("%d ",*(s++))); printf("\n"); } Пример 1 struct MCB { byte type; /* тип */ word owner; /* владелец */ word size; /* размер */ byte reserved[3]; /* не используется */ char pgmname[8]; /* имя (только DOS 4.0 и выше) */ }; Поле type MCB содеpжит код, показывающий, является ли этот MCB последним (код буквы Z - 0x5A.) или непоследним (код буквы M - 0x4D). Поле owner содеpжит PID (сегментный адpес пpефикса пpогpаммного сегмента) пpогpаммы, котоpой данный блок памяти пpинадлежит. Если значение этого поля нулевое, то блок свободен. Поле size содеpжит pазмеp блока памяти в паpагpафах (в это число не включен 1 паpагpаф, занимаемый самим MCB). Следующие 3 байта (поле reserved) заpезеpвиpованы во всех веpсиях. Поле pgmname заpезеpвиpовано (не используется) в веpсиях DOS ниже 4.0. Начиная с веpсии 4.0, в MCB, пpедваpяющем пpогpаммный сегмент, здесь записано имя (без pасшиpения) пpогpаммы, находящейся в этом сегменте (если длина имени меньше 8 символов, оно заканчивается нулевым байтом). Все MCB увязаны в цепочку. Зная адpес пеpвого MCB в цепочке, можно, пpибавив к нему длину (size) и еще 1 (паpагpаф, занимаемый самим MCB), получить адpес следующего MCB и так далее. Пpизнаком конца цепочки является значение 'Z' поля type. Адpес начала цепочки блоков памяти можно получить пpи помощи недокументиpованной функции DOS 0x52. Подpобности пpименения этой функции pассмотpены в следующей главе. Здесь же только сообщим читателю, что эта функция возвpащает в pегистpах ES:BX некотоpый адpес. Вычтя из этого адpеса 2, получим адpес того слова памяти, в котоpом DOS хpанит сегментный адpес пеpвого MCB в цепочке. Пpогpамма следующего пpимеpа позволяет пpосмотpеть "каpту pаспpеделяемой памяти" ПЭВМ - пpоиндициpовать, какие блоки свободны, а какие заняты и кем (какой пpогpаммой) заняты. Пример 1 /*===== Таблица Векторов Связи для DOS 3.0 и выше =====*/ struct CVT { word MCB_segment; /* Сегментный адрес 1-го Блока Управления Памятью */ dword DPB_ptr; /* Адрес 1-го Блока Параметров Диска */ dword SFT_ptr; /* Адрес 1-й Системной Таблицы Файлов */ dword clock_ptr; /* Адрес заголовка драйвера CLOCK$ */ dword con_ptr; /* Адрес заголовка драйвера CON */ word maxBlock; /* Максимальный размер блока на блочном устройстве */ dword BCB_ptr; /* Адрес 1-го буфера дисковых операций */ dword ACD_ptr; /* Адрес Массива Текущих Каталогов */ dword FCBtab_ptr; /* Адрес Таблицы Блоков FCB, открываемых в режиме разделения */ word FCBtab_size; /* Размер Таблицы Блоков FCB (параметр FCBS в CONFIG.SYS) */ byte driveCnt; /* Число блочных устройств в системе */ byte lastdrive; /* Число идентификаторов дисков (параметр LASTDRIVE в CONFIG.SYS) */ byte NUL_drive[18]; /* Заголовок драйвера NUL */ }; Недокументированная функция DOS 0x52, именуемая в нефирменных описаниях GetSysVars или GetCvt, возвращает в регистрах ES:BX адрес поля DPB_ptr таблицы векторов связи. Но перед этим полем в памяти находится 2-байтный сегментный адрес первого управляющего блока памяти, который логически также относится к векторам связи (мы уже использовали это свойство функции 0x52 в программах предыдущей главы). Поэтому мы включаем это поле в CVT и считаем, что функция 0x52 возвращает адрес, на 2 больший начального адреса таблицы. CVT имеет и поля, расположенные перед MCB_segment, таблица продолжается и за заголовком NUL-драйвера, но содержащаяся в этих частях информация весьма специфична, существенно зависит от версии DOS (не только от старшего, но и от младшего номера версии) и в нашем пособии рассматриваться не будут. Пример 1 u 0 i r c l l o гдеu - диск (1) или другое устройство (0); i - разрешено игнорировать (1); r - разрешено повторить (1); c - разрешено снять (1); ll - локализация места ошибки на диске (00 - системная область, 01 - таблица размещения файлов, 10 - каталог, 11 - область данных), имеет смысл только при u=1; o - операция чтения (0) или записи (1). Если в регистре AH бит u=1, то регистр AL содержит логический номер диска. Регистры BP:SI указывают не заголовок драйвера устройства, на котором произошла ошибка. Регистр DI (младший его байт) содержит код ошибки. Возможны следующие коды:
Обработчик критической ошибки должен вернуть в регистре AX код решения:
Из обработчика критической ошибки нельзя обращаться к функциям DOS с номерами выше 0x0C, исключение составляет функция 0x59, о которой будет сказано ниже. В программном примере 14.1 устанавливается собственный обработчик ошибки, который запоминает информацию об ошибке и заканчивается с кодом 3 - снять системный вызов. Информация об ошибке затем распечатывается. Ошибочные ситуации, которые возникают при работе примера 14.1, - чтение информации с неготового диска и вывод на неготовый принтер. Читатель может самостоятельно расширить пример другими сбойными ситуациями. Пример 1 /*== ПРИМЕР 15.1 ==*/ /*============== Резидентная программа ===================*/ /* Модель памяти - small. /* При компиляции установить: Options -> Compiler -> Code generation -> Test stack overflow -> Off !!! */ #include <dos.h> #include <stdio.h> #include <string.h> #define byte unsigned char #define word unsigned int /* адрес в виде - сегмент:смещение */ typedef struct { word segm,offs; } addr; /* Описания функций */ byte check_tsr(void); /* проверка наличия TSR-программы в памяти */ void get_context(void); /* запоминание своего контекста */ void set_vectors(void); /* установка своих векторов */ void restore_vectors(void); /* восстановление векторов */ void far *get_vector(int n); /* чтение вектора */ void set_vector(int n, void far *v); /* установка вект. */ void act_tsr(void); /* активизация TSR-программы */ void self_kill(void); /* самовыгрузка TSR-программы */ void tsr_exec(void); /* прикладная часть TSR-программы */ /* описания новых обработчиков прерываний */ void interrupt new_8(); void interrupt new_9(); void interrupt new_13(); void interrupt new_28(); void interrupt new_2F(); void interrupt new_24(); /* номера обрабатываемых прерываний */ int int_nums[] = { 8, 9, 0x13, 0x28, 0x2f }; void interrupt (* new_v[])() = { /* адреса новых обработчиков */ new_8, new_9, new_13, new_28, new_2F }; /* области сохранения старых векторов */ void interrupt (* old_v[5])(); addr a_2F; /* адрес старого обработчика INT 2F */ /* Флаги */ byte far *dos_flag; /* адрес флажка занятости DOS */ byte tsr_run = 0; /* "TSR выполняется" (1) */ byte hot_key = 0; /* "горячая клавиша нажата"(1) */ byte disk_op = 0; /* "дисковая операция" (1) */ byte key_byte; /* байт символа/состояния клавиатуры */ word cflag; /* регистр флагов процессора */ /* набор команд перехода на обработчики прерываний */ struct far_jmp { byte jmp; /* код команды */ void interrupt (* int_h)(); /* адрес */ } far *jmpar; word jmp_segm; /* сегм.адрес блока команд перехода */ /* переменные контекста */ byte old_ctrl_break; /* состояние обработки Ctrl-Break прерванной программы */ addr old_stack; /* сегмент и указатель стека прерванной программы */ addr tsr_stack; /* то же - TSR-программы */ word old_pid; /* PID прерванной программы */ word tsr_pid; /* то же - TSR-программы */ addr old_dta; /* адрес DTA прерванной программы */ addr tsr_dta; /* то же - TSR-программы */ void interrupt (* old_24)(); /* адрес обработчика критической ошибки прерванной программы */ /* общие переменные */ word sizeprogram = 17000/16; /* размер программы, определенный опытным путем */ word hot_scan = 2; /* scan-код горячей клавиши - "1" */ word hot_status =8; /* маска спец.клавиши - Alt */ char tsr_mark[] = "TSR-программа 15_1 загружена"; /* опознавательная строка символов */ char far *mark; byte com_func; /* номер коммуникационной функции прерывания 2F */ word far *ast; /* адрес в стеке */ word mcb_seg; /* адрес блока MCB */ byte screen[160]; /* для сохранения части экрана */ union REGS rr; struct SREGS sr; int i, n; /*-------------------------------------------------------*/ /* ==== Пусковая программа ====*/ main(int argn, char *argc[]) { /* проверка - не является ли программа уже резидентной */ /* check_tsr возвращает 0, если уже резидентна */ /* check_tsr присваивает значение переменной com_func */ mark=(char far *)tsr_mark; if (!check_tsr()) { /*--- программа уже резидентна --*/ /* с каким параметром вызвана программа ? */ if (argn>1) { if (!strcmp(argc[1],"/Q")) { /* /Q - обращение к коммуникации для выгрузки */ rr.h.ah=com_func; rr.h.al=0x30; /* коммуникационное прерывание */ int86x(0x2f,&rr,&rr,&sr); printf("TSR-программа 15_1 выгружена\n"); } else /* неправильный вызов */ printf("15_1 - инсталляция\n15_1 /Q - выгрузка\n"); } else { /* параметров нет - попытка повторной инсталляции */ printf("!!! УЖЕ- %s -УЖЕ !!!\n",tsr_mark); printf("Функция прерывания 2Fh - %02Xh\n",com_func); } } else { /*--- программа не резидентна --*/ get_context(); /* запоминание своего контекста */ /* освобождение своего блока окружения */ sr.es=peek(tsr_pid,0x2c); rr.h.ah=0x49; intdosx(&rr,&rr,&sr); set_vectors(); /* установка своих векторов */ /* получение адреса флажка занятости DOS */ rr.h.ah=0x34; intdosx(&rr,&rr,&sr); dos_flag=(byte far *)MK_FP(sr.es,rr.x.bx); /* завершение программы с оставлением ее резидентной */ printf("%s\n",tsr_mark); rr.h.ah=0x31; rr.h.al=0; rr.x.dx=sizeprogram; intdos(&rr,&rr); } } /*---------------------------------------------*/ /*==== Обработчик прерывания от клавиатуры ====*/ void interrupt new_9() { /* чтение scan-кода и проверка его на совпадение с кодом горячей клавиши; проверка состояния переключателей */ if (inportb(0x60)==hot_scan) { key_byte=peekb(0x40,0x17); if ((key_byte&hot_status) == hot_status) { /* посылка подтверждения в клавиатуру */ key_byte=inportb(0x61); outportb(0x61,key_byte|0x80); outportb(0x61,key_byte); /* сброс контроллера прерываний */ outportb(0x20,0x20); if (!tsr_run) /* блок.реентерабельного вызова */ hot_key=1; /* установка флага горячей клавиши */ } else (*old_v[1])(); /* системный обработчик */ } else (*old_v[1])(); /* системный обработчик */ } /*------------------------------------------*/ /*==== Обработчик прерывания от таймера ====*/ void interrupt new_8() { (*old_v[0])(); /* cистемный обработчик */ if (hot_key && !(*dos_flag)) /* если нажата горячая клавиша и не установлен флажок занятости DOS... */ if (!disk_op) { /* ...и не выплоняется дисковая операция */ hot_key=0; /* сброс флага горячей клавиши */ act_tsr(); /* активизация */ } } /*--------------------------------------------------*/ /*==== Обработчик прерывания обращения к дискам ====*/ void interrupt new_13(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,fl) word bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,fl; { disk_op++; /* флаг дисковой операции */ (*old_v[2])(); /* системный обработчик int 13 */ /* возврат регистров, установленных сист.обработчиком */ ax=_AX; cx=_CX; dx=_DX; /* обращение к int 2F, которая записывает регистр флагов в static-переменную cflags */ _AX=0x10; new_2F(); fl=cflag; --disk_op; /* сброс флага дисковых операций */ } /*-------------------------------------*/ /*==== Обработчик прерывания DOSOK ====*/ void interrupt new_28() { (*old_v[3])(); /* Системный обработчик */ if (hot_key && *dos_flag) { /* если нажата горячая клавиша и установлен флажок занятости */ hot_key=0; /* сброс флага горячей клавиши */ act_tsr(); /* активизация */ } } /*-------------------------------------------------------*/ /*==== Обработчик прерывания 24 - критические ошибки ====*/ void interrupt new_24 (bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,fl) word bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,fl; { ax=0; } /*------------------------------------------------*/ /*==== Чтение вектора ====*/ void far *get_vector(int in) { rr.h.ah=0x35; rr.h.al=in; intdosx(&rr,&rr,&sr); return(MK_FP(sr.es,rr.x.bx)); } /*------------------------------------------------*/ /*==== Запись вектора ====*/ void set_vector(int in, void far *v) { rr.h.ah=0x25; rr.h.al=in; sr.ds=FP_SEG(v); rr.x.dx=FP_OFF(v); intdosx(&rr,&rr,&sr); } /*------------------------------------------------*/ /*==== Перехват векторов прерываний ====*/ void set_vectors(void) { /* выделение памяти для команд перехода */ rr.h.ah=0x48; rr.x.bx=2; intdos(&rr,&rr); jmp_segm=rr.x.ax; jmpar=(struct far_jmp far *)MK_FP(jmp_segm,0); for (i=0;i<5;i++) { /* получение старых векторов */ old_v[i]=get_vector(int_nums[i]); /* запись кодов команд FAR JMP */ jmpar[i].jmp=0xea; /* запись адресов наших обработчиков */ jmpar[i].int_h=new_v[i]; /* установка вектора на соответствующий jmp */ set_vector(int_nums[i],(void far *)(jmpar+i)); } /* адрес мультиплексного прерывания запоминается */ a_2F.segm=FP_SEG(old_v[4]); a_2F.offs=FP_OFF(old_v[4]); } /*-----------------------------------------------*/ /*==== Восстановление векторов прерываний ====*/ void restore_vectors(void) { for (i=n=0;i<5;i++) { /* если вектор наш - восстановить его */ if ( get_vector(int_nums[i])== (void far *)(jmpar+i)) set_vector(int_nums[i],old_v[i]); else { /* если нет - запись адреса старого обработчика в команду перехода */ jmpar[i].int_h=old_v[i]; n++; } } /* если не все векторы восстановлены - блок команд перехода помечается принадлежащим DOS */ if (n) poke(jmp_segm-1,1,8); } /*-------------------------------------------------------*/ /*==== Запоминание своего контекста ====*/ void get_context(void) { /* сохранение своего сегмента стека */ tsr_stack.segm=_SS; tsr_stack.offs=_SP-100; /* сохранение адреса своей DTA */ rr.h.ah=0x2f; intdosx(&rr,&rr,&sr); tsr_dta.segm=sr.es; tsr_dta.offs=rr.x.bx; /* сохранение своего PID */ rr.h.ah=0x62; intdos(&rr,&rr); tsr_pid=rr.x.bx; } /*-------------------------------------------------------*/ /*==== Переключение контекстов и активизация TSR-программы ====*/ void act_tsr(void) { tsr_run=1; /* установка флага "TSR работает" */ /*= изменение контекста при активизации TSR-программы =*/ /* переключение на стек TSR-программы */ disable(); old_stack.offs=_SP; old_stack.segm=_SS; _SP=tsr_stack.offs; _SS=tsr_stack.segm; enable(); /* подключение к вектору критических ситуаций */ old_24=get_vector(0x24); set_vector(0x24,new_24); /* переключение статуса обработки Ctrl-Break */ rr.h.ah=0x33; rr.h.al=0; intdos(&rr,&rr); old_ctrl_break=rr.h.dl; rr.h.ah=0x33; rr.h.al=1; rr.h.dl=0; intdos(&rr,&rr); /* переключение на DTA TSR-программы */ rr.h.ah=0x2f; intdosx(&rr,&rr,&sr); old_dta.segm=sr.es; old_dta.offs=rr.x.bx; rr.h.ah=0x1e; sr.ds=tsr_dta.segm; rr.x.dx=tsr_dta.offs; intdosx(&rr,&rr,&sr); /* переключение на PID TSR-программы */ rr.h.ah=0x62; intdos(&rr,&rr); old_pid=rr.x.bx; rr.h.ah=0x50; rr.x.bx=tsr_pid; intdos(&rr,&rr); /*= выполнение "прикладной части" =*/ tsr_exec(); /*= восстановление контекста прерванной программы =*/ /* восстановление PID */ rr.h.ah=0x50; rr.x.bx=old_pid; intdos(&rr,&rr); /* восстановление DTA */ rr.h.ah=0x1e; sr.ds=old_dta.segm; rr.x.dx=old_dta.offs; intdosx(&rr,&rr,&sr); /* восстановление Ctrl-Break */ rr.h.ah=0x33; rr.h.al=1; rr.h.dl=old_ctrl_break; intdos(&rr,&rr); /* восстановление обработчика критических ситуаций */ set_vector(0x24,old_24); /* восстановление стека */ disable(); _SP=old_stack.offs; _SS=old_stack.segm; enable(); tsr_run=0; } /*------------------------------------------*/ /*==== "Прикладная" часть TSR-программы ====*/ void tsr_exec(void) { char *s; int i; /* сохранение экрана и вывод сообщения */ for (i=0, s=tsr_mark; *s; ) { screen[i]=peekb(0xb800,i); pokeb(0xb800,i++,*s++); screen[i]=peekb(0xb800,i); pokeb(0xb800,i++,0x40); } screen[i]=0; /* ожидание нажатия клавиши */ do { rr.h.ah=1; int86(0x16,&rr,&rr); } while (rr.x.flags&0x0040); rr.h.ah=0; int86(0x16,&rr,&rr); /* восстановление экрана */ for(i=0;screen[i];i++) pokeb(0xb800,i,screen[i]); } /*---------------------------------------------------*/ /*==== Обработчик прерывания 0x2F (коммуникации) ====*/ void interrupt new_2F(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,fl) word bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,fl; { if ((ax>>8)==com_func) { /* подфункции */ switch (ax&0xff) { case 0: /* проверка возможности установки */ ax|=0xff; /* запрет установки */ es=FP_SEG(mark); bx=FP_OFF(mark); break; case 0x10: /* программный вызов TSR */ act_tsr(); ax=0; break; case 0x20: /* получение регистра флагов */ cflag=fl; break; case 0x30: /* выгрузка */ self_kill(); ax=0; break; default: ax=0xffff; } } else { /* возврат из нашего обработчика в старый обработчик */ for (ast=(word far *)&bp; ast<=(word far *)&flags; ast++) *(ast-3)=*ast; cx=a_2F.offs; bx=a_2F.segm; _SP-=6; } } /*-------------------------------------------------------*/ /*==== Поиск свободных функций 2F или функции, занятой 15_1 ===*/ byte check_tsr(void) { byte a, b; char far *s1; char *s2; com_func=0; for (a=0xff; a>=0xc0; a--) { rr.h.ah=a; rr.h.al=0; int86x(0x2f,&rr,&rr,&sr); b=rr.h.al; /* запоминание первого свободного номера функции */ if (!(b+com_func)) com_func=a; if (b==0xff) /* функция занята */ { s1=(MK_FP(sr.es,rr.x.bx)); for (s2=tsr_mark; *s2; s1++,s2++) if (*s1!=*s2) break; if (*s2) continue; /* занята нами */ com_func=a; return (0); } } return (1); } /*-----------------------------------------------*/ /*==== Операции по уничтожению TSR-программы ====*/ void self_kill(void) { /* восстановление системных векторов прерываний */ restore_vectors(); /* определение начала цепочки MCB */ rr.h.ah=0x52; intdosx(&rr,&rr,&sr); mcb_seg=peek(sr.es,rr.x.bx-2); while (peekb(mcb_seg,0)==0x4d) { /* выборка из MCB признака последний/нет */ if (peek(mcb_seg,1)==tsr_pid) { /* выборка из MCB PID программы-хозяина, если он совпадает с нашим, блок памяти освобождается rr.h.ah=0x49; sr.es=mcb_seg+1; intdosx(&rr,&rr,&sr);*/ poke(mcb_seg,1,0); } /* переход к следующему MCB в цепочке */ mcb_seg+=peek(mcb_seg,3)+1; } } то результаты выполнения операторов:Пример 2 union REGS rr; то результаты выполнения операторов: Пример 2 int intdos(union REGS *inregs, union REGS *outregs); Вызов этой функции эквивалентен вызову функции int86 со значением параметра int_num = 0x21. В приведенных выше функциях для передачи параметров используются только регистры общего назначения. Если для передачи параметров требуется использовать также и сегментные регистры, то можно воспользоваться функциями: Пример 2 /*= ПРИМЕР 4.3. =*/ /*========== Горячая перезагрузка системы ===========*/ #include <dos.h> main() { /* 2-й и 3-й параметры вызова нулевые, т.к. для прерывания 0x19 никаких параметров не требуется */ int86(0x19,00,00); } Вы обратили внимание на слово "возможно" в предыдущей фразе? Скорее всего, программа приведет к "зависанию" системы. Дело в том, что при "горячей" перезагрузке не переустанавливаются векторы прерываний BIOS, таким образом, если прерывания BIOS были к моменту выдачи INT 19H перехвачены какими-то резидентными программами, после перезагрузки системы эти векторы будут указывать неизвестно куда. Поэтому не следует возлагать больших надежд на "горячую" перезагрузку. В "холодном" и "теплом" вариантах перезагрузка выполняется с самого начала - с выполнения программы POST. Различие между "холодным" и "теплым вариантами заключается в том, что в первом случае POST выполняется в полном объеме - как при нажатии кнопки RESET, а во втором из нее исключается тестирование оборудования и оперативной памяти - как при нажатии комбинации клавиш Ctrl+ Alt+Del. Для запуска перезагрузки надо просто передать управление по адресу FFFF:0000. Тип перезагрузки BIOS определяет из содержимого слова по адресу 0040:0072 - если там находится код 0x1234, выполняется "теплая" перезагрузка, иначе - "холодная". Программное выполнение такой перезагрузки представлено в примере 4.4. Пример 2 /*= ПРИМЕР 5.2 =*/ /*============= Получение списка оборудования ============*/ #include <dos.h> main() { union REGS rr; unsigned int d; /* список оборудования */ int i; /*== Чтение байта обоpудования через прерывание 0x11 ==*/ /* Прерывание 0x11 возвращает его в регистре AX */ int86(0x11,&rr,&rr); /* Побитная распечатка списка оборудования */ printf ("Список активного оборудования из прерывания 11 - "); for (i=15; i>=0; printf("%d",(rr.x.ax>>i--)&0x01)); printf(" (%04x)\n",rr.x.ax); /* == Чтение байта обоpудования из памяти BIOS == */ /* Будет получено то же самое */ d=peek(0x40,0x10); printf ("Список активного оборудования из памяти BIOS - "); for (i=15; i>=0; printf("%d",(d>>i--)&0x01)); printf(" (%04x)\n",d); /* == Раскодирование списка обоpудования == */ printf(" Дисководов ГМД - "); if (d&0x0001) printf("%d\n",((d&0x00c0)>>6)+1); else printf("нет\n"); printf(" Сопроцессор 8087 - "); if (d&0x0002) printf("есть\n"); else printf("нет\n"); printf(" Тип дисплейного адаптера - "); switch (d&0x0030) { case 0: printf("EGA/VGA"); break; case 0x10: printf("CGA,40-кол"); break; case 0x20: printf("CGA,80-кол"); break; case 0x30: printf("MDA"); break; } printf(" (неточно)\n"); printf(" Первичный блок памяти - "); switch (d&0x000c) { case 0: printf("16 Кбайт\n"); break; case 4: printf("32 Кбайт\n"); break; case 8: printf("48 Кбайт\n"); break; case 12: printf("64 Кбайт или больше\n"); break; } printf(" Портов RS232 - %d\n",(d&0x0e00)>>9); printf(" Джойстик - "); if (d&0x1000) printf("есть\n"); else printf("нет\n"); printf(" Принтеров - %d\n",(d&0xe000)>>14); } Бросается в глаза, что список оборудования содержит явно недостаточно информации по дисплейному адаптеру - видимо, при его проектировании не была учтена возможность появления новых типов адаптеров после CGA. Пример 2 /*== ПРИМЕР 6.1 ==*/ /*============== Генерация звука ====================*/ #include <dos.h> main() { unsigned int gamma[] = /* Коэффициенты деления для нот */ { 912,813,724,678,609,542,483 }; char *gnames[] = /* Названия нот */ { "до","ре","ми","фа","соль","ля","си" }; int i; for(i=0; i>8); /* Старший байт счетчика */ /* Включение динамика. Читается содержимое порта, в него записываются 1 в разряды 0, 1, затем пишется в порт */ outportb(0x61,inportb(0x61)|0x03); } /*==== Выключение звука ====*/ silence() { /* записываются 0 в разряды 0, 1 порта 0x61 */ outportb(0x61,inportb(0x61)&0xfc); } И еще одно замечание. Большинство языков программирования высокого уровня имеют стандартные функции, аналогичные нашим tone и silence (в Турбо-Си это sound и nosound). Но высота звука для этих функций задается не коэффициентом деления, а частотой, которая связана с коэффициентом деления соотношением: Частота = 1193180 / Коэфф.деления Пример 2 /*== ПРИМЕР 7.2 ==*/ /*============ Прерывание от клавиатуры ================*/ #include <dos.h> void interrupt (*old9)(); /* Для сохр. старого вектора */ void interrupt new9(); /* Описание нового обработчика */ unsigned char a_code=0x1e; /* Скан-код клавиши "a" */ union REGS rr; struct SREGS sr; void *readvect(int in); void writevect(int in, void *h); /*==== main ====*/ main() { char string[80]; /* Перехват вектора 9 */ old9=readvect(9); writevect(9,new9); printf("\nВводите строку символов>"); scanf("%s",string); /* Ввод строки */ /* Восстановление вектора 9 */ writevect(9,old9); printf("%s\n",string); } /*==== Обработчик прерывания 9 ====*/ void interrupt new9() { unsigned char c; /* Чтение scan-кода и сравнение его с "горячим" */ if (inportb(0x60)==a_code) { /* Если горячий код - подтверждение приема... */ c=inportb(0x61); outportb(0x61,c|0x80); outportb(0x61,c); /* и сброс контроллера прерываний. */ outportb(0x20,0x20); } else (*old9)(); /* Если нет - вызов сист.обработчика */ } /*==== Получение старого вектора ====*/ void *readvect(int in) { rr.h.ah=0x35; rr.h.al=in; intdosx(&rr,&rr,&sr); return(MK_FP(sr.es,rr.x.bx)); } /*==== Запись нового вектора ====*/ void writevect(int in, void *h) { rr.h.ah=0x25; rr.h.al=in; sr.ds=FP_SEG(h); rr.x.dx=FP_OFF(h); intdosx(&rr,&rr,&sr); } Пример 2 /*== ПРИМЕР 8.2 ==*/ /*=============== Печать символа на принтере =============*/ #include <dos.h> main() { int p1,p2,p3,i; /* Номера портов */ unsigned char stat; /* Байт состояния */ /* Символы для печати (10 - перевод строки) */ char a[]={24,'a','b','c','d','e','f','g','h','i','j',10}; /* Опеределение портов */ p1=peek(0x40,8); p2=p1+1; p3=p2+1; /* Инициализация принтера */ outportb(p3,0x08); delay(50); outportb(p3,0x0c); /* Проверка состояния */ stat=inport(p2); if ((stat&0x20)!=0) { printf("Ошибка принтера. Байт состояния = %02x\n",stat); exit(); } for(i=0;i<12;i++) { /* Ожидание готовности */ while ((stat&0x80)==0) stat=inport(p2); /* Символ в базовый порт */ outportb(p1,a[i]); /* Строб */ outportb(p3,0x0d); outportb(p3,0x0c); /* Проверка состояния */ stat=inport(p2); if ((stat&0x20)!=0) { printf("Ошибка принтера. Байт состояния = %02x\n",stat); exit(); } } } Пример 2 /*== ПРИМЕР 9.2 ==*/ /*============= Текстовые видеоpежимы (EGA) =============*/ #include <dos.h> #include <conio.h> # define byte unsigned char byte getmode(); int x_modes(); byte nmode,mode; /* Номеp, код pежима */ union REGS rr; byte k; int i; main() { /* заполнение экрана */ clrscr(); textcolor(2); for(i=1; i В этом примере код установленного режима и число позиций в строке экрана выбираются из области памяти BIOS. Приведем адреса ячеек этой области, связанных с видеоадаптерами:
Пример 2 /*== ПРИМЕР 10.1 ==*/ /*========== Чтение сектора средствами BIOS и DOS ========*/ #include <dos.h> /* типы и структуры данных */ #define byte unsigned char #define word unsigned int /* описание функций */ void Sect_to_Daddr(word sect,struct daddr *d); word Daddr_to_Sect(struct daddr *d); word get( word min, word max, char *name); struct daddr { byte h; /* головка */ word s, /* сектор */ t, /* дорожка */ ts; /* сектор, дорожка упакованные */ } a; /* физический дисковый адрес */ word ls; /* номер логического сектора */ /* параметры дискеты */ word nh=2, /* число головок */ ns=9, /* число секторов на дорожке */ nt=40, /* число дорожек */ s0=0, /* номер 1-го сектора */ nls=720; /* общее число секторов */ byte drive; /* номер логич.диска */ byte buff13[512]; /* буфер сектора */ byte buff25[512]; /* буфер сектора */ /*==== main ====*/ main() { union REGS rr; struct SREGS sr; byte fx, flag; byte mode; int i, i0, y; for (fx=0; fx==0;) { printf("диск (A|B) >"); drive=getche(); if ((drive!='a')&&(drive!='b')) printf("\7"); else fx=1; } drive-='a'; for (flag=0; flag==0;) { printf("\nA - задание физического адреса;\n"); printf("S - задание номера сектора;\n"); printf("Q - конец работы.\n"); printf("--->"); mode=getche(); printf("\n"); switch(mode) { case 'q': flag=1; continue; case 'a': a.h=get(0,nh-1,"головка"); a.t=get(0,nt-1,"дорожка"); a.s=get(1,ns,"сектор"); a.ts=(a.t<<8)|a.s|((a.t&0x300)>>2); ls=Daddr_to_Sect(&a); break; case 's': ls=get(0,nls-1,"логич.сектор"); Sect_to_Daddr(ls,&a); break; default: printf("\7"); continue; } /* чтение сектора при помощи прерывания 13. */ rr.h.ah=2; /* функция ЧТЕНИЕ */ rr.h.al=1; /* секторов 1 */ rr.h.dl=drive; /* адрес диска */ rr.h.dh=a.h; rr.x.cx=a.ts; /* адрес на диске */ sr.es=FP_SEG(buff13); /* адрес в ОП */ rr.x.bx=FP_OFF(buff13); int86x(0x13,&rr,&rr,&sr); /* Проверка ошибок чтения */ if (rr.x.cflag) { printf("Ошибка при INT 13 : %u\n",rr.h.ah); exit(0); } /* Чтение при помощи прерывания DOS 25 */ rr.h.al=drive; /* логич.диск */ rr.x.cx=1; /* секторов 1 */ rr.x.dx=ls; /* номер сектора */ sr.ds=FP_SEG(buff25); /* адрес в ОП */ rr.x.bx=FP_OFF(buff25); int86x(0x25,&rr,&rr,&sr); /* Проверка ошибок чтения */ if (rr.x.cflag) { printf("\nОшибка при INT 25 : %u. ",rr.h.ah); exit(0); } /* дамп буферов */ clrscr(); printf(" INT 13 : гол=%d дор=%-2d сект=%d |", a.h,a.t,a.s); printf("| INT 25 : лог.сект=%d \n",ls); printf(" -------------------------------------|"); printf("|-------------------------------------\n"); for(i0=0,y=3;y<25; y++,i0+=12) { for (i=0; i<12; printf(" %02X",buff13[i0+i++])); printf(" "); for (i=0; i<12; printf(" %02X",buff25[i0+i++])); printf("\n"); } printf(" Нажмите любую клавишу..."); getch(); } } /*=== формирование физич.дискового адреса из # сектора ===*/ void Sect_to_Daddr(word sect,struct daddr *d) { /* sect - номер сектора, a - адрес на диске */ int s; d->s=sect%ns+1; s=sect/ns; d->h=s%nh; d->t=s/nh; d->ts=(d->t<<8)|d->s|((d->t&0x300)>>2); } /*=== формирование # сектора из физич.дискового адреса ===*/ word Daddr_to_Sect(struct daddr *d) { word s; s=((d->t*nh)+d->h)*ns+d->s-1+s0; return(s); } /*==== ввод какого-либо параметра ====*/ word get(word min, word max, char *name) { byte fx; word val; for (fx=0; fx==0;) { printf("%s >",name); scanf("%d",&val); if ((val<min)(val>max)) printf("\7"); else fx=1; } return (val); } Пример 2 /*== ПРИМЕР 11.2 ==*/ /*============ Определение места PID в системе ===========*/ #include <dos.h> # define word unsigned int word mypid; /* PID программы */ word dosend; /* конец памяти DOS*/ word seg,off; /* текущий адрес */ word vpid; /* прочитанный PID */ word /* массивы для запоминания адресов */ mseg[10], moff[10]; int nmem; /* число адресов */ int i; union REGS rr; struct SREGS sr; main() { /* получение адреса конца памяти DOS */ rr.h.ah=0x52; intdosx(&rr,&rr,&sr); dosend=peek(sr.es,rr.x.bx-2); /* Получение своего PSP (PID )*/ rr.h.ah=0x62; intdos(&rr,&rr); mypid=rr.x.bx; printf("PID программы = %04X\n",mypid); /* Поиск PID в системе */ nmem=0; /* поиск происходит начиная от сегмента 60 до конца памяти, занимаемой DOS */ for(seg=0x60;seg<dosend;seg++) for(off=0;off<16;off++) if (peek(seg,off)==mypid) { /* PID найден */ /* изменяется текущий PID */ rr.h.ah=0x50; rr.x.bx=mypid+1; intdos(&rr,&rr); /* повторная выборка из памяти */ vpid=peek(seg,off); /* PID восстанавливается */ rr.h.ah=0x50; rr.x.bx=mypid; intdos(&rr,&rr); if (vpid==mypid+1) { /* адрес слова, хранящего PID, запоминается */ mseg[nmem]=seg; moff[nmem++]=off; } } /* Контрольные операции */ /* По найденным адресам записывается другой PID */ for(i=0; i<nmem; i++) poke(mseg[i],moff[i],mypid+1); /* проверяется, изменился ли PID, хранящийся в системе */ rr.h.ah=0x62; intdos(&rr,&rr); vpid=rr.x.bx; /* Восстанавливается содержимое */ for(i=0; i<nmem; i++) poke(mseg[i],moff[i],mypid); /* Печатаемое значение должно совпадать с mypid+1 */ printf("Измененный PID = %04X\n",vpid); /* Распечатка адресов хранения PID в DOS */ for (i=0;i<nmem; i++) printf("Адрес хранения PID = %04X:%04X\n", mseg[i],moff[i]); printf("Нажмите любую клавишу...\n"); getch(); } Примененная в примере 11.2 техника - поиск места PID в системе и изменение его прямой записью в память может быть использована в резидентных программах, если почему-либо невозможно применить функцию 0x50. Пример 2 /*== ПРИМЕР 12.1 ==*/ /*================== Выдача карты памяти =================*/ #define byte unsigned char #define word unsigned int #include <dos.h> #include <string.h> struct MCB { /* блок упpавления памятью */ char type; word owner, size; byte reserved[3]; char pgmname[8]; }; struct MCB *cmcb; /* адpес текущего MCB */ struct MCB *emcb; /* адpес MCB сpеды */ word memtop; /* сегм.адрес начала памяти */ word csegm; /* сегментный адpес текущего MCB */ word othersegm; /* сегм.адрес другого MCB */ word fathersegm; /* сегм.адрес родителя */ byte *envstr; /* адpес стpоки окружения */ int envlen; /* длина очередной строки окружения */ int envsize; /* размер блока окружения */ byte dos; /* номер версии DOS */ union REGS rr; struct SREGS sr; int i; char *s; main() { clrscr(); /* получить номер версии DOS */ rr.h.ah=0x30; intdos(&rr,&rr); dos=rr.h.al; /* получить адрес системных блоков */ rr.h.ah=0x52; intdosx(&rr,&rr,&sr); /* получить адрес начала цепочки */ memtop=csegm=peek(sr.es,rr.x.bx-2); do { cmcb=(struct MCB *)MK_FP(csegm,0); printf("Addr=%04X:0000 PID=%04X Size=%-6u ", csegm,cmcb->owner,cmcb->size*16); if (cmcb->owner==0) printf(" Free"); /* блок свободен */ else { /* блок занят */ /* блок принадлежит DOS ? */ if (cmcb->owner<memtop) printf(" Dos "); else { /* блок не принадлежит DOS */ /* если PID хозяина указывает на текущий блок, то это программный сегмент */ if (csegm==cmcb->owner-1) printf(" Pgm "); else { /* адpес блока сpеды для хозяина этого блока памяти находится в PSP хозяина со смещением 0x2C */ othersegm=peek(cmcb->owner,0x2c); /* адpес родителя для программы-хозяина этого блока находится в PSP хозяина со смещением 0x16 */ fathersegm=peek(cmcb->owner,0x16); /* если на текущий блок указывает адрес окружения хозяина, то это блок окружения */ if (csegm==othersegm-1) printf(" Env "); /* иначе - это блок данных */ else printf(" Data "); } /* если хозяин сам себе родитель, то это COMMAND */ if (cmcb->owner==fathersegm) printf("COMMAND.COM"); else { /* для другой программы узнаем ее имя */ if (dos>3) { emcb=(struct MCB *)MK_FP(cmcb->owner-1,0); for (i=0,s=emcb->pgmname; i<8; i++) { if (*s>0) printf("%c",*(s++)); else printf(" "); } printf(" "); } if (dos>2) { /* для DOS 3.0 и выше имя - из строки вызова */ emcb=(struct MCB *)MK_FP(othersegm-1,0); envsize=emcb->size*16; /*размер окружения */ envstr=(char *)MK_FP(othersegm,0); do { /* пропуск строк окружения до пустой строки */ envlen=strlen(envstr)+1; envstr+=envlen; envsize-=envlen; } while ((envlen>1)&&(envsize>0)); envstr+=2; envsize-=2; /* 2 байта - кол.строк */ /* envstr - указатель на строку вызова */ if (envsize>0) printf("%s",envstr); } } } } printf("\n"); csegm+=(cmcb->size+1); /* переход к следующему блоку */ } while (cmcb->type!='Z'); /* выход по последн.блоку */ getch(); } Пример 2 struct DR_HEAD { struct dr_head *next; /* адрес следующего */ word attr; /* атрибуты */ word strat_off, intr_off; /* смещения секций */ char name[8]; /* имя */ }; Слово атрибутов драйвера в старшем разряде содержит признак символьного (1) или блочного (0) устройства. Интерпретация остальных разрядов слова атрибутов существенно разная для символьных и блочных устройств. Для символьных устройств слово атрибутов имеет формат: Пример 2 /*==== ПРИМЕР 14.1 ====*/ /*==== Обработка критической ошибки ====*/ #include <dos.h> #include <stdio.h> #define byte unsigned char # define word unsigned int void interrupt new_24(); void interrupt (* old_24)(); /* адрес старого обработчика критической ошибки */ /* информация об ошибке, запомненная в обработчике */ byte save_ah, save_al, class, actions; word save_di, save_bp, save_si, ext_code, locate; /* сообщения */ char *msg1[]= {"ИГНОРИРОВАТЬ","ПОВТОРИТЬ","СНЯТЬ"}; char *msg2[]= {"системная обл.","FAT","каталог","обл.данных"}; char *msg3[]={"защита от записи","неизвестное устройство", "диск не готов","неизвестная команда","ошибка CRC", "неправильная структура запроса","ошибка поиска", "неизвестный тип диска","сектор не найден", "конец бумаги","ошибка записи","ошибка чтения", "общий сбой","наруш.режима разделения", "наруш.блокировки","ошибка смены диска", "FCB недоступен","переполн.буфера разделения" }; union REGS rr; struct SREGS sr; int dos; FILE *f; main() { /* получить версию DOS */ rr.h.ah=0x30; intdos(&rr,&rr); if ((dos=rr.h.al)==3) dos=0; /* перехват вектора */ old_24=getvect(0x24); setvect(0x24,new_24); /* эксперимент 1 */ save_ah=save_al=save_di=save_bp=save_si=0; /* получить адрес DPB (при неготовом диске) */ rr.h.ah=0x32; rr.h.dl=2; intdosx(&rr,&rr,&sr); error_scan(); /* эксперимент 2 */ save_ah=save_al=save_di=save_bp=save_si=0; /* печатать (при неготовом принтере) */ fprintf(stdprn,"aaa"); error_scan(); /* восстановление вектора (необязательно) */ setvect(0x24,old_24); } /*-------------------------------------*/ /*== Распечатка информации об ошибке ==*/ error_scan() { int i; byte k; char *s; if (!(save_ah|save_al|save_di|save_bp|save_si)) printf("Ошибки нет\n"); else { printf("\nОшибка при "); if (save_ah&1) printf("записи, "); else printf("чтении, "); if (save_ah&0x80){ printf("устройство "); s=(char far *)MK_FP(save_bp,save_si)+10; for (i=0; i<8; i++,s++) printf("%c",*s); printf("\n"); } else { printf("диск %c\n",save_al+'A'); printf("Место ошибки - %s\n",msg2[(save_ah>>1)&3]); } for (i=0,k=0x20; k>0x04; k>>=1,i++) if (save_ah&k) printf("Можно %s\n",msg1[i]); else printf("Нельзя %s\n",msg1[i]); printf("Тип ошибки - %s\n",msg3[save_di]); printf("РАСШИРЕННАЯ ИНФОРМАЦИЯ:\n"); printf(" Код ошибки - %04Xh\n",ext_code); printf(" Класс ошибки - %02Xh\n",class); printf(" Действия - %02Xh\n",actions); printf(" Место - %02Xh\n",locate); } printf("Нажмите любую клавишу...\n"); getch(); } /*-------------------------------------*/ /*== Обработчик критических ситуаций ==*/ void interrupt new_24 (bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs) word bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs; { /* сохранение основной информации об ошибке */ save_ah=ax>>8; save_al=ax&0xff; save_di=di; save_bp=bp; save_si=si; /* получение расширенной информации об ошибке */ rr.h.ah=0x59; /* функция 59 */ rr.x.bx=dos; intdos(&rr,&rr); ext_code=rr.x.ax; class=rr.h.bh; actions=rr.h.bl; locate=rr.h.ch; /* действия - отменить запрос */ ax=3; } Структура SREGS, служащая для заданияПример 4 rr.h.ah=0x11; rr.h.al=0x22; будут одинаковы. Структура SREGS, служащая для задания содержимого сегментных регистров: Пример 4 void geninterrupt(int int_num); geninterrupt представляет собой функцию, основное содержание которой составляет единственная команда Ассемблера INT int_num. Значения регистров можно передать прерыванию через псевдорегистры - _AX, _BX и т.д., из них же получить и результаты. Но применение этой функции требует большой осторожности: во-первых, при формировании входных регистров имеется риск в процессе формирования второго и последующих испортить содержимое регистров, сформированных ранее, во-вторых, при выполнении прерывания может быть изменено содержимое регистров - в первую очередь DS и ES, а также и BP, DI, SI (поэтому мы всегда, применяя geninterrupt, сохраняем их содержимое в статической памяти). Еще несколько подобных функций мы опускаем, так как они не используются в программах нашего пособия. Пример 4 /*== ПРИМЕР 6.3 ==*/ /*============= Системная служба даты ================*/ #include <dos.h> main() { union REGS rr; int y,m,d; /* Исходный год, месяц, день */ /* Получение текущей даты */ rr.h.ah=0x2a; /* Чтение даты */ intdos(&rr,&rr); d=rr.h.dl; m=rr.h.dh; y=rr.x.cx; printf("%02d:%02d:%02d\n",d,m,y); /* Убедимся, что во флаге смены даты BIOS - 0 */ printf("Флаг смены даты = %d\n",peekb(0x40,0x70)); /* Установка флага смены даты */ pokeb(0x40,0x70,1); /* Пока нет запроса даты, флаг остается взведенным (запрос будет выдан по клавише Esc) */ while(getch()!=27) printf("Флаг смены даты = %d\n",peekb(0x40,0x70)); /* Запрос даты. Мы получим дату на 1 большую исходной */ rr.h.ah=0x2a; /* Чтение даты */ intdos(&rr,&rr); printf("%02d:%02d:%02d\n",rr.h.dl,rr.h.dh,rr.x.cx); /* а флаг смены даты сбрасывается */ printf("Флаг смены даты = %d\n",peekb(0x40,0x70)); /* Восстановление исходной даты */ rr.h.ah=0x2b; /* Запись даты */ rr.h.dl=d; rr.h.dh=m; rr.x.cx=y; intdos(&rr,&rr); /* и вывод ее */ rr.h.ah=0x2a; /* Чтение даты */ intdos(&rr,&rr); printf("%02d:%02d:%02d\n",rr.h.dl,rr.h.dh,rr.x.cx); } В AT имеются независимые часы реального времени, показания которых содержатся в CMOS-памяти. Регистры CMOS-памяти, связанные с временем и датой следующие: 0 - секунды, 2 - минуты, 4 - часы, 6 - день недели (0 - воскресенье), 7 - день месяца, 8 - месяц, 9 - год. Доступ к этим данным - либо через порты 0x70, 0x71, либо через прерывание 0x1A. Функция 2 этого прерывания (AH=2) - чтение часов реального времени, функция 3 - установка часов, функции 4, 5 - чтение и установка даты соответственно. Используются те же регистры, что и в функциях DOS 0x2C, 0x2A, но все данные представляются в двоично-десятичном коде. При загрузке системы на AT время дня и дата выбираются из этих часов, далее эти часы и системная служба времени работают независимо друг от друга. Кроме того, в AT имеется также возможность запрограммировать прерывание на заданное время, в описаниях это часто называют сигналом тревоги (alarm). Время поступления этого сигнала заносится в регистры CMOS-памяти: 1 - секунды, 3 - минуты, 5 - часы, а прерывание по достижению заданного времени разрешается единицей в разряде 5 регистра 0x0B. При достижении заданного времени происходит прерывание 0x4A. Сигнал тревоги может быть задан при помощи функции 6 прерывания 0x1A, а отменен - функцией 7, как это показано в следующем примере. Пример 4 /*== ПРИМЕР 8.5 ==*/ /*=============== Печать символа на принтере =============*/ #include <dos.h> main() { union REGS rr; /* Символы для печати */ char a[]={24,'a','b','c','d','e','f','g','h','i ','j',10}; int i; for(i=0;i<12;i++) { rr.h.ah=5; /* Функция 5 DOS */ rr.h.dl=a[i]; intdos(&rr,&rr); } } Кроме того, с принтером в DOS связан стандартный файл печати stdprn. Для вывода на принтер можно использовать функцию файлового вывода 0x40, указывая для нее номер файла - 4. Пример 4 0 R G B r g b, где r, g, b - соответственно красная, зеленая, синяя составляющие цвета 1/3 интенсивности; R, G, B - красная, зеленая, синяя составляющие 2/3 интенсивности. Таким обазом, возможны, например, три градации красного цвета:
При выборе палитры в BL задается код изменяемого цвета, а в BH - код устанавливаемой для него палитры. В примере 9.3 код изменяемого цвета выбирается случайным (по счетчику тиков таймера), и для него перебираются все возможные палитры. Обратите внимание на то, что при окончании работы программы для нашего цвета восстанавливается палитра по умолчанию. Пример 4 /*== ПРИМЕР 10.2 ==*/ /*=== Чтение и анализ гл.загруз.записи твердого диска ===*/ #include <dos.h> #define byte unsigned char #define word unsigned int #define dword unsigned long #define SECT(x) x&0x3f #define TRK(x) (x>>8)|((x<<2)&0x300) main() { /* структура элемента раздела */ struct Part { byte ActFlag; /* описатель*/ /* физический адрес начала раздела */ byte Begin_Hd; /* # головки */ word Begin_SecTrk; /* # сектора и дорожки */ byte SysCode; /* код системы */ /* физический адрес конца раздела */ byte End_Hd; /* # головки */ word End_SecTrk; /* # сектора и дорожки */ dword RelSec; /* # сектора начала */ dword Size; /* число секторов */ }; /* стpуктуpа главной загpузочной записи */ struct MBR { char LoadCode[0x1be]; /* пpогpамма загpузки */ struct Part rt[4]; /* 4 эл-та pазделов */ word EndFlag; /* подпись MBR */ } mbr; int x=10,y; /* экpанные кооpдинаты */ byte head=0; /* номеp головки (0) */ word Sect_Trk=1; /* номеp доpожки и сектоpа (0,1) */ int ndrive=0; /* номеp лог.диска */ word *EndList; /* указатель на подпись */ union REGS rr; struct SREGS sr; word i; clrscr(); printf("Разделы жесткого диска # 1\n"); printf("==========================\n\n"); printf("Лог.диск \nПризнак = \n printf("Код системы = \nНачало: гол.= \n"); printf(" дор. = \n сект. = \n"); printf("Конец : гол.= \n дор. = \n"); printf(" сект.= \nНач.сектор = \n"); printf("Размер = \n"); NEXT: /* Чтение при помощи прерывания 13. Только таким путем можно прочитать MBR, т.к. она не принадлежит никакому логическому диску. */ rr.h.ah=2; /* Чтение */ rr.h.al=1; /* Секторов 1 */ rr.h.dl=0x80; /* Тв.диск */ rr.h.dh=head; /* Головка */ rr.x.cx=Sect_Trk; /* Дорожка, сектор */ sr.es=FP_SEG(&mbr); /* Адрес буфера в ОП */ rr.x.bx=FP_OFF(&mbr); int86x(0x13,&rr,&rr,&sr); /* Проверка ошибок чтения */ if (rr.x.cflag) { printf("Ошибка чтения: %x. ",rr.h.ah); printf("Нажмите любую клавишу...\n\7"); getch(); exit(); } /* В нач.установках EndList указывает на 1-й байт 1-го элемента pаздела */ for (EndList=(word *)&mbr.rt[(i=0)]; /* пока не встpетилась подпись MBR или pаздел нулевого pазмеpа */ (*EndList!=0xaa55)&&(mbr.rt[i].Size>0L); /* пеpеход к след. Пример 4 /*== ПРИМЕР 11.4 ==*/ /*======== Обработка таблицы перемещений EXE-файла =====*/ #include <dos.h> #include <string.h> #include <stdlib.h> #define byte unsigned char #define word unsigned int # define dword unsigned long struct EXEH { /* заголовок EXE-файла */ word ExeFlag, LastPag, PageCnt, ReloCnt, HdrSize, MinMem, MaxMem,ReloSS, ExeSP, ChkSum, ExeIP, ReloCS, TabOff, Overlay; } exeh; /* Элемент таблицы перемещений */ struct ReloItem { word offs; /* смешение */ word segm; /* сегмент */ } ri; word psp, /* сегм.адрес PSP */ startseg, /* стартовый сегмент */ reloseg, /* сегмент модифицированного слова */ d; /* разность между памятью и файлом */ char fname[80],*fn; /* имя EXE-файла */ int exef; /* дескриптор EXE-файла */ dword fileoff, /* адрес в файле начала модуля */ itoff, /* адрес в файле модиф.слова */ seekoff; /* адрес в файле эл-та таблицы */ byte buff[10], /* буфер в ОП */ *s; int i, ni; union REGS rr; struct SREGS sr; main() { /* получение сегментного адреса PSP и открытие файла */ rr.h.ah=0x51; intdos(&rr,&rr); psp=rr.x.bx; for(fn=(char *)MK_FP(peek(psp,0x2C),0);*fn|*(fn+1);fn++); strcpy(fname,fn+4); if (strchr(fname,'.')==NULL) strcat(fname,".EXE"); rr.h.ah=0x3d; rr.h.al=0; sr.ds=FP_SEG(fname); rr.x.dx=FP_OFF(fname); intdosx(&rr,&rr,&sr); if (rr.x.cflag) { printf("Невозможно открыть файл %s\n",fname); exit(0); } else exef=rr.x.ax; /* чтение заголовка */ dread(&exeh,sizeof(struct EXEH)); startseg=psp+0x10; printf("\nСтартовый сегмент = %04Xh\n",startseg); printf("Число элементов = %d\n\n",exeh.ReloCnt); fileoff=exeh.HdrSize*16; /* перебор таблицы перемещений */ for (seekoff=exeh.TabOff,ni=0; ni<exeh.ReloCnt; ni++) { /* чтениe эл-та табл.перемещений */ dseek(seekoff); dread(&ri,sizeof(struct ReloItem)); printf("Элемент перемещения #%d : %04X:%04X\n", ni+1,ri.segm,ri.offs); /* выборка модифицированного адреса из программы в ОП */ reloseg=startseg+ri.segm; s=(byte *)MK_FP(reloseg,ri.offs)-4; printf(" Память : %Fp -> ",s); d=*((word *)(s+4)); prtmem(s); /* выборка немодифицированного адреса из EXE-файла */ itoff=fileoff+ri.segm*16+ri.offs-4; dseek(itoff);__ _.dread(buff,10); printf(" Файл : %9ld -> ",itoff); d-=*((word *)(buff+4)); prtmem(buff); printf(" Разность = %04X\n",d); seekoff+=sizeof(struct ReloItem); if (getch()==27) exit(0); } } /*==== Чтение из файла ====*/ dread(void *addr,int len) { rr.h.ah=0x3f; rr.x.bx=exef; rr.x.cx=len; sr.ds=FP_SEG(addr); rr.x.dx=FP_OFF(addr); intdosx(&rr,&rr,&sr); if (rr.x.cflag) { printf("Ошибка чтения %s\n"); exit(0); } } /*==== Позиционирование в файле ====*/ dseek(dword off) { rr.h.ah=0x42; rr.x.bx=exef; rr.h.al=0; rr.x.cx=off>>16; rr.x.dx=off&0xffff; intdos(&rr,&rr); if (rr.x.cflag) { printf("Ошибка позиционирования\n"); exit(0); } } /*==== Дамп участка памяти ====*/ prtmem(byte *a) { int i; for (i=0; i<10; i++) { if ((i==4)(i==6)) printf(" "); printf("%02X",*(a++)); } printf("\n"); } Заголовок EXE-файла нужен только при загрузке программы в оперативную память - он не сохраняется после загрузки. Приведем итоговую схему загрузки EXE-программ: форматированную часть заголовка DOS считывает в свой буфер; определяется адрес в файле начала программного модуля: HdrSize * 16 и размер программного модуля: (PageCn - 1) * 512 + LastPage - HdrSize * 16; выделяется память для программного сегмента и строится PSP; вычисляется startseg - сегментный стартовый адрес; определяется начало в файле таблицы перемещений и элементы таблицы читаются в буфер DOS; для каждого элемента выбирается слово по адресу (startseg + segm) : offs, к нему прибавляется startseg, результат записывается на то же место; в регистры ES и DS записывается адрес PSP, в регистр SS - startseg + ReloSS, в SP - ExeSP, в CS - startseg + ReloCS, в IP - ExeIP, тем самым управление передается загруженной программе. Пример 4 /*== ПРИМЕР 12.3 ==*/ /*=========== Дисциплины распределения памяти ===========*/ #include <dos.h> #define byte unsigned char # define word unsigned int void memfree(byte *a); byte *memget(int blksize); void setorder(byte d); byte getorder(); void init(void); void memmap(int x); struct MCB { byte type; word owner, size; byte reserved[11]; } *c; word freetop, cs; union REGS rr; struct SREGS sr; main() { byte *a[10] ,*b, order; word sz, i; init(); /* составление списка исходных блоков */ /* Формирование фрагментированной памяти */ for (sz=84,i=0; i<10; i++) { a[i]=memget(sz); b=memget(8); if (i>=5) sz+=4; else sz-=4; } for (i=0; i<10; i++) memfree(a[i]); order=getorder(); clrscr(); printf(" Исходное |Дисциплина_0"); printf("|Дисциплина_1|Дисциплина_2|\n"); printf("------------|------------"); printf("|------------|------------|"); memmap(1); /* отображение памяти */ setorder(0); /* установка дисциплины */ b=memget(70); /* выделение памяти */ memmap(14); /* отображение памяти */ memfree(b); /* возврат к исходному распределению */ setorder(2); b=memget(70); memmap(27); memfree(b); setorder(1); b=memget(70); memmap(40); memfree(b); setorder(order); /* восстановление исходной дисциплины */ } /*==== Установка дисциплины распределения ====*/ void setorder(byte d) { rr.h.ah=0x58; /* Ф-ция 58 */ rr.h.al=1; /* Установить */ rr.x.bx=d; /* Дисциплина */ intdos(&rr,&rr); } /*==== Чтение дисциплины распределения ====*/ byte getorder() { rr.h.ah=0x58; /* Ф-ция 58 */ rr.h.al=0; /* Прочитать */ intdos(&rr,&rr); return (rr.x.ax); /* Дисциплина - в AX */ } /*==== Выделение памяти ====*/ byte *memget(int blksize) { rr.h.ah=0x48; rr.x.bx=blksize; intdos(&rr,&rr); if (rr.x.cflag) printf("\7Неудовл.запрос mem=%d\n",rr.x.bx); else return(MK_FP(rr.x.ax,0)); } /*==== Освобождение памяти ====*/ void memfree(byte *a) { rr.h.ah=0x49; sr.es=FP_SEG(a); intdosx(&rr,&rr,&sr); if (rr.x.cflag) printf("\7Некорректный free\n"); } /*==== Определение адреса последнего свободного блока ====*/ void init(void) { rr.h.ah=0x52; intdosx(&rr,&rr,&sr); cs=peek(sr.es,rr.x.bx-2); do { freetop=cs; c=(struct MCB *)MK_FP(cs,0); cs+=(c->size+1); } while(c->type!='Z'); } /*==== Выдача карты памяти ====*/ void memmap(int x) { int y; cs=freetop; y=3; do { c=(struct MCB *)MK_FP(cs,0); cs+=(c->size+1); gotoxy(x,y++); printf("%04X %-4u",cs,c->size); if (c->owner==0) printf("...|\n"); else printf("***|\n"); } while(c->type!='Z'); } Пример 4 0 e m 0 p 0 0 0 0 l 0 0 0 0 s 0, где i - драйвер консоли ввода; o - драйвер консоли вывода; n - драйвер нулевого (пустого) устройства; c - драйвер часов; l - драйвер поддерживает функции, введенные в DOS 3.2; p - драйвер выполняет операции Open/Close; b - драйвер неIBM-овского блочного устройства; e - драйвер поддерживает функции IOCTL; s - драйвер поддерживает 32-битную адресацию сектора; m - драйвер определяет тип диска проверкой 1-го байта FAT. По правилам DOS кодовая часть драйвера состоит из двух секций, называемых секцией стратегии и секцией обработки прерываний. Поля strat_off и intr_off заголовка содержат смещения этих секций от начала драйвера. Поле name для символьного устройства содержит имя устройства (возможно, дополненное пробелами), а для блочного - первый байт этого поля содержит количество устройств, поддерживаемых драйвером. При помощи поля next, заполняемого системой при загрузке драйвера, все драйверы (системные и устанавливаемые) связываются в список. Начало этого списка - заголовок драйвера NUL в CVT. Значение 0xFFFF в части смещения поля next является признаком конца списка (такой же признак конца используется и в списках, образуемых другими управляющими блоками). Программа примера 13.1 отслеживает этот список. unsigned int es, cs, ss,Пример 5 struct SREGS { unsigned int es, cs, ss, ds; }; Наконец, структура REGPACK, обеспечивающая наиболее полный набор регистров микропроцессора: Пример 5 void interrupt int_handler (int bp,int di, int si, int ds, int es,int dx,int cx,int bx,int ax, int ip,int cs,int flags); Рассмотрим элементы описания подробнее. Имя функции (у нас int_handler) может быть произвольным. Тип функции void очевиден - программа обработки прерывания не может возвращать значение. Описатель interrupt заставляет Си транслятор генерировать некоторые дополнительные коды для этой функции. Как известно, по команде INT в стек заносится содержимое регистра флагов и регистров CS:IP. Дополнительные коды, гене- рируемые для interrupt-функции, обеспечивают сохранение в стеке остальных регистров. При возврате управления из interrupt-функции содержимое регистров восстанавливается, и возврат выполняется командой IRET. (При программировании на языке Ассемблера программист должен сам заботиться о сохранении и восстановлении регистров). Поскольку для обычных (не interrupt) функций Си через стек передаются значения параметров, в функции обработки прерывания могут быть описаны параметры, как это показано в нашем примере, и программист может работать с регистрами как с параметрами, переданными его функции. Порядок сохранения регистров в стеке - всегда такой как показано в примере. Более того, в отличие от обычных функций Си параметры interrupt-функции являются также и выходными. Поскольку содержимое регистров перед возвратом восстанавливается из стека, любое изменение параметра будет произведено в стеке, и при возврате восстановится измененное содержимое регистра. Если же в программе обработки прерываний не требуется обработки содержимого регистров, она может быть описана как функция без параметров. И еще одно требование к функции обработки прерывания. Если она обрабатывает аппаратное прерывание, то такую функцию необходимо заканчивать оператором сброса контроллера прерываний: Пример 5 /*== ПРИМЕР 6.4 ==*/ /*============== Сигнал тревоги в AT ================*/ #include <dos.h> #include <stdio.h> /* Выражения преобразования BCD->int и наоборот */ #define bcd_to_int(x) (x>>4)*10+(x&0x0f) #define int_to_bcd(x) ((x/10)=60) { m-=60; rr.h.ch=int_to_bcd(bcd_to_int(rr.h.ch)+1); } rr.h.cl=int_to_bcd(m); /* Запись увеличенного времени в регистры тревоги */ rr.h.ah=6; /* функция 6 */ int86(0x1a,&rr,&rr); rr.h.ah=0x2c; intdos(&rr,&rr); printf("\nВремя запуска - %02d:%02d:%02d\n", rr.h.ch,rr.h.cl,rr.h.dh); /* Ожидание тревоги */ flag=0; /* Переменная flag установится в 1 по сигналу тревоги в обработчике прерывания 4A */ while(flag==0); rr.h.ah=0x2c; intdos(&rr,&rr); printf("Время тревоги - %02d:%02d:%02d\n", rr.h.ch,rr.h.cl,rr.h.dh); /* Отмена тревоги */ rr.h.ah=7; /* функция 7 */ int86(0x1a,&rr,&rr); } /*==== Обработчик прерывания 4A - обработчик тревоги ====*/ void interrupt new4A() { putchar(7); /* Звуковой сигнал */ flag=1; /* Установка флага */ writevect(0x4a,old4A); /* Восстановление вектора */ } /*==== Получение старого вектора ====*/ void *readvect(int in) { rr.h.ah=0x35; rr.h.al=in; intdosx(&rr,&rr,&sr); return(MK_FP(sr.es,rr.x.bx)); } /*==== Запись нового вектора ====*/ void writevect(int in, void *h) { rr.h.ah=0x25; rr.h.al=in; sr.ds=FP_SEG(h); rr.x.dx=FP_OFF(h); intdosx(&rr,&rr,&sr); } Пример 5 /*== ПРИМЕР 7.5 ==*/ /*============= Прерывание BIOS 16 ============*/ #include <dos.h> main() { union REGS rr; int i; printf("\nПрерывание 0x16, функция 1 >"); /* Ожидание нажатия клавиши */ do { rr.h.ah=1; int86(0x16,&rr,&rr); } while((rr.x.flags&0x0040)!=0); printf("%02x %02x",rr.h.ah,rr.h.al); /* Прием клавиши */ rr.h.ah=0; int86(0x16,&rr,&rr); /* Здесь выведется то же, что и в предыдущей строке, т.к. клавиша, код которой прочитан по функции 1 из клавиа- туры не удалена. */ printf(" %02x %02x\n",rr.h.ah,rr.h.al); /* Прием новой клавиши с ожиданием */ printf("\nПрерывание 0x16, функция 0 >"); rr.h.ah=0; int86(0x16,&rr,&rr); printf("%02x %02x\n",rr.h.ah,rr.h.al); printf("\nПрерывание 0x16, функция 2 >"); /* Теперь надо нажимать клавиши состояний и смотреть, что выводится */ do { rr.h.ah=2; int86(0x16,&rr,&rr); printf("%02x ",rr.h.al); delay(300); /* Проверка нажатия любой (кроме переключателя) клавиши. Цикл продолжается до нажатия клавиши "пробел" */ rr.h.ah=1; int86(0x16,&rr,&rr); } while(rr.h.al!=13); /* Очистка буфера клавиатуры */ poke(0x40,0x1a,peek(0x40,0x1c)); } 7.3. Функции стандартного ввода DOS В DOS нет функций, ориентированных непосредственно на клавиатуру. Функции ввода DOS работают с файлом стандартного ввода, который по умолчанию связан с клавиатурой. Но при переназначении стандартного ввода функции DOS будут успешно работать и с другим источником. Коротко перечислим функции DOS. Функция 1 - ввод символа с эхо, эта функция обрабатывает комбинацию клавиш Ctrl+Break. Возвращает ASCII-код в регистре AL, для расширенных ASCII-кодов требуется два обращения к функции: первое возвращает 0, а второе - код. Функция 7 - ввод символа без эхо, не обрабатывает комбинацию Ctrl+Break. Возвращает то же, что и функция 1. Функция 8 - ввод символа без эхо, обрабатывает Ctrl+Break, возвращает то же, что и функция 1. Функция 6 - ввод-вывод символов. Пример 5 /*== ПРИМЕР 8.6 ==*/ /*============ Управляющие коды режимов печати ===========*/ #include <stdio.h> #define prt(x) putc(x,stdprn); main() { /* Инициализация */__ _.prt(27); prt(64); /* Обычный режим */ fprintf(stdprn,"Default mode\n"); /* Режим двойной ширины */ prt(14); fprintf(stdprn,"Set double width mode\n"); prt(20); fprintf(stdprn,"Close double width mode\n"); /* Режим плотной печати */ prt(15); fprintf(stdprn,"Set empassed mode\n"); prt(18); fprintf(stdprn,"Close empassed mode\n"); /* Режим подчеркивания */ prt(27); prt(0x2d); prt(1); fprintf(stdprn,"Set underline mode\n"); prt(27); prt(0x2d); prt(0); fprintf(stdprn,"Close underline mode\n"); /* Режим двойной жирности */ prt(27); prt(0x45); fprintf(stdprn,"Set double strike mode\n"); prt(27); prt(0x46); fprintf(stdprn,"Close double strike mode\n"); /* Режим верхних индексов печати */ prt(27); prt(0x53); prt(0); fprintf(stdprn,"Set superscript mode\n"); prt(27); prt(0x54); fprintf(stdprn,"Close superscript mode\n"); /* Режим нижних индексов печати */ prt(27); prt(0x53); prt(1); fprintf(stdprn,"Set subscript mode\n"); prt(27); prt(0x54); fprintf(stdprn,"Close subscript mode\n"); } Отдельный программный пример иллюстрирует интересную возможность формирования пользователем собственных печатных символов. Образы выводимых символов хранятся в ПЗУ принтера, но в принтере есть еще и ОЗУ, в которое могут быть загружены образы, созданные пользователем. Образ формируется на сетке высотой 8 и шириной 11 точек. В памяти образ представляется в двоичном виде, причем один байт описывает один столбец образа. Сформировав двоичный образ символа, следует записать его в ОЗУ, для чего используется Esc-последовательность вида: 0x1B, 0x26, 0 - задание действия "загрузка шрифта в ОЗУ"; <начальный код>, <конечный код> - одной последовательностью можно загрузить в ОЗУ несколько образов, каждому из которых присваивается свой код в ОЗУ; <байт описатель> - младший полубайт содержит ширину символа, в старшем 0 в старшем разряде означает, что при печати весь образ сдвигается на 1 точку вниз; 11 байт описания образа. Пример 5 /*== ПРИМЕР 9.3 ==*/ /*==== Цветовые атрибуты. Прямая запись в видеопамять ====*/ #include <dos.h> /* Сегментный адрес видеопамяти */ #define VSEG 0xb800 /* Вычисление смещения в видеопамяти */ #define VADDR(x,y) y*160+x*2-162 #define byte unsigned char # define word unsigned int char st[]=" x "; /* Обpазец */ word voff; /* Смещение в видеопамяти */ byte color; /* Цветовой атpибут */ int x,y; /* Экpанные кооpдинаты */ byte key; /* Код клавиши */ byte mode; /* Режим мерцания/код палитры */ byte pl[]= /* Исходные палитры 16 цветов */ { 0,1,2,3,4,5,6,56,7,8,16,24,32,40,48,56 }; union REGS rr; char *s; int i; main() { /* очистка экрана */ for (voff=0; voff Образ экрана размером 80 x 25 символов в видеопамяти занимает 4000 байт. Но видеопамять часто имеет значительно больший объем. Так, для EGA объем ее может достигать 256 Кбайт. Половина этого объема используется только в графических режимах, вторая половина составляет адресное пространство в 32 Кбайта, что позволяет разместить в ней 8 образов экрана. Это пространство разбито на 4-Кбайтные участки, называемые страницами. Таким образом, в EGA может быть 8 текстовых страниц (а при 40 символах в строке - 16 страниц по 2 Кбайта). По умолчанию построением изображения на экране управляет образ, записанный в нулевой странице, но имеется возможность переключить адаптер на отображение 1-й, 2-й и т.д. страниц. Это обеспечивает функция 5 (номер страницы задается в AL). Получить номер активной в данный момент страницы можно из области памяти BIOS. Программа примера 9.4 заполняет видеопамять текстом (разным на разных страницах), а затем обеспечивает переключение активной страницы. Пример 5 /*== ПРИМЕР 10.3 ==*/ /*==== Корневой (загрузочный) сектор логического диска ===*/ #include <dos.h> #define byte unsigned char #define word unsigned int #define dword unsigned long /* Структура корневой записи DOS 4.x */ struct RootRec { byte jmp[3]; /* Переход на загрузку */ char ident[8]; /* Идентификатор системы */ /* Расширенный Блок Параметров BIOS */ /* стандартная часть */ word SectSize; /* Размер сектора (байт) */ byte ClustSize; /* Размер кластера (сект) */ word ResSect; /* Резервных секторов */ byte FatCnt; /* Число копий FAT */ word RootSize; /* Размер корневого оглавления ( число элементов оглавления по 32 байта) */ word TotSecs; /* Общее число секторов */ byte Media; /* Тип диска (то же, что 1-й байт FAT */ word FatSize; /* Размер FAT (секторов) */ /* расширение; следующие 3 поля не входят в BPB для DOS 3.x, но входят в загрузочную запись */ word TrkSecs; /* Секторов на дорожке */ word HeadCnt; /* Число поверхностей */ word HidnSecL; /* Число спрятанных секторов (младшая часть) */ /* эта часть имеется только для DOS 4.x и больше */ word HidnSecH; /* (старшая часть) */ /* для диска >32 Мбайт используется вместо TotSecs */ dword LongTotSecs; /* Число секторов */ /* конец расширенного BPB */ byte Drive; /* Физический номер дисковода */ byte reserved1; byte DOS4_flag; /* Код 41 в этом поле - признак расширенного формата загр.записи */ dword VolNum; /* Серийный номер тома */ char VolLabel[11]; /* Метка тома */ char FatForm[8]; /* FAT12 или FAT16 */ /* Далее следуют программа и данные загрузки */ } *rt; byte buffer[512]; /* Структура параметров для INT 25 при работе с большим диском (>32 Мбайт) */ struct{ dword first_sect; /* # логического сектора */ word count; /* число секторов */ byte *ptr; /* адрес в памяти */ } parm; union REGS rr; struct SREGS sr; main() { char drive; /* идентификатор дисковода */ byte sys; /* признак объем > 32 Мбайт */ ASK1: printf("\nУкажите имя диска >"); drive=getche(); if (drive>'b') { ASK2:printf("\nОбьем лог. Пример 5 /*== ПРИМЕР 11.5 == Файл 11_5.C ==*/ /*======== Программа, вызывающая другую программу ========*/ #include <dos.h> #include <string.h> #include <stdlib.h> #define byte unsigned char # define word unsigned int struct EPB { /* блок параметров EXEC */ word env; /* адрес окружения */ char far *parm_str; /* адрес строки параметров */ byte far *fcb1; /* адреса FCB */ byte far *fcb2; } epb; char call_string[128]; /* строка вызова */ char parm_string[128] = "*"; /* строка параметров, 1-ым байтом будет длина строки */ char env_str[]= "Специальное окружение\0 для иллюстрации\0\0$"; char father_file[] = "11_5.EXE"; char sun_file[] = "11_5_A.EXE"; word env; /* адрес подготовленного окружения */ word pid; /* свой PID */ union REGS rr; struct SREGS sr; char far *s, *s1; main() { /* определение своего PID */ rr.h.ah=0x62; intdos(&rr,&rr); pid=rr.x.bx; /* формирование строки вызова */ for(s=(char *)MK_FP(peek(pid,0x2C),0); *s|*(s+1); s++); strcpy(call_string,s+4); if ((s=strstr(call_string,father_file))==NULL) exit(0); *s='\0'; strcat(call_string,sun_file); printf("\nПрограмма-родитель: PID = %04X\n",pid); printf("Вызов: >%s %s\n",call_string,parm_string+1); /* выделение памяти для нового окружения */ rr.h.ah=0x48; rr.x.bx=4; intdos(&rr,&rr); if (rr.x.cflag) printf("Ошибка 48h\n"); else { /* формирование нового окружения и EPB */ epb.env=env=rr.x.ax; s=(char *)MK_FP(env,0); for(s1=env_str; *s1!='$'; *(s++)=*(s1++)); /* формирование строки параметров */ printf("Введите строку параметров >"); gets(parm_string+1); epb.parm_str=parm_string; parm_string[0]=strlen(parm_string)-1; epb.fcb1=epb.fcb2=NULL; /* загрузить и выполнить */ rr.h.ah=0x4b; /* функция 4B */ rr.h.al=0; /* подфункция 0 */ sr.ds=FP_SEG(call_string); /* адр.строки вызова */ rr.x.dx=FP_OFF(call_string); sr.es=FP_SEG(&epb); /* адр.EPB */ rr.x.bx=FP_OFF(&epb); intdosx(&rr,&rr,&sr); if (rr.x.cflag) printf("Ошибка EXEC - %d\n",rr.x.ax); else { /* получение кода возврата */ rr.h.ah=0x4d; /* функция 4D */ intdos(&rr,&rr); printf("Возврат из вызова - %d, с кодом - %02xh\n", rr.h.ah,rr.h.al); } /* освобождение памяти */ rr.h.ah=0x49; sr.es=env; intdosx(&rr,&rr,&sr); if (rr.x.cflag) printf("Ошибка 49h\n"); } } /*== Файл 11_5_A.C ==*/ /*======== Программа, вызываемая из другой программы ========*/ #include <dos.h> #include <stddef.h> main(int argn, char *argv[]) { union REGS rr; int i; char *eee; printf("Hello, I`m 11_5_A!\n"); printf("ПАРАМЕТРЫ:\n"); for (i=0; i<argn; i++) printf(">>%s<<\n",argv[i]); printf("ОКРУЖЕНИЕ:\n"); for (i=0; environ[i]!=NULL; i++) printf("%s\n",environ[i]); rr.h.ah=0x4c; rr.h.al=0x47; intdos(&rr,&rr); } Пример 5 /*== ПРИМЕР 13.1 ==*/ /*================= Просмотр списка драйверов ============*/ #include <dos.h> #define byte unsigned char #define word unsigned int #define ATR(x,z) if(drv->attr&x){printf(" %s\n",z);y++;} #define DA(x,y) (struct DR_HEAD *)MK_FP(x,y); struct DR_HEAD { /* заголовок драйвера */ struct DR_HEAD *next; word attr, strat_addr, intr_addr; char name[8]; } *drv; /* адрес текущего драйвера */ struct DR_HEAD *clock, *con; /* Адреса CLOCK$ и CON */ union REGS rr; struct SREGS sr; int i, y, y1; main() { /* получение адреса CVT */ rr.h.ah=0x52; intdosx(&rr,&rr,&sr); /* адрес драйвера часов */ clock=DA(peek(sr.es,rr.x.bx+10),peek(sr.es,rr.x.bx+8)); /* адрес драйвера консоли */ con=DA(peek(sr.es,rr.x.bx+14),peek(sr.es,rr.x.bx+12)); /* адрес NUL-драйвера */ drv=DA(sr.es,rr.x.bx+34); printf("\nСписок драйверов устройств\n"); while(FP_OFF(drv)!=0xffff) { printf("Адрес - %Fp атрибуты - %04X ", drv,drv->attr); if (drv->attr&0x8000) for (i=0;i<8; printf("%c",drv->name[i++])); else printf("блочный - %d",drv->name[0]); printf("\n"); y=0; if (drv==clock) { printf(" активный CLOCK$\n"); y++; } if (drv==con) { printf(" активный CON\n"); y++; } if (drv->attr&0x8000) { ATR(1,"консоль ввода") ATR(2,"консоль вывода") ATR(4,"нулевое устройство") ATR(8,"CLOCK$ (часы)") ATR(0x2000,"поддерживает OUB") } else { ATR(2,"32-байтный адрес сектора") ATR(0x2000,"читает media FAT") } ATR(0x40,"поддерживает функции DOS 3.2") ATR(0x800,"поддерживает Open/Close") ATR(0x4000,"поддерживает IOCTL") y=(y1=wherey())-y; getch(); for(i=y;i<y1;gotoxy(1,i++),clreol()); gotoxy(1,y); drv=drv->next; /* адрес след.драйвера */ } printf("Конец списка. Нажмите любую клавишу...\n"); getch(); } Проанализировав результаты выполнения этой программы, читатель может прийти к следующим выводам. Пустой (NUL) драйвер, который состоит из одного заголовка выполняет единственную функцию - быть "головой" списка драйверов. Системные драйверы (драйверы, включенные в файл IO.SYS) образуют исходный список драйверов. Драйверы, устанавливаемые после них (по команде DEVICE в CONFIG.SYS), включаются в начало этого списка - сразу после NUL-драйвера. Таким образом, если устанавливаемый драйвер имеет то же имя, что и системный, то выбираться при обращении по этому имени будет устанавливаемый драйвер, так как он находится ближе к началу списка. При обращениях к драйверам CON и CLOCK$ поиск в списке не выполняется, для ускорения обращения их адреса выбираются из CVT. unsigned int r_ax, r_bx, r_cx,Пример 6 struct REGPACK { unsigned int r_ax, r_bx, r_cx, r_dx; unsigned int r_bp, r_si, r_di; unsigned int r_ds, r_es, r_flags; }; Данные этих типов служат агрументами функций обращения к прерываниям. 3). Интересный способ доступа к регистрам возможен в программах обработки прерываний, он будет рассмотрен ниже вместе с такими программами. Пример 6 /*== ПРИМЕР 5.6 ==*/ /*================ Определение версии DOS ================*/ #include <dos.h> union REGS rr; main() { rr.h.ah=0x30; intdos(&rr,&rr); printf("Версия MS-DOS %d.%d\n",rr.h.al,rr.h.ah); } Пример 6 /*== ПРИМЕР 6.5 ==*/ /*============== Фоновая музыка ===================*/ #include <dos.h> #define TIMEINT 8 /* Возможно также #define TIMEINT 0x1c */ void interrupt (*oldtime)(); /* Область сохранения системного вектоpа пpеpывания таймера */ void interrupt newtime(); /* Новый обpаботчик пpеpываний */ static int NM; /* Счетчик длительности ноты */ static int N; /* Индекс в массиве MUS */ static int MUS[]= { /* Кодировка мелодии. 1-е число в каждой паре - коэфф.деления, второе - длительность в тиках */ 1218,10,767,10,813,10,912,10,966,10,912,30,1218,10,609,10, 678,10,767,10,813,10,724,30,912,10,574,15,609,5,678,15, 767,5,813,5,767,5,678,20,574,10,609,10,678,10,767,10,678, 10,813,30,1218,10,767,10,813,10,912,10,966,10,912,30,1218, 10,609,10,678,10,767,10,813,10,724,30,912,10,574,15,609,5, 678,15,767,5,813,5,767,5,678,20,574,10,609,10,678,10,767, 10,678,10,813,30,0,2,609,10,609,10,912,10,724,10,609,10, 609,5,678,5,724,5,678,5,574,15,678,5,678,10,1024,10,813, 10,678,10,678,5,767,5,813,5,724,5,609,20,678,5,609,5,574, 5,456,5,342,5,384,5,406,5,456,5,456,20,483,20,678,5,609,5, 574,5,456,5,342,5,384,5,406,5,456,5,456,10,483,10,456,20, 0,2,456,5,456,10,483,10,456,20,0,30,-1 }; union REGS rr; struct SREGS sr; void *readvect(int in); void writevect(int in, void *h); /*==== Пусковая пpоцедуpа, программа переднего плана ====*/ main() { int c,i; union REGS rr; initmus(); /* Инициpование */ for ( ; ; ) { c=getch(); rr.h.ah=0x2c; intdos(&rr,&rr); printf("%02d:%02d:%02d\n",rr.h.ch,rr.h.cl,rr.h.dh); /* Завершение по клавише Esc */ if (c==27) break; } stopmus(); /* Завеpшение */ } /*==== Инициpование ====*/ initmus() { /* Начальные значения пеpеменных */ N=0; NM=1; /* Подмена вектоpа таймера */ oldtime = readvect(TIMEINT); writevect(TIMEINT,newtime); } /*==== Завеpшение. Выкл.звука и восстановление вектоpа ===*/ stopmus() { silence(); writevect(TIMEINT,oldtime); } /*==== Новый обpаботчик пpеpываний таймеpа ====*/ void interrupt newtime() { int i; /* Если мы подключились к вектору 8, то пpи отсутствии следующего опеpатоpа системная служба вpемени не pаботает, в main-функции значение t не меняется. */ (*oldtime)(); if (--NM==0) { /* Подсчет тактов */ /* Вpемя звучания ноты истекло. Пример 6 /*== ПРИМЕР 7.6 ==*/ /*====== Функции обслуживания стандартного ввода DOS =====*/ /* ВНИМАНИЕ! Для проверки реакции на Ctrl+Break программу следует запускать вне Турбо-среды */ #include <dos.h> main() { union REGS in, rr; /* Регистры для вызовов DOS */ struct SREGS sr; /* Регистры для вызовов DOS */ int x,y; /* Коорд.курсора (1,6,7,8) или счетчик (A) */ unsigned char dosnum; /* Номер функции */ unsigned long d; /* Счетчик опросов (6) */ char string[13]; /* Буфер (A) */ char *s; /* Указатель в буфере (A) */ L0: clrscr(); printf("Укажите # функции DOS (1,6,7,8,B,A) >"); scanf("%x",&dosnum); printf("DOS-функция %02X.\n",dosnum); switch (dosnum) { case 1: printf("Ввод символа с эхо; "); printf("реагирует на Ctrl-Break.\n"); goto F; case 7: printf("Ввод символа без эхо; "); printf("не реагирует на Ctrl-Break.\n"); goto F; case 8: printf("Ввод символа без эхо; "); printf("реагирует на Ctrl-Break.\n"); goto F; case 6: printf("Ввод символа без эхо и без ожидания; "); printf("не реагирует на Ctrl-Break.\n"); printf("Окончание по Esc\n"); printf("Вводите >"); for(; ; ) { /* Цикл по клавишам */ for (d=0 ; ;d++ ) { /* Цикл ожидания нажатия клавиши */ rr.h.ah=6; rr.h.dl=0xff; /* FF в DL - запрос на ввод. */ intdos(&rr,&rr); /* Если символ готов, флаг ZF=0, а в AL - код символа, в противном случае ZF=1. */ if ((rr.x.flags&0x0040)==0) break; } x=wherex(); y=wherey(); gotoxy(1,7); clreol(); printf("Код==>%02x%c%d\n",rr.h.al); if (rr.h.al==0xff) break; delay(500); } break; case 0xa: printf("Буферизованный ввод строки; "); printf("реагирует на Ctrl-Break.\n"); string[0]=11; /* Макс.длина - 10 (без Enter) */ for(x=1; x"); for(x=0; x"); rr.h.ah=0x0a; sr.ds=FP_SEG(string); /* DS:DX - адрес буфера */ rr.x.dx=FP_OFF(string); intdosx(&rr,&rr,&sr); printf("\nБуфер после ввода>"); for(x=0; x"); for(s=string+2; *s!=13; printf("%c",*(s++))); printf("\n"); break; default: goto L0; } exit(); F: /* Общий алгоритм применения для функций 1,7,8 */ printf("Окончание по Esc\nВводите >"); for (; ; ) { rr.h.ah=dosnum; intdos(&rr,&rr); /* В AL - код символа. */ x=wherex(); y=wherey(); gotoxy(1,7); clreol(); printf(" Код==>%02x%c И еще две возможности предоставляет DOS. Функция 0x0C. При ее вызове в регистре AL должен быть номер одной из функций ввода (1, 6, 7, 8, 0x0A). Функция очищает буфер клавиатуры в памяти BIOS, а затем вызывает указанную в AL функцию. Кроме того, поскольку стандартный ввод представляет собой файл DOS, с ним можно работать при помощи функции файлового ввода 0x3F, задавая дескриптор файла 1. Файловые функции будут рассмотрены позднее. Пример 6 /*== ПРИМЕР 8.7 ==*/ /*=================== Загружаемый шрифт ==================*/ #include <dos.h> #include <stdio.h> void interrupt (* old17)(); void interrupt h17(); int p1,p2,p3; /* Номера портов принтера */ #define prt(x) putc(x,stdprn) #define byte unsigned char /*==== main ====*/ main() { char *st[]={ /* Текст для печати */ "He's a real Nowhere Man,", "Sitting in his Nowhere Land,", " Making all his nowhere plan for nobody." ; byte esct[]= { /* Esc-последовательность */ 27, 38, 0, /* Начало загрузки шрифта */ 40, 40, /* Номера начального и конечного кодов */ 0x8a, /* Ширина символа - 0a и сдвиг 80 */ /* Образ символа */ 32,28,34,42,42,42,42,42,16,0,0 }; int i; prt(27); prt(64); /* Инициализация принтера */ /* Выдача Esc-последовательности */ for (i=0; i<17; prt(esct[i++])); p1=peek(0x40,8); p2=p1+1; p3=p2+1; /* Опр.портов */ /* Подключение к вектору 17 */ *old17=getvect(0x17); setvect(0x17,h17); /* Печать текста */ for(i=0;i<3; fprintf(stdprn,"%s\n",st[i++])); setvect(0x17,old17); /* Восстановление вектора */ prt(27); prt(64); /* Инициализация принтера */ } /*==== Обработчик 17-го прерывания ====*/ void interrupt h17() { if (_AH==0) { /* Вывод символа */ if (_AL=='a') { /* Символ "a" - особая обработка */ /* Esc-послед. переключения на шрифт ОЗУ. */ ownchar(27); ownchar(0x25); ownchar(1); /* Печать символа ОЗУ с кодом 40 */ ownchar(40); /* Esc-послед. переключения на шрифт ПЗУ. */ ownchar(27); ownchar(0x25); ownchar(0); } else ownchar(_AL); /* Печать символа не "a". */ } else (*old17)(); /* Старый обработчик */ } /*==== Печать одного символа ====*/ ownchar(byte f) { byte stb; /* Байт состояния */ /* Ожидание готовности */ stb=0; while ((stb&0x80)==0) stb=inport(p2); outportb(p1,f); /* Вывод символа */ outportb(p3,0x0d); outportb(p3,0x0c); /* Строб */ } Напрашивается мысль о том, что матричный принтер можно заставить выводить на печать не только символы, но и любые изображения, и такая возможность действительно имеется - это использование принтера в графическом режиме. Пример 6 /*== ПРИМЕР 9.4 ==*/ /*==== Демонстpация страничной организации видеопамяти ===*/ #include <dos.h> #include <stdio.h> #define PgUp 0x49 #define PgDown 0x51 #define Esc 27 #define byte unsigned char #define word unsigned int #define VSEG 0xb800 /* Адpес начала видеопамяти */ #define NPAGE 8 main() { char st[20]; /* Идентификатоp стpаницы */ char *s; word voff; /* Смещение относительно начала стp. */ word vpage; /* Сегментный адpес начала стpаницы */ byte tpage; /* Текущий номеp стpаницы */ int flag; /* Пpизнак окончания */ byte attr; /* Цвет */ /* Разметка стpаниц */ for (tpage=0; tpage<NPAGE; tpage++) { sprintf(st,"==page %d==",tpage); /* Каждая следующая стpаница в памяти сдвинута на 4096 байт или на 256 паpагpафов. */ vpage=VSEG+tpage*256; /* Код фонового цвета стpаницы совпадает с ее номеpом, код цвета символов - на 3 больше. */ attr=(tpage0xf0) tpage=NPAGE-1; textpage(tpage); break; case PgDown: /* Сдвиг на стpаницу впеpед */ if (++tpage>=NPAGE) tpage=0; textpage(tpage); break; default: putchar(7); break; } } } } /*==== Установка текущей стpаницы видеопамяти ====*/ textpage(byte pg) { union REGS rr; rr.h.ah=5; /* Функция 5 */ rr.h.al=pg; /* Номеp стpаницы */ int86(0x10,&rr,&rr); } Роль альтернативных страниц в текстовом режиме уменьшается с повышением быстродействия компьютера. По-видимому, изначально дополнительные страницы были задуманы как средство быстрой смены изображения на экране: сначала новое изображение строится на невидимой в данный момент странице, а затем следует мгновенное переключение на эту страницу. Однако, даже на ПЭВМ с тактовой частотой 8 мгц разница во времени между построением на экране (методом прямой записи в видеопамять) даже весьма сложного изображения и переключением страниц практически не улавливается глазом. Поэтому этот метод сейчас не пользуется большой популярностью, многие программисты используют дополнительные страницы не по прямому назначению, а как обычную память для хранения данных, размещения буферов и т.п. Пример 6 /*== ПРИМЕР 10.4 ==*/ /*========== Чтение Таблицы Размещения Файлов ==========*/ #include <dos.h> #include <alloc.h> #define byte unsigned char #define word unsigned int #define dword unsigned long /* Структура корневой записи */ struct RootRec { byte jmp[3], ident[8]; word SectSize; byte ClustSize; word ResSect; byte FatCnt; word RootSize, TotSecs; byte Media; word FatSize, TrkSecs, HeadCnt, HidnSecL, HidnSecH; dword LongTotSecs; byte Drive; byte reserved1, DOS4_flag; dword VolNum; char VolLabel[11], FatForm[8]; } *rt; /* Структура параметров для INT 25 */ struct{ dword first_sect; word count; byte *ptr; } parm; union REGS rr; struct SREGS sr; main() { byte *buff; /* адрес буфера в ОП */ byte sys; /* признак диска > 32 Мбайт */ char drive; /* идентификатор диска */ byte fat16; /* признак 16-битной FAT */ word ss; int i,k,m,j; ASK1:printf("\nУкажите идентификатор диска (A,B...) >"); drive=getche(); if (drive>'b') { ASK2:printf("\nОбьем лог. диска больше 32 Мбайт? (y/n) >"); sys=getche(); switch(sys) { case 'y': sys=1;break; case 'n': sys=0;break; default: goto ASK2; } } else sys=0; buff=(byte *)malloc(512); /* Чтение boot-сектора */ rr.h.al=drive-'a'; /* Диск */ if (!sys) { rr.x.cx=1; rr.x.dx=0; sr.ds=FP_SEG(buff); rr.x.bx=FP_OFF(buff); } else { sr.ds=FP_SEG(&parm); rr.x.bx=FP_OFF(&parm); parm.first_sect=0; parm.count=1; parm.ptr=buff; rr.x.cx=0xffff; } int86x(0x25,&rr,&rr,&sr); readerror(); rt=(struct RootRec *)buff; /* определение формата FAT */ if (rt->DOS4_flag==41) { /* для диска, отформатированного в DOS 4 можно определить формат FAT из Boot-сектора */ if (!memcmp(rt->FatForm,"FAT16",5)) fat16=1; else fat16=0; } else { ASK3:printf("\nФормат FAT 12-битный? (y/n)"); fat16=getche(); switch(fat16) { case 'y': fat16=0; break; case 'n': fat16=1; break; default: goto ASK3; } } /* Выделение памяти под FAT */ buff=(byte *)realloc(buff,rt->FatSize*512); if (buff==NULL) { printf("Нехватка памяти\n"); exit(); } /* Чтение FAT */ rr.h.al=drive-'a'; if (!sys) { /* маленький диск */ rr.x.cx=rt->FatSize; rr.x.dx=rt->ResSect; sr.ds=FP_SEG(buff); rr.x.bx=FP_OFF(buff); } else { /* большой диск */ parm.first_sect=rt->ResSect; parm.count=rt->FatSize; parm.ptr=buff; sr.ds=FP_SEG(&parm); rr.x.bx=FP_OFF(&parm); rr.x.cx=0xffff; } int86x(0x25,&rr,&rr,&sr); readerror(); clrscr(); /* Форматная распечатка FAT */ printf(" Диск %c: FAT - %d бит\n", drive-32,fat16*4+12); printf(" |"); for(i=0;i<10;printf("%5d ",i++)); printf("\n____|____________________________________"); printf("________________________"); for(j=0,i=0,k=0;i<220;i++) { if (!k) printf("\n%3d |",j); if (!fat16) { m=(i*3)/2; ss=*(word *)(buff+m); if(i%2) /* нечетный элемент */ ss>>=4; else /* четный элемент */ ss&=0x0fff; if (ss>0x0fef) printf(" %03xH ",ss); else printf("%5d ",ss); } else { m=i*2; ss=*(word *)(buff+m); if (ss>0xffef) printf("%04xH ",ss); else printf("%5d ",ss); } if (++k>=10) { j+=10; k=0; } } getch(); free(buff); } /*==== Проверка ошибок чтения ====*/ readerror() { if (rr.x.cflag) { printf("\nОшибка чтения: %x. ",rr.h.ah); printf("Нажмите любую клавишу...\n\7"); getch(); exit(); } } Пример 6 struct DPB { byte dev_num; /* # драйвера */ byte dev_num_mod; /* # устр-ва в драйвере */ word SectSize; /* размер сектора (байт) */ byte MaxSect; /* макс.номер сектора в кластере */ byte Log2Sect; /* LOG2(размера кластера) */ word ResSect; /* число резервных секторов */ byte FatCnt; /* число копий FAT */ word RootSize; /* размер корневого каталога */ word Data1st; /* # 1-го сектора области данных */ word MaxClust; /* максимальный номер кластера */ byte FatSize; /* размер FAT */ word Root1st; /* 1-й сектор корневого каталога */ void *drv_ptr; /* адрес драйвера */ byte Media; /* тип носителя */ byte access; /* доступ к носителю */ struct DPB *next; /* ссылка */ word Free1st; /* первый свободный кластер */ word FreeCnt; /* число свободных кластеров */ }; Поля SectSize, ResSect, RootSize, FatCnt, FatSize, Media копируются из одноименных полей загрузочного сектора носителя (см.10.3.2). Поля MaxClust, Data1st и Root1st получются из данных загрузочного сектора путем несложных вычислений. Поле MaxSect содержит значение на 1 меньшее поля ClustSize загрузочного сектора. Поле Log2Sect дает возможность получить число секторов в кластере путем сдвига 1 на Log2Sect разрядов влево. Поле access имеет значение 0, если к носителю было обращение, или 0xFF - если не было, в последнем случае значения полей, источником которых является загрузочный сектор, могут не соответствовать действительности. Наконец, последние два поля содержат некоторую информацию о свободных кластерах, если такая информация отсутствует, то значения этих полей - 0 и 0xFFFF соответственно. При помощи поля next все DPB связываются в список (смещение 0xffff - признак конца спсика). Формат DPB для DOS 4.0 и последующих отличается от вышеприведенного тем, что размер поля FatSize составляет здесь не 1, а 2 байта и, следовательно, смещения всех последующих полей увеличиваются на 1. В DOS имеются функции 0x1F и 0x32, возвращающие в DS:BX адрес DPB. Функция 0x1F возвращает DPB текущего диска, а 0x32 - диска, логический номер которого задается в DL (0 - текущий, 1 - A и т.д.; если задан неправильный номер диска, функция возвращает 0xFF в регистре AL). Эти функции, однако, "не удовлетворяются" значением доступа (access) 0xFF, а, если обращений к носителю не было, пытаются прочитать его загрузочную запись. В программных примерах главы 10 мы могли обращаться к функции 0x32 вместо того, чтобы читать загрузочный сектор. В программе следующего примера, выводящей на экран содержимое всех блоков DPB, структура блока разбита на 2 части: struct DPB_1 и struct DPB_2. Поле FatSize описано как 2-байтное, но в зависимости от версии DOS адрес второй части блока (начиная с поля Root1st) назначается со смещением 16 (DOS 3.x) или 17 (DOS 4.x) относительно первой части. Программа выводит все DPB дважды - получая их адреса как из системного списка, так и по функции DOS 0x32. unsigned int segm, unsigned intПример 7 int peek( unsigned int segm, unsigned int offs); char peekb(unsigned int segm, unsigned int offs); Для записи данных в память испольэуются функции: Пример 7 /*= ПРИМЕР 3.1 =*/ /*=============== Перехват прерывания ===================*/ #include <dos.h> #define VECT_ADDR(x) x*4 /* Вычисление адреса вектора */ int intr_num = 9; /* Номер прерывания */ int intr_count = 0; /* Счетчик прерываний */ void interrupt new_handler(); /* Описание нового обработчика прерывания */ void interrupt (* old_handler)(); /* Переменная для сохранения старого вектора */ unsigned int segm, offs; /* Сегмент и смещение из старого вектора */ main() { /* Получение старого вектора */ offs=peek(0,VECT_ADDR(intr_num)); segm=peek(0,VECT_ADDR(intr_num)+2); old_handler=MK_FP(segm,offs); /* Запись нового вектора */ disable(); poke(0,VECT_ADDR(intr_num),FP_OFF(new_handler)); poke(0,VECT_ADDR(intr_num)+2,FP_SEG(new_handler)); enable(); /* Ожидание 10-кратного срабатывания */ while (intr_count Прежде всего - что делает эта программа. Она перехватывает прерывание 9 (аппаратное прерывание, поступающее при нажатии и при отпускании любой клавиши клавиатуры), а затем ожидает, пока счетчик прерываний на достигнет числа 10. После этого программа восстанавливает вектор и выводит на экран значение счетчика. Ее обработчик прерывания вызывает старый обработчик, а в дополнение к этому подсчитивает количество прерываний. Номер прерывания задан в программе переменной intr_num, макрос VECT_ADDR определяет физический адрес вектора прерывания с заданным номером. Счетчик прерываний - переменная intr_count. Новый обработчик прерываний new_handler описан в программе как было рассказано выше. Интересно определение переменной old_ handler, служащей для размещения в ней старого вектора - она определена как указатель на функцию, имеющую тип void interrupt. Переменные segm и offs служат для сохранения адресных частей старого вектора. Получение старого вектора состоит в чтении из памяти двух слов. По адресу вектора считывается смещение, а из следующих двух байт - сегмент. Запись нового вектора состоит в записи по тем же адресам двух слов: первое слово получается как смещение обработчика new_handler, а второе - как его сегмент. Пример 7 /*== ПРИМЕР 6.6 ==*/ /*==================== Модель АЦП ===============*/ #include <dos.h> #include <math.h> #define TIMEINT 8 #define NN 100 /* Максимальное число отсчетов */ void interrupt (*oldtime)(); void interrupt newtime(); static int y[NN]; /* Накопитель отсчетов */ static int ny; /* Индекс в массиве y */ static int yc; /* Текущее значение sin */ static int kf; /* Счетчик вызовов oldtime */ union REGS rr; struct SREGS sr; void *readvect(int in); void writevect(int in, void *h); main() { unsigned oldtic=65535; /* Старый коэфф.деления */ unsigned newtic=4095; /* Новый коэфф.деления */ unsigned char d; int k; double x; /* Аргумент ф-ции sin */ char line[81]; /* Строка для вывода */ for (ny=0; ny>8); /* Старший байт счетчика */ ny=-1; /* Признак того, что АЦП еще не началось */ kf=15; /* Подключение к вектору */ oldtime=readvect(TIMEINT); writevect(TIMEINT,newtime); /* Запуск "непрерывного процесса" */ for (x=ny=0; ny>8); /* Старший байт счетчика */ /* Вывод запомненных результатов */ for(ny=0; ny=0) /* Если АЦП началось, */ &&(ny<NN)) /* и NN отсчетов еще не набрано, */ y[ny++]=yc; /* запоминание очередного отсчета */ } /*==== Получение старого вектора ====*/ void *readvect(int in) { rr.h.ah=0x35; rr.h.al=in; intdosx(&rr,&rr,&sr); return(MK_FP(sr.es,rr.x.bx)); } /*==== Запись нового вектора ====*/ void writevect(int in, void *h) { rr.h.ah=0x25; rr.h.al=in; sr.ds=FP_SEG(h); rr.x.dx=FP_OFF(h); intdosx(&rr,&rr,&sr); } Последний пример этой главы демонстрирует принципиальную возможность обеспечения работы с разделением времени. MS-DOS - однопрограммная система, она не поддерживает разделения ресурсов вычислительной системы между процессами, но у пользователя есть возможность самостоятельно обеспечить разделение, хотя это и непросто. В нашей программе имеется два процесса, каждый из которых программно реализован своей функцией. Каждому процессу отводится свой квант времени, заданный в числе тиков таймера. Пример 7 /*== ПРИМЕР 7.7 ==*/ /*============ Действие комбинации Ctrl+Break ============*/ /* ВНИМАНИЕ! Для проверки реакции на Ctrl+ Break программу следует запускать вне Турбо-среды */ #include <dos.h> void main() { union REGS rr; int i,k,o,m; clrscr(); printf ("При выполнении этого цикла Ctrl+Break не сработает"); for (o=160,i=0,k=0; i В системе имеется так называемый статус обработки Ctrl+ Break. Если этот статус 1 (включен), то комбинация обрабатывается при любом системном вызове, если 0 (отключен) - только при вызовах, связанных со стандартным вводом и выводом. Прочитать или установить этот статус позволяет функция DOS 0 x33. Если при вызове функции в регистре AL - 1, то регистр DL должен содержать устанавливаемое значение статуса, если в AL - 0, статус читается. В обоих случаях в DL возвращается текущее значение статуса. В примере 7.8 предлагается испытать действие Ctrl+Break при разных значениях статуса обработки. В теле циклов применяется ни на что не влияющий системный вызов "получение версии DOS" (функция 0x30). Но если заменить этот вызов на вызов, например, функции 0x0B, то и первый цикл (выполняемый при статусе 0) будет прерываемым, так как для функций стандартного ввода обработка Ctrl+ Break не отключается. Пример 7 /*== ПРИМЕР 8.8 ==*/ /*========== Работа принтера в графическом режиме ========*/ #include <dos.h> #include <math.h> #include <stdio.h> #include <alloc.h> #define prt(x) putc(x,stdprn); union REGS rr; main() { int col=4; /* Цвет - красный */ paint(col); /* Построение картинки */ getch(); prtgraph(1,1,80,80,col); /* Вывод на принтер */ } /* Построение картинки (из окружностей) */ paint(int col) { int x,y,fy,fx,k; /* Установка графического режима */ rr.x.ax=0x0010; int86(0x10,&rr,&rr); /* Построение контуров */ for (x=-24; x<=24; x++) { y=(int)sqrt((double)(576-x*x)); point(x+32,32-y,col); point(x+32,32+y,col); } for (x=-12; x<=12; x++) { y=(int)sqrt((double)(144-x*x)); point(x+20,32+y,col); point(x+44,32-y,col); } for (x=-4; x<=4; x++) { y=(int)sqrt((double)(16-x*x)); point(x+20,32-y,col); point(x+20,32+y,col); point(x+44,32+y,col); point(x+44,32-y,col); } /* Закрашивание */ for (fx=-24; fx<=24; fx++) { fy=(int)sqrt((double)(576-fx*fx)); x=fx+32; for (k=0, y=32-fy; y<=32+fy; y++) { rr.x.dx=y; rr.x.cx=x; rr.h.bh=0; rr.h.ah=0x0d; int86(0x10,&rr,&rr); if ((rr.h.al==col)&&((y!=32)(x==32))) k=1-k; if (k) point(x,y,col); } } } /* Вывод одной графической точки */ point( int x, int y, int c) { rr.x.dx=y; rr.x.cx=x; rr.h.bh=0; rr.h.al=c; rr.h.ah=0x0c; int86(0x10,&rr,&rr); } /*----------------------------------------------------*/ /* Графическая копия экрана. (x1,y1),(x2,y2) - координаты окна, c - цвет */ prtgraph(int x1, int y1, int x2, int y2, int c) { char *str; /* Графический образ строки для печати */ char *s; int strsize; /* Размер образа */ char esc[] = /* Начало Esc-послед.графической печати */ { 27,42,0,0,0 }; int x,y; /* Экранные координаты */ int bit; /* Счетчик разрядов в образе */ int i; /* Инициализация принтера */ prt(27); prt(64); /* Установка расстояния между строк */ prt(27); prt(51); prt(25); /* Выделение памяти для образа */ strsize=x2-x1+1; str=malloc(strsize); for (s=str, i=0; i<strsize; i++, s++) *s=0; /* Запись размера образа в Esc-послед. */ esc[3]=strsize%256; esc[4]=strsize/256; /* Перебор строк */ for (bit=7, y=y1; y<=y2; y++) { /* Перебор точек в строке */ for (s=str, x=x1; x<=x2; x++, s++) { /* Чтение точки */ rr.x.dx=y; rr.x.cx=x; rr.h.bh=0; rr.h.ah=0x0d; int86(0x10,&rr,&rr); /* Если цвет точки совпадает - заносится 1 в соответствующий разряд образа */ if (rr.h.al==c) *s|=(1<<bit); } if (--bit<0) { /* Если сформированы 8 разрядов образа, то: выводится начало Esc-последовательности, */ for(i=0;i<5;i++) prt(esc[i]); /* выводится образ и обнуляется, */ for (i=0, s=str; i<strsize; i++,s++) { prt(*s); *s=0; } prt(10); /* перевод строки */ bit=7; } } /* Если не весь образ выведен - вывод остатка */ if (bit<7) { for (i=0; i<5; i++) prt(esc[i]); for (i=0, s=str; i<strsize; i++,s++) prt(*s); prt(10); } /* Инициализация принтера */ prt(27); prt(64); } Пример 7 /*== ПРИМЕР 9.5 ==*/ /*=================== Управление курсором ================*/ #include <dos.h> #define byte unsigned char #define word unsigned int #define Esc 27 #define Enter 13 #define Up 0x48 #define Down 0x50 #define Left 0x4b #define Right 0x4d # define Home 0x47 union REGS rr; main() { word posc; /* Позиция курсора в линейных координатах */ word post; /* Позиция вывода текста в линейных коорд. */ byte y,x; /* Позиция в координатах x,y */ byte s1, s2; /* Нач.и кон.строки образа курсора */ byte mode=0; /* Атрибут мигания */ char *modes[]= { "нормальный", "невидимый" }; byte flag; /* признак окончания */ /*== 1. Перемещение курсора ==*/ for(clrscr(),posc=post=0; ;posc++) { y=post/80; x=post%80; setcurpos(x,y); printf("текст->%d,курсор->%d ",post,posc); getcurpos(&x,&y); post=y*80+x; y=posc/80; x=posc%80; setcurpos(x,y); if (getch()==27) break; } /*== 2. Удаление курсора ==*/ clrscr(); printf("Курсор удален\n"); setcurpos(1,26); getch(); /*== 3. Управление формой курсора ==*/ clrscr(); printf("Изменение формы курсора\n"); printf(" \\ s1 "); for(s1=0; s10) s1--; else s1=8; break; case Up: if (s2>0) s2--; else s2=13; break; case Right: if (++s1>9) s1=0; break; case Down: if (++s2>13) s2=0; break; case Home: s1=7; s2=13; break; } } } } /*==== Установка курсора ====*/ setcurpos(byte x,byte y) { rr.h.ah=2; /* функция 2 */ rr.h.bh=0; /* страница 0 */ rr.h.dh=y; /* координата y */ rr.h.dl=x; /* координата x */ int86(0x10,&rr,&rr); } /*==== Чтение позиции курсора ====*/ getcurpos(byte *x,byte *y) { rr.h.ah=3; /* функция 3 */ rr.h.bh=0; /* страница 0 */ int86(0x10,&rr,&rr); *y=rr.h.dh; /* координата y */ *x=rr.h.dl; /* координата x */ } /*==== Изменение формы курсора ==*/ curform(byte s1, byte s2, byte mode) { rr.h.ah=1; /* функция 1 */ rr.h.ch=(mode Набор средств BIOS и DOS для вывода на терминал весьма богат. В прерывании BIOS 0x10 функции 8 и 9 - соответственно чтение и вывод символа и атрибута в текущей позиции курсора (курсор при этом не сдвигается); в BH задается номер страницы, в AL получается/задается код символа, в AH - цветовой атрибут. Пример 7 0 a d l s h r, где
Поля time и date содержат время и дату последней модификации файла. Формат времени: ЧЧЧЧЧММММММССССС (Часы, Минуты, Секунды); формат даты: ГГГГГГГММММДДДДД (Год, Месяц, День). Поле cl - номер первого кластера, распределенного файлу, то есть, начало той цепочки, которая продолжается в FAT. Поле size - размер файла в байтах. В DOS не предусмотрены какие-либо специальные признаки конца файлов. Применяемый в некоторых случаях символ ^Z (код 26) интерпретируется как признак конца конкретными программами, но не DOS. DOS же определяет конец файла по его размеру, получаемому из этого поля Элемента Каталога. Для подкаталога это поле содержит 0. Программа примера 10.5 считывает и выводит на печать содержимое Корневого Каталога. Как и в предыдущих примерах, программа вынуждена сначала прочитать Boot-сектор. Из Boot-сектора программа узнает: количество элементов в каталоге - поле RootSize; размер каталога в секторах - RootSize/16 (в один сектор помещаются 16 Элементов Каталога); номер сектора, с которого начинается Корневой Каталог - ResSect+FatSize*FatCnt (следом за двумя копиями FAT). Пример 7 /*== ПРИМЕР 13.2 ==*/ /*================= Распечатка всех DPB =================*/ #include <dos.h> #define byte unsigned char # define word unsigned int struct DPB_1 { /* 1-я часть DPB */ byte dev_num, dev_num_mod; word SectSize; byte MaxSect, Log2Sect; word ResSect; byte FatCnt; word RootSize, Data1st, MaxClust, FatSize; } *dpb_1; struct DPB_2 { /* 2-я часть DPB */ word Root1st; void *drv_ptr; byte Media, access; struct DPB_1 *next; word Free1st, FreeCnt; } *dpb_2; word dd_seg, dd_off; /* адрес 1-го DPB */ byte ldrive; /* номер диска */ byte dos; /* номер версии DOS */ union REGS rr; struct SREGS sr; main() { /* номер версии DOS */ rr.h.ah=0x30; intdos(&rr,&rr); dos=rr.h.al; /* адрес CVT */ rr.h.ah=0x52; intdosx(&rr,&rr,&sr); printf("\nТаблица DPB по спискам\n"); /* адрес 1-го DPB */ dd_off=peek(sr.es,rr.x.bx); dd_seg=peek(sr.es,rr.x.bx+2); dpb_1=(struct DPB_1 *)MK_FP(dd_seg,dd_off); while(FP_OFF(dpb_1)!=0xffff) { /* движение по списку */ print_dpb(); dpb_1=dpb_2->next; } printf("\nТаблица DPB по INT 32H\n"); for(ldrive=1; ;ldrive++) { /* перебор всех дисков */ rr.h.ah=0x32; /* функция 32 */ rr.h.dl=ldrive; /* номер диска */ intdosx(&rr,&rr,&sr); if (rr.h.al==0xff) break; /* адрес DPB для диска ldrive */ dpb_1=(struct DPB_1 *)MK_FP(sr.ds,rr.x.bx); print_dpb(); } } /*==== распечатка содержимого DPB ====*/ print_dpb() { /* смещение 2-й части DPB зависит от версии DOS */ if (dos<4) dpb_2=(struct DPB_2 *)((char *)dpb_1+0x10); else dpb_2=(struct DPB_2 *)((char *)dpb_1+0x11); printf("\nАдрес DPB - %Fp\n",dpb_1); printf(" устройство %u(%u) - диск %c\n", dpb_1->dev_num,dpb_1->dev_num_mod,'A'+dpb_1->dev_num); printf(" тип носителя - %02X\n",dpb_2->Media); printf(" адрес драйвера - %Fp\n",dpb_2->drv_ptr); printf(" разм.сектора - %u(байт)\n", dpb_1->SectSize); printf(" разм.кластера - %u(сект) %u\n", dpb_1->MaxSect+1, 1<<dpb_1->Log2Sect); printf(" рез.секторов - %u\n",dpb_1->ResSect); printf (" всего кластеров - %u, начиная с сектора %u\n", dpb_1->MaxClust,dpb_1->Data1st); printf(" FAT - "); if (dos<4) printf("%u",(byte)dpb_1->FatSize); else printf("%u",dpb_1->FatSize); printf("(сект) * %u\n",dpb_1->FatCnt); printf (" корневой каталог - %u(элементов) с сектора %u\n", dpb_1->RootSize,dpb_2->Root1st); printf(" доступ - %X\n",dpb_2->access); printf(" свободный кластер - %04X",dpb_2->Free1st); printf(" (всего - %u)\n",dpb_2->FreeCnt); if (getch()==27) exit(); } Одно из полей CVT содержит указатель на начало Массива Текущих Каталогов. В версиях DOS до 3.0 такого массива не было, подобная информация содержалась в DPB. В современных версиях DOS возникла необходимость в такой структуре данных в связи с тем, что новые команды DOS SUBST и JOINT позволяют присвоить некоторому узлу дерева каталогов идентификатор диска или наоборот - описать диск как подкаталог другого диска. Общее количество возможных идентификаторов дисков задается параметром команды LASTDRIVE в CONFIG.SYS и хранится в CVT. Каждый элемент массива - Структура Текущего Каталога (CDS - Current Directory Structure) сохраняет информацию о текущем каталоге на логическом диске. Структура CDS для версий DOS 3.x следующая: unsigned int segm, unsigned intПример 8 void poke( unsigned int segm, unsigned int offs, int val); void pokeb(unsigned int segm, unsigned int offs, char val); Аргументы этих функций: segm - сегментный адрес памяти, offs - смещение в сегменте. Функции peek и peekb возвращают прочитанное значение, а в функциях poke и pokeb записываемое в память значение задается аргументом val. Функции peekb и pokeb работают с одним байтом, peek и poke - с двухбайтным словом. Следует помнить, что слово в памяти ПЭВМ хранится в двух последовательных байтах памяти, причем в байте с меньшим адресом - младшая часть слова. Поэтому при побайтном (peekb) чтении из двух последовательных адресов мы получим сначала младшую, а затем старшую часть слова, а при чтении слова (peek) - целое слово, в котором обе части находятся "на своих местах". Определение физических адресов переменных возможно благодаря наличию в языке Си данных типа "указатели", представляющих собой адреса переменных. Указатель может быть преобразован в физический адрес при помощи макросов: Пример 8 /*= ПРИМЕР 3.2 =*/ /*=============== Перехват прерывания ===================*/ #include <dos.h> int intr_num = 9; /* Номер прерывания */ int intr_count = 0; /* Счетчик прерываний */ void interrupt new_handler(); /* Описание нового обработчика прерывания */ void interrupt (* old_handler)(); /* Переменная для сохранения старого вектора */ union REGS rr; /* Регистры общего назначения */ struct SREGS sr; /* Сегментные регистры */ void *readvect(int in); void writevect(int in, void *h); main() { /* Получение старого вектора */ old_handler=readvect(intr_num); /* Запись нового вектора */ writevect(intr_num,new_handler); /* Ожидание 10-кратного срабатывания */ while (intr_count Введенные здесь дополнительные переменные rr и sr служат для передачи параметров функциям DOS. Чтение старого вектора производится при помощи функции 0x35 (ее номер перед обращением к DOS заносится в регистр AH). По спецификациям функции 0x35 в регистр AL должен быть занесен номер прерывания, вектор которого читается. Функция возвращает в регистре ES сегментную часть вектора, а в регистре BX - смещение, эти значения наша программа запоминает в переменных segm и offs. Установка нового вектора производится при помощи функции 0x25. По спецификациям этой функции в регистр AL должен быть занесен номер прерывания, вектор которого мы устанавливаем, в регистре DS - сегментная часть вектора, а в регистр DX - смещение. Обратите внимание на то, что здесь при записи вектора мы не запрещаем прерывания - эти действия функция 0x25 выполняет сама. Восстановление вектора производится также при помощи функции 0x35. В этой программе у нас нет необходимости вычислять адрес, по которому расположен вектор прерывания, поэтому макрос VECT_ ADDR здесь отсутствует. В прикладных задачах использованию функций DOS для чтения/установки векторов следует отдавать предпочтение еще и потому, что в новых версиях DOS адреса векторов, возможно, не будут так легко доступны пользователю. В дальнейшем мы неоднократно будем использовать функции readvect и writevect, приведенные в примере 3.2. Пример 8 /*== ПРИМЕР 6.7 ==*/ /*================== Разделение времени ==================*/ /* Два процесса - PROCESS1 и PROCESS2 - поочередно активизируются, каждый на заданный интервал времени */ /* ВНИМАНИЕ !!! При компиляции этого модуля в Турбо-Си необходимо установить Options -> Compiler ->Code generation ->Test stack overflow -> Off */ #include <dos.h> #define word unsigned int #define byte unsigned char /* Номер используемого прерывания таймера */ #define TIMEINT 8 /* Размер стека (подобран экспериментально) */ #define StackSize 600 /* Максимальное число переключений */ #define NSWITCH 10 /* Кванты времени (в тиках таймера) */ static int TimeC[3]={1,40,120}; /* TimeC[0] - не используется; TimeC[i] - квант, отведенный i-му процессу */ /* Флаги инициализации */ static byte initF[3]={0,0,0}; /* initF[0] - общий флаг; initF[i] - флаг i-го процесса */ /* Сегменты и указатели стека прерванных процессов */ static word newSS[3], newSP[3]; /* newSS[0] и newSP[0] - для пусковой программы newSS[i] и newSP[i] - для i-го процесса */ static int TimeCount; /* Счетчик квантов времени */ static byte nproc; /* Номер текущего процесса */ static byte newstack[StackSize]; /* Стек для процессов. 1-я половина этого масива - для процесса 2, 2-я для процесса 1 */ static byte Nswitch=0; /* Счетчик переключений */ /* Вектор системного обработчика прерываний таймера */ void interrupt (*oldtime)(); /* Описание нового обработчика прерываний от таймера */ void interrupt RTS(); void *readvect(int in); void writevect(int in, void *h); union REGS rr; struct SREGS sr; /*==== main ====*/ main() { word st_seg,st_off; clrscr(); /* Запоминание старого стека */ newSP[0]=_SP; newSS[0]=_SS; /* Определение адреса нового стека */ st_seg=FP_SEG(newstack); st_off=FP_OFF(newstack); /* Подключение к вектору таймера */ TimeCount=TimeC[0]; *oldtime=readvect(TIMEINT); writevect(TIMEINT,RTS); /* Инициализация процесса 1 */ /* При перекл.на другой стек запрещаются прерывания */ nproc=1; disable(); _SS=st_seg; _SP=st_off+StackSize; enable(); process1(); /* Инициализация процесса 2 */ nproc=2; disable(); _SS=st_seg; _SP=st_off+StackSize/2; enable(); process2(); /* Восстановление стека */ disable(); _SS=newSS[0]; _SP=newSP[0]; enable(); initF[0]++; /* Инициализация закончена */ /* Запуск в рабочее состояние */ nproc=0; TimeCount=1; /* Этот цикл прервется таймером. Пример 8 /*== ПРИМЕР 7.8 ==*/ /*============ Статус обработки Ctrl+Break ===============*/ /* ВНИМАНИЕ! Для проверки реакции на Ctrl+ Break программу следует запускать вне Турбо-среды */ #include <dos.h> void main() { union REGS rr; int i,k,o,m; clrscr(); /* Отключение Ctrl+Break */ rr.h.ah=0x33; rr.h.al=1; /* Подфункция установка Ctrl+Break */ rr.h.dl=0; /* OFF */ intdos(&rr,&rr); printf("Статус Ctrl+Break = %d\n",rr.h.dl); /* Этот цикл будет непрерываемым */ for (o=160,i=0,k=0; i Мы можем перехватить вектор 0x23 и производить собственную обработку Ctrl+Break. Это демонстрирует пример 7.9, в котором обработка комбинации заключается в смене цвета выводимого на экран символа. Пример 8 /*== ПРИМЕР 9.6 ==*/ /*================ Функции прерывания 0x10 ===============*/ /* функции 6, 7 - сдвиг экрана; 8, 9 - читать/писать сим- вол/атрибут, 0x0A, 0x0E - вывод символа */ #include <dos.h> #include <stdlib.h> #define byte unsigned char #define word unsigned int #define Esc 27 #define Up 0x48 #define Down 0x50 #define Left 0x4b # define Right 0x4d union REGS rr; struct SREGS sr; byte x1=34, y1=7; /* координаты окна (левый,верхний) */ byte x2, y2; /* координаты окна (правый,нижний) */ byte xc, yc; /* координаты курсора */ char *msg[]={"=========Вывод по функции 0x0A==========", "=========Вывод по функции 0x0E==========", "=========Вывод по функции 0x09==========", "=========Вывод по функции 0x13==========", "========Вывод по функции DOS 2==========", "========Вывод по функции DOS 6==========", "========Вывод по функции DOS 9==========$" }; word save[5]; /* для сохранения регистров */ word i; char *s; main() { xc=0; yc=0; /*== Демонстрация функций вывода ==*/ /* очистка экрана и заполн. его случайными атрибутами */ randomize(); for (i=0; i1) { for(y=y1; y=x1; xx1--,xx2--) { getca(xx1,y1,&c,&a); putca(xx2,y2,c,a); } } } /*==== Посимвольный вывод строки функцией 0A ====*/ putstring0A() { for (s=msg[0]; *s; s++) { rr.h.ah=0x0a; /* функция 0A */ rr.h.bh=0; /* страница 0 */ rr.h.al=*s; /* символ */ rr.x.cx=1; /* число повторений */ int86(0x10,&rr,&rr); /* перевычисление координат и сдвиг курсора */ if (++xc>79) { xc=0; yc++; } setcurpos(xc,yc); } } /*==== Посимвольный вывод строки функцией 0E ====*/ putstring0E() { for (s=msg[1]; *s; s++) { rr.h.ah=0x0e; /* функция 0E */ rr.h.al=*s; /* символ */ int86(0x10,&rr,&rr); } /* перевычисление координат, курсор сдвигается сам */ xc+=40; yc+=xc/80; xc=xc%80; } /*==== Посимвольный вывод строки функцией 9 ====*/ putstring09() { char a, c; getca(xc,yc,&c,&a); /* определение атрибута 1-й позиции */ for (s=msg[2]; *s; s++) { putca(xc,yc,*s,a); /* перевычисление коорд., курсор сдвигается в putca */ if (++xc>79) { xc=0; yc++; } } setcurpos(xc,yc); } /*==== Посимвольный вывод строки функцией 13 ====*/ putstring13() { char a, c; getca(xc,yc,&c,&a); /* определение атрибута 1-й позиции */ save[0]=_BP; save[1]=_DI; save[2]=_SI; save[3]=_DS; save[4]=_ES; _BP=FP_SEG(msg[3]); /* адрес строки */ _ES=FP_OFF(msg[3]); _BH=0; /* страница 0 */ _BL=a; /* атрибут */ _DH=xc; _DL=yc; /* координаты начала */ _AX=0x1300; /* функция 13, подфункция 0 */ geninterrupt(0x10); _BP=save[0]; _DI=save[1]; _SI=save[2]; _DS=save[3]; _ES=save[4]; /* перевычисление координат, курсор сдвигается сам */ xc+=40; yc+=xc/80; xc=xc%80; } /*==== Посимвольный вывод строки функцией DOS 2 ====*/ putstringdos2() { for (s=msg[4]; *s; s++) { rr.h.ah=2; /* функция 2 */ rr.h.dl=*s; /* символ */ intdos(&rr,&rr); } /* перевычисление координат, курсор сдвигается сам */ xc+=40; yc+=xc/80; xc=xc%80; } /*==== Посимвольный вывод строки функцией DOS 6 ====*/ putstringdos6() { for (s=msg[5]; *s; s++) { rr.h.ah=6; /* функция 6 */ rr.h.dl=*s; /* символ */ intdos(&rr,&rr); } /* перевычисление координат, курсор сдвигается сам */ xc+=40; yc+=xc/80; xc=xc%80; } /*==== Вывод строки функцией DOS 9 ====*/ putstringdos9() { rr.h.ah=9; /* функция 9 */ rr.x.dx=FP_OFF(msg[6]); /* адрес строки */ sr.ds=FP_SEG(msg[6]); /* символ */ intdosx(&rr,&rr,&sr); /* перевычисление координат, курсор сдвигается сам */ xc+=40; yc+=xc/80; xc=xc%80; } /*==== Установка курсора ====*/ setcurpos(byte x,byte y) { rr.h.ah=2; rr.h.bh=0; rr.h.dh=y; rr.h.dl=x; int86(0x10,&rr,&rr); } В BIOS имеются средства, позволяющие осуществлять быстрый сдвиг изображения по вертикали: функции 6 (сдвиг окна вверх) и 7 (сдвиг вниз). В регистрах CH и CL задаются соответственно y- и x-координаты левого верхнего угла сдвигаемого окна, в DH и DL - правого нижнего угла; в AL - число строк сдвига (при AL=0 функция 6 очищает окно); а в BH - атрибут, которым заполняются освобождающиеся строки. Во второй части примера 9.6 при сдвиге вверх самая верхняя строка посимвольно копируется под самую нижнюю, а затем все окно, начиная со 2-й строки сдвигается; при сдвиге вверх - наоборот. При сдвигах вправо и влево каждая строка изображения посимвольно (функции 8 и 9) переносится на новое место. При выполнении этой программы хорошо заметно, что эти сдвиги выполняются гораздо медленнее, чем сдвиги по вертикали, лучше для этих целей использовать прямое копирование областей видеопамяти. Пример 8 /*== ПРИМЕР 10.5 ==*/ /*============= Чтение корневого оглавления ==============*/ #include <alloc.h> #include <dos.h> #define byte unsigned char #define word unsigned int #define dword unsigned long /* Структура, описывающая элемент оглавления */ struct Dir_Item { char fname[11]; /* имя файла */ byte attr; /* атрибут */ byte reserved[10]; word time; /* время */ word date; /* дата */ word cl; /* номер 1-го кластера */ dword size; /* размер файла */ } *dir; /* Структура корневой записи */ struct RootRec { byte jmp[3], ident[8]; word SectSize; byte ClustSize; word ResSect; byte FatCnt; word RootSize, TotSecs; byte Media; word FatSize, TrkSecs, HeadCnt, HidnSecL, HidnSecH; dword LongTotSecs; byte Drive; byte reserved1; byte DOS4_flag; dword VolNum; char VolLabel[11], FatForm[8]; } *rt; /* Структура параметров для INT 25 */ struct{ dword first_sect; word count; byte *ptr; } parm; union REGS rr; struct SREGS sr; char *attrs[]={"ТОЛЬКО_ЧТЕНИЕ","СКРЫТЫЙ_ФАЙЛ", "СИСТЕМНЫЙ_ФАЙЛ","МЕТКА_ТОМА","ПОДКАТАЛОГ"}; main() { byte *buff; /* адрес буфера в ОП */ byte sys, /* признак диска > 32 Мбайт */ drive; /* идентификатор диска */ int i, j, k, m; byte ms; /* маска атрибутов */ word RootSect, /* номер сектора */ RootCnt, /* число элементов */ RootSects; /* число секторов */ ASK1: printf("\nУкажите идентификатор диска (A,B...) >"); drive=getche(); if (drive>'b') { ASK2:printf("\nОбьем лог. диска больше 32 Мбайт? (y/n) >"); switch(sys=getche()) { case 'y' : sys=1; break; case 'n' : sys=0; break; default: goto ASK2; } } else sys=0; buff=(byte *)malloc(512); /* Чтение boot-сектора */ rr.h.al=drive-'a'; /* Диск */ if (!sys) { rr.x.cx=1; rr.x.dx=0; sr.ds=FP_SEG(buff); rr.x.bx=FP_OFF(buff); } else { parm.first_sect=0; parm.count=1; parm.ptr=buff; sr.ds=FP_SEG(&parm); rr.x.bx=FP_OFF(&parm); rr.x.cx=0xffff; } int86x(0x25,&rr,&rr,&sr); readerror(); rt=(struct RootRec *)buff; /* Параметры корневого каталога */ RootCnt=rt->RootSize; RootSect=rt->ResSect+rt->FatSize*rt->FatCnt; RootSects=rt->RootSize/16; /* Выделение памяти под корневой каталог */ buff=(byte *)realloc(buff,RootSects*512); if (buff==NULL) { printf("Нехватка памяти\n"); exit(); } dir=(struct Dir_Item *)buff; clrscr(); /* Чтение каталога */ rr.h.al=drive-'a'; if (!sys) { /* маленький диск */ rr.x.cx=RootSects; rr.x.dx=RootSect; sr.ds=FP_SEG(buff); rr.x.bx=FP_OFF(buff); } else { /* большой диск */ parm.first_sect=RootSect; parm.count=RootSects; parm.ptr=buff; sr.ds=FP_SEG(&parm); rr.x.bx=FP_OFF(&parm); rr.x.cx=0xffff; } int86x(0x25,&rr,&rr,&sr); readerror(); /* Распечатка оглавления */ for (j=0, k=0; j<RootCnt; j++) { printf("#%3d ",j); if (dir[j].fname[0]!=0) { /* Если элемент оглавления непустой */ for (i=0; i<11; i++) { if (i==8) printf("."); if ((i==0)&&(dir[j].fname[i]==0xe5)) printf("?"); else printf("%c",dir[j].fname[i]); } if (dir[j].fname[0]==0xE5) printf(" ******УДАЛЕН****** "); printf(" атрибут=%02x (",dir[j].attr); if (!(dir[j].attr&0x01f)) printf("ЧТЕНИЕ/ЗАПИСЬ"); else for(ms=1, m=0; m<5; m++,ms<<=1) if (dir[j].attr&ms) printf("%s ",attrs[m]); printf(")\n "); printf(" время=%02d/%02d/%02d", (dir[j].time>>11)&0x001f,(dir[j].time>>5)&0x003f, (dir[j].time&0x001f)*2); printf(" дата=%02d/%02d/%04d", ((dir[j].date>>9)&0x007f)+1980, (dir[j].date>>5)&0x000f, dir[j].date&0x001f); printf(" нач.кластер=%03x",dir[j].cl); printf(" размер=%lu\n",dir[j].size); } else printf(" свободен\n"); if (++k>7) { k=0; if (getch()==27) goto EOJ; } } EOJ:free(buff); } /* Проверка ошибок чтения */ readerror() { if (rr.x.cflag) { printf("\nОшибка чтения: %x. ",rr.h.ah); printf("Нажмите любую клавишу...\n\7"); getch(); exit(); } } Пример 8 struct CDS { char path[67]; /* ASCIIZ-строка, содержащая полный путь к текущему каталогу */ word flags; /* слово признаков, позволяющее определить, связан ли данный идентификатор с физическим диском или введен командой SUBST */ void *dpb; /* адрес DPB для этого диска */ word dir_clust; /* номер 1-го кластер текущ.каталога */ byte reserved[6]; }; Для DOS 4.x размер последнего поля reserved - 13 байт. Для DOS 5.0 и выше структура CDS иная: которые возвращают сегментную часть адресаПример 9 unsigned int FP_SEG(void far *ptr); unsigned int FP_OFF(void far *ptr); которые возвращают сегментную часть адреса и смещение соответственно. Обратное преобразование производится макросом: Пример 9 /*= ПРИМЕР 3.3 =*/ /*======== Маскирование аппаратных прерываний ============*/ #include <dos.h> #include <time.h> main() { unsigned char mask; /* Исходная маска прерываний */ int i; /* Индикация исходного состояния */ for (i=0; i Из текста программы видно, что порт 0x21 доступен и для записи, и для чтения. Таким образом, для корректного запрета выбранного прерывания необходимо прочитать текущее состояние маски прерываний, и занести в него запрещающую единицу при помощи операции "логическое ИЛИ". Для AT разряд 2 используется для каскадирования второго контроллера прерываний. Доступ к маске второго контроллера - через порт 0xA1. Назначения разрядов второй маски следующие:
Пример 9 /*== ПРИМЕР 7.9 ==*/ /*========= Пользовательская обработка Ctrl+Break =======*/ /* ВНИМАНИЕ! Для проверки реакции на Ctrl+ Break программу следует запускать вне Турбо-среды */ #include <dos.h> void interrupt (*old23)(); /* Адрес старого обработчика */ void interrupt new23(); /* Описание нового обработчика */ unsigned char col; /* Цвет вывода */ union REGS rr; struct SREGS sr; void *readvect(int in); void writevect(int in, void *h); void main() { union REGS rr; int i,k,o,m; col=0x21; /* Исходный цвет */ /* Включение Ctrl+Break */ rr.h.ah=0x33; rr.h.al=1; /* Подфункция установка Ctrl+Break */ rr.h.dl=1; /* ON */ intdos(&rr,&rr); /* Перехват вектора */ old23=readvect(0x23); writevect(0x23,new23); for (o=0,i=0,k=0; i Восстановление вектора 0x23, произведенное нами в конце программы - действие необязательное, так как при завершении программы система сама восстанавливает тот вектор этого прерывания, который был установлен до ее запуска. Пример 9 /*== ПРИМЕР 9.7 ==*/ /*===== Управление выводом средствами драйвера ANSI ======*/ #include <dos.h> #define byte unsigned char #define Esc 27 #define Up 'A' #define Down 'B' #define Left 'D' #define Right 'C' byte xc, yc, y0; /* координаты курсора */ byte dx,dy; /* приращения координат */ byte mode; /* видеорежим */ byte attr[2]; /* цвета текста [0] и фона [1] */ int i,j; main() { y0=1; message("Очистка экрана"); getch(); attr[0]=37; attr[1]=40; set_colors(); clear_scr(); attr[0]=30; attr[1]=42; set_colors(); message("Установка цвета"); message("Запоминание позиции курсора"); message("Позиционирование курсора"); getch(); xc=40; yc=12; dx=dy=1; set_cur_pos(xc,yc); save_cur(); for(i=0; i Пример 9 /*== ПРИМЕР 10.6 ==*/ /*======== Поиск и чтение файла средствами BIOS ========*/ #include <dos.h> #include <string.h> #include <stdlib.h> #include <ctype.h> /*== типы и структуры данных ==*/ #define byte unsigned char #define word unsigned int #define dword unsigned long #define daddr struct DADDR struct DADDR { /* физический дисковый адрес */ byte h; /* головка */ word s, /* сектор */ t, /* дорожка */ ts; /* сектор, дорожка упакованные */ }; struct PART { /* структура элемента раздела */ byte Boot, Begin_Hd; word Begin_SecTrk; byte SysCode, End_Hd; word End_SecTrk; dword RelSec, Size; }; struct MBR { /* стpуктуpа главной загpузочной записи */ char LoadCode[0x1be]; struct PART rt[4]; word EndFlag; }; struct BootRec { /* структура корневой записи */ byte jmp[3], ident[8]; word SectSize; byte ClustSize; word ResSect; byte FatCnt; word RootSize ,TotSecs; byte Media; word FatSize, TrkSecs, HeadCnt; word HidnSecL, HidnSecH; dword LongTotSecs; byte Drive, reserved1, DOS4_flag; dword VolNum; char VolLabel[11], FatForm[8]; }; struct Dir_Item { /* структура элемента оглавления */ char fname[11]; byte attr; char reserved[10]; word time, date, cl; dword size; }; /*== описания функций ==*/ void Read_Mbr(void); void Read_Boot(void); void Get_First(void); void Read_Fat(void); void Read_13(void *mem); void Sect_to_Daddr(dword sect); dword Clust_to_Sect(word clust); word Next_Clust(word clust); char *Get_Name(char *s, char *d); int Find_Name(); void End_of_Job(int n); /*== переменные ==*/ struct PART part; /* текущий элемент раздела */ byte buff1[512]; /* буфер MBR и boot */ struct MBR *mbr; /* указатель на таблицу разделов */ struct BootRec *boot; /* указатель на корневую запись */ byte buff2[512]; /* буфер каталога и текста */ struct Dir_Item *dir; /* указатель на часть каталога */ char *text; /* указатель на текстовый буфер */ byte *fat; /* указатель на FAT */ char job[81]; /* строка-задание */ char *jobptr; /* текущий указатель в job */ char cname[12]; /* текущее имя для поиска */ byte Fdisk; /* физический номер диска */ daddr caddr; /* текуший дисковый адрес */ dword sect; /* текуший номер сектора */ word clust; /* текуший номер кластера */ byte fat16; /* признак формата FAT */ dword fsize; /* размер файла */ int dirnum; /* номер элемента в оглавлении */ dword FirstSect; /* абс.сектор начала */ byte rootdir=1; /* признак корневого каталога или подкаталога (1/0) */ word lastsect; /* последний сектор при чтении */ byte fatalloc=0; /* признак выделения памяти */ /*-------------------------------------------------*/ main() { int n; /* ввод имени файла */ printf("Укажите имя файла >"); scanf("%s",job); /* перевод в верхний регистр */ strupr(job); /* проверка правильности идентификатора диска */ if ((!isalpha(job[0]))(job[1]!=':')(job[2]!='\\')) { printf("%c%c%c -",job[0],job[1],job[2]); End_of_Job(0); } jobptr=job+3; if (job[0]>'A') { /* для жесткого диска - физический номер и чтение MBR */ Fdisk=0x80; Read_Mbr(); } else /* для гибкого диска - физический номер */ Fdisk=job[0]-'A'; Read_Boot(); /* чтение boot-сектора */ Read_Fat(); /* чтение FAT */ dir=(struct Dir_Item *)buff2; do { /* движение по каталогам */ if (!rootdir) clust=dir[dirnum].cl; /* начальный кластер */ /* выделение следующего элемента из строки-задания */ jobptr=Get_Name(jobptr,cname); do { /* пока не дойдем до последнего кластера */ if (rootdir) { /* корневой каталог */ /* нач.сектор корневого огл. Пример 9 struct CDS { char path[67]; /* см.выше */ word flags; /* см.выше */ byte drive; /* номер DPB, для этого диска */ byte reserved1; word par_clust; /* номер 1-го кластера родительского каталога */ word par_entry; /* номер элемента в родительском каталоге */ word dir_clust; /* см.выше */ byte reserved2[4]; }; Массив Текущих Каталогов представляет собой именно массив, а не список - его элементы располагаются в смежных областях памяти один за другим. В программе следующего примера, выводящей на экран содержимое Массива Текущих Каталогов, для независимости от версии DOS в описание структуры CDS включено объединение VAR, описывающее различие между версиями. unsigned int segm, unsigned intПример 10 void far *MK_FP( unsigned int segm, unsigned int offs); Следует учитывать, что внутреннее представление указателей зависит от модели памяти, в которой транслируется программа или от явного описания. Указатель может быть либо ближним (явное описание: near) и представляться двухбайтным смещением в текущем сегменте, либо дальним (явное описание: far) и иметь четырехбайтное представление - сегментная часть адреса и смещение. Указанные макросы работают только с дальними указателями. Программные примеры данного пособия компилируются в модели памяти large (если в тексте программы не указана другая модель). По умолчанию в этой модели все указатели являются far. При компиляции в той модели памяти, где указатель по умолчанию near необходимо включить в описания всех указателей явные описатели far. Пример 10 /*== ПРИМЕР 7.10 ==*/ /*= Использование Esc-последовательностей драйвера ANSI =*/ main() { char string[81]; /* 1). Клавише с ASCII-кодом 97 ( буква a) назначается целая строка "клавиша a" */ printf("\33[97;\"клавиша a\"p"); /* 2). Клавише с ASCII-кодом 98 (буква b) назначается код 66 (буква B) */ printf("\33[98;66p"); /* 3). Клавише с ASCII-кодом 99 (буква c) назначается последовательность кодов 65,66,67 (буквы ABC) */ printf("\33[99;65;66;67p"); /* 4). Клавише с ASCII-кодом 100 (буква c) назначается строка "перевод строки" и еще символ с кодом 10 */ printf("\33[100;\"перевод строки\";10p"); /* 5). Клавише с расширенным ASCII-кодом 0,59 (F1) назначается строка "клавиша F1" 6). Клавише с расширенным ASCII-кодом 0,60 (F2) назначается код 98 (буква b) - при обработке этого назначения не будет учитываться ранее сделанное нзначение для кода 98 */ printf("\33[0;59;\"клавиша F1\"p\33[0;60;98p"); /* При вводе этой строки убедимс, что назначение сработало */ gets(string); /* Восстановление кодов */ printf("\33[97;97p\33[98;98p\33[99;99p"); printf("\33[100;100p\33[0;59;0;59p\33[0;60;0;60p"); /* При вводе этой строки убедимся, что восстановление произошло */ gets(string); } Пример 10 /*== ПРИМЕР 9.8 ==*/ /*======= Формирование пользовательского шрифта ======*/ #include <dos.h> unsigned char font[56] = { /* пользовательский шрифт 8x14 */ /* буква T */ 0x00,0x00,0x3c,0x18,0x18,0x18,0x18,0x18,0x5a, 0x7e,0x7e,0x00,0x00,0x00, /* буква U */ 0x00,0x00,0x7c,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6, 0xc6,0xc6,0x00,0x00,0x00, /* буква V */ 0x00,0x00,0x10,0x38,0x6c,0xc6,0xc6,0xc6,0xc6, 0xc6,0xc6,0x00,0x00,0x00, /* буква W */ 0x00,0x00,0x6c,0x7c,0xd6,0xd6,0xc6,0xc6,0xc6, 0xc6,0xc6,0x00,0x00,0x00 }; unsigned save_es, save_bp; /* для сохр.адреса */ unsigned reg[5]; /* для сохранения регистров */ main() { int i; clrscr(); for ( i = 'A'; i Подфункции 1 и 2 могут использоваться для восстановления исходных обpазов в знакогенеpатоpе, пpи их выполнении из ПЗУ адаптеpа загpужаются таблицы символов 8 x 14 (подфункция 1) или 8 x 8 (подфункция 2). В нашей пpогpамме пpи нажатии клавиши загpуженный нами шpифт заменяется шpифтом из ПЗУ. Однако, может оказаться, что пpи этом будет испоpчена втоpая стpока вывода - символы киpиллицы. Действительно, обpазы букв киpиллицы необязательно пpисутствуют в ПЗУ адаптеpа заpубежного пpоизводства. Дpайвеp-pусификатоp создает свою таблицу обpазов, котоpую он загpужает в знакогенеpатоp, используя те же сpедства, что и мы (подфункцию 0). Для того, чтобы коppектно восстановить знакогенеpатоp, пpогpамма должна пpежде чем загpужать свой шpифт узнать и запомнить адpес той таблицы, котоpая загpужена в знакогенеpатоp в настоящий момент, что наша пpогpамма и делает пpи помощи подфункции 0x30. Пpи окончании pаботы с оpигинальным шpифтом пpогpамма загpужает в знакогенеpатоp таблицу, адpес котоpой она запомнила pанее. Пример 10 struct FCB { byte drive; /* Логич. номер диска: 0-текущий, 1 - A, 2 - B и т.д. */ char fname[8]; /* Имя файла (дополн.пробелами) */ char fext[3]; /* Расширение файла (доп.пробелами) */ word curblk; /* Текущий номер блока (в блоке 128 записей) */ word recsize; /* Размер записи (байт) */ dword fsize; /* Размер файла (байт) */ word date; /* Дата создания (в формате элемента каталога) */ word time; /* Время создания (в формате элемента каталога) */ char reserved1[8]; byte currec; /* Текущий номер записи в блоке */ dword randrec; /* Относительный номер записи */ }; Во всех операциях с файлом идентификатором файла служит адрес FCB. Программист перед открытием файла должен заполнить поля drive, fname и fext, остальные поля заполняются при открытии файла системой и поддерживаются DOS при работе с файлом. Если при создании файла необходимо задать его атрибуты в каталоге, FCB должен иметь расширенный формат, а именно: Пример 10 /*== ПРИМЕР 13.3 ==*/ /*======= Распечатка массива текущих каталогов ==========*/ #include <dos.h> #include <stdlib.h> #define byte unsigned char # define word unsigned int struct CDS { /* структура текущего каталога */ char path[67]; word flags; union VAR { struct { /* для DOS 3.x, 4.x */ void *ddcb; word dir_clust; byte reserved[11]; } dos4; struct { /* для DOS 5.x и выше */ byte drive reserved1; word par_clust, par_entry,dir_clust; byte reserved2[4]; } dos5; } dosx; } *cds; word a_seg, a_off; /* нач.адрес массива */ byte lastdrive; /* LASTDRIVE из CONFIG */ byte ndrive; /* счетчик */ word units; /* число блочных устр-в */ byte dos; /* версия DOS */ int cds_size; /* размер CDS */ union REGS rr; struct SREGS sr; main() { /* номер версии DOS */ rr.h.ah=0x30; intdos(&rr,&rr); dos=rr.h.al; if (dos!=4) cds_size=81; else cds_size=88; /* адрес CVT */ rr.h.ah=0x52; intdosx(&rr,&rr,&sr); printf("\nМассив текущих каталогов\n"); /* выборка информации из CVT */ a_off=peek(sr.es,rr.x.bx+22); a_seg=peek(sr.es,rr.x.bx+24); cds=(struct CDS *)MK_FP(a_seg,a_off); lastdrive=peekb(sr.es,rr.x.bx+33); printf("LASTDRIVE - %u\n",lastdrive); for(ndrive=0; ndrive<lastdrive; ndrive++) { printf("Адрес CDS - %Fp\n",cds); printf(" path - %s\n",cds->path); if (dos<5) { printf(" адр.DPB - "); if (cds->dosx.dos4.ddcb==NULL) printf("пусто\n"); else { printf(" %Fp\n",cds->dosx.dos4.ddcb); printf(" флаги - %04X\n",cds->flags); printf(" 1-й кластер тек.каталога - %04X\n", cds->dosx.dos4.dir_clust); } } else { printf(" флаги - %04X\n",cds->flags); printf(" 1-й кластер тек.каталога - %04X\n",cds->dosx.dos5.dir_clust); printf(" лог.диск #%d\n",cds->dosx.dos5.drive); printf(" верхний каталог - кластер %04X,",cds->dosx.dos5.par_clust); printf(" вход - %d\n",cds->dosx.dos5.par_entry); } cds=(struct CDS *)((byte *)cds+cds_size); if (getch()==27) exit(0); } } в порт испольэуются функции:Пример 11 int inport(int port); unsigned char inportb(int port); Для записи данных в порт испольэуются функции: Пример 11 /*== ПРИМЕР 9.9 ==*/ /*================ Графические режимы EGA ================*/ #include <dos.h> #include <math.h> #define byte unsigned char #define word unsigned int /* коды режимов и размеры экранов */ byte graph_modes[]={ 13,14,16 }; word x_modes[]={ 320,640,640 }; word y_modes[]={ 200,200,350 }; int n; /* номер режима */ int x,y; /* текущие координаты */ byte xc, yc; /* координаты курсора */ char str[40], *s; char any[] = "Нажмите любую клавишу..."; byte k, c; int yy; union REGS rr; main() { for (n=0; n10) { c=4; k=1; } for(x=0; x<x_modes[n]; x++) point(x,y,c,0); c=0x0f; } /* рисование вертикальных линий на 0-й странице */ for(c=0x0f,k=x=0; x<x_modes[n]; x+=10) { if (++k>10) { c=4; k=1; } for (y=0; y<y_modes[n]; y++) /* чтобы не перечеркивать горизонталь */ if (getpoint(x,y)!=4) point(x,y,c,0); c=0x0f; } sprintf(str,"Графический режим %02Xh %dx%d", graph_modes[n],x_modes[n],y_modes[n]); /* вывод текста поверх сетки */ for (xc=5,yc=3,s=str; *s; s++) putca(xc++,yc,*s,0x02); /* вывод текста с совмещением цветов */ for (xc=5,yc=12,s=str; *s; s++) putca(xc++,yc,*s,0x82); /* рисование и закрашивание окружности на 1 странице */ for (x=-40; x Для читателя имеет смысл здесь веpнуться к pассмотpению пpогpаммы пpимеpа 7.10, в котоpой более сложное гpафическое изобpажение (символ дpевнекитайской натуpфилософии) фоpмиpовалось теми же сpедствами (функция getpoint оттуда пеpенесена в пpимеp 9.9 без изменений, в функции point добавлен паpаметp - номеp стpаницы). Выполнив пpогpаммы этих пpимеpов, читатель может убедиться в невысоком быстpодействии описанных гpафических сpедств. К сожалению, ничего более эффективного сpедствами BIOS осуществить нельзя. Пример 11 struct Ext_FCB { byte flag; /* Признак. Его значение 0xFF говорит о том, что представлен расширенный FCB */ char reserved2[5]; byte attr; /* Атрибуты файла в каталоге */ struct FCB fcb; /* Стандартный FCB */ }; На первый взгляд может показаться, что обрабатываемый файл должен состоять из записей фиксированной длины, но на самом деле это не так. Поле recsize после открытия файла имеет значение 128, но оно доступно программисту и может быть изменено в любой момент, что позволяет работать с записями переменной, в том числе и динамически изменяющейся длины. Метод FCB использует так называемую Дисковую Передаточную Область (DTA - Disk Transfer Area). При загрузке программы эта область выделяется программе системой, но программист может организовать собственную DTA и сообщить системе ее адрес. Весь обмен с дисками ведется только через DTA - назначенную системой или установленную программистом. Это, однако, не требует обязательной пересылки данных между DTA и рабочими областями программы, так как программист может в любой момент переназначить адрес DTA на свою рабочую область. 10.4.2. Функции ввода-вывода Оба метода предоставляют программисту полный набор функций DOS для ввода-вывода. Поскольку эти средства подробно описаны в справочной литературе, мы здесь ограничимся далеко не исчерпывающим их обзором. При использовании метода дескрипторов адрес ASCIIZ-строки, содержащей полный путь и имя файла, передается функциям DOS, выполняющим создание/открытие файла, в регистрах DS:DX, а в AL - код режима разделения. Эти функции возвращают дескриптор открытого файла в регистре AX. Тем функциям, которые работают с уже открытым файлом, файловый дескриптор передается в регистре BX. Для функций, выполняющих обмен с диском, адрес буфера в памяти задается в DS:DX, а в CX - счетчик байт для обмена, эти функции возвращают в AX число действительно переданных байт. Для всех функций флаг CY служит индикатором ошибки, если он взведен, то AX содержит код ошибки. В операциях чтения/записи обмен начинается с того байта файла, на который указывает текущее значение указателя чтения/записи (при открытии файла это значение устанавливается в 0), после каждой операции значение указателя увеличивается на число прочитанных/записанных байт. Пример 11 struct SFT { struct SFT *next; /* адрес заголовка следующей таблицы */ word n_files; /* число элементов в этой таблице */ }; Через поле next таблицы SFT увязываются в список. Сразу вслед за заголовком таблицы в памяти следует массив элементов таблицы, которыми являются Блоки Управления Файлами DOS (DFCB - DOS File Control Block, не путать с FCB!). Формат и размер блока DFCB различен для разных версий DOS. Для версии 3.x DFCB имеет размер 53 байта, формат следующий: и inportb возвращают прочитанное изПример 12 void outport(int port, int val); void outportb(int port, unsigned char val); Аргумент этих функций port - номер порта ввода-вывода. Функции inport и inportb возвращают прочитанное из порта значение, а в функциях outport и outportb записываемое в порт значение задается аргументом val. Функции inportb и outportb работают с однобайтными, а inport и outport - с двухбайтными портами. Программист должен точно знать, работает он с одно- или с двухбайтным портом. Применение функций inport, outport к однобайтным портам может привести к весьма неожиданным результатам. Так, например, оператор программы: Пример 12 struct FIND { byte reserved[21]; byte attr; /* атрибут */ word ftime; /* время */ word fdate; /* дата */ dword fsize; /* размер */ char fname[13]; /* имя.расширение, ASCIIZ-строка */ }; Функция 0x4E ищет первое подходящее имя (параметры передаются только ей), все последующие обращения к 0x4F (без параметров) выдают следующие подходящие имена; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
0x5C | - блокировать (AL=0) / разблокировать (AL=1) доступ к области, начинающейся со смещения (двойное слово в CX:DX) размером (двойное слово в SI:DI) файла, дескриптор которого задан в BX; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
0x47 | - получить текущий каталог на диске, заданном в DL, DS:SI указывают на буфер, в котором формируется ASCIIZ-строка текущего пути; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
0x3b | - изменить текущий каталог, DS:DX указывают на строку, содержащую имя подкаталога; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
0x39 | - создать новый подкаталог, DS:DX указывают на строку, содержащую имя подкаталога; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
0x3a | - удалить подкаталог, DS:DX указывают на строку, содержащую имя подкаталога; |
10.4.4. Функции работы с дисками
Следующие функции оперируют с логическими дисками.
0x1B | - информация о FAT текущего диска, на выходе DS:BX указывают на первый байт FAT, DX - число кластеров, AL - число секторов в кластере, CX - размер сектора; |
0x1C | - то же, но в DL задается логический номер требуемого диска; |
0x32 | - получить информацию о диске (диск задается в DL), на выходе DS:BX содержат адрес Блока Параметров Драйвера (DPB), который будет рассмотрен позже; |
0x36 | - получить информацию о дисковом пространстве (DL - номер диска), на выходе: AX - число секторов в кластере, BX - число доступных кластеров, CX - размер сектора, DX - общее число кластеров; |
0x19 | - получить номер текущего диска (в регистре AL); |
0x0E | - задать номер текущего диска (в регистре DL). |
10.4.5. Программные иллюстрации
Вместо того, чтобы приводить маленькие примеры, каждый из которых иллюстрирует применение какой-то одной функции, приведем "программное изделие" солидного размера, которое включает в себя почти все описанные нами функции метода дескрипторов - как ввода-вывода, так и работы с файлами и каталогами.
Пример 12
struct DFCB { word n_handles; /* число дескрипторов */ byte open_mode; /* режим открытия */ byte reserv1; byte attr; /* атрибуты файла */ word info; /* состояние устройства */ void *drv_ptr; /* адрес драйвера/DPB */ word First_clust; /* номер начального кластера */ word F_time, F_date; /* время и дата */ dword F_size; /* размер файла */ dword F_seek; /* текущее смещение в файле */ word lst_cl_n; /* относит.номер текущ.кластера */ word lst_clust; /* абс.номер текущ.кластера */ word dir_sect; /* номер сектора каталога */ byte dir_num; /* номер элемента в секторе */ char fname[11]; /* имя и расширение */ byte reserved2[6]; word owner; /* PID хозяина */ byte reserved4[2]; };
Для версии 4.x размер DFCB - 59 байт и формат следующий:
однобайтный порт) эквивалентен двум операторам:
Пример 13
outport(0x20,0x11);
(а 0x20 - однобайтный порт) эквивалентен двум операторам: outportb(0x20,0x11); outportb(0x21,0);
Пример 13
/*== ПРИМЕР 10.7 == файл 10_7.H ==*/ /*================= Работа с библиотекой ================ */ /* элемент оглавления библиотеки */ struct lib_dir { char memb_name[8]; /* имя раздела */ unsigned long memb_addr; /* адрес раздела (смещение в байтах от начала файла) */ unsigned memb_size; /* размер (число строк) */ }; /* параметры файла */ struct file_info { unsigned char attr; /* атрибуты */ unsigned date; /* дата */ unsigned time; /* время */ unsigned long size; /* размер */ }; /*== ПРИМЕР 10.7 == файл 10_7.C ==*/ /*================= Работа с библиотекой ================ */ /* Главная программа и сервисные функции */ #include <stdio.h> #include <10_7.h> int memb_max; /* max число разделов */ char libname[80]; /* имя выбранной библиотеки */ char fmemb[9]; /* имя раздела */ int SIZE; /* размер эл-та оглавления */ /*==== main ====*/ main() { char opt; int flag;
init_l(); /* нач.установки */ for(flag=0; flag==0; ) { opt=getopt(); /* меню и выбор опции */ switch (opt) { /* выполнение опций */ case '0': getlib(); break; case '1': if (checkname()) creat_lib(); break; case '2': list_lib(); break; case '3': query_lib(); break; case '4': ren_lib(); break; case '5': del_lib(); break; case 'A': add_memb(); break; case 'B': repl_memb(); break; case 'C': find_out_memb(); break; case 'D': del_memb(); break; case 'E': ren_memb(); break; case 'F': look_membs(); break; case 27: flag++; break; default: printf("\7"); } } } /*==== начальные установки ====*/ init_l() { libname[0]=0; /* имя не выбрано */ SIZE=sizeof(struct lib_dir); /* размер эл-та оглавления */ } /*==== ввод имени библиотеки ====*/ getlib() { printf("\nИмя файла-библиотеки >"); scanf("%s",libname); strupr(libname); strcat(libname,".LBR"); } /*==== ввод имени раздела ====*/ getmemb() { char member[9]; int i; printf("\nИмя раздела >"); scanf("%s",member); for(i=0;i<8;fmemb[i++]=' '); for(i=0;(i<8)&&member[i];i++) fmemb[i]=member[i]; } /*==== проверка имени библиотеки ====*/ checkname() { if (libname[0]) return (1); prt("Не выбрано имя библиотеки"); return(0); } /*==== вывод на экран меню и ввод кода операции ====*/ int getopt() { char f; clrscr(); printf("===== Операции с библиотекой =====\n"); printf("0 - выбор имени библиотеки\n"); printf("1 - создание библиотеки\n"); printf("2 - список .LBX-файлов\n"); printf("3 - информация о выбранном файле\n"); printf("4 - переименование библиотеки\n"); printf("5 - уничтожение библиотеки\n"); printf("====== Операции с разделами ======\n"); printf("A - добавление раздела\n"); printf("B - замена раздела\n"); printf("C - поиск раздела\n"); printf("D - удаление раздела\n"); printf("E - переименование раздела\n"); printf("F - оглавление библиотеки\n"); printf("\nEsc - конец работы\n"); if (libname[0]) { gotoxy(45,1); printf("%s",libname); } gotoxy(1,18); f=getche(); printf("\n"); f=toupper(f); return(f); } /*==== пауза ====*/ pressany() { printf("\nНажмите любую клавишу...\n"); getch(); } /*==== печать элемента оглавления ====*/ print_head(struct lib_dir *a) { int i; printf("Раздел "); for(i=0;i<8;printf("%c",a->memb_name[i++])); printf(" размер - %d ",a->memb_size); printf("адрес - %lxH\n",a->memb_addr); } /*==== печать сообщения с паузой ====*/ prt(char *s) { printf("%s",s); pressany(); } /*==== вывод информации о файле ====*/ prt_info(struct file_info *a) { if (a->attr!=0xff) printf("Атрибут - %02Xh\n",a->attr); printf("Дата - %04d/%02d/%02d\n", ((a->date>>9)&0x007f)+1980,(a->date>>5)&0x000f, a->date&0x001f); printf("Время - %02d/%02d/%02d\n", (a->time>>11)&0x001f,(a->time>>5)&0x003f, (a->time&0x001f)*2); printf("Размер - %ld\n",a->size); printf("Мест в оглавлении - %d\n",memb_max); pressany(); } /*== ПРИМЕР 10.7 == файл 10_7_A.C ==*/ /*================= Работа с библиотекой =================*/ /* Организация операций над библиотекой */ #include <stdlib.h> #include <10_7.h> unsigned long fptr; /* текущая позиция в файле */ extern char fmemb[9]; /* имя раздела */ extern char libname[80]; /* имя файла-библиотеки */ extern int memb_max; /* число мест в оглавлении */ static int memb_num; /* номер эл-та оглавления */ static char strbuf[200]; /* буфер строки */ static struct lib_dir a; /* буфер эл-та оглавления */
Пример 13
struct DFCB { word n_handles; /* число дескрипторов */ byte open_mode; /* режим открытия */ byte reserved; byte attr; /* атрибуты файла */ word info; /* состояние устройства */ char *drv_ptr; /* адрес драйвера/DPB */ word First_clust; /* номер начального кластера */ word F_time, F_date; /* время и дата */ dword F_size; /* размер файла */ dword F_seek; /* текущее смещение в файле */ word lst_cl_n; /* относит.номер текущ.кластера */ word dir_sect; /* номер сектора каталога */ byte reserved2[2]; byte dir_num; /* номер элемента в секторе */ char fname[11]; /* имя и расширение */ byte reserved3[6]; word owner; /* PID хозяина */ word lst_clust; /* абс.номер текущего кластера */ byte reserved4[6]; };
Рассмотрение полей DFCB упорядочим по их назначению, а не по порядку их расположения в памяти.
В поле fname содержится имя файла формата FCB, то есть имя дополняется до восьми, а расширение - до трех символов пробелами. Поле n_handles содержит число дескрипторов, связанных с этим файлом. Для закрытого файла (неиспользуемого DFCB) это число - 0. Поле info содержит информационное слово для файла, формируемое драйвером устройства. Интерес в этом слове для нас представляет разряд 7, являющийся индикатором того, дисковый это файл (0) или устройство (1), и разряды 0-5, которые для дисковых файлов содержат номер дисковода (0 - A, 1 - B и т.д.). Поле drv_ptr содержит адрес. Для дисковых файлов это - адрес DPB, а для файлов-устройств - адрес заголовка драйвера. Поле owner содержит PID (сегментный адрес PSP) программы, открывшей этот файл. Поле open_mode содержит режим доступа, заданный при открытии файла (1 в старшем разряде этого байта означает, что файл открыт методом FCB).
Следующая группа полей имеет смысл только для дисковых файлов и содержит данные из элемента оглавления, описывающего этот файл в каталоге. First_clust - первый кластер файла по FAT, F_time и F_date - время и дата последней модификации файла в формате каталога, F_size - размер файла в байтах, attr - байт атрибутов файла по каталогу.
Следующие поля определяют текущее состояние чтения/записи для дискового файла. F_seek - текущее смещение от начала файла в байтах, lst_cl_n - текущее смещение от начала файла в кластерах (относительный номер последнего считанного/записанного кластера), lst_clust - абсолютный номер на диске последнего считанного/записанного кластера (смещение этого поля различно для разных версий DOS).
Наконец, еще два поля используются для внесения изменений в элемент оглавления, описывающий этот файл: dir_sect - номер сектора, содержащего часть каталога, в котором файл описан, dir_num - номер элемента в этом секторе.
Программа следующего примера позволяет просмотреть таблицы DFCB. Поскольку длины блоков разные для разных версий DOS, в описании DFCB - struct DFCB - имеется первая часть, инвариантная для всех версий DOS, и вторая - union VAR - описывающая варианты структуры DFCB для DOS 3.x и 4.x.
static union REGS rr; static
Пример 14
/*== ПРИМЕР 10.7 == файл 10_7_F.C == */ /*================= Работа с библиотекой =================*/ /* Функции FCB-ориентированного ввода-вывода */ #include <dos.h> #include <10_7.h> static struct { /* FCB для библиотеки */ unsigned char drive; /* идентификатор диска */ char fname[11]; /* имя и расширение */ unsigned curblk; /* текущий блок */ unsigned recsize; /* размер записи */ unsigned long fsize; /* размер файла */ unsigned date; /* дата */ unsigned time; /* время */ char reserved[8]; unsigned char currec; /* текущий номер записи */ unsigned long randrec; /* относительный номер записи */ } lib; extern int SIZE; /* размер эл-та оглавления */ extern int memb_max; /* число мест в оглавлении */ extern char libname[80]; /* имя файла-библиотеки */ extern unsigned long fptr; /* текущая позиция в файле */ static struct lib_dir a; /* буфер эл-та */ static union REGS rr; static struct SREGS sr; static struct { /* формат структуры, возвращаемой при поиске по FCB */ char drive; char fname[8]; /* имя файла */ unsigned char attr; /* атрибут */ char v[10]; /* резерв */ unsigned time; /* время */ unsigned date; /* дата */ unsigned cl; /* номер 1-го кластера */ unsigned long sz; /* размер файла */ } find; /*==== формирование неоткрытого FCB ====*/ make_fcb(void *n, /* адрес ASCIIZ-строки */ void *f) /* адрес FCB */ { rr.h.ah=0x29; /* функция ParseFnam */ sr.ds=FP_SEG(n); /* адрес ASCIIZ-строки */ rr.x.si=FP_OFF(n); sr.es=FP_SEG(f); /* адрес FCB */ rr.x.di=FP_OFF(f); intdosx(&rr,&rr,&sr); iferror("преобразования имени"); } /*==== индикация ошибки ввода-вывода ====*/ iferror(char *s) { if (rr.h.al) { printf("Ошибка %s: %x\n",s,rr.h.al); exit(); } } /*==== создание библиотеки ====*/ creat_lib() { int f; make_fcb(libname,&lib); rr.h.ah=0x0f; /* функция OpenFCB */ sr.ds=FP_SEG(&lib); /* адрес FCB */ rr.x.dx=FP_OFF(&lib); intdosx(&rr,&rr,&sr); if (!rr.h.al) { close_lib(); printf("Файл %s уже существует\n",libname); do { printf("Создать файл заново (y/n)?"); if ((f=getche())=='n') { prt("\nБиблиотека не создана"); return; } } while (f!='y'); } rr.h.ah=0x16; /* функция CreatFCB */ sr.ds=FP_SEG(&lib); /* адрес FCB */ rr.x.dx=FP_OFF(&lib); intdosx(&rr,&rr,&sr); iferror("создания"); printf("\nЧисло разделов MAX >"); scanf("%d",&memb_max); form_lib(); /* формирование пустого оглавления */ close_lib(); prt("Библиотека создана"); } /*==== открытие файла ====*/ int open_lib() { make_fcb(libname,&lib); rr.h.ah=0x0f; /* функция OpenFCB */ sr.ds=FP_SEG(&lib); /* адрес FCB */ rr.x.dx=FP_OFF(&lib); intdosx(&rr,&rr,&sr); if (rr.h.al!=0) { printf("Файл не открыт\n"); return (0); } fptr=0; seek_dir(0); /* чтение 0-го элемента и */ read_dir(&a); /* установка memb_max */ memb_max=a.memb_size; return (1); } /*==== закрытие файла ====*/ close_lib() { rr.h.ah=0x10; /* функция CloseFCB */ sr.ds=FP_SEG(&lib); /* адрес FCB */ rr.x.dx=FP_OFF(&lib); intdosx(&rr,&rr,&sr); iferror("закрытия"); } /*==== запись элемента в оглавление ====*/ write_dir(void *a) { f_rdwr(0x15,"записи",SIZE,a); } /*==== чтение элемента из оглавления ====*/ read_dir(void *a) { f_rdwr(0x14,"чтения",SIZE,a); } /*==== запись в раздел строки текста ====*/ write_memb(char *s) { fb_rdwr(0x28,"записи",strlen(s)+1,s); } /*==== чтение из раздела строки текста ====*/ read_memb(char *s) { unsigned long p; p=fptr; fb_rdwr(0x27,"чтения",200,s); fptr=p+strlen(s)+1; } /*==== ввод/вывод записи ====*/ f_rdwr(unsigned char op,char *m,int s,void *a) { /* op - функция DOS, m - сообщение на случай ошибки, s - кол.байт; a - адрес */ /* назначение DTA */ rr.h.ah=0x1a; /* функция SetDTA */ sr.ds=FP_SEG(a); /* адрес данных */ rr.x.dx=FP_OFF(a); intdosx(&rr,&rr,&sr); rr.h.ah=op; /* ф-ция ReadSeqFCB(0x14)/WriteSeqFCB(0x15) */ lib.recsize=s; /* размер записи */ sr.ds=FP_SEG(&lib); /* адрес FCB */ rr.x.dx=FP_OFF(&lib); intdosx(&rr,&rr,&sr); iferror(m); } /*==== блочный вывод/вывод ====*/ fb_rdwr(unsigned char op,char *m,int s,void *a) { rr.h.ah=0x1a; /* функция SetDTA */ sr.ds=FP_SEG(a); rr.x.dx=FP_OFF(a); intdosx(&rr,&rr,&sr); rr.h.ah=op; /* ф-ция ReadRdmBlk(0x27)/WriteRdmBlk(0x28) */ lib.recsize=1; /* размер записи = 1 */ lib.randrec=fptr; /* номер байта */ rr.x.cx=s; /* размер блока */ sr.ds=FP_SEG(&lib); /* адрес FCB */ rr.x.dx=FP_OFF(&lib); intdosx(&rr,&rr,&sr); /* для чтения ситуация EOF не аварийная */ if (!((op==0x27)&&(rr.h.al==1))) iferror(m); fptr+=s; } /*=== позиционирование указателя на элемент оглавления ===*/ seek_dir(int n) { lib.curblk=n/128; lib.currec=n%128; lib.recsize=SIZE; rr.h.ah=0x24; /* функция SetRdmBlk */ sr.ds=FP_SEG(&lib); /* адрес FCB */ rr.x.dx=FP_OFF(&lib); iferror("позиционирования"); } /*==== установка указателя на конец файла ====*/ seek_eof() { lib.curblk=lib.fsize/128; lib.currec=lib.fsize%128; lib.recsize=1; rr.h.ah=0x24; /* функция SetRdmBlk */ sr.ds=FP_SEG(&lib); /* адрес FCB */ rr.x.dx=FP_OFF(&lib); fptr=lib.fsize; } /*==== установка указателя на начало раздела ====*/ seek_memb(unsigned long ptr) { lib.curblk=ptr/128; lib.currec=ptr%128; lib.recsize=1; rr.h.ah=0x24; /* функция SetRdmBlk */ sr.ds=FP_SEG(&lib); /* адрес FCB */ rr.x.dx=FP_OFF(&lib); iferror("позиционирования"); fptr=ptr; } /*==== поиск первого файла .LBR ====*/ int first_f(char *c) { char *s; int i; /* подготовка маски для поиска */ lib.drive=0; for(i=0;i<8;lib.fname[i++]='?'); lib.fname[i++]='L'; lib.fname[i++]='B'; lib.fname[i]='R'; rr.h.ah=0x1a; /* функция SetDTA */ sr.ds=FP_SEG(&find); /* адрес области результатов */ rr.x.dx=FP_OFF(&find); intdosx(&rr,&rr,&sr); rr.h.ah=0x11; /* функция Find1stFCB */ sr.ds=FP_SEG(&lib); /* адрес FCB */ rr.x.dx=FP_OFF(&lib); intdosx(&rr,&rr,&sr); if (rr.h.al) return(0); /* перевод результата в ASCIIZ-строку */ for(s=c,i=0; (i<8)&&(find.fname[i]!=' '); *(s++)=find.fname[i++]); *(s++)='.'; for(i=8; (i<11)&&(find.fname[i]!=' '); *(s++)=find.fname[i++]); *s=0; return(1); } /*==== поиск следующего файла .LBR ====*/ int next_f(char *c) { char *s; int i; rr.h.ah=0x12; /* функция FindNxtFCB */ sr.ds=FP_SEG(&lib); /* адрес FCB */ rr.x.dx=FP_OFF(&lib); intdosx(&rr,&rr,&sr); if (rr.h.al) return(0); for(s=c,i=0; (i<8)&&(find.fname[i]!=' '); *(s++)=find.fname[i++]); *(s++)='.'; for(i=8; (i<11)&&(find.fname[i]!=' '); *(s++)=find.fname[i++]); *s=0; return(1); } /*==== переименование файла ====*/ int ren_f(char *old, char *new) { make_fcb(old,&lib); /* формир.
Пример 14
/*== ПРИМЕР 13.4 ==*/ /*================ Системные таблицы файлов ==============*/ #define byte unsigned char #define word unsigned int #define dword unsigned long #define P(x) (dos>3)?dfcb->var.dos4.x:dfcb->var.dos3.x #include <dos.h> /* Заголовок таблицы файлов */ struct SFT { struct SFT *next; word n_files; } *sft; /* Элемент таблицы файлов */ struct DFCB { /* инвариантная часть */ word n_handles; byte open_mode, reserv1, attr; word info; char *drv_ptr; word First_clust, F_time, F_date; dword F_size, F_seek; word lst_cl_n; union VAR { struct { /* для DOS 3.x */ word lst_clust, dir_sect; byte dir_num; char fname[11]; byte reserved2[6]; word owner; byte reserved4[2]; } dos3; struct { /* для DOS 4.0 и выше */ word dir_sect; byte reserved2[2], dir_num; char fname[11]; byte reserved3[6]; word owner, lst_clust; byte reserved4[6]; } dos4; } var; } *dfcb; byte dos; /* номер версии DOS */ int dfcb_size; /* размер DFCB */ word sft_seg, sft_off; /* сегм.,смещ. начала */ int files; /* счетчик файлов */ byte file; /* признак файл/устройство */ union REGS rr; struct SREGS sr; word i, j, u; char *s; main() { /* получение номера версии DOS */ rr.h.ah=0x30; intdos(&rr,&rr); dos=rr.h.al; /* установка размера DFCB */ dfcb_size= (dos>3) ? 59 : 53; /* получение адреса системных блоков */ rr.h.ah=0x52; intdosx(&rr,&rr,&sr); /* получение адреса 1-й таблицы файлов */ sft_off=peek(sr.es,rr.x.bx+4); sft_seg=peek(sr.es,rr.x.bx+6); sft=(struct SFT *)MK_FP(sft_seg,sft_off); files=0; /* счетчик файлов */ while(FP_OFF(sft)!=0xffff) { /* смещение FFFF - признак конца списка */ printf("\n\STF по адресу - %Fp (%u файлов)\n", sft,sft->n_files); dfcb=(struct DFCB *)((byte *)(sft+1)); /* 1-й DFCB */ for (i=0; i<sft->n_files; i++) { /* перебор таблицы */ printf("\nФайл %d - ",files++); if (dfcb->n_handles) { /* файл открыт */ for (j=0;j<11;j++) printf("%c",P(fname[j])); if (file=!(dfcb->info&0x0080)) printf(" (дисковый)"); else printf(" (устройство)"); printf(" \"хозяин\" - %04X ",P(owner)); if (dos>3) { /* определяется для DOS-4 и выше */ s=(char *)MK_FP(P(owner)-1,8); for(j=0;j<8;printf("%c",s[j++])); } printf("\n дескрипторов - %u;",dfcb->n_handles); printf(" режим доступа - %02X (",dfcb->open_mode); switch (dfcb->open_mode) { case 0: printf("Только чтение)");break; case 1: printf("Только запись)");break; case 2: printf("Чтение/Запись)");break; } if (file) /* только для дисковых файлов */ printf(";\n DRIVE = %c:;", 'A'+(dfcb->info&0x007)); if (!file) { printf("\n драйвер "); for(s=dfcb->drv_ptr+10,j=0; j<8; printf("%c",s[j++])); } else printf(" адр.DPB"); printf(" - %Fp;",dfcb->drv_ptr); printf(" состояние устр-ва - %04X\n",dfcb->info); if (file) { /* только для дисковых файлов */ printf(" КАТАЛОГ: нач.сектор - "); /* выбирается в зависимости от версии DOS */ if (dos<4) printf("%04X",P(dir_sect)); else printf("%04X",P(lst_clust)); printf(", номер в секторе - %u\n",P(dir_num)); printf(" ЭЛЕМЕНТ КАТАЛОГА: атрибут - %02X, ", dfcb->attr); /* время и дата - упакованном формате */ printf("время - "); u=dfcb->F_time; printf("%02d:%02d:%02d, ", u>>11,(u>>5)&0x3f,(u&0x1f)<<1); printf("дата - "); u=dfcb->F_date; printf("%d:%02d:%02d\n", u&0x1f,(u>>5)&0x0f,(u>>9)+1980); printf(" размер - %lu", dfcb->F_size); printf(", нач.класт - %04X\n",dfcb->First_clust); printf (" ТЕКУЩЕЕ СОСТОЯНИЕ: последн.кластер - "); /* выбирается в зависимости от версии DOS */ if (dos<4) printf("%04X",P(lst_clust)); else printf("%04X",P(lst_clust)); printf(" (%d), ",dfcb->lst_cl_n); printf("смещение - %lu\n",dfcb->F_seek); } } else printf("не используется\n"); if(getch()==27) exit(); /* размер элемента зависит от версии DOS */ dfcb=(struct DFCB *)((byte *)dfcb+dfcb_size); } sft=sft->next; } }
Выполнение программы в том виде, в каком она приведена здесь, не даст сколько-нибудь интересных результатов: программа "покажет" только три постоянно открытых файла - AUX, CON, PRN. Для получения более наглядного представления о функционировании STF рекомендуем читателю оформить программу в виде функции и вставить многочисленные обращения к этой функции в какую-либо программу, выполняющую интенсивную работу с файлами (например, пример 10.7).
Для файлов, открытых при помощи метода FCB в режиме разделения, создается собственная таблица, адрес и размер которой содержатся в CVT. Размер элементов этой таблицы равен размеру DFCB, формат их практически совпадает с форматом DFCB.
union REGS rr; struct SREGS
Пример 15
/*== ПРИМЕР 10.8 ==*/ /*============== Перенаправление вывода =============*/ #include <dos.h> #include <stdio.h> union REGS rr; struct SREGS sr; main() { int userout; int sysout_main=1; int sysout_alt; char *mess[]= { "Перенаправление вывода", "Этот текст направляется, казалось бы, на экран,", "но попадет в файл SYSOUT.ALT", "Переназначение сохранит силу даже", "после закрытия файла SYSOUT.ALT", "Направление восстановлено", "Для вывода на экран можно пользоваться", "альтернативным дескриптором для sysout" }; clrscr(); /* создается новый файл */ userout=creat_file("SYSOUT.ALT"); write_file(sysout_main,mess[0]); /* создается альтернативный дескриптор для sysout */ sysout_alt=dup_handle(sysout_main); /* перенаправление из sysout в userout */ redirect(sysout_main,userout); write_file(sysout_main,mess[1]); write_file(sysout_main,mess[2]); /* этот вывод идет в sysout_alt */ write_file(sysout_alt,mess[6]); write_file(sysout_alt,mess[7]); close_file(userout); write_file(sysout_main,mess[3]); write_file(sysout_main,mess[4]); /* восстановл. дескриптора sysout из альтернативного */ redirect(sysout_main,sysout_alt); write_file(sysout_main,mess[5]); getch(); } /*==== получение альтернативного дескриптора ====*/ int dup_handle(int h) { rr.h.ah=0x45; /* функция DupHandle */ rr.x.bx=h; /* дескриптор */ intdos(&rr,&rr); iferror("дублирования"); return (rr.x.ax); } /*==== переназначение дескрипторов ====*/ redirect(int old, int new) { rr.h.ah=0x46; /* функция ReDir */ rr.x.bx=new; /* дескриптор */ rr.x.cx=old; intdos(&rr,&rr); iferror("перенаправления"); } /*==== индикация ошибки ввода-вывода ====*/ iferror(char *s) { if (rr.x.cflag) { printf("Ошибка %s: %x\n",s,rr.x.ax); exit(); } } /*==== создание ====*/ int creat_file(char *fname) { rr.h.ah=0x3c; rr.x.cx=0; sr.ds=FP_SEG(fname); rr.x.dx=FP_OFF(fname); intdosx(&rr,&rr,&sr); iferror("создания"); return(rr.x.ax); } /*==== закрытие файла ====*/ close_file(int h) { rr.h.ah=0x3e; rr.x.bx=h; intdos(&rr,&rr); iferror("закрытия"); } /*==== запись в файл строки текста с ВК, ПС ====*/ write_file(int h, char *s) { char a[3] = { 13, 10, 0 }; write_filex(h,s); /* запись строки */ write_filex(h,a); /* перевод строки */ } /*==== запись в файл ====*/ write_filex(int h,char *s) { rr.h.ah=0x40; rr.x.bx=h; rr.x.cx=strlen(s); sr.ds=FP_SEG(s); rr.x.dx=FP_OFF(s); intdosx(&rr,&rr,&sr); iferror("записи"); }
Наконец, пример 10.9 иллюстрирует применение дисковых функций DOS при получении информации о дисковом пространстве.
Пример 15
struct BCB { /* Управляющий блок буфера DOS 3.x */ struct BCB *next; /* адрес следующего */ byte drive; /* номер устройства */ byte flag; /* байт состояния буфера */ word sect; /* номер сектора */ byte copies /* число копий */ byte sect_off; /* смещение 2-й копии */ void *dpb; /* адрес DPB устройства */ byte reserved[2]; };
а для DOS 5.0 такая:
define word unsigned int union
Пример 16
/*== ПРИМЕР 10.9 ==*/ /*=========== Получение информации о диске ==========*/ #include <dos.h> #include <ctype.h> #define byte unsigned char # define word unsigned int union REGS rr; struct SREGS sr; /* полученная информация о диске */ struct di { byte error; /* признак ошибки */ byte media; /* тип носителя */ word tot_clust; /* общее число кластеров */ word free_clust; /* число свободных кластеров */ word clust_size; /* секторов в кластере */ word sect_size; /* байт в секторе */ } info[3]; byte cdrive; /* текущий диск */ byte drive; /* анализируемый диск */ int n; main() { do { printf("\nУкажите идентификатор диска >"); drive=getche(); } while (!isalpha(drive)); drive=toupper(drive)-'A'; /*== 1-й способ ==*/ n=0; /* получить номер текущего диска */ rr.h.ah=0x19; intdos(&rr,&rr); cdrive=rr.h.al; /* задать номер текущего диска */ rr.h.ah=0x0e; rr.h.dl=drive; intdos(&rr,&rr); /* проверить результат */ rr.h.ah=0x19; intdos(&rr,&rr); if (rr.h.al!=drive) { printf("\nНеправильный идентификатор диска\n"); exit(); } /* получение информации о FAT текущего диска */ rr.h.ah=0x1b; intdosx(&rr,&rr,&sr); info[n].media=peekb(sr.ds,rr.x.bx); info[n].tot_clust=rr.x.dx; info[n].clust_size=rr.h.al; info[n].sect_size=rr.x.cx; info[n].free_clust=0xffff; /* восстановить номер текущего диска */ rr.h.ah=0x0e; rr.h.dl=cdrive; intdos(&rr,&rr); /*== 2-й способ ==*/ n++; /* получение информации о FAT заданного диска */ rr.h.ah=0x1c; rr.h.dl=drive+1; intdosx(&rr,&rr,&sr); info[n].media=peekb(sr.ds,rr.x.bx); info[n].tot_clust=rr.x.dx; info[n].clust_size=rr.h.al; info[n].sect_size=rr.x.cx; info[n].free_clust=0xffff; /*== 3-й способ ==*/ n=0; n++; /* получить информацию о дисковом пространстве */ rr.h.ah=0x36; rr.h.dl=drive+1; intdosx(&rr,&rr,&sr); info[n].media=0; info[n].tot_clust=rr.x.dx; info[n].clust_size=rr.x.ax; info[n].sect_size=rr.x.cx; info[n].free_clust=rr.x.bx; /* вывод результатов */ clrscr(); printf("Информация о диске %c",drive+'A'); printf(" | INT 1B | INT 1C | INT 36 |\n"); printf("Тип носителя | "); for(n=0;n<3;n++) if (info[n].media) printf("%02x | ", info[n].media); else printf("-- |"); printf("\nВсего кластеров |"); for(n=0;n<3;printf(" %-6d |",info[n++].tot_clust)); printf("\nСвободных кластеров |"); for(n=0;n<3;n++) if (info[n].free_clust!=0xffff) printf(" %-6d |",info[n].free_clust); else printf(" -- |"); printf("\nСекторов в кластере |"); for(n=0;n<3;printf(" %-6d |",info[n++].clust_size)); printf("\nБайт в секторе |"); for(n=0;n<3;printf(" %-6d |",info[n++].sect_size)); getch(); }
Пример 16
struct BCB { /* BCB DOS 4.0 и далее */ word next; /* смещение следующего */ word prev; /* смещение предыдущего */ byte drive; byte flag; dword sect; byte copies; word sect_off; void *dpb; word count; /* счетчик обращений */ byte reserved[1]; };
Поля drive и sect включают логический номер диска и номер сектора этого диска, содержимое которого скопировано в данный буфер (в ни разу не использованных буферах drive=0xff), поле dpb - ссылку на DPB этого устройства. Поле состояния буфера - flag - будет нами рассмотрено ниже. Поле copies содержит число копий данного сектора на диске (оно отлично от 1 только для секторов, входящих в состав FAT), а поле sect_off - смещение на диске второй копии относительно первой (в секторах).
Все BCB связаны в список, но организация списка различна для разных версий DOS. В DOS 3 начальный адрес этого списка находится в CVT со смещением 0x12 от адреса, возвращаемого функцией 0x52. Каждый элемент списка в поле next содержит адрес (сегмент и смещение) следующего элемента. Признаком конца списка является значение 0xFFFF в смещении адреса следующего элемента.
В DOS 5 все буфера расположены в одном сегменте памяти. Поля next и prev в BCB связывают их в двухсвязный список. Эти поля содержат только смещения, так как сегментный адрес у всех буферов один и тот же. Для этой версии поле CVT со смещением 0x12 включает адрес области памяти, в которой хранится адрес начала списка, для выхода на первый элемент списка необходима двойная адресация. Список - кольцевой, то есть поле next его последнего элемента указывает на первый элемент, а поле prev первого - на последний.
Теперь об интерпретации разрядов байта состояния буфера. Наши эксперименты подтверждают следующие назначения его битов:
признак того, что буфер содержит
Пример 17
0 w r 0 a d f 0,
где f - признак того, что буфер содержит сектор FAT; d - признак принадлежности сектора каталогу (корневому или подкаталогу); a - признак принадлежности области данных; w - признак, устанавливаемый в 1, если в буфер записаны данные из программы, но не в полном объеме сектора; r - признак того, что данные, записанные в буфер, были полностью переданы в программу.
Те разряды, в которых мы при всех экспериментах получали нули, возможно, тоже содержат какие-либо признаки, нами не расшифрованные.
Программа следующего примера показывает как найти начало списка буферов и двигаться по этому списку.
byte drive, flag; union VAR
Пример 18
/*== ПРИМЕР 13.5 ==*/ /*======= Карта буферов дискового ввода/вывода DOS =======*/ #define byte unsigned char #define word unsigned int #define dword unsigned long #include <dos.h> #define V(x) (dos>3)?bcb->var.var5.x:bcb->var.var3.x struct BCB { /* Управляющий блок буфера */ union PTR { struct BCB *next3; struct { word next, prev; } next5; } ptr; byte drive, flag; union VAR { struct VAR3 { word sect; byte copies, sect_off; void *dpb; } var3; struct VAR5 { dword sect; byte copies; word sect_off; void *dpb; } var5; }var; } *bcb, *bcb0; word bbc_seg, bbc_off; /* адрес CVT */ word bseg, boff; /* адрес 1-го буфера */ int nbuf; /* счетчик */ byte dos; /* версия DOS */ union REGS rr; struct SREGS sr; main() { /* получение версии */ rr.h.ah=0x30; intdos(&rr,&rr); dos=rr.h.al; /* получение адреса CVT */ rr.h.ah=0x52; intdosx(&rr,&rr,&sr); bseg=sr.es; boff=rr.x.bx; printf("\nТаблица буферов\n"); printf("Pазмер буфера = %u\n",peek(bseg,boff+16)); /* адрес 1-го буфера */ bbc_off=peek(bseg,boff+0x12); bbc_seg=peek(bseg,boff+0x14); bcb=(struct BCB *)MK_FP(bbc_seg,bbc_off); if (dos>3) /* еще одна адресация */ bcb0=bcb=bcb->ptr.next3; for (nbuf=1;;nbuf++) { printf("\nБУФЕР #%-2d",nbuf); printf(" Адр.BCB=%Fp",bcb); if (dos>3) printf(", след.=%04X, пред.=%04X",bcb->ptr.next5.next,bcb->ptr.next5.prev); printf(", адр.DPB=%Fp\n",V(dpb)); printf(" диск=%d(%c),", bcb->drive,'A'+bcb->drive); printf(" сектор="); if (dos>3) printf("%lu",V(sect)); else printf("%u",V(sect)); printf(", копий=%d, смещение=%u\n",V(copies),V(sect_off)); printf(" флаг состояния=%02X\n",bcb->flag); if (getch()==27) exit(); /* переход к следующему BCB в списке */ if (dos>3) { /* старый сегмент : новое смещение */ bcb=(struct BCB *)MK_FP(FP_SEG(bcb),bcb->ptr.next5.next); /* выход из цикла, если кольцо замкнулось */ if (bcb==bcb0) break; } else { /* полный адрес выбирается из next */ bcb=bcb->ptr.next3; /* выход из цикла по признаку конца списка */ if (FP_OFF(bcb)==0xffff) break; } } }
Применение этой программы "в чистом виде", как и в предыдущем примере, не может дать интересных результатов. Гораздо интереснее оформить ее в виде функции и вызывать эту функцию из программы, работающей с файлом (или с файлами) после каждой операции чтения/записи, что мы и делали в процессе экспериментирования. Особенно показательными будут результаты ее, если включить в ее состав функцию, определяющую по номеру сектора имя файла, которому этот сектор принадлежит - все необходимые для этого средства читатель получил в главе 10, так что, надеемся, он справится с этой задачей самостоятельно.
Принтер
8. Принтер
Принтер является, пожалуй, самым "неудобным" периферийным устройством ПЭВМ. Это "неудобство" заключается в том, что IBM-совместимые машины комплектуются принтерами разных производителей, которые подчас отличаются друг от друга по управлению. В качестве стандарта для IBM-совместимых машин принят принтер Epson. Принтеры других фирм ориентированы на этот стандарт в той или иной степени. Во всяком случае, можно с уверенностью утверждать, что вывод символа на принтер всегда выполняется одинаково. Что же касается управления спецификациями печати, то здесь гарантировать точное совпадение нельзя, к сожалению, не гарантируется и одинаковая установка разрядов байта состояния. Мы в нашем описании и в наших программах ориентируемся на стандарт Epson.
в программе пользователя команды INT
Схема 1
Рисунок 1.1. Уровни программного доступа к средствам ПЭВМ Перед выполнением в программе пользователя команды INT 21H программист должен занести в регистр AH некоторый код - номер той программы из состава DOS, которую требуется выпол- нить. Остальные регистры, как и при обращении к BIOS, используются для передачи параметров. Принято говорить о "функции DOS номер NN", имея в виду обращение к прерыванию 21 с кодом NN в регистре AH. Некоторые функции созданы разработчиками системы "для внутреннего использования": они вызываются из других функций и программ DOS, их описания отсутствуют в документации фирм IBM и Microsoft и, видимо, не предполагалось обращение к ним из программ пользователя.
Несмотря на это, в некоторых источниках можно найти описание ряда недокументированных функций DOS. Для некоторых задач обращение к недокументированной функции DOS является полезным, а возможно, и единственным путем решения, но следует всегда иметь в виду, что одинаковая работа одной и той же недокументированной функции в разных версиях DOS не гарантируется.
Существуют различия между наборами функций, доступными в разных версиях DOS. Как правило, новая версия наследует все возможности предшествовавшей и расширяется дополнительными возможностями. Наиболее значительным нам представляется скачок между версиями 2 и 3. И дело тут даже не столько в существенном расширении возможностей, а в изменении самого подхода разработчиков: начиная с версии 3.0 в создании системы стали использоваться методы, применявшиеся для больших вычислительных систем. Поэтому далее в нашем пособии там, где упоминаются "старые" и "новые" ("современные") версии DOS, имеются в виду версии до 3.0 и от 3.0 включительно.
Наконец, языки высокого уровня включают в себя процедуры и функции, обеспечивающие еще более высокий уровень интеграции. Как правило, эти процедуры/функции имеют содержательные имена и использующий их программист не обязан знать, через какой уровень эти процедуры/функции воздействуют на аппаратные средства. Средства языков должны быть предметом отдельного рассмотрения, которое в наши задачи не входит.
Идеальным, конечно, является использование на каждом уровне только средств ближайшего снизу уровня, но идеал редко осуществим полностью, поэтому, в частности, программа пользователя имеет доступ ко всем уровням. Хотя в соответствии с целями нашего пособия смешения обращений на разные уровни встречаются здесь почти повсеместно, в практической работе автор является убежденным приверженцем идеала и горячо рекомендует его своим читателям. При решении прикладных задач каждое обращение к более низкому уровню должно быть непременно оправдано либо функциональной необходимостью, либо существенным выигрышем в эффективности. Такой подход, во-первых, существенно упрощает программирование и дает лучшую защиту от программистских ошибок, а во-вторых, лучше обеспечивает независимость программного изделия от конфигурации аппаратных средств и от версии DOS.
Схема 1
Рисунок 3.1. Совместная обработка прерывания несколькими программами <
Схема 1
Рисунок 4. 1 Распределение адресного пространства ПЭВМ
(все адреса шестнадцатиричные).
Адреса c 0000:0000 по 0000:03FF (1 Кбайт) занимает таблица векторов прерываний. Отметим, что векторы могут содержать адреса программ-обработчиков, адреса таблиц данных или быть свободными. По спецификациям DOS вектора с номерами от 0x60 по 0x67 свободны и могут заниматься программами пользователей. Однако, если пользователю необходимо использовать собственное прерывание, ему нельзя выбрать любой вектор из указанного диапазона без проверки. В этот момент в памяти ПЭВМ могут находиться какие-то резидентные программы (например, драйверы-русификаторы), которые могут использовать эти же вектора. Свободные векторы (а они могут найтись и вне специфицированной области) найти легко: эти векторы содержат нулевые адреса или константу NULL Турбо-Си.
Программа, приведенная ниже, выводит на экран таблицу векторов прерываний и определяет свободные векторы. Впоследствии мы укажем путь создания более совершенного варианта такой программы.
/*= ПРИМЕР 4.1. =*/ /*============= Анализ таблицы векторов =============*/ #include <dos.h> #include <stdlib.h> void *readvect(int in); main() { void far *vect; /* вектор */ int intnum; /* номер прерывания */ int line, i; for (clrscr(),intnum=line=0; intnum<l;256; intnum++) { for (i=0;i<17;i+=16) { vect=readvect(intnum+i); printf("INT %02Xh - %Fp - ",intnum+i,vect); if (vect==NULL) printf("%-16s","Свободен"); else printf("%-16s","Занят"); } if (i) { printf("\n"); if (++line>15) { intnum+=16; line=0; getch(); clrscr(); } } } } /*==== Получение вектора ====*/ void *readvect(int in) { union REGS rr; struct SREGS sr; rr.h.ah=0x35; rr.h.al=in; intdosx(&rr,&rr,&sr); return(MK_FP(sr.es,rr.x.bx)); }
Адреса с 0040:0000 по 0040:00FF (256 байт) называются областью памяти BIOS. В некоторых описаниях - областью памяти ROM BIOS. Приставка ROM (Read Only Memory, т.е. ПЗУ) говорит не о том, что эта память защищена от чтения, а о том, что данные в этой области используются программами, находящимися в ПЗУ.
Схема 1
Рисунок 13. 1 Связи между системными блоками DOS
На основании предложенной схемы можно попытаться строить гипотезы о выполнении системой запросов пользовательских программ на операции, например, ввода-вывода. Так, для чтения информации из файла пользовательская программа передает системе в составе запроса дескриптор обрабатываемого файла. Система при получении запроса выбирает PID активной в данный момент программы, являющийся адресом ее PSP. PSP программы содержит ссылку на ее JFT, полученный системой файловый дескриптор является номером элемента JFT. Элемент, выбранный по дескриптору из JFT, является входом в системную таблицу файлов, по которому выбирается DFCB требуемого файла. По-видимому, для символьных устройств система сразу же формирует запрос к драйверу, адрес которого содержится в DFCB. Для блочных устройств система по ссылке в DFCB выбирает DPB для блочного устройства. Используя данные о физических парамет- рах диска и системные структуры данных (FAT и каталоги, не- которые из которых, возможно, уже находятся в дисковых буфе- рах, а недостающие - считываются в них), система формирует основной параметр для обращения к драйверу блочного устройс- тва - номер сектора. Однако, прежде чем выдать драйверу зап- рос на ввод требуемого сектора система просматривает список BCB - для каких-то случаев информация может уже быть прочи- тана в дисковый буфер. Читатель сам может распространить подход, пример которого мы привели, на другие виды операций ввода-вывода.
Представляет интерес также анализ распределения памяти DOS, который можно провести, зная структуру связей между системными блоками. Анализ распределения памяти, выполняемый программой примера 12.1, во всех случаях показывает, что первый, значительный по размеру блок распределяемой памяти всегда принадлежит DOS. Что же содержится в этом блоке? Предоставим читателю самому сделать такую компиляцию из примеров этой главы, которая позволит проанализовать, какие системные структуры располагаются в ядре DOS, а какие - в распределенной DOS памяти, здесь же мы приводим (Рисунок 13.2) результат одного из выполнений такой программы. Цифры названий областей обозначают:
*1 | - назначение описано в источниках, восходящих к фирмам IBM и Microsoft; |
*2 | - назначение области определено экспериментально; |
*3 | - назначение предположительно; |
*4 | - назначение неизвестно. |
Схема 1 Рисунок 15. 1 Манипуляции со стеком при обращении к прежнему обработчику |
к началу процесса загрузки. Как
Схема 2
Рисунок 4.2 Область памяти "DOS и транзитные программы"
Прерывание BIOS 0x19 позволяет выполнить перезагрузку системы из программы. При этом перезагрузка начинается с действий, следующих за выполнением программы POST - начального загрузчика BIOS - и носит название "горячей" перезагрузки. Следующая программа, возможно, приведет к "горячей" перезагрузке системы:
Схема 2
Рисунок 13. 2 Карта памяти DOS. (IBM PC/XT, DOS 3.30)
Видеоадаптеры
9. Видеоадаптеры
Эта тема на лекциях вызывает живейший интерес слушателей, по-видимому, потому, что здесь имеется возможность приложив минимум усилий, получить эффективный и, главное, зримый результат. Вместе с тем автор предупреждает, что материал этой главы более далек от полноты, чем какой-либо другой. Это можно объяснить следующими причинами.
а) Для полного "раскрытия тайн" видеоадаптеров нам пришлось бы рассматривать их на уровне портов ввода-вывода, что не соответствует выбранному нами подходу и к тому же сделало бы нас существенно зависимыми от типа видеоадаптера. Мы намеренно рассматриваем только одно (почти универсальное) средство доступа нижнего уровня: прямую запись в видеопамять, в остальном ограничиваясь средствами BIOS и DOS.
б) Даже ограничиваясь уровнем BIOS, исчерпывающее рассмотрение средств управления видеоадаптерами всех типов погребло бы нас под лавиной информации справочного характера. Мы ограничиваемся только основными средствами, ориентированными на адаптер EGA, большинство из них применимо также для MDA и CGA, все они являются подмножеством средств управления VGA.
Внешняя память на магнитных дисках
10. Внешняя память на магнитных дисках
В этой главе мы полностью оставляем вне рассмотрения программирование контроллера дисков на уровне портов ввода-вывода как ввиду сложности этого уровня и принадлежности его скорее курсу "Периферийные устройства", так и потому, что экспериментирование на таком уровне может привести к выводу из строя аппаратуры. Предметом нашего рассмотрения станут средства чтения/записи BIOS и DOS, логическая структура дисков и средства, обеспечивающие работу с файловой подсистемой DOS. Отметим, что экспериментировать на этих уровнях также следует с осторожностью, так как ошибки программиста, хотя и не приведут к порче аппаратуры, но могут привести к потере информации на носителях (что и случалось у автора при отладке приводимых ниже программных примеров). Из этих соображений мы во всех примерах ограничиваемся операциями чтения, не затрагивая запись на диски.
приходится бежать со всех ног,
Деревянко. СПО ПЭВМ.
Введение
- Ну, а здесь, - сказала Королева, - приходится бежать со всех ног, чтобы только остаться на том же месте! Ну, а если хочешь попасть в другое место, тогда нужно бежать по крайней мере вдвое быстрее! | Л.Кэрролл. "Сквозь зеркало и что там увидела Алиса" |
Цитата, вынесенная в эпиграф, как нельзя лучше отражает ситуацию, в которую попадает преподаватель, ведущий курс "Системное программное обеспечение ЭВМ". Автору хотелось бы верить в то, что к моменту выхода этого пособия литература, содержащая исчерпывающую справочную информацию по компьтеру IBM PC и его программному обеспечению, будет легко доступна любому пользователю. Кроме того, автор не уверен в том, что его сегодняшние студенты, придя в ближайшем будущем на инженерные рабочие места, не встретят на них качественно новую технику и программное обеспечение. Какова же в таком случае цель нашего пособия? К сожалению, в нашем постоянном стремлении "бежать вдвое быстрее" мы подчас забываем о том, что на каждом этапе гонки за прогрессом все же остаются идеи, решения, структуры, алгоритмы, которые не обесцениваются, а либо наследуются следующим этапом, либо неожиданно для многих "всплывают" через несколько поколений. Поэтому главной нашей задачей являлось не заранее обреченное на поражение соперничество со справочниками, а стремление показать "как это сделано внутри" применительно к системному программному обеспечению.
Программы, тексты которых представляют, по-видимому, основную ценность данной работы, не предназначены для "промышленного" применения; не являются они также и "заготовками" для пользовательских программ, выполняющих некоторые системные действия (хотя не исключается и такое их применение); - они задумывались прежде всего как иллюстрации возможностей и модели системных программ , изучая которые мы получаем шанс лучше понять организацию и функционирование Системного Программного Обеспечения.
Предполагается, что читатель знаком с архитектурой ПЭВМ IBM PC (желательно, но не обязательно, и с языком Ассемблера) и с основами языка программирования Си в объеме, например, книги Кернигана и Ритчи. Мы полностью оставили без внимания "внешнюю" сторону MS DOS - команды, утилиты и т.п., потому что эти вопросы достаточно полно рассмотрены в общедоступной литературе (Брябрин, Фигурнов). Некоторые главы пособия непосредственно пересекаются с темами курса "Периферийные устройства ЭВМ", в таких случаях мы старались по возможности ограничиваться чисто программными вопросами. Программные примеры, представленные в этом пособии, разрабатывались и отлаживались на ПЭВМ класса XT в среде операционной системы MS DOS 3.30 и на ПЭВМ класса AT в среде MS DOS 5.0. Во всех случаях, когда могут возникнуть противоречия между описаниями и текстом программного примера, следует отдавать предпочтение тексту программы.
Об обозначениях. Поскольку в данном пособии используется в качестве инструмента язык Си, в тексте применяется Си-нотация шестнадцатеричных чисел (префикс 0x), хотя в выдачах приведенных программ можно встретить Ассемблерную нотацию (постфикс H).
Для сокращения записи во многих программных примерах вводятся макроопределения:
#define byte unsigned char #define word unsigned int #define dword unsigned long
Условимся, что эти макроопределения включены и для структур данных, описываемых в тексте.
В описаниях DOS последовательность символов кода ASCII, заканчивающуюся кодом 0 - признаком конца строки, именуется ASCIIZ-строкой. Везде в нашем тексте, где упоминаются "ASCIIZ-строка" или просто "строка" речь идет именно о таком представлении (если не оговорено другое).
В учебном пособии рассмотрены вопросы
16. Заключение
В учебном пособии рассмотрены вопросы внутренней организации системного программного обеспечения и доступа к ресурсам вычислительной системы IBM PC в операционной среде MS DOS на разных уровнях. Основное внимание в изложении уделялось описанию системных структур данных и алгоритмов их обработки. Описание позволяет не только получить достаточно полное представление об использовании системных средств на персональном компьютере рассмотренного типа, но и применить описанные методы в других аппаратных и программных средах.
В заключение автор считает своим долгом еще раз напомнить о разграничении уровней доступа к системным ресурсам и подчеркнуть необходимость органичиваться в прикладной работе ограничиваться по возможности только верхними уровнями доступа. В перспективных операционных средах (Windows, Unix, OS/2) принцип открытости системы соблюдается далеко не так полно, и нижние уровни доступа могут быть просто недоступны для пользователя. С другой стороны, необходимость обеспечения в новых системах преемственности облегчает возможность переноса программных продуктов, корректно использующих системные вызовы в новые операционные среды.
Литература
1. | Керниган Б., Ритчи Д. Язык программирования Си./пер. с англ. - М.: Финансы и статистика, 1992. - 271 с. |
2. | Абель П. Язык Ассемблера для IBM PC и программирования/пер.с англ. - М.: Высш.шк., 1992. - 447 с. |
3. | Брябрин В.М. Программное обеспечение персональных ЭВМ. - М.: Наука, 1989. - 271 с. |
4. | Фигурнов В.Э. IBM PC для пользователя. - М.: Финансы и статистика, 1991. - 283 с. |
5. | Смирнов Н.Н. Программные средства персональных ЭВМ. - Л.: Машиностроение, 1990. - 271 с. |
6. | Максимов Ю.Я., Осипов С.В., Симоненков О.С. Практическая работа на компьютерах семейства IBM PC в операционной среде MS-DOS 4.01: Учебное пособие. - М.: ДИАЛОГ-МИФИ, 1991. - 160 с. |
7. | Нортон П. Программно-аппаратная организация IBM PC./пер.с англ. - М.: Радио и связь, 1991. - 328 с. |
8. | Нортон П. Персональный компьютер IBM PC и операционная система MS DOS./пер.с англ. - М.: Радио и связь, 1992. - 416 с. |
9. | Джордейн Р. Справочник программиста персональных компьютеров IBM PC, XT и AT./пер.с англ. - М.: Финансы и статистика, 1992. - 544 с. |
10. | Нортон П., Джордейн Р. Работа с жестким диском IBM PC./пер.с англ. - М.: Мир, 1992. - 560 с. |
11. | Фролов А.В., Фролов Г.В. Аппаратное обеспечение IBM PC: ч.1. - М.: ДИАЛОГ-МИФИ, 1992. - 208 с. |
12. | Фролов А.В., Фролов Г.В. Аппаратное обеспечение IBM PC: ч.2. - М.: ДИАЛОГ-МИФИ, 1992. - 234 с. |
13. | Фролов А.В., Фролов Г.В. Операционная система MS DOS: кн.1,2. - М.: ДИАЛОГ-МИФИ, 1992. - 238 с. |
14. | Фролов А.В., Фролов Г.В. Операционная система MS DOS: кн.3. - М.: ДИАЛОГ-МИФИ, 1992. - 224 с. |
15. | Справочное руководство по IBM PC: ч.1: Методические материалы - М.: ТПП "Сфера", 1991. - 174 с. |
16. | Справочное руководство по IBM PC: ч.2: Аппаратные средства ПЭВМ - М.: ТПП "Сфера", 1991. - 302 с. |
17. | Язык Си для профессионалов/пер.с англ. - М.: И.В.К.-СОФТ, 1992. - 320 с. |
18. | Техника программирования на Турбо-Си./пер. с англ.- М.: И.В.К.-СОФТ, 1990. - 212 с. |
19. | Rollins D. Tech Help! database version 4.0a/The Electronic Technical Reference Manual. - Flambeaux Software, Inc., 1990. |