МИНИСТРЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РФ КАБАРДИНО-БАЛКАРСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ им. Х. М. БЕРБЕКОВА МЕТОДИЧЕСКИЕ УКАЗАНИЯ ПО ВЫПОЛНЕНИЮ ЛАБОРАТОРНЫХ РАБОТ К ДИСЦИПЛИНЕ «СИСТЕМНОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ» Для специальностей 230102 – Автоматизированные системы обработки информации и управления и 230105 – Программное обеспечение вычислительной техники и автоматизированные системы НАЛЬЧИК 2005 Содержание Введение ....................................................................................................................... 3 Лабораторная работа №1. Турбо отладчик (Turbo Debugger) ................................ 4 Лабораторная работа №2. Работа с функциями BIOS Доступ к регистрам и флагам через псевдопеременные ............................................................................. 10 Лабораторная работа №3. Работа с функциями BIOS Доступ к регистрам через функции и структуры ................................................................................................ 12 Лабораторная работа №4. Работа с функциями BIOS Запись символа в видеопамять напрямую, используя указатели far или near ................................... 16 Лабораторная работа №5. Работа с памятью. Выделение памяти посредством прерывания Int 21h DOS ........................................................................................... 18 Лабораторная работа №6. Работа с внешними устройствами, на примере видеоадаптера ............................................................................................................ 20 Лабораторная работа №7. Управление задачами и памятью в Windows ............ 23 Лабораторная работа №8. Управление вводом/выводом и файловые системы . 28 Лабораторная работа №9. Реализация функций API на уровне ОС .................... 33 Литература ................................................................................................................. 40 2 Введение Дисциплина «Системное программное обеспечение» относится к обязательным специальным дисциплинам учебного плана по направлению «Информатика и вычислительная техника». Согласно Государственному образовательному стандарту в данной дисциплине основное внимание уделяется операционным системам, средам и системам программирования. Именно в таком ключе и строится данное методическое пособие. Целью данного пособия является закрепление теоретического материала, изучение приемов составления программ, развитие навыков практической реализации системного программного обеспечения. Методическое пособие включает 9 лабораторных работ, каждая из которых посвящена конкретной теме, с описанием теоретической части, практическими примерами, заданиями и вопросами для самостоятельной работы. Методические указания ориентированы на выполнение программ в среде Borland C/C++ 3.1, Borland C++ Builder. 3 Лабораторная работа №1 Тема: «Турбо отладчик (Turbo Debugger)» (2 часа) Турбо-отладчик – программа, позволяющая вести отладку программ написанных на языках Си и ассемблере. В особенности турбо-отладчика входят: 1. Исполнение программы по шагам; 2. Поддержка режима виртуального процессора 8086 при работе на компьютерах, оснащенных процессором 80386; 3. Возможность удаленной отладки, когда отладчик занимает минимальный объем памяти и передает отладочную информацию на другой компьютер, связанный через последовательный или параллельный интерфейс; 4. Возможности пошагового выполнения программ вперед и назад; 5. Возможность резидентной работы, что позволяет довольно просто отлаживать резидентные программы; 6. “Многоязычность” – турбо-отладчик одинаково хорошо работает с программами, написанными на Pascal, C, C++, ассемблере; 7. Возможность отлаживать программы в среде Windows, начиная с версии 2.5 турбо-отладчика. Чтобы использовать внешний отладчик при выполнении программы из среды Borland C++, программа должна компилироваться со следующими включенными опциями: 1) Основное меню BorlandC++OptionsCompileAdvanced Code GenerationDebug info in OBJs (должна быть включена обязательно); 2) Основное меню Borland C++OptionsCompileAdvanced Code GenerationLine numbers debug info (включается только при выключенной оптимизации безусловных переходов). Безусловный переход – скачок по метке на другое место в программе, не используя условие. 3) Основное меню Borland C++Debugger->Source Debugging, установленное в состояние Standalone. 4 Для вызова отладчика из среды Borland C++ выбирается команда: Основное меню Borland C++ (т. е. три горизонтальные полоски)Turbo Debugger. Турбо-отладчик при запуске из Borland C++ наследует много полезной информации, а после завершения возвращается в интегрированную среду, т.е. Borland C++. Запуск турбо-отладчика: td.exe – запуск отладчика без загрузки отлаживаемого файла. td file.exe - запуск отладчика и загрузка отлаживаемого файла file.exe. Управление работой турбо-отладчика выполняется через систему меню: 1. Главное меню (основное Main Menu), занимает верхнюю строку экрана и активизируется нажатием клавиши F10. 2. Подменю (меню команды), связанное с каждой из команд главного меню. С помощью подменю может быть выполнена команда, открыты: свое меню (команды подменю, помеченные стрелкой) и окно диалога (команды подменю, помеченные многоточием). 3. Локальное подменю, открываемое нажатием клавиши Alt-F10, либо нажатием правой кнопки манипулятора ”мышь”. Программа file.exe загружается в отладчик, и ее точка входа отмечается символом ‘’. С помощью горячих клавиш можно настраивать работу отладчика по своему усмотрению. Например, изменение размера и (или) перемещение активного окна выполняются следующим образом: 1. Выбирается Main MenuWindowSize/Move или нажимается “горячая клавиша” Ctrl-F5. 2. Клавиши со стрелками будут перемещать активное окно в любое место экрана; для изменения правой или нижней границы окна используются клавиши со стрелками и одновременно удерживается нажатой клавиша shift; 3. Новый размер и (или) положение окна фиксируется нажатием клавиши Enter. 5 И т.д., точно так, как это делается в среде Borland C++. Команды меню View: открывают различные специализированные окна отладчика. 1. Команда CPU – при ее выборе на экране открывается окно CPU, образуемое пятью подокнами, называемые панелями. 1.1. В левом верхнем углу “панель машинного кода” содержит дизассемблированные инструкции программы вместе с СИ-строками, эквивалентом которых они являются. Панель машинного кода позволяет наблюдать за процессом исполнения отлаживаемой программы на уровне машинных инструкций. 1.2. Справа вверху к ней примыкает “панель регистров”, показывающая текущее значение регистров процессора. 1.3. Далее справа вверху располагается “8 флагов процессора”. Панели регистров и флагов позволяют изменить значение регистра или флага. Задание нового значения регистра требует активизации панели и высвечивания его символического обозначения. Затем начинается набор нового значения. Отладчик открывает окно истории и отображает ввод. После завершения ввода и нажатия клавиши Enter регистр изменяет свое значение. Изменение флагов выполняется проще. Нужный флаг высвечивается, и нажимается клавиша Enter. 1.4. Слева внизу – “панель данных”, в которой отображается дамп памяти, соответствующий текущему значению регистра DS (это текущий дамп сегмента данных, активизировав эту панель, можно просмотреть и изменить содержимое сегмента данных). 1.5. В правом нижнем углу помещается “панель стека”, где дается дамп текущей его вершины. Активной является та панель, в которой помещен курсор. Переход из одной панели в другую выполняется клавишей TAB (Shift-Tab). Информация в панелях соответствует текущему состоянию процессора и обновляется после каждой выполненной команды. 6 2. Выбор команды RunGo to cursor или клавиша F4 ,вызовет выполнение программы до новой границы. Пошаговое выполнение программы осуществляется с помощью клавиш F7 или F8. Клавиша F7(Trace Into) – пошаговая отладка программы с заходом в тело функции и просмотром ее содержимого. Клавиша F8(step over) – пошаговая отладка программы без захода в функцию, но с получением результата ее выполнения. Если выбрана команда Run (F9), то выполнение программы продолжается до тех пор, пока не будет достигнут ее конец, либо до первой точки останова. Исполнение программы может быть прервано нажатием клавиш CtrlBreak. Команда Execute to… (горячая клавиша Alt-F9) позволяет задать адрес инструкции, при достижении которой выполнение программы приостанавливается. Команда Back Trace (клавиша Alt-F4) позволяет вернуться назад по коду отлаживаемой программы, но с некоторыми ограничениями. Например, такими: нельзя вернуть назад связанную с портами инструкцию (команду), выполнить движение назад в коде обработчика прерывания. А само движение назад возможно только потому, что турбо-отладчик хранит последние выполненные инструкции. Посмотреть их можно, активизировав окно Execution history через меню View. При желании отладчик может хранить и записывать выполняемые нажатия клавиш для того, чтобы некоторые из них ”взять обратно” (отменить). Команда Unit return продолжает исполнение функции без остановки до тех пор, пока не происходит возврат в точку вызова, команда удобна в тех случаях, когда вместо клавиши F8(step over) нажимается F7(Trace Into) и начинается трассировка функции, которую следовало бы выполнить за один шаг отладчика. В «панели регистров» записаны следующие регистры: AX, BX, CX, DX, SP, BP, SI, DI, CS, DS, SS, ES, IP Флаги CF, ZF, SF, OF, PF, AF, IF, DF 7 Регистры представляют собой разновидность памяти специального назначения, которую микропроцессор использует для определенных конкретных целей. Регистрами является набор из четырнадцати 16-битовых мест, в которых можно хранить числа, каждое из них является неотъемлемой, внутренней частью микропроцессора. Первая группа регистров называется универсальными регистрами или регистрами общего назначения (РОН). Имеется четыре таких регистра, которые именуются AX, BX, CX и DX. Если для работы требуется применение лишь половины емкости любого из этих регистров, то они могут делиться на половины старшего и младшего порядка, которые обозначаются AH и AL, BH и BL и т.д. Следующая группа состоит из четырех регистров, и используется для оказания помощи микропроцессору в отыскании пути в памяти компьютера. Регистры из этой группы называются регистрами сегментов. Каждый из них используется для оказания помощи в получении доступа к разделу (или сегменту) памяти объемом 64 Кбайт. Сегмент кода (или регистр CS) указывает, в каком месте памяти располагается программа. Сегмент данных (или регистр DS) определяет местонахождение данных, используемых программой, а дополнительный сегмент (или регистр ES) дополняет сегмент данных. Сегмент стека (или регистр SS) определяет местонахождение стека компьютера. В то время как регистры сегментов используются для получения доступа к большим разделам (по 64 К) памяти, последняя группа регистров используется для оказания помощи в отыскании пути к определенным байтам в памяти. Они применяются совместно с регистром сегмента для указания на точное место в памяти. Имеется пять таких регистров, каждый из которых используется для конкретной цели. Указатель команды (инструкции) IP, который называют также программным счетчиком, РС, сообщает микропроцессору только о месте, где выполняется программа. Указатель стека, SР, и указатель базы, BP, используются для оказания помощи в отслеживании выполняющейся работы. Индекс источника, SI, и индекс местоназначения 8 (выхода) DI, используются для оказания помощи программам в перемещении больших объемов данных из одного места в другое. Наконец имеется еще один регистр, называемый регистром флагов, который используется для фиксации флагов состояния. Различные флаги сообщают программам только о том, в каком состоянии находится компьютер: результаты арифметических операций, разрешены ли прерывания и другие аналогичные состояния. Микропроцессоры РС в основном управляются посредством последовательности 1-битовых флагов, каждый из которых отмечает или устанавливает определенное состояние в компьютере. Флаги действуют независимо друг от друга, однако, для удобства, они собраны вместе в регистр флагов. Отдельные флаги могут проверяться и устанавливаться посредством специальных команд, устанавливаться а вся посредством группа пары флагов команд, может считываться или которые считывают или устанавливают весь регистр флагов. Всего имеется девять стандартных флагов. Шесть из них используются для индикации результатов арифметических и подобных им операций: флаг нуля – ZF – указывает на нулевой результат (или равенство при сравнении), флаг знака – SF – указывает на отрицательный результат, флаг переноса – CF – означает перенос в следующую позицию, флаг дополнительного переноса – AF – указывает на перенос из первых четырех битов (что требуется для моделирования десятичных операций), флаг переполнения – OF – отмечает получение слишком большого результата и, наконец, флаг четности – PF – отмечает четность или нечетность результата. Три оставшихся флага используются для управления. Флаг направления, DF, управляет направлением повторения операций (например, при побайтовом перемещении данных): справа налево или наоборот. Флаг прерываний, IF, контролирует возможность прерываний: разрешены они или временно запрещены. Флаг захвата, TF, вызывает генерацию компьютером специального прерывания "захвата" после выполнения одной команды. Это делает 9 возможным пошаговое выполнение программы с трассировкой результатов каждой отдельной команды. Задание на выполнение: Рассмотреть на примере любой программы работу турбо-отладчика. Вопросы к отчету по лабораторной работе 1: 1. Особенности турбо-отладчика. 2. Команда CPU и ее подокна. 3. С помощью какой клавиши выполняется трассировка программы. 4. Какая команда позволяет вернуться назад по коду отлаживаемой программы и в каких случаях возврат невозможен. 5. Для чего используется команда Unit return 6. Регистры и флаги. Лабораторная работа №2 Тема: «Работа с функциями BIOS. Доступ к регистрам и флагам через псевдопеременные» (2 часа) Язык С поддерживает доступ к внутренним регистрам микропроцессора (МП) через псевдопеременные, ссылка на которые в С-программе компилируется в ссылку на внутренний регистр МП. Перечислим имена псевдопеременных: _AX; _AL; _AH; _BX; _BL; _BH; _CX; _CL; _CH; _DX; _DL; _DH; _SI; _DI; _BP; _SP; _CS; _ES; _DS; _SS. Пример: Чтобы узнать текущее значение сегментных регистров, в программу помещается следующий код: ... unsigned cur_code_seg, cur_data_seg, cur_stack_seg, cur_es_seg; 10 cur_code_seg = _CS; cur_data_seg = _DS; cur_stack_seg = _SS; cur_es_seg = _ES; ... Псевдопеременная может также использоваться в левой части оператора присваивания. Но необходимо помнить, что: 1. Невозможно непосредственно присвоить значение регистрам CS, IP; 2. Изменение значений сегментных регистров DS, SS, ES приводит к непредсказуемым последствиям для программы и требует особой быть неявно осторожности; 3. Установленные в регистры значения могут переопределены последующими операторами программы и к моменту использования в других выражениях будут содержать не ранее присвоенное, а случайное значение; 4. При использовании псевдопеременных следует отключить использование регистровых переменных и регистровой оптимизации в опциях компилятора. Пример: Вывод символа на экран с помощью псевдопеременных: #include<dos.h> void main(){ /*используется 10-е прерывание и выводится символ на активную видеостраницу*/ _AH=0x0e; _AL='N'; /*вызывает 10-е прерывание*/ geninterrupt(0x10); } Функция void geninterrupt(int intr_num) выполняет обращение к обработчику прерывания с номером intr_num. Для задания необходимых 11 значений регистрам использовать и получения псевдопеременные. значений из обработчика следует функции является Достоинством минимальный размер и максимально возможная производительность кода при выполнении прерывания. Недостаток – это возможность переопределения при загрузке регистров ранее установленных значений. Задание: 1. Написать программу вывода пяти символов, используя псевдопеременные. 2. Написать программу, используя псевдопеременные, в которой необходимо реализовать функцию AH=01h (0x01) прерывания 10h BIOS, чтобы установить размер/форму курсора (текст), причем если курсор виден, то он мерцает. Для этого в качестве входа используются регистры CH и CL, где CH = начальная строка (значения от 0 до 1Fh; 20h=подавить курсор) CL = конечная строка (0-1Fh). Лабораторная работа №3 Тема: «Работа с функциями BIOS. Доступ к регистрам через функции и структуры» (2 часа) Доступ к регистрам может осуществляться не только через псевдопеременные, но и через функции и структуры. И одной из таких функций является int86(int intno, union REGS *inregs, union REGS *outregs). Данная функция загружает внутренние регистры МП значениями, записанными в объединении по шаблону union REGS, на начало которого указывает inregs (в примере первый &rr –является указателем состояния на входные регистры), и выполняет прерывание с номером intno. Значения внутренних регистров на выходе из прерывания записываются в объединении по шаблону union REGS, на начало которого указывает outregs (второй &rr – указатель состояния выходных регистров). Описание объединений выполняет точка вызова функции. Шаблон union REGS описан в заголовочном файле <dos.h> и представляет собой объединение двух структур: 12 struct WORDREGS{ unsigned int ax,bx, cx, dx, si, di, cflag,flags; }; struct BYTEREGS{ unsigned char al,ah,bl,bh,cl,ch,dl,dh; }; union REGS{ struct WORDREGS x; struct BYTEREGS h; }; Структура WORDREGS используется для доступа к регистрам как двух – байтовым единицам. Структура BYTEREGS позволяет осуществлять доступ к отдельным байтам регистров общего назначения (РОН). Поле структуры flags позволяет перед вызовом задать значение регистра флагов и после вызова прочесть его. Так как многие функции MS-DOS используют флаг переноса для сигнализации об ошибках в программе-обработчике прерывания, в структуре WORDREGS специально выделено поле cflag для значения флага переноса. Функция int86() возвращает значение регистра AX. Недостатком функции является возможность доступа к ограниченному числу регистров (т.е. не использует сегментные регистры). Для получения значения сегментного регистра используется библиотечная функция segread() файла <Dos.h>. Пример: Вывод символа на экран с помощью функций и структур: #include<dos.h> void main(){ union REGS rr; /*структура описывает регистры общего назначения*/ /*используется 10-е прерывание и выводится символ на активную видео-страницу*/ rr.h.ah=0x0e; rr.h.al='K'; 13 int86(0x10,&rr,&rr); } BIOS имеет ряд средств для определения и настройки параметров видеосистемы. В основном эти услуги выполняют функции прерываний 10h и 15h, а также использование областей данных BIOS. Использование областей данных BIOS – наиболее быстрый способ для определения текущего режима работы видеоадаптера, номера активной страницы, числа столбцов текста, текущей позиции курсора. Воздействие на область данных BIOS позволяет изменять позицию курсора на любой из страниц. Для проверки наличия адаптера EGA или VGA запрашивается функция BIOS, поддерживаемая только конфигурациями компьютера с таким адаптером. Например, функция AH=0Fh прерывания 10h BIOS – позволяет читать текущий видеорежим. Тогда выходной информацией является: AL = текущий режим; AH = число текстовых колонок на экране; BH = текущий номер активной страницы дисплея. Установка видео режима осуществляется с помощью функции AH=00h прерывания 10h BIOS: Вход: AL = режим тип AL Формат Цвета Адаптер Адрес CGA,EGA B800 0 Текст 40х25 16/8 полутона 1 Текст 40х25 16/8 2 Текст 80х25 16/8 полутона CGA,EGA B800 3 Текст 80х25 16/8 CGA,EGA B800 CGA,EGA B800 Пример a) Считываем текущий видеорежим и выводим его значение на экран: #include<stdio.h> #include<dos.h> void main(){ 14 union REGS rr;/*структура описывает регистры общего назначения*/ /*используется 10-е прерывание и читаем текущий видео режим*/ rr.h.ah=0x0f; int86(0x10,&rr,&rr); printf("текущий режим=%d,число текстовых колонок=%d", rr.h.al, rr.h.ah); } Пример b) Установим нулевой видеорежим, а затем вернем 3 видеорежим #include<stdio.h> #include<dos.h> #include<conio.h> void main(){ union REGS rr;/*структура описывает регистры общего назначения*/ /*используется 10-е прерывание и устанавливает нулевой видео режим 40х25*/ rr.h.ah=0x00; rr.h.al=0x00; int86(0x10,&rr,&rr); /*используется 10-е прерывание и читаем текущий видео режим*/ rr.h.ah=0x0f; int86(0x10,&rr,&rr); printf("текущий режим=%d,число текстовых колонок %d", rr.h.al, rr.h.ah); getch(); /*используется 10-е прерывание и устанавливает нулевой видео режим 80х25*/ rr.h.ah=0x00; 15 rr.h.al=0x03; int86(0x10,&rr,&rr); /*используется 10-е прерывание и читаем текущий видео режим*/ rr.h.ah=0x0f; int86(0x10,&rr,&rr); printf("текущий режим=%d,число текстовых колонок %d", rr.h.al, rr.h.ah); getch(); } Задание: 1. Написать программу, используя доступ через функции и структуры. В программе необходимо реализовать функцию AH=01h (0x01) прерывания 10h BIOS, чтобы установить размер/форму курсора (текст), причем если курсор виден, то он мерцает. Для этого в качестве входа используются регистры CH и CL, где CH = начальная строка (значения от 0 до 1Fh; 20h=подавить курсор) CL = конечная строка (0-1Fh) 2. Написать программу, использующую функцию AH=00h прерывания 10h BIOS, которая устанавливает 2 видео-режим (AL=2), показывает результат установки на экране монитора, затем переустанавливает в 1 видео-режим (AL=1) и выводит результат на экран и по окончанию программы возвращает 3 видео-режим (AL=3). Лабораторная работа №4 Тема: «Работа с функциями BIOS. Запись символа в видеопамять напрямую, используя указатели far или near» (2 часа) “Близкий” (near) указатель задает только смещение адреса объекта, на который ссылается указатель. В качестве сегментной части адреса объекта используется текущее значение сегментного регистра DS. Поэтому near- 16 указатель занимает в памяти компьютера только два байта и позволяет адресовать не более 64 Кбайт, т.е. работает только внутри сегмента DS. “Далекий” (far) указатель задает полный адрес объекта: значение сегмента и значение смещения. Поэтому far-указатель занимает в памяти компьютера 4 байта и позволяет адресовать любой байт в адресном пространстве. Причем первое слово, рассматривается как число без знака и задает смещение, а второе слово – значение сегмента адреса. При доступе к объекту данных по значению указателя (т.е. при выполнении операции *) сегмент адреса помещается в сегментный регистр (как правило, это регистр ES). far-указатель применяют очень часто, например, для доступа к памяти видеоадаптера или специальным областям BIOS. Пример: Вывод символа на экран и задание атрибутов (атрибуты символа – это фон и цвет символа) void main(){ /*программа показывает пример применения записи символа в видеопамять напрямую, используя указатель far*/ /*указатель на первый байт видео памяти третьего видео режима*/ char far *video=(char far*)0xB8000000L; *video='N';//символ video++;//указатель на атрибут символа //IRGB IRGB //0x42 //0100 0010 *video=0x42;/*атрибуты символа: зеленый символ на красном*/ } Пример: вывод символа на экран программой, использующей ассемблерную вставку: void main() //пример применения ассемблерных вставок { 17 asm { mov ah,0eh //пересылаем значение 0еh в регистр ah mov al,'N' //пересылаем символ N в регистр al int 10h //вызываем 10-е прерывание } } Задание: Написать программу вывода семи символов (символы должны иметь различные атрибуты), используя указатели. Лабораторная работа №5 Тема: «Работа с памятью. Выделение памяти посредством прерывания Int 21h DOS» (2 часа) При выделении памяти посредством прерывания Int 21h DOS используется понятие блока (определенное количество байт). DOS выделяет память блоками, эти блоки последовательно соединены друг с другом в виде цепочки, и если освобождается блок в середине цепочки, то DOS может использовать его при выделении памяти, но не соединяет его со свободной памятью оставшейся вне цепочки. #include<dos.h> #include<stdio.h> #include<conio.h> void main(){ clrscr(); unsigned int perem,perem_AX,perem2; //выделим блок памяти функцией 48h INT21h _AH=0x48; _BX=0xffff;/*запрос явно большого участка памяти с целью получения доступной памяти в параграфах*/ geninterrupt(0x21); perem=_BX; 18 /*умножаем на 16, чтобы получить количество в байтах*/ printf("Свободно байт = %u\n",perem*16); /*выделим участок памяти на 2 параграфа меньше, чем кол-во свободной памяти*/ _AH=0x48; _BX=perem-2; geninterrupt(0x21); perem_AX=_AX; /*проверка размера свободного блока памяти функцией 48h INT21h*/ _AH=0x48; _BX=0xffff; geninterrupt(0x21); perem2=_BX; printf("Свободно байт после выделения памяти=%u\n", perem2*16); //перераспределение участка памяти (сжать, добавить) _ES=perem_AX; _BX=perem-200; //освобождаем 200 параграфов _AH=0x4a; geninterrupt(0x21); /*проверка размера свободного блока памяти функцией 48h INT21h*/ _AH=0x48; _BX=0xffff; geninterrupt(0x21); perem2=_BX; //умножаем на 16, чтобы получить количество в байтах printf("Свободно байт после перераспр. памяти=%u\n", perem2*16); 19 //освобождение участка памяти _AH=0x49; _ES=perem_AX; geninterrupt(0x21); /*проверка размера свободного блока памяти функцией 48h INT21h после освобождения памяти*/ _AH=0x48; _BX=0xffff; geninterrupt(0x21); perem2=_BX; printf("Свободно байт после освоб. памяти=%u\n", perem2*16); getch(); } Задание: Выделить участок памяти в параграфах, занести в него массив элементов 1. char; 2. int, прочитать его и освободить участок памяти. Примечание: память выделяется в параграфах, поэтому байты надо перевести в параграфы: (количество элементов)%16 +1. Лабораторная работа №6 Тема: «Работа с внешними устройствами, на примере видеоадаптера» (2 часа) В видеопамяти вся информация записывается в виде длинной ленты данных. А при выводе символов на экран происходит ее преобразование в матрицу (разбивают на строки). Для видео режима 80х25 одна строка занимает 160 байт = 80байт * 2 байта на каждый символ. Для того чтобы вывести символ на экран, используя видеоадаптер, необходимо вычислить смещение относительно используемого адреса, на который настроен указатель. Для этого используется формула: (y*X1 + x)*2, где y – строка, x – колонка, X1 – количество столбцов используемых в данном режиме. 20 Работа с ВУ через адресное пространство: #include<stdio.h> #include<conio.h> void main(){ /*записываем в область памяти, принадлежащую внешнему устройству (контроллеру видеоадаптера)*/ clrscr(); char far *ykazat=(char far *)0xB8000000; *ykazat='*'; } Пример перехвата прерывания в ОС MS-DOS #include<dos.h> #include<stdio.h> //задаем адрес видеопамяти char far *ykazat=(char far*)0xB8000000; /*объявляем переменную, в которой сохраняется перехваченный вектор прерывания*/ void interrupt( *old_vect) (...); //новая функция обработки прерывания void interrupt fun_vector(...){ //сохраняем в стеке используемые перехваченным прерыванием регистры asm{ push ax push bx push es } //переводим номер функции в ASCII код if (_AH>9) *ykazat=_AH+0x41-10; else *ykazat=_AH+0x30; //восстанавливаем регистры 21 asm{ pop es pop bx pop ax } old_vect(); //вызов старого прерывания }; void main(){ old_vect=getvect(0x10); /*сохраняем значение перехватываемого вектора*/ setvect(0x10,fun_vector); /*записываем новое значение вектора*/ _AH=0x0e; _AL='@'; geninterrupt(0x10); setvect(0x10,old_vect);/*восстанавливаем перехвач. вектор*/ } Прерывание по таймеру 1Ch (пользовательское) – этот вектор (0:0070) BIOS берет по каждому тику аппаратных часов. Первоначально он использует IRET, но может быть изменен пользовательской программой с целью адресовать фоновую программу пользователя, базирующуюся на таймере. И поскольку программа INT 1Ch выполняется во время низкоуровневого аппаратного прерывания, и система еще не сбрасывает контроллер прерываний, поэтому другие аппаратные прерывания (в том числе прерывание от клавиатуры) не будут происходить при работе INT 1Ch, т.е. не будет работать ввод пользователя. Поэтому большинство программ перехватывают вектор INT 08h, вызывают нужный вектор, а только затем выполняют операции, зависящие от времени, после того как BIOS закончит свою работу. Задание: 1. Записать в виде прямой диагонали фамилию и имя, используя прямой 22 доступ к видеопамяти (выполнить то же самое для обратной диагонали). 2. Дополнительное задание: Написать программу, перехватывающую вектор 1Ch (таймер), с целью увеличения счетчика задержки по времени. В векторе прерывания увеличивать счетчик на 1. В основной программе использовать счетчик для задержки на определенное количество секунд. 1секунда=18 тиков (18,2) Пояснение: создаваемой функции дается задание увеличения тиков. Пример: 18*3=3 секунды. Лабораторная работа №7 Тема: «Управление задачами и памятью в Windows» (2 часа) Для управления задачами используется механизм «предок-потомок». Программа, которая запускается на исполнение, называется программойпотомком (child program – дочерняя программа). Программа, которая запускает дочернюю программу, называется программой-предком (parent program – родительская программа). Функции языка С, работающие с процессами, находятся в библиотеке <PROCESS.H>. Для того чтобы запустить дочернюю программу с возвратом в родительскую, используются следующие функции: int spawnl(int mode, char *path, char *arg0, …, NULL); int spawnle(int mode, char *path, char *arg0, …, NULL, char **env); int spawnlp(int mode, char *path, char *arg0, …, NULL); int spawnlpe(int mode, char *path, char *arg0, …, NULL, char **env); Параметры: int MODE – определяет режим. Режим имеет 5 предопределенных значений: 1. P_WAIT – предок останавливается и запускается потомок, после завершения которого, управление возвращается в предок; 2. P_DETACH – потомок и родитель работают параллельно, но потомок работает в фоновом режиме; 23 3. P_NOWAIT – потомок и родитель работают параллельно; 4. P_NOWAITO – потомок и родитель работают параллельно, но процесс потомка не сохраняется; 5. P_OVERLAY – потомок замещает предка, т.е. предок прекращает свое существование и поэтому невозможен возврат управления в программу предок. Если функция spawnl() по каким-либо причинам не может быть выполнена, в точку вызова возвращается -1 и во внешнюю переменную errno записывается код ошибки. Коды ошибок, возвращаемых функциями spawnl(): Константа Причина ошибки EINVAL Неправильный аргумент E2BIG Список аргументов слишком велик; ENOENT Не найден файл загрузочного модуля; ENOEXEC Запускаемый на выполнение файл не является запускаемым или поврежден; ENOMEM Ошибка при выделении оперативной памяти потомку. Если работа программы-потомка была успешно исполнена, то функции spawnl() возвращают код завершения потомка. Буква ‘p’ в названии функции говорит о том, что для поиска файла потомка будут просматриваться не только маршрут, заданный параметром path, но и маршруты, указанные в строке среды PATH, если поиск по маршруту path завершается неудачей. Буква ‘е’ говорит о том, что при запуске программы-потомка будет переопределяться среда. Массив указателей envp содержит указатели на ASCIIZ-строки новой среды. Если буква ‘е’ в названии функции отсутствует, потомок наследует среду предка в неизменном виде. Буква ‘l’ в названии функции свидетельствует о том, что в точку входа main() потомка передается известное заранее число слов, указатели на которые задаются при вызове функции. Если число параметров в функции заранее 24 неизвестно, то единственный выход – использовать массив указателей и передавать адрес начала этого массива. Все функции spawn***(), имеющие в названии букву ‘v’, передают в качестве второго аргумента указатель на массив указателей argv. char *PATH – указатель на ASCIIZ – строку спецификации файла загрузочного модуля программы-потомка. Если не задается директория и/или накопитель, то поиск файла выполняется в текущих директории и (или) накопителе. char *ARG0, char *ARG1, …, NULL – указатели на ASCIIZ – строки, образующие в совокупности командную строку запускаемой на выполнение программы (указатели на отдельные слова командной строки). Пример 1. Запуск дочерней программы (вызываемой) с возвратом в родительскую (вызывающую). z1.cpp: //родительская программа (процесс) #include<stdio.h> #include<process.h> #include<conio.h> void main(void){ clrscr(); printf("Запушена родительская программа\n"); spawnl(P_WAIT,"test.exe","arg1","arg2",NULL); } test.cpp: //дочерняя программа (процесс) #include<stdio.h> void main(int kol_arg, char **arg){ printf("\nДанная программа является дочерней\n"); for(int i=0;i<kol_arg; i++){ printf("Аргумент %d = %s\n",i,arg[i]); } } 25 Данная программа запускает процесс потомок и создает для него среду окружения (набор внешних переменных, т.е. строк), которые программа может использовать. z1e.cpp: //родительская программа #include<stdio.h> #include<process.h> #include<conio.h> void main(void){ char *env[]={"System Programm = 3 and 2 course", "Laboratornay = Number 2", "Rk.com=c:\\rk.com",0,0 }; clrscr(); printf("Запушена родительская программа\n"); spawnle(P_WAIT,"test_z1e.exe","arg1","arg2",NULL,env); } test_z1e.cpp:// дочерняя программа #include<stdio.h> #include<stdlib.h> void main(int kol_arg, char **arg){ printf("\nДанная программа является дочерней\n"); printf("Значение аргумента 1=%s\n", getenv("Laboratornay ")); printf("Значение аргумента 2=%s\n", getenv("System Programm ")); printf("Значение аргумента 3 \ (передача дочерней программе \ пути к программе \n \ rk.com)=%s\n",getenv("Rk.com")); } Задание на выполнение Написать программы предок и потомок. В программе-потомке выделить 26 динамическую память под матрицу размерности NxM, в которой вычислить: 1 вариант: сумму элементов, меньших заданного K. 2 вариант: сумму отрицательных элементов. 3 вариант: сумму положительных элементов. 4 вариант: количество положительных элементов. 5 вариант: сумму элементов K-го столбца. Полученное значение вывести на экран в программе-потомке, передать в программу-предок и вывести на экран в программе-предке. Потомок запускать в режиме P_WAIT. Запуск осуществлять с пояснительными сообщениями. Пояснить разницу между режимами. Работу выполнять в среде программирования BorlandC++Builder. Для работы в системе программирования Borland C++ Builder необходимо выполнить следующие действия: 1. Запустить Borland C++ Builder; 2. В главном меню выбрать пункт Файл (File); 3. В меню Файл (File) выбрать пункт Новый (New); 4. В появившемся окне выбрать Консольное приложение (Console Wizard), как показано на рис. Нажать кнопку OK. 27 5. В появившемся окне установить параметры, как показано на рис. Нажать кнопку OK. 6. В появившемся окне осуществляется набор и редактирование текста программы. Для запуска программы использовать клавишу F9 или пункт главного меню Запуск (Run). Лабораторная работа №8 Тема: «Управление вводом/выводом и файловые системы» (2 часа) Информация во внешней памяти (на диске) хранится в виде файлов – именованных объектов, доступ к которым обеспечивает операционная система. Поддержка ОС состоит в том, что в ней имеются средства: Создания файлов; Уничтожения файлов; Поиска файлов на внешнем носителе информации; Чтения и записи данных из файлов и в файлы; Открытия файлов; Закрытия файлов; Позиционирования файлов. Рассматривая взаимосвязь файлов с потоками ввода/вывода, следует отметить существование следующих процедур: Создание файла; Создание потока; Открытие файла; «присоединение» файла к потоку; 28 обмены с файлом с помощью потока; «отсоединение» файла от потока; закрытие файла; уничтожение файла. Все перечисленные действия могут быть выполнены с помощью средств библиотеки классов ввода/вывода языка С++. Потоки для работы с файлами создаются как объекты следующих классов: ofstream – для вывода (записи) данных в файл; ifstrem – для ввода (чтения) данных из файла; fstream – для чтения и для записи данных (двунаправленный обмен). Пример: #include<fstream.h> ofstream outFile; /*определяет выходной файловый поток*/ ifstream inFile; //определяет входной файловый поток fstream ioFile; /*определяет файловый поток для ввода и вывода*/ Создание файлового потока связывает имя потока с выделяемым для него буфером и инициализирует переменные состояния потока. Открытие файла в самом общем смысле означает процедуру, информирующую систему о тex действиях, которые предполагается выполнять с файлом. Создав файловый поток, можно "присоединить" его к конкретному файлу с помощью компонентной функции open(). С ее помощью можно не только открыть файл, но и связать его с уже определенным потоком. Формат функции: void open (const char *fileName, int mode=умалчиваемое_значение, int protection=умалчиваемое_значение); 29 Первый параметр – fileName – имя уже существующего или создаваемого заново файла. Это строка, определяющая полное или сокращенное имя файла в формате, регламентированном операционной системой. Второй параметр - mode (режим) – дизъюнкция флагов, определяющих режим работы с открываемым файлом(например, только запись или только чтение). Флаги определены следующим образом: enum ios::open_mode { in = 0x01, // Открыть только для чтения out = 0x02, // Открыть только для записи ate = 0x04,//При открытии искать конец файла app =0x08,//Дописывать данные в конец файла trunk =0x10,// Вместо существующего создать новый файл , nocreate=0x20,//Не открывать новый файл (Для несуществующего файла функция open выдаст ошибку) noreplace=0x40,// Не открывать существующий файл (Для существующего выходного файла, не имеющего режимов ate или арр, выдать ошибку) binary=0x80,//Открыть для двоичного (не текстового) обмена. } Умалчиваемое значение параметра mode зависит от типа потока, для которого вызывается функция open () . Третий параметр - protection (защита) - определяет защиту и достаточно редко используется. Точнее, он устанавливается по умолчанию, и умалчиваемое значение обычно устраивает программиста. Открытие и присоединение файла к конкретному файловому потоку обеспечивается таким вызовом функции open(): имя_потока.open (имя_файла , режим , защита) ; Здесь имя_потока - имя одного из объектов, принадлежащих классам ofstream, ifatream, fstream. Примеры вызовов для определенных выше потоков: outFile.open(“C:\\X\\RESULT.DAT"); inFile.open("DATA.TXT"); ioFile.open("CHAHGE.DAT", ios :: out); 30 При открытии файлов с потоками класса ofstream второй параметр по умолчанию устанавливается равным ios::out, т.е. файл открывается только для вывода. Таким образом, файл C:\X\RESULT.DAT после удачного выполнения функции open() будет при необходимости (если он не существовал ранее) создан, а затем открыт для вывода (записи) данных в текстовом режиме обмена и присоединен к потоку outFile. Теперь к потоку outFile может применяться, например, операция включения «, как к стандартным выходным потокам cout, cerr. Поток inFile класса ifstream в нашем примере присоединяется функцией open() к файлу с именем DATA.TXT. Этот файл открывается для чтения из него данных в текстовом режиме. Если файла с именем DATA.TXT не существует, то попытка вызвать функцию inFile.open() приведет к ошибке. Для проверки удачности завершения функции ореn() используется следующая конструкция: if(!inFile) { cerr << "Ошибка при открытии файла!\n"; exit(l); } Для потоков класса fstream второй аргумент функции ореn() должен быть задан явно, так как по умолчанию неясно, в каком направлении предполагается выполнять обмен с потоком. В классе fstreambase, который служит основой для файловых классов, имеются и другие средства для открытия уже существующих файлов. Если файл явно создан с помощью библиотечной функции "нижнего уровня" create(), то для него определен дескриптор файла. Этот дескриптор можно использовать в качестве фактического параметра функции fstreambase::attach(). При вызове этой функции используется уточненное имя, содержащее название того потока, который предполагается присоединить к уже созданному файлу с известным дескриптором: В классах ifstream, ofstream, fstream определены конструкторы, позволяющие по-иному выполнять создание и открытие файлов. 31 Работая со средствами библиотечных классов ввода-вывода, чаще всего употребляют конструктор без параметров и конструктор, в котором явно задано имя файла. Примеры обращений к конструкторам без параметров: ifstream fi; // Создает входной файловый поток fi ostream fo; // Создает выходной файловый поток fo fstream ff; //Создает файловый поток ввода-вывода ff После выполнения каждого из этих конструкторов файловый поток можно присоединить к конкретному файлу, используя уже упомянутую компонентную функцию open (): fi.open("Filel.txt",ioe::in); // Поток fi соединен с файлом Filel.txt fi.close(); // Разорвана связь потока fi с файлом Filel.txt fi.open(“File2.txt”); // Поток fi присоединен к файлу File2.txt fo.open("NewFile"); // Поток fо присоединяется к файлу NewFile; если такой файл отсутствует - он будет создан При обращении к конструктору с явным указанием в параметре имени файла остальные параметры можно не указывать, они выбираются по умолчанию. Примеры: ifstream flowl("File.l"); создает входной файловый поток с именем flow1 для чтения данных. Разыскивается файл с названием File.1. Если такой файл не существует, то конструктор завершает работу аварийно. Проверка: if (!flowl) cerr << "Не открыт файл File.l!"; ofstream flow2(“File.2”); создается выходной файловый поток с именем flow2 для записи информации. Если файл с названием File.2 не существует, он будет создан, открыт и соединен с потоком flow2. Если файл уже существует, то предыдущий вариант будет удален и пустой файл создается заново. fstream flow3("File.3"); создается файловый поток flow3, открывается файл File.3 и присоединяется к потоку flow3. 32 Функцию close(), позволяющую очистить буфер потока, отсоединить поток от файла и закрыть файл необходимо явно вызывать при изменении режимов работы с файловым потоком. Автоматически эта функция вызывается только при завершении программы. Задание 1. Написать программу, результатом выполнения которой будет постраничный вывод на экран текстового файла, имя которого набирает на клавиатуре пользователь по «запросу» программы. Размер страницы - 20 строк. Читать пробельные символы позволяет компонентная функция getline() класса ifstream. 2. Написать программу, результатом выполнения которой будет запись в файл информации, набранной с клавиатуры. Лабораторная работа №9 Тема: «Реализация функций API на уровне ОС» (2 часа) При реализации функций API на уровне ОС за их выполнение ответственность несет ОС. Объектный код, выполняющий функции, либо непосредственно входит в состав ОС (или даже ядра ОС), либо поставляется в составе динамически загружаемых библиотек, разработанных для данной ОС. Система программирования ответственна только за то, чтобы организовать интерфейс для вызова этого кода. В таком варианте результирующая программа обращается непосредственно к ОС. Поэтому достигается наибольшая эффективность выполнения функций API по сравнению со всеми другими вариантами реализации API. Недостатком организации API по такой схеме является практически полное отсутствие переносимости не только кода результирующей программы, но и кода исходной программы. Программа, созданная для одной архитектуры вычислительной системы, не сможет исполняться на вычислительной системе другой архитектуры даже после того, как ее объектный код будет полностью перестроен. Чаще всего система программирования не сможет выполнить 33 перестроение исходного кода для новой архитектуры вычислительной системы, поскольку многие функции API, ориентированные на определенную ОС, будут в новой архитектуре просто отсутствовать. Таким образом, в данной схеме для переноса прикладной программы с одной целевой вычислительной системы на другую будет требоваться изменение исходного кода программы. Переносимости можно было бы добиться, если унифицировать функции API в различных ОС. С учетом корпоративных интересов производителей ОС такое направление их развития представляется практически невозможным. В лучшем случае при приложении определенных организационных усилий удается добиться стандартизации смыслового (семантического) наполнения основных функций API, но не их программного интерфейса. Хорошо известным примером API такого рода может служить набор функций, предоставляемых пользователю со стороны ОС типа Microsoft Windows – WinAPI (Windows API). Надо сказать, что даже внутри этого корпоративного API существует определенная несогласованность, которая несколько ограничивает переносимость программ между различными ОС типа Windows. Еще одним примером такого API можно считать набор сервисных функций ОС типа MS-DOS, реализованный в виде набора подпрограмм обслуживания программных прерываний. В современных сложных приложениях часто недостаточно работать с одним файлом – необходимо иметь возможность работать с файловой системой, поддерживающей эти файлы. Хотя С++ предусматривает ряд некоторых структур высокого уровня, помогающих работать с файловыми системами, большинство из них относится к категории интерфейсов экспонируемых (т. е. определенных в качестве доступных) API операционной системы, используемых программистом. Для работы с файловой системой используются в числе прочих следующие функции API Win32: FindFirstFile(), FindFirstFileEx(), FindNextFile(). Все эти функции работают со структурой WIN32_FIND_DATA. Структура содержит 10 полей: 34 typedef struct WIN32_FIND_DATA{ DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD nFileSizeHigh; DWORD nFileSizeLow; DWORD dwReserved0; DWORD dwReserved1; CHAR cFileName[MAX_PATH]; CHAR cAltenateFileName[14]; } В таблице 1 приведено детальное описание структуры. Таблица 1. элемент dwFileAttributes Описание Специфицирует атрибуты файлового объекта. Атрибуты, файла описывают, какой разрешен доступ к нему, файл это или каталог и т.д. Этот элемент может содержать одно или несколько значений, описанных в таблице 2. ftCreationTime Содержит структуру FILETIME (64-битное значение, представляющее число 100-наносекундных интервалов, прошедших с 1-го января 1601 г.), специфицирующую время первоначального создания файла операционной системой. Структура имеет 2 элемента DWORD, которые функции Find устанавливают в 0, если файловая система, содержащая файл, не поддерживает этот элемент. ftLastAccessTime Содержит структуру FILETIME, специфицирующую время последнего обращения пользователя к файлу. Как и в случае ftCreationTime, оба ее элемента содержат 0, если файловая система не предусматривает этой информации. 35 ftLastWriteTime Содержит структуру FILETIME, специфицирующую время, когда пользователем или процессом производилась последняя запись в файл. Как и в случае ftCreationTime и ftLastAccessTime, оба ее элемента содержат 0, если файловая система не предусматривает этой информации. nFileSizeHigh Размер файла сохраняется операционной системой в двух значениях DWORD, которые комбинируются в соответствии с формулой (nFileSizeHigh * MAXDWORD) + nFileSizeLow, которая дает полный размер файла. (MAXDWORD обычно р но 232-1 .) Данный элемент специфицирует старшую часть paj ла; он равен 0, если только размер файла не больше MAXDW nFileSizeLow Специфицирует младшую часть размера файла. Такая структураиз двух DWORD позволяет файлу иметь размер порядка терабайт. dwReserved0, Зарезервировано корпорацией Microsoft для будущего dwReservedl использования. cFileName Ограниченная нулем строка, содержащая длинное имя файла. Его длина всегда меньше МАХ РАТН, системной константы, равной обычно 255 или 260. cAlternateFileName Ограниченная нулем строка, содержащая альтернативное имя. Это имя представлено в классическом формате 8.3 (filename.ext). Используйте этот элемент, если вы не уверены, поддерживает ли каждая функция или система, обращающиеся к файлу, длинные имена. Как указано в таблице 1, элемент dwFileAttributes может содержать одно или комбинацию из нескольких значений констант, характеризующих свойства файла. Эти константы перечислены и описаны в таблице 2. 36 Таблица.2. Значения констант для dwFileAttributes Константа Описание FILE_ATTRIBUTE_ARCHIVE Приложение используют данное значение, чтобы пометить файл для резервного копирования или удаления. Всякий раз, когда файл изменяется, Windows устанавливает архивный бит. Когда программа копирования резервного архивирует файл, она сбрасывает данный бит. FILE_ATTRIBUTE_COMPRESSED Для файла атрибут означает, что все данные в нем сжаты, т.е. это файл ZIP или он имеет подобный сжатый формат. Для каталога атрибут означает, что для новых файлов и подкаталогов сжатие выполняется по операционная система инструментальное умолчанию (т.е. или какое-то средство сжимает новые файлы и подкаталоги во время создания) FILE_ATTRIBUTE_DIRECTORY Файловый объект соответствует каталогу или папке, – т.е. это логический объект операционной системы, не содержащий собственных данных, инструментом для а служащий организации других объектов. FILE_ATTRIBUTE_HIDDEN Файл является скрытым, т. е. он не включается в обычный листинг каталога, этот атрибут системными, обычно используется вспомогательными или 37 другими файлами, которые разработчик приложения хочет защитить от случайного обнаружения. FILE_ATTRIBUTE_NORMAL Файл обычный, чтение/запись, допускающий не скрытый и не системный. Короче, это означает, что все другие атрибуты файла не установлены и, таким образом, данное значение действительно только в случае, если используется оно одно. FILE_ATTRIBUTE_OFFLINE Данные файла не непосредственно являются доступными. Показывает, что данные были физически перемещены для внешнего хранения. Этот атрибут используется крайне редко, только в некоторых сетевых средах. FILE_ATTRIBUTE_READONLY Файл допускает только чтение. Приложения могут его читать, но не могут модифицировать или удалить. Приложение, которому требуется это делать, должно либо обнулить сначала данный бит, либо сохранить файл под другим именем. FILE_ATTRIBUTE_SYSTEM Файл является системы частью операционной (например, библиотекой) либо динамической используется исключительно операционной системой (например, ядро операционной системы). Вообще модификация системных файлов – небезопасное занятие, потому 38 что в результате, Windows может начать работать неправильно или даже перестанет загружаться. FILE_ATTRIBUTE_TEMPORARY Файл используется для временного хранения. Этот атрибут присваивается файлу, если он создается функцией tmpfile() или любой из функций Windows для временных tmpnam() файлы, и файлов, _tempnam() созданные таких, как Временные приложением, не являются «стабильными» – они должны быть стерты приложением при завершении. Если файл имеет длинное имя, полный его вариант будет находиться в cFileName. API в этом случае возвратит классический (формат 8.3), усеченный его вариант в поле cAlternateFileName. Если операционная сися не поддерживает длинные имена файлов либо в данном случае файл это имя не использует, элемент cAlternateFileName будет пустым, a cFtleName будет содержать имя в формате 8.3. Чтобы получить версию имени _ 8.3, вы можете также вызвать функцию API GetShortPathName(), которая возвращает 12символьную форматированную строку. Задание 1. Изучить функцию FindFirstFile(). 2. Прочитать и вывести на экран атрибуты файла, используя функцию FindFirstFile(). 39 Литература 1. Гордеев А. В., Молчанов А. Ю. «Системное программное обеспечение», М. Питер, 2003 2. Фридман А., Кландер Л., Михаэлис М., Шильдт Х. «С/С++ Алгоритмы и приемы программирования», М. БИНОМ, 2003 3. Александров Е. К., Рудня Ю. Л. «Микропроцессор 80386: как он работает и как работать с ним: Учебное пособие». С.-Пб:Элмор, 1994 4. Богумирский Б. С. «Руководство пользователя ЭВМ: В 2-х ч.» – С.-Пбю: Ассоциация OILCO, 1992 5. Донован Дж. «Системное программирование» –М.:Мир, 1975 6. Джордейн Р. «Справочник программиста персональных компьютеров типа IBM PC XT и AT/ пер. с англ.» –М.:Финансы и статистика, 1991 40