
- •Операционные системы для программиста
- •Введение
- •1. Основные понятия
- •1.1. Понятие операционной системы
- •1.2. Системные соглашения для доступа к функциям ос
- •1.3. Особенности разработки программ в базовых ос
- •1.4. Командный интерфейс пользователя в ос
- •1.5. Информация об ошибках системной функции
- •2. Программный доступ к файловой системе
- •2.1. Понятия дескрипторов, идентификаторов и хэндлов
- •2.2. Ввод и вывод в стандартные файлы.
- •2.3. Базовые средства использования файлов
- •2.4. Многопользовательская блокировка файлов
- •2.5. Установка произвольной позиции в файле
- •3. Принципы построения ос
- •3.1. Модульная структура построения ос
- •3.2. Использование прерываний в ос
- •3.3. Управление системными ресурсами
- •3.4 Строение ядра операционной системы
- •3.5. Структура операционной системы типа Windows nt
- •4. Многофункциональный консольный вывод
- •4.1. Функции управления курсором
- •4.2. Многократный вывод символов и атрибутов
- •4.3. Вывод в произвольную позицию экрана
- •4.4. Ввод данных, размещенных предварительно на экране
- •5. Системные функции ввода для консольных устройств
- •5.1. Системные функции ввода текстовых строк
- •5.2. Событийно-управляемый ввод
- •5.3. Системные функции ввода с клавиатуры
- •5.4. Опрос ввода с клавиатуры в программе
- •5.5. Системные функции мыши для текстового режима
- •6. Файловые системы
- •6.1. Структуры файловых систем для пользователя
- •6.2. Методы распределения внешней памяти
- •6.3. Принципы построения файловых систем типа fat
- •6.4. Современные модификации файловой системы fat
- •6.5. Особенности построения файловой системы hpfs
- •6.6. Принципы построения файловой системы ntfs
- •6.7. Особенности строения файловых систем для Unix
- •6.8. Программный опрос файловой системы
- •7. Обеспечение множественности процессов
- •7.1. Основные понятия теории вычислительных процессов
- •7.2. Программное порождение процессов
- •7.3. Уничтожение процессов
- •7.4. Ожидание завершения процессов
- •8. Многопоточное функционирование ос
- •8.1. Понятие нити и связь Хе с процессом
- •8.2. Создание нитей (thread) в программе
- •8.3. Уничтожение нитей
- •8.4. Приостановка и повторный запуск нити
- •8.5. Ожидание завершения нити
- •9. Средства взаимодействия программных единиц
- •9.1. Абстрактные критические секции
- •9.2. Абстрактные семафоры
- •9.3. Семафоры взаимоисключения
- •9.4. Семафоры событий
- •9.5. Средства группового ожидания
- •9.6. Программные критические секции
- •9.7. Программные семафоры с внутренним счетчиком
- •10. Управление памятью
- •10.1. Виртуальная память
- •10.2. ЏодкРчка страниц для реализациШ виртуальной памяти
- •10.3. Системные функции распределения памяти
- •10.4. Совместное использование памяти
- •10.5. Отображение файлов в оперативную память
- •10.6. Динамически распределяемая память
- •11. Средства коммуникации процессов
- •11.1. Неименованные коммуникационные каналы Unix
- •11.2. Переназначение хэндлов для доступа к каналу
- •11.3. Неименованные каналы в Windows
- •11.4. Именованные каналы в Windows nt
- •11.5. Именованные каналы в Unix
- •12. Взаимодействие пользователя с ос
- •12.1. Интерфейсы операционных систем
- •12.2. Командные и операционные оболочки (shells)
- •12.3. Основные команды базовых операционных систем
- •12.4. Групповое выполнение и фоновый запуск команд
- •12.5. Стандартный ввод-вывод и конвейеры командной строки
- •12.6. Командные файлы и сценарии
- •Библиографический список
1.5. Информация об ошибках системной функции
Проблемой, почти никогда не возникающей вне использования системных средств, является сама принципиальная выполнимость вызываемой функции. Когда в прикладном программировании задается управляющая конструкция или выражение, то заданные ими действия всегда выполняются по заложенным в них правилам. При обращении же к системным функциям возможные ситуации не столь однозначны. Чтобы их понять, достаточно вспомнить работу с файлами в прикладном программировании (работа с файлом, по существу, неизбежно требует использования средств именно операционной системы). При семантически верном задании открытия файла, он может быть и не открыт, например, когда для чтения открывают файл, который не существует. Значительная часть системных функций совсем не обязательно обеспечивает запращиваемое действие! Поэтому в API вводятся вспомогательные средства, позволяющие программе обнаружить, выполнилось ли в действительности запрашиваемое действие.
Наиболее традиционно эта проблема решается в Unix. Большинство системных функций Unix имеют вид
int имяфункции(список_аргументов),
возвращая в качестве основного значения целое число. Это число может нести значение, определяющее результат действия функции, но одно из значений (обычно ‑1), зарезервировано за условным кодом ошибки. В описании системной функции обязательно присутствует информация, описывающая ошибочные ситуации, которые могут возникать при выполнении функции, и соответствующие им значения кода возврата (возвращаемого значения). Описание кодов ошибочных ситуаций в Unix совершенно обязательно в справочной информации по любой системной функции.
Заметим, что само получение справочной информации по некоторой системной функции в Unix вызывается командой man текстового режима. Эта команда является сокращением слова manual и задается в виде
man имяфункции
Если по такому вызову появится информация об одноименной команде, то следует использовать вызов справки в форме
man 2 имяфункции
Справочная информация появляется в текстовом окне консоли и имеет несколько архаичный вид (без возможности использования гипертекста).
Другой формой контроля ошибок в Unix является специальная переменная errno. Для основных функций операционной системы Unix код ошибки неявно заносится в эту специальную глобальную переменную, описанную в заголовочном файле errno.h. Такой прием дает принципиальную возможность анализировать значение этой переменной после обращения к системной функции. Но делать это следует сразу же после системного вызова, так как следующий системный вызов, в свою очередь, изменяет эту переменную.
Практическое использование значений переменной ошибок errno достигается с помощью вспомогательных функций perror() и strerror(). Первая из них имеет прототип
void perror(const char *s),
а вторая имеет прототип
char *strerror(int errnum).
Вторая функция возвращает указатель на текст, описывающий ошибку, а первая непосредственно отображает в стандартном потоке ошибок этот текст, причем предваряемый любой пользовательской текстовой информацией, которая содержится в аргументе s. Заметим, что к настоящему моменту в операционной системе Linux имеется более двухсот кодов ошибок, которые отображаются этими функциями. Следует также отметить, что указанные функции могут использоваться для локальной диагностики ошибок и в других операционных системах, а именно для программ, написанных на языке С. Такая возможность имеется даже в MS DOS, она присутствует и в OS/2, и в Windows. Но во всех этих последних ОС такая возможность является вспомогательной и не гарантируется для всех системных функций, которые используют свои собственные способы самодиагностики. (В MS DOS и Windows функции, основанные на глобальной переменной errno, позволяют определить только около 50 типов ошибок).
В операционной системе OS/2 системные функции делились на несколько функциональных групп, определяемых префиксом их имени. Основные функции имеют имена вида DosСобственноеимя. Функции обслуживания дисплея (видеосистемы) имели имена вида VioСобственноеимя, для обслуживания клавиатуры использовались функции с именами KbdСобственноеимя, а для обслуживания мыши – с именами MouСобственноеимя.
Все основные системные функции возвращали в этой ОС в качестве основного значения функции величину типа APIRET, а функции видеосистемы, клавиатуры и мыши – типа APIRET16. Принято строгое правило, что нулевое значение кода возврата однозначно определяет безошибочное выполнение системной функции. Все иные значения типа APIRET и APIRET16 дают код ошибки. Коды ошибки были описаны в заголовочном файле
bseerrs.h
систем программирования для OS/2, а их описание приведено в одном из файлов справочной системы. Обязательным в этой справочной системе являлось приведение всех возможных кодов ошибки с соответствующими комментариями. Отдельным пунктом справочной подсистемы, являлся пункт с наименованием Errors, которые содержал удобное для программиста соотнесение номеров кодов ошибок их содержательному толкованию.
Наиболее экзотической является получение информации об ошибках в MS Windows. Во первых, отсутствует какое-либо подобие систематичности в системных функциях. Возвращаемые значения системных функций могут быть описаны как VOID, BOOL, HANDLE, PVOID, LONG или DWORD. При использовании типа BOOL возвращаемое значение 0 обозначает ситуацию ошибки (категорически не рекомендуется [13] проверять это значение на TRUE). При типах HANDLE, LONG или DWORD ситуацию ошибки дает значение -1 или 0 в зависимости от конкретной функции. Функции с возвращаемым значением PVOID индицируют ошибку значением NULL.
Для получения же собственно кода ошибки в MS Windows программисту приходится принимать немалые дополнительные усилия. Если по возвращаемому значению системной функции определяется, что ошибка есть, следует немедленно вызывать специальную функцию GetLastError(), которая не имеет аргументов и возвращает значение типа DWORD. Функция GetLastError() возвращает последнюю ошибку, возникшую в ходе выполнения программы (точнее нити программы). Именно это 32-битное значение дает код ошибки. Собственно коды ошибок, общие для всех системных функций, содержатся в заголовочном файле WinError.h.
Отличительной и не очень приятной особенностью MS Windows является отсутствие информации о возможных кодах ошибки для конкретных функций. Известный специалист и один из редакторов журнала "Microsoft Systems Journal" Дж. Рихтер в книге [13] пишет буквально следующее: "Время от времени меня кто-нибудь да спрашивает, составит ли Microsoft полный список кодов всех ошибок, возможных в каждой функции Windows. Ответ: увы, нет. Скажу больше, такого списка никогда не будет – слишком уж сложно его составлять и поддерживать для все новых и новых версий системы. Проблема с подобным списком еще и в том, что вы вызываете одну API-функцию, а она может обратиться к другой, та – к третьей и т.д. Любая из этих функций может завершиться неудачно (и по самым различным причинам). Иногда функция более высокого уровня сама справляется с ошибкой в одной из вызванных ею функций и в конечном счете выполняет то, что Вы от нее хотели. В общем, для создания такого списка Microsoft пришлось бы проследить цепочки вызовов в каждой функции, что очень трудно. А с появлением новой версии системы эти цепочки нужно было бы пересматривать заново."
Числовые коды ошибок, возвращаемые функцией GetLastError(), достаточно сложно для разработчика соотнести с наименованием ошибки. Если требуется распознавание вида ошибки при автоматическом выполнении программы, то разработчики этой ОС предлагают для использования специальную функцию FormatMessage.
В конечном счете эта функция делает почти то же самое, что и функция strerror в Unix, но сложность использования функции FormatMessage неизмеримо больше. Прежде всего она имеет прототип
DWORD FormatMessage(DWORD dwFlags, LPCVOID lpSource,
DWORD dwMessageId, DWORD dwLanguageId,
LPTSTR lpBuffer, DWORD nSize, va_list *Arguments).
Здесь не будет рассматриваться все многообразие ее возможностей, которые в большинстве ситуаций излишни (от функции требуется лишь выдать текст, называющий причину ошибки). Главным по значению является третий ее аргумент dwMessageId, который и задает распознаваемый код ошибки. Если не использовать "хитрых" возможностей динамического выделения памяти под буфер текста, то адрес буфера для текста задается в аргументе lpBuffer, а его размер в аргументе nSize. Важным в применении является и первый аргумент, который дает возможность использовать не только системные, но и пользовательские коды ошибок. Для системных ошибок в аргументе dwFlags должно присутствовать логическое слагаемое FORMAT_MESSAGE_FROM_SYSTEM. Мощные потенциальные возможности заложены в аргументе dwLanguageId, призванном обеспечить многоязыковую поддержку сообщений, но к настоящему моменту для России работающим является только простейшей вариант языка по умолчанию. (В Windows 9x неправильно функционирует даже вариант, принудительно запрашивающий англоязычный американский текст.) Работающий вариант задается макросом MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT). Оставшиеся без рассмотрения аргументы следует брать нулевыми. В результате типовой вызов данной функции имеет вид
len=FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
NULL, k, // k - номер ошибки, возвращенный функцией GetLastError()
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
txtmess, sizeof(txtmess), NULL);
где имя txtmess определено предварительно в описании вида char txtmess[300]. Возвращаемое функцией значение дает число символов в тексте, сформированном ею в заданном буфере.
Функция FormatMessage обладает возможностью возвращать порядка тысячи различных наименований ошибок на языке текущей локализации (т.е. при использовании русифицированной версии Windows – на русском языке). Заметим кстати, что в более точных терминах программный интерфейс MS Windows называется Win32. Существенной особенностью рассматриваемой функции оказывается использование возвращаемых текстов сообщений об ошибках, представленных в кодировке для графического режима. Исторически сложилось так, что тексты, записанные не с помощью латинского алфавита, имеют различное представления в графическом и текстовом режиме. (Для англоязычных стран и пользователей эта особенность совершенно незаметна и часто может быть даже неизвестной.)
Для других стран профессиональное использование программирования для ОС типа Windows требует учета указанной особенности. Решение возникающих при этом проблем обеспечивается парой функций преобразования из одной формы представления в другую. Эти функции задаются упрощенными прототипами
BOOL CharToOem(char *textsource, char *textresult),
BOOL OemToChar(char *textsource, char *textresult).
При использовании указанных функций следует иметь в виду, что разработчики условным буквосочетанием Char в названии функций обозначают кодировку графического режима, а обозначением Oem в названии – кодировку текстового режима. Таким образом функция CharToOem задает преобразование текста из кодировки графического режима Windows в текстовый режим, а функция OemToChar – преобразование текста из кодировки текстового режима в графический режим.
Наиболее совершенные средства обработки ошибок были заложены в OS/2, но рассматривать мы их не будем.