Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
1. Понятие операционной системы (ОС), ее отношение к аппаратному и программному обеспечению
Операционной системой (ОС) называют комплекс программ, обеспечивающих взаимодействие прикладных программ и пользователей с аппаратурой компьютера. Аппаратура современных компьютеров настолько сложна и многофункциональна, что управление ею непосредственно из прикладной программы катастрофически сложная задача. Не касаясь малопонятных в начальном знакомстве проблем, можно отметить сложность программного управления современными внешними устройствами. Это управление требует понимания действий десятков управляющих кодов отдельного устройства. В конечном счете, требует глубокого понимания функционирования сложного электронного или электромеханического устройства. Ясно, что решение соответствующих задач управления прикладному программисту и пользователю приходится перекладывать на разработчика операционной системы.
2. Состав и основные функции ОС.
Среди многообразия функций операционной системы можно выделить главные:
1) обслуживание информации, хранимой в компьютере;
2) программное управление устройствами компьютера;
3) обеспечение простого информационного диалога компонентов ОС с пользователем или прикладной программой;
4) обеспечение эффективного использования вычислительной системы путем совместного задействования общих ресурсов или одновременного выполнения нескольких прикладных программ.
3. Классификация ОС.
По причине небольшого числа используемых операционных систем классификация ОС основана на наличии или отсутствии существенных особенностей из небольшого набора последних.
По возможности обеспечивать одновременную работу многих пользователей различают однопользовательские и многопользовательские ОС.
По способности выполнять одновременно более одной задачи их классифицируют как многозадачные или однозадачные.
По числу процессоров, управляемых ОС, различают однопроцессорные и многопроцессорные.
Относительно числа отдельных компьютеров ранее различали одномашинные и многомашинные ОС. К настоящему времени термин многомашинные ближе всего по значению к признаку сетевые ОС, хотя исторически и не совпадает с ним.
Кроме того, различают аппаратно зависимые, в более позднем обозначении зависимые от платформы операционные системы, и мобильные ОС.
Наиболее общим делением является разбиение на универсальные и специализированные. Последние по существу почти полностью совпадают с управляющими ОС. Основной задачей управляющих является обслуживание аппаратных и технологических комплексов в автоматическом режиме. Поэтому характерными особенностями таких ОС является включение в них средств, обеспечивающих особо высокую надежность функционирования и быстроту реакции на отдельные виды внешних событий, воздействующих на вычислительную аппаратуру электрическими сигналами специального назначения. Конструирование управляющих ОС является предметом особого внимания разработчиков современного технологического оборудования, военной и космической техники.
До недавнего времени ОС по типу используемого в них интерфейса с пользователем разделяли на текстовые и графические. К настоящему времени все современные ОС включают графический интерфейс либо как основной (в системах фирмы Microsoft), либо как дополнительную интерфейсную оболочку (в системах Unix).
4. Понятия дескрипторов и хэндлов (handle).
Управляющие блоки размещаются в служебной области операционной системы, причем, как правило, в виде таблицы, и для доступа к ним используется номер строк такой таблицы (порядковый номер управляющего блока во внутренней таблице управляющих блоков для выполняемой программы). Этот номер строк таблицы управляющих блоков и называют handle.
Иным наименованием для хэндла, использовавшимся как раз в Unix, служит слово дескриптор.
Термин "дескриптор", хотя и является традиционным в операционной системе Unix, на самом деле вызывает ложные аналогии. В действительности его целесообразней применять как синоним управляющего блока, так как именно в последнем содержится учетная информация, описывающая файл и необходимая для выполнения файловых операций. Для операций над файлом при обращении к системной функции в качестве одного из параметров вызова используется значение такого хэндла. По этому значению подпрограмма ОС обращается к соответствующей строке таблицы управляющих блоков и использует информацию из управляющего блока, отвечающего этой строке.
Хэндл просто некоторый специальный номер для выполнения операций с конкретным открытым файлом. Более существенным его пониманием, важным для более сложных ситуаций использования, должно быть осознание, что на самом деле этому номеру хэндлу однозначно соответствует строка специальной таблицы управляющих блоков, которая и дает необходимую информацию для оперативной работы с файлом.
Хэндлы различных по содержанию и собственному названию файлов могут быть одинаковыми. Причем одинаковыми в один и тот же момент работы компьютера. Такие ситуации имеют место, когда в компьютере выполняется несколько программ одновременно (несколько программ начали свое выполнение, но еще не завершились). В этих выполняемых программах очень часто оказываются одинаковые значения хэндлов.
5. Ввод и вывод в стандартные файлы.
При начальном знакомстве с новой операционной системой, поддерживающей соглашения о стандартном вводе и выводе на основе фиксированных хэндлов для целей ввода и вывода текстов достаточно познакомиться только с системными функциями чтения из файлов и записи в файл.
В Unix для этих целей предназначены универсальные системные функции с прототипами
unsigned int read(int handle, void* buffer, unsigned int len),
unsigned int write(int handle, void* buffer, unsigned int len).
В операционных системах клонов Windows использование стандартных файлов для программиста значительно усложняется. Дело в том, что эти операционные системы не предоставляют автоматически в программу фиксированные при разработке значения хэндлов для стандартного ввода и вывода. При необходимости программист может запросить такие значения принудительно, причем заранее неизвестны числовые значения этих хэндлов. Для такого запроса служит специальная функция API Windows, имеющая прототип
HANDLE GetStdHandle(DWORD nStdHandle),
После получения значений хэндлов для стандартного ввода и вывода их можно использовать в универсальных системных функциях файлового ввода и вывода для этой операционной системы. Такие функции имеют здесь прототипы
BOOL WINAPI ReadFile(HANDLE hFile, void* buffer, DWORD len, DWORD *pactualen, OVERLAPPED *pOverlapped);
BOOL WINAPI WriteFile(HANDLE hFile, void* buffer, DWORD len, DWORD *pactualen, OVERLAPPED *pOverlapped),
При использовании в программах на языке Си стандартного вывода нужно учитывать следующую существенную особенность. Ряд функций вывода из стандартной библиотеки языка Си используют так называемый буферизованный вывод. К этим функциям относятся все те программные функции, которые явно или неявно используют для обозначения файлов указатели типа FILE*. В частности, к ним относятся функции printf() и fprintf().
6. Базовые средства использования файловой системы.
Для выполнения операций над файлом, если он отличен от стандартного, файл необходимо открыть.
Рассмотрим вначале базовые средства операционных систем типа MS Windows.
Для получения хэндла открываемого файла в них используется функция CreateFile, которая предназначена и для собственно создания и, в частности, для открытия уже существующего файла.
На языке Си прототип функции функция CreateFileA записывается в виде
HANDLE CreateFile(LPCSTR pFileName имя открываемого файла, DWORD DesiredAccess код доступа (GENERIC_READ(WRITE)), DWORD ShareMode, LPSECURITY_ATTRIBUTES pSecurityAttributes, DWORD CreationDisposition, DWORD FlagsAndAttributes, HANDLE hTemplateFile),
Для закрытия файла используется функция CloseHandle, назначение которой значительно шире, чем просто функций закрытия файла в других ОС. Функция эта имеет прототип
BOOL CloseHandle(HANDLE hObject),
где хэндл его управляющего блока для закрытия доступа к файлу должен задаваться в качестве аргумента функции. Логическое значение, возвращаемое функцией, позволяет определить, удалось ли закрыть хэндл.
В операционной системе Unix для открытия файла служит функция с прототипом
int open(char* filename, int access_mode, mode_t permission),
Для закрытия файла в Unix служит функция с прототипом
int close(int handle).
7. Переназначение стандартных устройств.
8. Особенности работы с файлами в многопрограммной системе.
9. Многопользовательская блокировка файлов.
Непосредственное использование в Unix только функций открытия, закрытия, записи в файл и чтения из файла не обеспечивает полных возможностей совместного использования файлов. Для управления совместным доступом к открытым файлам Unix содержит дополнительные средства, обеспечиваемые через системную функцию fcntl. Функция эта имеет прототип
int fcntl(int handle, int cmd, struct flock *ldata).
В пояснение описанных деталей отметим, что средства Unix позволяют управлять совместным доступом не только ко всему файлу, но и к отдельным участкам его. В частности, разрешить другим процессам только читать отдельные участки файла, а другим, может быть, разрешить изменять их.
В операционных системах Windows для блокировки участков файлов предназначена функция, имеющая прототип
BOOL LockFile(HANDLE hFile, DWORD FileOffsetLow, DWORD FileOffsetHigh, DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh).
Данная функция при удачном выполнении блокирует доступ к указанному в ней участку для всех других процессов.
Обратной к функции LockFile является системная функция UnlockFile, которая имеет прототип
BOOL UnlockFile(HANDLE hFile, DWORD FileOffsetLow, DWORD FileOffsetHigh, DWORD cbUnlockLow, DWORD cbUnlockHigh). Данная функция снимает блокировку с указанного в ней участка, давай тем самым свободный доступ к нему, если это конечно согласуется с общими режимами доступа для других процессов.
BOOL LockFileEx и BOOL UnlockFileEx
По существу эти расширенные функции очень похожи на только что рассмотренные, но позволяют более сложное управление блокировкой за счет параметра Flags, в котором может находиться одна из следующих констант:
LOCKFILE_FAIL_IMMEDIATELY
LOCKFILE_EXCLUSIVE_LOCK или их объединение. Первая из этих констант задает немедленное возвращение из системной функции при невозможности установить блокировку. При отсутствии этой константы происходит переход к состоянию ожидания, если заданный участок или его часть уже заблокированы другим процессом. Вторая константа дополнительно задает исключающую блокировку. При отсутствии последней константы блокировка является разделяемой между процессами, т.е. может быть установлена более чем одним процессом.
10. Функции управления курсором.
Классические языки высокого уровня не содержат средств управления позицией вывода на экране и цветом символов текста. Такая ситуация в значительной степени предопределена тем, что когда создавались эти языки, подобные средства были недоступны по аппаратным причинам. Именно глубокая связь упомянутых возможностей с конкретной аппаратурой и привела к тому, что подобные средства оказались зависимыми от операционной системы. К настоящему времени подавляющее большинство мониторов поддерживают как позиционирование курсора, так и многоцветные изображения, но особенности управления этими возможностями по-прежнему оказываются зависимыми от операционных систем.
Позиционирование курсора в ОС Windows. Здесь функция позиционирования имеет прототип
BOOL SetConsoleCursorPosition(HANDLE hConsOut, COORD pos).
Windows вынуждает предварительно описать экземпляр структуры типа COORD, например в виде
COORD pos;
а затем задать запись целых трех операторов
pos.X=5; pos.Y=3;
SetConsoleCursorPosition(hout, &pos).
В операционной системе Unix для управления курсором и некоторых других действий с экраном предназначены управляющие последовательности.
Для задания установки курсора служит управляющая последовательность, записываемая на языке Си как текстовая константа
"\033[строка;столбецH"
Здесь компоненты строка и столбец должны быть обязательно заданы десятичными числами и обязательно без дополнительных пробелов.
11. Многократный вывод символов и атрибутов.
В современных ОС имеются специальные функции для многократного вывода однотипной информации, иногда облегчающие программисту его работу. Рассмотрим вначале средства многократного вывода одного символа.
Для многократного вывода одного символа в Windows предназначена функция FillConsoleOutputCharacter, а для многократного вывода одного и того же атрибута функция FillConsoleOutputAttribute. Эти функции имеют прототипы
BOOL FillConsoleOutputCharacter(HANDLE hConsOut, CHAR character, WORD len, COORD pos, DWORD* actlen);
BOOL FillConsoleOutputAttribute(HANDLE hConsOut, WORD attr, DWORD len, COORD pos, DWORD* actlen).
В Windows имеется еще одна возможность установки цвета для вывода на экран консоли. Ее предоставляет функция с прототипом
BOOL SetConsoleTextAttribute(HANDLE houtput, WORD attrib).
Эта функция устанавливает цвет, автоматически используемый далее при выводе на экран функцией WriteFile, а также используемый при отображении символов в процессе ввода функцией ReadFile.
В операционной системе Unix отсутствуют системные функции многократного вывода символов и атрибутов. Желающие, впрочем, легко могут написать их для себя, опираясь на стандартные средства этой операционной системы.
Проблема использования цвета в Unix была решена с помощью управляющих последовательностей. Для задания цвета используются управляющие последовательности, последним символом которых служит латинская буква m. Сама управляющая последовательность имеет вид
esc[цветm
Цвет, установленный управляющей последовательностью, действует до тех пор, пока другой подобной последовательностью он не будет переустановлен.
12. Вывод в произвольную позицию экрана.
В операционной системе Windows для вывода строки текста в произвольную позицию экрана служит функция
BOOL WriteConsoleOutputCharacter(HANDLE hConsOut, CSTR* text, DWORD len, COORD pos, DWORD* actlen).
Параметры, используемые в функции WriteConsoleOutputCharacter, имеют те же назначения, что и одноименные описанные ранее в предыдущих функциях. Парное сочетание функций FillConsoleOutputAttribute и WriteConsoleOutputCharacter характерно для консольных приложений Windows, в которых требуется выполнять вывод с новым цветом в непоследовательных позициях экрана.
Заметим, что в Windows нет системной функции, объединяющей указанные действия функций FillConsoleOutputAttribute и WriteConsoleOutputCharacter.
В операционной системе Windows рассмотренной функции вывода символов вместе с их атрибутами соответствует функция
BOOL WriteConsoleOutput(HANDLE hConsOut, CHAR_INFO* cells, COORD dwBufferSize, COORD dwBufferCoord, SMALL_RECT* rect).
Возможности этой функции значительно шире, чем будет нами рассмотрено, и позволяют вернуть на экран, не обязательно в то же его место, запомненный ранее в памяти фрагмент текстового изображения экрана с полным учетом цветовых атрибутов.
В операционных системах Windows имеется функция для вывода на экран. Это функция WriteConsoleOutputAttribute, предназначенная для вывода последовательности различных атрибутов на экран.
В операционной системе Unix для вывода строки текста с различными атрибутами следует использовать управляющие последовательности между отдельными выводимыми символами:
"\033[1;31;44mП\033[0;32;40mр\033[1;36;40mи\033[33;44mв\033[31;42mе\033[34;43mт\n"
В этой последовательности комбинируются отдельные символы текста вместе с требуемыми для них атрибутами.
13. Событийное программирование и его использование в функциях ввода.
Поведение современных программных систем, активно взаимодействующих с пользователем, определяется не столько последовательностью операторов в исходной программе, сколько внешними воздействиями на компьютер. Такими воздействиями являются нажатия клавиш клавиатуры, перемещение мыши и нажатие ее кнопок, а также более сложные действия, реализуемые обычно также с помощью мыши. Примером такого сложного действия является перетаскивание какого-то графического объекта на экране с помощью мыши (dragging).
Такая реализация потребовала отказа от безоговорочного следования за идеей алгоритма (как последовательности действий предварительно и строго описанной). По существу она оказалась возможной на основе сложного аппаратно-программного механизма, встроенного во все современные компьютеры и называемого механизмом прерываний.
Разработка программ обработчиков прерываний очень не простая задача и поэтому ее выполняют разработчики операционных систем. Остальным предоставляется неявно пользоваться этим механизмом. Его использование в прикладных программах основывается на специализированных структурах данных о внешних по отношению к программе событиях.
Для текстовой консоли Windows, по замыслу разработчиков, теоретически возможны сообщения от нажатия клавиши клавиатуры, от мыши, сообщения об изменении размера окна, о переходе активности к текущему окну или о потере такой активности. Свойство активности визуально отражается изменением цвета заголовка окна и содержательно состоит в том, что только активное окно получает данные от клавиатуры.
В Windows программа для текстового окна может запросить сообщение путем вызова системной функции ReadConsoleInput. Эта функция имеет прототип
BOOL ReadConsoleInput(HANDLE hConsInput, INPUT_RECORD* buffer, DWORD len, DWORD* actlen).
14. Средства чтения содержимого экрана в Windows.
В Windows для чтения информации с экрана имеется даже три функции. Это функция ReadConsoleOutputCharacter для чтения с экрана только собственно символов текста, функция ReadConsoleOutputCharacter для чтения с экрана символов текста вместе с их атрибутами и функция ReadConsoleOutputAttribute для чтения из последовательных позиций экрана атрибутов символов. Перечисленные функции имеют следующие прототипы:
BOOL ReadConsoleOutputCharacter(HANDLE hConsOut, STR* buffer, DWORD len, COORD dwReadCoord, DWORD* actlen);
BOOL ReadConsoleOutput(HANDLE hConsOut, CHAR_INFO* cells, COORD dwBufferSize, COORD dwBufferCoord, SMALL_RECT* rect);
BOOL ReadConsoleOutputAttribute(HANDLE hConsOut, WORD* attribs, DWORD len, COORD pos, DWORD* actlen).
Вторая из перечисленных функций является обратной к функции с именем WriteConsoleOutput, которая в свою очередь предназначена для размещения на экране информации из двухмерного массива элементов, состоящих из пары (символ, его атрибут). Функция ReadConsoleOutputCharacter выполняет чтение только символов в буфер программы с именем buffer (или по адресу, который дает указатель buffer в зависимости как конкретно определено это имя), а функция ReadConsoleOutputAttribute выполняет чтение только атрибутов в массив слов, указанный параметром attribs. Число читаемых при этом символов или атрибутов задается параметром len, чтение выполняется с начальной позиции экрана, задаваемой параметром с именем pos, а действительное число прочитанных символов или атрибутов возвращается с помощью параметра actlen.
15. Средства чтения содержимого экрана в Unix.
В операционной системе Unix отсутствуют стандартные средства чтения символов с экрана. Это объясняется тем, что основные принципы работы с Unix формировались еще в то время, когда основным средством вывода информации для отдельного пользователя интерактивной ОС были телетайпы, осуществляющие вывод на бумажную ленту, считывать информацию с которой было невозможно по техническим причинам. Как показывает опыт работы в этой ОС, без средств чтения информации с экрана вполне можно обойтись без снижения потребительских возможностей операционной системы и прикладных программ.
В то же время современные версии Unix, в частности Linux, ориентируясь на возможности современных электронных устройств вывода информации пользователю, предлагают дополнительные виртуальные устройства. Одним из таких устройств является "виртуальный экран консоли", обозначаемый в файловой системе Unix как vcs (сокращение от virtual consoe screen). В связи с замечательными особенностями файловой системы Unix устройства в ней рассматриваются как файлы, только специализированные. Это позволяет использовать операцию открытия файлов для доступа к устройствам. Заметим, что специальные файлы устройств стандартным образом размещаются в каталоге /dev, поэтому полное наименование устройства виртуального экрана консоли следует задавать в виде /dev/vcs.
16. Перенастройка консоли Unix для немедленного ввода нажатий отдельных клавиш.
17. Программные средства использования мыши в текстовом режиме Windows.
В операционной системе Windows, как уже пояснялось, информация от мыши получается в результате универсального запроса событий для текстовой консоли посредством вызова функции ReadConsoleInput. При получении указанной функцией сообщения от мыши, поле EventType этого сообщения оказывается равным константе MOUSE_EVENT.
Поле dwEventFlags позволяет определить двойное нажатие мыши и двигалась ли мышь в момент формирования сообщения. Для представления этой информации отдельными битами в данном поле служат следующие символические константы
#define MOUSE_MOVED 0x0001
#define DOUBLE_CLICK 0x0002
18. Программные средства использования мыши в текстовом режиме Linux.
Для использования мыши в Linux служит программная подсистема gpm. Построена она по технологии клиент-сервер. Основой подсистемы служит сервер мыши. В некоторой степени это аналог драйвера мыши в других системах, но здесь последовательно проводится принцип независимости обслуживающей программы как совершенно отдельного процесса, предназначенного для выполнения вычислительных услуг, которые специальными сообщениями запрашивают другие процессы.
Перед началом использования мыши процесс должен зарегистрироваться у сервера и передать ему информацию, какие сообщения от мыши интересуют этот процесс. Получаемые сообщения от мыши процесс-клиент может обрабатывать как полностью сам, так и передавать другой процедуре обработчика сообщений от мыши.
Для регистрации у сервера программа процесса должна заполнить структуру Gpm_Connect и выполнить вызов функции Gpm_Open с этой структурой в качестве аргумента. Функция Gpm_Open имеет прототип
int Gpm_Open (Gpm_Connect *CONN, int flag).
После удачной регистрации программа клиента может запрашивать и получать сообщения от мыши, для этого служит функция Gpm_GetEvent с прототипом
int Gpm_GetEvent (Gpm_Event *EVENT)
Поля x, y этой структуры дают координаты позиции курсора мыши на момент формирования сообщения. Поле buttons выдает код нажатия клавиш и является логической комбинацией следующих констант:
#define GPM_B_LEFT 4 // Левая клавиша мыши
#define GPM_B_MIDDLE 2 // Средняя клавиша мыши
#define GPM_B_RIGHT 1 // Правая клавиша мыши
После завершения использования мыши клиентом следует вызвать функцию Gpm_Close(), которая разрывает связь клиента с сервером. Функция эта не имеет аргументов. Заголовочным файлом для программ с использованием подсистемы gpm служит файл gpm.h.
При разработке программ, использующих подсистему gpm, следует иметь в виду необходимость явного подключения библиотеки поддержки этой подсистемы. Такое подключение может задаваться в командной строке дополнительным параметром полного имени библиотеки
Другим, более компактным вариантом, является использование вспомогательной опции задания дополнительной библиотеки.
gcc -o prim1.exe prim1.c -lgpm
19. Понятие процесса; параллельные и последовательные процессы.
В профессиональном программировании процессом называют действия, выполняемые над конкретными данными под управлением программы. Следует отметить существенное отличие процесса от программы и даже от выполнения программы. Процесс разворачивается во времени, это динамический объект, программа это статический, неизменяемый объект. Замечательной особенностью современных операционных систем является их способность обеспечивать одновременное выполнение нескольких программ.
Реентерабельной называют программу, которая обеспечивает правильное функционирование множества одновременных процессов, функционирующих на ее основе.
Процессы называются одновременно выполняющимися или параллельными, если каждый из них начат, но не завершен. Процессор компьютера может быстро переключаться (с помощью операционной системы и средств системной архитектуры) с одного процесса на другой, запоминая при этом переключении всю необходимую информацию, чтобы в дальнейшем продолжить выполнение процесса с места его приостановки.
Переключение между процессами происходит как независимо от их собственных действий, так и в результате ряда запросов к ОС. Первый вариант имеет место, когда ОС решает, что выполняемый процесс уже достаточно долго использует аппаратуру процессора и, руководствуясь управляющей программой, передает использование процессора другому процессу. Простейший запрос к ОС, приводящий к переключению процессов, это запрос на ввод информации. Запросив, например ввод символа с клавиатуры, процесс вынужден ждать, пока пользователь соберется нажать на клавиши. Для клавиатуры такое ожидание может продолжаться очень долго не только для временного масштаба действий внутри компьютера, но и с точки зрения человека.
После порождения некоторым процессом другого процесса, первый из них становится родительским (parent), а порожденный дочерним (child). При порождении одним процессом многих дочерних возникают связи между процессами, отражающие эти отношения parent-child. Аналогично естественной иерархии родства, между процессами компьютера образуется при этом иерархия связей parent-child. Она используется для организации совместных работ многими процессами. При этом в простейшем случае процесс, становящийся родителем, порождает процесс для выполнения некоторых вычислительных работ, а сам при этом приостанавливается. Его дочерний процесс выполняет запланированную работу и завершает свои действия специальным обращением к операционной системе, информирующей последнюю об успешном или неуспешном завершении. В ответ на это обращение операционная система переводит приостановившийся родительский процесс опять в функционирующее состояние. При таком порядке выполнения родительского и дочернего процессов их называют последовательными процессами.
20. Состояния процесса, диспетчеризация процессов.
Для технической организации переключения процессов очень важное значение имеет характеристика, которую называют состоянием процесса. Эта характеристика записывается как одно из важнейших полей в управляющий блок процесса или управляющий блок нити, в зависимости от того, используются нити в операционной системе или нет. Для большей общности изложения и для его некоторого сокращения будем называть абстрактным процессом нить, если ОС допускает использование нитей, и обычный процесс в противном случае. Абстрактный процесс обязательно использует как минимум три различных состояния. Эти состояния называют состоянием готовности (ready), состоянием выполнения (run) и состоянием блокировки или заблокированности (blocked). В состоянии выполнения процессор выполняет команды (операторы) именно этого абстрактного процесса.
Рис. 7.1. Диаграмма состояний абстрактного процесса
Функционирование операционной системы, когда она обеспечивает выполнение параллельных процессов на основе использования квантов времени, называют режимом разделения времени.
Заметим, что, так как абстрактных процессов, находящихся в состоянии готовности, как правило, более одного, то все они организуются в одну или более очередей, описываемых как связные списки. Поле связи для такого списка также входит в состав управляющего блока абстрактного процесса. Действия операционной системы по установке на процессор и снятии с процессора в связи с истечением кванта выполняет компонент ОС, называемый диспетчером или планировщиком задач. Сами действия этого диспетчера называют диспетчеризацией абстрактных процессов.
21. Виды программных единиц в современных ОС.
22. Программное порождение процессов в Unix.
Процесс не программа, но без программы, которая определяет действия процесса, он функционировать не может. Поэтому при создании процесса, в частности путем заявки другого процесса на создание процесса, как минимум необходимо дать ОС информацию, какой программой воспользоваться при создании и запуске процесса. Заметим, что в дальнейшем процесс может сменить используемую программу соответствующим системным вызовом.
Оригинальное и очень изящное решение для создания процессов принято в Unix. Для создания процесса здесь служит системная функция fork(), которая даже не имеет аргументов!
Заметим, что кроме одной текстовой строки аргументов в ОС, начиная с Unix родоначальницы всех современных ОС, представляется возможность применения так называемого окружения (environment) процесса. Окружение процесса (называемое также средой процесса) это специальная текстовая область, служащая для передачи данных создаваемому процессу. В Unix она предназначена в первую очередь для передачи настроек программы от операционной системы или командной оболочки.
23. Программное порождение процессов в Windows.
Процесс не программа, но без программы, которая определяет действия процесса, он функционировать не может. Поэтому при создании процесса, в частности путем заявки другого процесса на создание процесса, как минимум необходимо дать ОС информацию, какой программой воспользоваться при создании и запуске процесса. Заметим, что в дальнейшем процесс может сменить используемую программу соответствующим системным вызовом.
Для ОС семейства Windows характерны наиболее сложные функции из рассматриваемых функций создания процессов. Порождение нового процесса достигается в этих ОС функцией CreateProcess с десятью параметрами, имеющей прототип:
BOOL CreateProcess(LPCTSTR pNameModule, LPCTSTR pCommandLine, SECURITY_ATTRIBUTES *pProcessAttr, SECURITY_ATTRIBUTES *pThreadAttr, BOOL InheritFlag, DWORD CreateFlag, LPVOID penv, LPCTSTR pCurrDir, STARTUPINFO *pstartinfo, PROCESS_INFORMATION *pProcInfo).
24. Программное уничтожение процессов.
Процесс не просто понятие для изучения, а конкретный информационный объект в операционной системе. Над информационными объектами, находящимися в компьютере, естественными оказываются операции, которые осуществляют действия целиком над каждым таким объектом. Для вычислительных процессов оказалось возможным выполнять следующие операции: создание, уничтожение и ожидание завершения. Эти операции далее рассматриваются более детально. Вначале рассмотрим смысловую альтернативу создания процесса операцию уничтожения имеющегося процесса.
При построении приложений на основе нескольких процессов может возникнуть необходимость принудительно и окончательно прекратить функционирование некоторого другого процесса. Процесс-родитель может уничтожить процесс, который он создал, используя соответствующий системный вызов и идентификатор этого процесса.
Особенно велика потребность в функции уничтожения процессов внутри Unix, где права пользователя-администратора настолько широки, что ему следует позволить уничтожать любые процессы. В Unix для уничтожения процессов предназначена функция kill(), возможности которой даже несколько шире, чем только уничтожение процесса. Непосредственно для уничтожения процесса ее употребление имеет вид
kill(pid, SIGKILL)
Для уничтожения процессов в операционных системах Windows предназначена функция TerminateProcess(), которая имеет прототип
BOOL TerminateProcess(HANDLE hProcess, UINT uExitCode).
25. Ожидание завершения процессов.
Последней из операций, которую "удалось придумать" для информационных объектов-процессов, является ожидание завершения процесса. Следует обратить внимание, что эта операция в действительности чрезвычайно ценная, так как обеспечивает гарантированное использование результатов процесса родительским процессом.
Из содержательных соображений непосредственно вытекает, что такая операция должна задаваться информацией вида
ждать_завершения_процесса(какой_процесс_ждать),
кроме того, в вызов этой функции может быть включен возвращаемый параметр, который позволяет получить код завершения процесса, обычно формируемый функцией exit(код_возврата).
В операционной системе Unix имеется ранний вариант функции ожидания wait() с прототипом
pid_t wait(int *status).
Эта функция переводит текущий процесс в состояние ожидания до тех пор, пока не завершится какой-то из его дочерних процессов.
В операционных системах Windows для ожидания завершения процесса применяется универсальная функция ожидания WaitForSingleObject, имеющая прототип
DWORD WaitForSingleObject(HANDLE hProcess, DWORD Timeout).
26. Иерархия процессов и ее использование.
27. Понятие нити и связь ее с процессом.
В современных операционных системах широко используются нити (thread), называемые несколько неточно в русском переводе также потоками. Это понятие возникло как результат развития понятия абстрактного процесса. Иногда целесообразно процесс разбить на части, которые выполнялись бы параллельно, конкурируя за главный ресурс процессор, но в остальном выполняли бы общую работу.
Процесс в современных ОС это владелец всех основных ресурсов кооперативно работающих нитей. У этих нитей общее виртуальное адресное пространство, у них общие дескрипторы и соответствующие им хэндлы, причем дескрипторы и хэндлы всевозможных допустимых в ОС объектов: файлов, созданных дочерних процессов, графических окон, семафоров и т.п.. Практически нити одного процесса используют глобальные переменные, в которые каждая из нитей может записывать данные или брать их оттуда. Нити пользуются процессором компьютера по очереди, так что он не принадлежит ни одной из них, но и не принадлежит самому процессу.
Обычно нить своей работой реализует действия одной из процедур программы. Теоретически любой нити процесса доступны все части программы процесса, в частности, все его процедуры, но реально работа организуется так, чтобы нити отвечала отдельная процедура. Учитывая, что процедуре для нормальной работы необходимы локальные переменные, становится понятным закрепление области этих переменных за нитью.
28. Создание нитей (thread) в программах Windows.
Наиболее сложная ситуация оказывается с порождением нитей в операционных системах Windows. И дело все в том, что для порождения нитей в этих системах имеется не одна функция, а несколько. Самой ранней является системная функция CreateThread, но в ее реализации при использовании на языке программирования Си позже были обнаружены некоторые проблемы некорректного поведения и было предложено заменить ее функцией _begintread.
К настоящему времени наиболее безопасными функциями порождения нитей являются _beginthreadNT и _beginthreadex, именно их и рассмотрим в первую очередь, предлагая для ближайшего использования.
Функция _beginthreadex от Microsoft имеет прототип
unsigned long _beginthreadex(void* security_attrib, unsigned stack_size, unsigned (*proc)(void*), void *arglist, unsigned flag, unsigned *tid).
Функция _beginthreadNT имеет прототип
unsigned long _beginthreadNT(void (*proc)(void *), unsigned stack_size, void *arglist, void *security_attrib, unsigned long flags, unsigned long *tid).
Процедура нити, используемая функциями _beginthreadex и _beginthreadNT, может для завершения своих действий использовать системный вызов функции _endthreadex() или соответственно _endthread().
Исходной системной функцией для построения всех описанных функций создания нитей в Windows служит функция CreateThread, которой можно пользоваться в тех программах, процедуры задания нитей которых не содержат стандартных функций базовой библиотеки языка Си и неявно не используют их. Функция CreateThread имеет прототип
HANDLE CreateThread(SECURITY_ATTRIBUTES *security_attrib, DWORD stack_size, THREAD_START_ROUTINE *proc, VOID *arglist, DWORD flags, DWORD * tid).
В программах для Windows при выводе на консоль несколькими нитями возможно нежелательное взаимное влияние их друг на друга, выражающееся в искажении выводимой информации.
29. Создание POSIX нитей в программе.
Главная нить процесса создается автоматически при создании процесса. Если процесс нуждается в дополнительных нитях, то его программа вызывает системные функции создания нити.
В операционной системе Unix многопоточное программирование появилось достаточно поздно. К настоящему времени эта возможность входит в стандарт POSIX для Unix и поддерживается во всех современных ОС. Использование нитей при этом требует подключения заголовочного файла pthread.h. Системная функция создания нити в Unix по указанному стандарту имеет прототип
int pthread_create(pthread_t* tid, const pthread_attr_t* att, void*(*fun)(void*), void* argp).
Если функция создания нити возвращает значение >0, то в ходе выполнения системной функции произошла ошибка, и нить не была создана. Эту ситуацию рекомендуется анализировать в программе. Нулевое возвращаемое значение соответствует безошибочному выполнению, а положительное значение выдает непосредственно числовое значение кода ошибки.
Действия процедуры нити могут завершаться специальной функцией pthread_exit(). Эта функция имеет прототип
int pthread_exit(void *status),
Указанная функция требуется только, если эта нить должна передавать код завершения или при задании завершения внутри программы процедуры. На конечном месте процедуры вызов функции завершения можно опускать.
30. Уничтожение (отмена) нитей.
Средства управления нитями лучше всего развиты в современных версиях Unix, в частности в Linux. Прекращение работы нити по явному приказу другой нити является довольно решительной мерой, учитывая, что нити процесса выполняют общую работу. Поэтому уничтожение нити в общем случае может иметь непредусмотренный побочный эффект на результаты работы всего процесса. Эти соображения привели в Unix к введению средств, гибко управляющих возможностью уничтожения нити. В настоящее время такие средства состоят из двух вспомогательных функций pthread_setcanceltype() и pthread_setcancelstate() и состояний нити относительно возможности уничтожения. Переключение между отменяемыми состояниями и неотменяемым производится с помощью функции pthread_setcancelstate(), имеющей прототип
int pthread_setcancelstate(int state, int *oldstate),
Переключение между асинхронно отменяемым и синхронно отменяемым состояниями нити задается функцией pthread_setcanceltype(), имеющей прототип
int pthread_setcanceltype(int type, int *oldtype),
В операционных системах Windows для уничтожения процессов служит системная функция TerminateThread, которая имеет прототип
BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode).
31. Приостановка и повторный запуск нити.
В операционной системе Unix для приостановки отдельной нити нужно использовать функцию pthread_kill со вторым параметром, задаваемым символической константой SIGSTOP, а для возобновления работы нити - ту же функцию, но уже с параметром SIGCONT.
Программные средства для приостановки и возобновления нитей в операционных системах типа Windows состоят из двух функций ResumeThread и SuspendThread. Обе они имеют единственный аргумент хэндл нити, на которую они должны подействовать приостановить или возобновить ее работу. Прототипы этих функций описываются в виде:
DWORD SuspendThread(HANDLE hthread);
DWORD ResumeThread(HANDLE hthread).
Использование этих функций не имеет никаких особенностей в сравнении с другими рассмотренными операционными системами.
Заметим, что в программе для управления поведением нитей используются их хэндлы, но никак не используются идентификаторы нитей. Практически в Windows всегда заметна некоторая избыточность средств, которая частично оправдывается большой суммарной сложностью системы.
32. Ожидание завершения нити.
Другой формой управления нитями внутри программы являются функции ожидания завершения нити. В определенной степени эта функция аналогична функциям ожидания окончания процессов, но относится к уровню нитей. В конечном счете, она предназначена для той же цели согласования совместной работы нитей на основе самого глобального по смыслу действия полного завершения работы отдельной выполняемой программной единицы.
В операционной системе Unix, поддерживающей стандарт POSIX, для ожидания нити служит функция pthread_join, имеющая прототип
int pthread_join(pthread_t tid, void** status).
В операционных системах Windows для ожидания завершения нити служит уже частично рассматривавшаяся универсальная функция ожидания WaitForSingleObject. Для ожидания завершения нити в качестве первого аргумента этой функции следует взять хэндл ожидаемой нити, а в качестве второго значение предельного времени ожидания, в простейшем случае INFINITE. Каких-либо особенностей, отличающих ее от других форм ожидания, эта функция не имеет при использовании для ожидания нити.
Считается, что разработка программ со многими нитями требует гораздо более высокой квалификации, чем обычное программирование. Отчасти это связано с инерционностью мышления программистов, сформировавшихся под влиянием идеи алгоритма. Напомним, что в алгоритме действия выполняются строго одно за другим, и никаких одновременных действий не допускается.
33. Абстрактные критические секции.
Если несколько процессов или нитей используют общие данные, то результат совместного использования этих данных может быть совершенно непредсказуем. Примером таких общих данных является использование общей для процессов очереди, куда эти процессы включают запрос на некоторое действие.
Введем некоторые термины, сложившиеся в теории параллельных процессов. Зависимость результатов процесса от непредусмотренного в программе взаимодействия с другими процессами называют состязанием процессов. Теоретическим средством устранить состязание между процессами является решение запретить прерывания, на основе которых организована параллельность процессов. Но такое решение запрещает (хотя бы на время) параллельное выполнение процессов, к тому же годится только для однопроцессорных систем.
Более общим понятием, чем общие данные является, понятие ресурса, которое кроме общих данных может быть и общедоступной аппаратурой. Участок программы, в котором нить производит обращение к общему (разделяемому с другими нитями) ресурсу, называется критическим интервалом нити.
Для устранения состязаний процессов (или нитей) используют монополизацию ресурса. Монополизация ресурса это временное предоставление его в исключительное использование одной нити. Критический интервал нити поэтому ограничивают с начала и конца специальными управляющими конструкциями операционной системы, в абстрактном изложении называемыми прологом и эпилогом критического интервала. Назначение этих конструкций обеспечить нахождение в критическом интервале только одной нити.
34. Абстрактные двоичные семафоры.
Общий подход к монополизации ресурсов с помощью специальных системных операций предложил голландский математик Е. Дейкстра. Этот подход состоит в построении и использовании семафоров. Семафор это защищенная от прямого доступа переменная, значения которой можно опрашивать и изменять только с помощью специальных операций P и V; кроме того, в самом начале к семафору применяется операция инициализации.
Простейшей формой семафоров являются двоичные семафоры, которые могут принимать только значения 0 и 1. Операции P и V над двоичными семафорами определяются следующим образом. Пусть S - двоичный семафор.
Для удобства и сокращения формулировок будем временно называть абстрактный процесс задачей.
Операция P(S) выполняет действия: если S равно 1, то занести в S значение 0, иначе БЛОКИРОВАТЬ нить по семафору S, и если очередь готовых задач не пуста, то УСТАНОВИТЬ на процессор задачу из этой очереди. Операция V(S) выполняет действия: если список задач, заблокированных по семафору S, не пуст, то ДЕБЛОКИРОВАТЬ задачу из этой очереди (т.е. перевести ее в очередь готовых задач), иначе занести в S значение 1.
Для двоичных семафоров значение семафора 1 обозначает, что ресурс, защищаемый семафором, свободен, а значение семафора 0 что ресурс занят.
35. Абстрактные семафоры с внутренним счетчиком.
Более сложной формой семафора является считающий, который по определению принимает любые неотрицательные значения. Операции P и V для считающего семафора S определяются следующими описаниями действий:
P(S) : если S > 0, то S S - 1, иначе WAIT(S);
V(S) : если список задач, заблокированных по семафору S, не пуст, то ДЕБЛОКИРОВАТЬ задачу из этой очереди (т.е. перевести ее в очередь готовых задач), иначе S S +1,
Эта форма семафоров обусловила название операций: P proberen (проверить), V verholen (увеличить) на голландском языке. Считающий семафор показывает число единиц свободного ресурса.
Считающий семафор может быть обобщен путем распространения его значений на отрицательные числа следующим определением операций P и V:
P(S) : S S - 1; если S < 0, то WAIT(S);
V(S) : S S +1; если S 0, то ДЕБЛОКИРОВАТЬ задачу из этой очереди (т.е. перевести ее в очередь готовых задач).
Обобщенный считающий семафор своим значением равен разности между числом единиц наличного ресурса и числом запросов на них. При S 0 значение такого семафора показывает число свободных единиц ресурса, при S < 0 число задач, заблокированных по семафору S.
36. Семафоры взаимоисключения в Windows.
Наиболее простой и естественной формой стали семафоры взаимоисключения (mutual exclusion semaphore), сокращенно именуемые (по сокращению из английского названия mutex) семафорами. Семафоры mutex отличаются от двоичных семафоров тем, что операцию V над семафором может осуществить только тот процесс, который выполнил предыдущую операцию P над ним без блокировки от такого действия. Для описания и использования этого ограничения вводится понятие владения семафором.
Операции, аналогичные абстрактным P и V, над семафорами взаимоисключения называются по-разному. Так, в операционной системе Windows запрос на владение таким семафором (аналогичный операции P) использует универсальную функцию ожидания любого объекта с именем WaitForSingleObject. Для аналога операции V в рассматриваемых ОС разработчиками приняты следующие названия: в Windows ReleaseMutex.
Семафор не просто какая-то специальная переменная, а системный объект, особым образом используемый операционной системой. Поэтому для его создания необходимо явное приказание операционной системе. Это приказание имеет имя CreateMutex в ОС Windows. В результате этих вызовов исполняемая программа (точнее, конкретный процесс, работающий по этой программе) получают хэндл семафора взаимоисключения в качестве результата вызова.
Имея хэндл, нить может использовать функции запроса владения и в дальнейшем освобождения семафора владельцем mutex.
После использования семафора в данном процессе его следует в Windows явно закрывать (закрывать доступ к нему) с помощью системной функции CloseHandle. Функция CloseHandle применяется Windows по завершении использования в процессе всевозможных объектов и формально закрывает хэндл объекта для прекращения возможности доступа к объекту.
В операционной системе Windows прототипы функций, предназначенных специально для работы с mutex-семафором, следующие:
HANDLE CreateMutex(SECURITY_ATTRIBUTES* MtxAttrs, BOOL bInitialOwner, STR* Name);
HANDLE OpenMutex(DWORD DesiredAccess, BOOL bInheritHandle, STR* Name);
BOOL ReleaseMutex(HANDLE hMutex).
Для запроса владения семафора Windows использует общую функцию ожидания WaitForSingleObject, а для его закрытия - общую функцию CloseHandle.
37. Семафоры взаимоисключения в POSIX.
В операционной системе Unix функции для работы с mutex-семафором имеют прототипы:
int pthread_mutex_init(ptread_mutex_t* hmtx, ptread_mutexattr_* pattr);
int pthread_mutex_lock(ptread_mutex_t* hmtx);
int pthread_mutex_trylock(ptread_mutex_t* hmtx);
int pthread_mutex_unlock(ptread_mutex_t* hmtx);
int pthread_mutex_destroy(ptread_mutex_t* hmtx)
Кроме рассмотренных функций работы с mutex-семафорами,в некоторых ОС присутствуют дополнительные функции, облегчающие программисту разработку программ с такими семафорами.
В Linux семафоры взаимоисключения предоставляют дополнительные возможности. Они связаны с попытками нити захватить семафор, уже принадлежащий ей. Эти возможности называют типами mutex семафоров. Имеются три таких типа: быстрый семафор, рекурсивный семафор и контролирующий семафор. Попытка захвата быстрого семафора приводит к блокировке нити, но такая же попытка для рекурсивного семафора не приводит к блокировке. При этом для освобождения mutex семафора оказывается нужным выполнить вызов функции pthread_mutex_unlock() столько раз, сколько перед этим выполнялось вызовов функции pthread_mutex_lock().
При использовании контролирующих семафоров операционная система обнаруживает попытки повторного захвата и сигнализирует об этом значением ошибки, возвращаемой функцией pthread_mutex_lock(), а именно возвращается значение, задаваемое символической константой EDEADLK.
38. Семафоры событий.
Другой современной формой семафоров является семафор событий. Он предназначен для организации ожидания некоторого события, и когда такое событие произойдет, выполнение сможет продолжить не одна, а все нити, дожидающиеся этого события.
Ожидание события, связанного с event-семафором, в Windows выполняет вездесущая функция WaitForSingleObject, которая на этот раз должна в качестве аргумента использовать хэндл именно семафора событий.
Перед использованием семафора событий, созданного другим процессом для получения доступа к нему необходимо выполнить функцию открытия доступа к event-семафору. Для этих целей предназначена функция OpenEvent.
После завершения использования семафора событий (системного объекта, расходующего системные ресурсы) его следует закрыть, что и выполняется универсальной функцией CloseHandle в Windows.
В операционной системе Windows функции, предназначенные специально для работы с event-семафором, имеют следующие прототипы
HANDLE CreateEvent(SECURITY_ATTRIBUTES* pattributes, BOOL ManualReset, BOOL fState, STR* pName);
BOOL SetEvent(HANDLE hEvent);
BOOL ResetEvent(HANDLE hEvent);
HANDLE OpenEvent(DWORD access, BOOL inherit, STR* pName);
BOOL PulseEvent(HANDLE hEvent).
В операционной системе Unix нет даже термина «семафор событий», но в ней имеются универсальные семафоры, даже более мощные, чем считающие абстрактные семафоры, и еще одна интересная вариация идеи семафора, названная условными переменными.
Для создания условной переменной используется системная функция с именем pthread_cond_init. Она имеет прототип
int pthread_cond_init(pthread_cond_t * pcond, pthread_condattr_t * pcond_attr),
Для задания срабатывания условной переменной, т.е. появления события выполнения условия этой переменной, существует функция pthread_cond_signal с прототипом
int pthread_cond_signal(pthread_cond_t * cond).
В результате выполнения этой функции всем нитям, ожидающим срабатывания условной переменной, посылается сигнал об этом событии.
Ожидание события срабатывания условной переменной задается функцией pthread_cond_wait с прототипом
int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex).
После использования, условная переменная должна быть уничтожена вызовом функции с прототипом
int pthread_cond_destroy(pthread_cond_t * cond).
Эта функция освобождает системные ресурсы, занятые условной переменной.
Для смыслового эквивалента ручного режима семафоров событий Unix содержит функцию с именем pthread_cond_broadcast, которая разблокирует все нити, которые ожидают события срабатывания условной переменной. Эта функция имеет прототип
int pthread_cond_broadcast(pthread_cond_t * cond)).
39. Средства множественных ожиданий.
Множественные ожидания событий или освобождения общих ресурсов возникают, когда по существу решаемой задачи может оказаться необходимым ожидать не одного, а более чем одного события или ресурса.
В Windows для множественного ожидания предназначена универсальная функция WaitForMultipleObjects с прототипом
DWORD WaitForMultipleObjects(DWORD cObjects, CONST HANDLE *phObjects, BOOL fWaitAll, DWORD Timeout);
где параметр cObjects для данного применения задает число семафоров в наборе, параметр phObjects адрес массива хэндлов отдельных семафоров в наборе, параметр Timeout время ожидания в миллисекундах или записывается символической константой INFINITE для бесконечного ожидания. С помощью параметра fWaitAll определяется вариант ожидания ожидать срабатывания всех семафоров в наборе (значение параметра для этого должно быть TRUE) или ожидание завершается при срабатывании хотя бы одного семафора в наборе (при значении FALSE этого параметра). Возвращаемые значения этой функции, равные сумме константы WAIT_OBJECT_0 и числового значения k, информируют программу, что ожидание было прекращено по причине срабатывания k-го семафора в наборе.
В операционной системе Unix не предусмотрено стандартных средств для множественного ожидания срабатывания набора mutex-семафоров или семафоров ожидания (условных переменных). Вместо этого присутствуют мощные программные средства, позволяющие строить произвольные наборы считающих семафоров.
40. Программные семафоры с внутренним счетчиком в Windows.
В операционной системе Windows считающие семафоры создаются функцией CreateSemaphore. Функция имеет прототип
HANDLE CreateSemaphore(SECURITY_ATTRIBUTES *pSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName).
Для выполнения операции, аналогичной операции P абстрактных семафоров, следует использовать все ту же универсальную функцию WaiForSingleObject. Каждое выполнение этой функции, когда ее первым аргументом является хэндл считающего семафора, уменьшает внутренний счетчик семафора на единицу, но только в том случае, если он был больше нуля. Блокировка нити при этом не производится. Если же значение внутреннего счетчика семафора на момент выполнения функции WaiForSingleObject было равно нулю, то производится блокировка данной нити. Блокировка действует до тех пор, пока какая-то другая часть текущего комплекса программ (другая нить процесса, другой процесс) не выполнит функцию ReleaseSemaphore, освобождающую семафор. Функция эта имеет прототип
BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG cReleaseCount, LONG *plPreviousCount).
Когда значение внутреннего счетчика семафора больше нуля, он считается открытым, т.е. выполнение функции ожидания для такого состояния счетчика не блокирует нить.
Все нити процесса, создавшего считающий семафор, могут им пользоваться. Если считающий семафор создан другим процессом, то для доступа к нему этот семафор необходимо открыть с помощью функции OpenSemaphore, которая имеет прототип
HANDLE OpenSemaphore(DWORD fdwAccess, // access flag BOOL fInherit, LPCTSTR lpszSemName).
Напомним, что после использования считающий семафор, как и любой другой системный объект, должен быть явно закрыт вызовом функции CloseHandle для хэндла этого семафора.
41. Программные семафоры с внутренним счетчиком в POSIX.
42. Массивы семафоров в Unix (семафоры System V).
43. Проблема тупиков при взаимодействии программных единиц.
44. Средства реализации виртуальной памяти; страничная переадресация.
Память является важнейшим после процессора ресурсом компьютера. Поэтому ее распределению в простейших случаях, и управлению в современных ОС уделяется пристальнейшее внимание.
Оперативная память по своей сущности физический объект и поэтому существенно зависит от организации работы процессора с памятью. Иначе говоря, непосредственно зависит от особенностей архитектуры процессора.
Виртуальная память требует глубоко встроенных в аппаратуру процессора внутренних средств, которые в совокупности со служебными структурами данных, создаваемыми и поддерживаемыми программно, создают видимость для отдельной программы полного владения ею всей совокупностью
Так вот, адресная переадресация в виртуальной памяти аналогична некоторому развешиванию адресных табличек, где функцию развешивания адресных табличек берет на себя операционная система. Причем для каждого вычислительного процесса используется своя подсистема развешивания адресов на аналоги домов, и адресные таблички одного процесса совершенно не видны другому процессу.
Каждому процессу для такой переадресации необходима своя система замены видимых для него адресных табличек на действительные физические места расположения, причем система незаметная самому процессу. В качестве такой системы не придумано ничего проще, чем таблицы страниц. Страницей в данной переадресации называют аналог дома в нашей вымышленной модели. Номера внутри страницы обеспечивают нумерацию ячеек, находящихся в пределах одной страницы, аналогично номерам квартир внутри дома. Таблица страниц для процесса в общем случае содержит информацию для перехода от номеров страниц, видимых процессу, к номерам реального размещения этих страниц в оперативной памяти. В простейшем, точнее упрощенном случае, строка такой таблицы с номером k содержит всего лишь номер реального размещения этой страницы в оперативной памяти. Когда для нашего примера видимый процессу обобщенный номер квартиры записывается десятичными цифрами как 007345804, то это обозначает квартиру номер 804 в доме с табличкой для процесса, обозначенной 007345. Если в таблице переадресации для данного процесса в строке с номером 007345 стоит число 017421, то в действительности за указанным обобщенным номером стоит квартира 804 в доме с физическим номером расположения 017421, т.е. физический адрес 017421804.
45. Средства реализации виртуальной памяти; стратегия подкачки страниц.
Виртуальная память требует глубоко встроенных в аппаратуру процессора внутренних средств, которые в совокупности со служебными структурами данных, создаваемыми и поддерживаемыми программно, создают видимость для отдельной программы полного владения ею всей совокупностью имеющихся ячеек физической памяти. Эта видимость называется также страничной переадресацией.
Кроме страничной переадресации, обеспечиваемой архитектурными средствами в совокупности с ОС, для функционирования виртуальной памяти используют так называемую подкачку страниц. Существо подкачки страниц в том, что часть внешней памяти используется ОС как средство временного хранения менее нужных страниц оперативной памяти, причем такое временное сохранение и восстановление информации из внешней памяти в оперативную организуется совершенно незаметно для вычислительных процессов. Практически единственное, что в этом методе (временной подмене части оперативной памяти внешней) становится заметным, так это замедление действий процесса, который ОС вынужденно приостанавливает для такого восстановления информации.
Если же в анализируемой строке таблицы страниц соответствующим битом указано, что страница не находится в физической памяти, то в ней присутствует информация, где же во внешней памяти находится содержимое этой страницы. Такая ситуация вызывает прерывание, называемое страничным прерыванием. Обрабатывая его, операционная система находит место в последовательности физических страниц, содержимое которых по ее алгоритмам можно временно вытеснить во внешнюю память. Система производит это перемещение, делает изменение в той строке таблицы страниц, которая ранее описывала вытесненное содержимое (возможно в таблице страниц другого процесса). Затем она переносит нужное содержимое страницы из внешней памяти в выбранную страницу и корректирует исходную строку таблицы страниц. По завершении прерывания вычислительный процесс возвращается к началу выполнения той исходной команды, которую начали рассматривать. Со второй попытки выполнения той же команды уже обнаруживается конкретное значение физической страницы, куда требуется доступ, и обращение осуществляется, как описано выше.
Виртуальная память позволяет выполнять программы, размер которых значительно больше размера имеющейся физической памяти. Это достигается за счет того, что из исходного файла в оперативную память переносятся только те страницы, которые действительно необходимы на текущий момент. Остальная часть исходного файла используется путем подкачки страниц по действительному запросу на них, причем часть ранее используемых страниц обычным образом вытесняется в свопинг-файл или отмечается как неизменно присутствующая в области хранимого исполняемого файла.
46. Системные функции распределения памяти в Unix.
Кроме незаметного для выполняемой программы использования виртуальной памяти, современные ОС содержат средства явного получения заказываемых объемов памяти. Операционные системы содержат системные средства, которые позволяют запрашивать для работы программы дополнительные области данных, причем для последующего доступа к этим областям системная функция возвращает значение указателя, которое может быть запомнено в именованном указателе.
Проще всего такие средства организованы в Unix. Здесь имеются всего четыре системные функции, связанные с выделением и распределением оперативной памяти, называемые malloc, free, calloc и realloc. Первая из них имеет прототип
void* malloc(size_t nbytes).
Единственным аргументом этой функции служит число байтов в запрашиваемой области данных, а возвращается указатель на не детализированный тип данных. При невозможности выделить операционной системой заданный объем памяти, эта функция возвращает нулевой указатель (NULL).
Как только запрошенная функцией malloc() область памяти становится ненужной программе, настоятельно рекомендуется отдать приказ об ее освобождении. Для этого освобождения предназначена функция с именем free. Она имеет прототип
void free(void* ptr),
Функция calloc имеет прототип
void* calloc(size_t nelem, size_t nbytes)
и позволяет выделять область данных для размещения массива из nelem элементов, каждый из которых занимает nbytes байтов.
Наконец последняя из перечисленных функций имеет прототип
void* realloc(void* oldptr, size_t newsize).
Она используется для изменения размера дополнительной области памяти, ранее запрошенной у ОС и выделенной ею. При выполнении этой функции исходная область памяти может быть перемещена операционной системой в новое положение, и возвращаемое значение уже не будет совпадать со старым значением oldptr.
47. Системные функции распределения памяти в Windows.
В операционных системе Windows совокупность основных функций распределения памяти гораздо сложнее.
Практическое получение памяти программой, запрашивающей у ОС дополнительную область памяти (блок памяти), включает два этапа. На первом этапе у ОС запрашивается диапазон адресов виртуального адресного пространства, для которого ОС должна построить таблицы страниц в памяти и заполнить строки таблицы каталога. На втором этапе память выделяется уже непосредственно для запрошенного блока памяти, и при этом ОС заполняет соответствующие строки ранее построенных таблиц страниц текущего процесса.
Для первого из перечисленных этапов сложилось название резервирование памяти (reserve). Второй этап обозначается английским словом commit. Его можно называть задействовать память.
Действия двух рассмотренных системных функций в Windows может задавать универсальная функция с прототипом
VOID* VirtualAlloc(VOID *pvAddress, DWORD size, DWORD type, DWORD protect).
Эта функция может выполнять как первый из перечисленных выше, так и второй этапы распределения памяти, но может выполнять и их оба одновременно.
Обратной функцией к рассмотренной служит функция с прототипом
BOOL VirtualFree(VOID *pvAddress, DWORD size, DWORD type),
где в качестве значения последнего из аргументов может быть использована одна из констант MEM_DECOMMIT, MEM_RELEASE.
В практической работе с перечисленными функциями распределения памяти очень полезными, особенно при отладке приложений, оказываются информационные функции запроса информации о блоках виртуальной памяти.
В операционных системах Windows аналогичная функция имеет прототип
DWORD VirtualQuery(void* pvAddress, MEMORY_BASIC_INFORMATION *pmbiBuffer, DWORD size),
Дополнительно к только что рассмотренным функциям в Windows предлагается функция для изменения прав доступа к участку виртуальной памяти, имеющая прототип
BOOL VirtualProtect(void * pAddress, DWORD size, DWORD protect, DWORD *OldProtect).
В ней третий аргумент определяет новый вид прав доступа, задаваемый рассмотренными выше константами, а последний аргумент возвращает код старых прав доступа. Первый и второй аргументы задают адрес начала участка и его размер.
48. Совместное использование оперативной памяти в Windows.
В состав всех современных ОС включены средства использования памяти для взаимодействия независимых процессов. Основой этих средств является разделяемая память, сами средства представляют набор системных функций использования этой памяти, а на более детальном уровне и информационные средства описания этой памяти.
В операционной системах Windows для организации обмена через разделяемую память применяются функции, имеющие более универсальное назначение и относящиеся к отображению файлов на виртуальное адресное пространство. Основная из этих функций создает именованный или неименованный объект отображения в памяти и имеет прототип
HANDLE CreateFileMapping(HANDLE hFile, SECURITY_ATTRIBUTES *pFileMappingAttributes, DWORD protect, DWORD MaxSizeHigh, DWORD MaxSizeLow, CTSTR *pName).
Базовый адрес разделяемой памяти получается для использования с помощью функции, имеющий прототип
void* MapViewOfFile(HANDLE hFileMappingObject, DWORD DesiredAccess, DWORD OffsetHigh, DWORD OffsetLow, DWORD size),
которая и возвращает требуемый базовый адрес памяти или NULL при невозможности выполнения.
Здесь первый параметр должен быть взят от предыдущего вызова функции CreateFileMapping, предоставляющего хэндл объекта отображения. Третий и четвертый аргументы этой функции совместно задают 64-битное смещение внутри виртуальной области памяти, созданной вызовом функции CreateFileMapping.
После завершения использования разделяемой памяти для освобождения уже ненужных ресурсов следует вызывать функцию UnmapViewOfFile, имеющую прототип
BOOL UnmapViewOfFile( void* pBaseAddress),
передав ей в качестве аргумента базовый адрес разделяемой памяти. Кроме того, в общем случае следует закрыть хэндл объекта отображаемой памяти с помощью функции CloseHandle.
49. Совместное использование оперативной памяти в Unix.
В ОС Unix разделяемая память находится под непосредственным управлением ядра, которое содержит таблицу описания областей разделяемой памяти. Каждая из областей обозначается в этой таблице целочисленным идентификатором (а не текстовым именем, как в других ОС). Кроме того, каждая такая область описывается в этой таблице атрибутами доступа и размером. Области разделяемой памяти относятся к адресному пространству ядра ОС.
Доступ к разделяемой памяти со стороны процесса осуществляется в два этапа. На первом из них получается хэндл области памяти, причем на этом этапе либо открывается доступ к уже имеющейся в ОС области памяти, либо такая область создается операционной системой. На втором этапе процесс подключается к разделяемой области (to attach), используя ранее полученный хэндл.
Для получения разделяемой памяти предназначена функция с прототипом
int shmget(key_t key, int size, int flag),
возвращающая при удачном выполнении целочисленное значение требуемого идентификатора.
Для подключения процесса к запрошенной ранее области разделяемой памяти служит функция с прототипом
void* shmat(int shmid, void* addr, int flag),
Для отсоединения разделяемой памяти должна использоваться функция с прототипом
int shmdt(void* addr),
аргументом которой является адрес, ранее полученный от функции shmat. При успешном выполнении она возвращает 0, а при неудаче -1.
Кроме рассмотренных базовых функций для разделяемой памяти, в Unix имеется функция расширенного управления разделяемой памятью с прототипом
int shmctl(int shmid, int cmd, struct shmid_ds *buf).
При использовании разделяемой памяти в Linux программисту предоставляется очень удобное системное средство команда ipcs. Для получения информации о разделяемой памяти эту команду следует вызвать с опцией m, так что весь вызов имеет вид
ipcs -m
Эта команда выводит информацию о присвоенном ключе-идентификаторе, идентификаторе области памяти, владельце, размере и правах доступа к ней.
50. Структуры файловых систем для пользователя.
Практической основой любой ОС служит ее базовая файловая система.
Файловая система это совокупность всех информационных средств, обеспечивающих использование файлов, причем сюда традиционно не относят драйверы устройств и их менеджеры. В значительной степени файловая система определяется именно структурами длительного хранения информации в операционной системе, причем структурами, независимыми от конкретных устройств.
Практически любая файловая система имеет две существенно различных стороны построения: для внешнего пользователя и для программ-исполнителей.
В первом приближении файловые системы с внешней стороны подразделяются на использующие и на не использующие обозначения логических дисков (в более ранних ОС логическим дискам соответствовало, хотя и не совсем точно, понятие имени тома). Файловые системы типа FAT, NTFS и HPFS используют логические диски.
В файловой системе с логическими дисками внешняя структура файловой системы описывается несколькими деревьями (называемыми в теории также кустами) по одному на каждый логический диск. Различие таких составляющих деревьев и, как следствие, отдельного файла обеспечивается внесением в систему обозначений еще и наименования логического диска. В современных ОС, использующих логические диски, принято обозначать последние одной латинской буквой, за которой в контексте обозначений следует служебный символ двоеточия. Последний прием дает удобное и наглядное средство обозначать логические диски в составе полного наименования файла.
Кроме внутренней информации, хранимой собственно в файле, любой файл может иметь атрибуты доступа. В простейших системах типа FAT и преемственных к ним HPFS и NTFS для обслуживания таких атрибутов служит команда ATTRIB.
Аргументом команды может быть имя отдельного файла или задание совокупности файлов через метасимволы. В простейшей форме без опций эта команда выводит атрибуты, обозначаемые символами S, H, R, A (System, Hidden, ReadOnly, Archive). Кроме того, в формах
ATTRIB -атрибут файл
ATTRIB +атрибут файл
эту команду можно использовать для сброса соответствующего атрибута (операция -) или установки его (операция +). При этом атрибут задается одним символом, и в одной команде может быть задано несколько операций сброса или установки атрибутов.
51. Принципы построения файловых систем типа FAT
Основной внутренней структурой файловых систем типа FAT является таблица размещения файлов. Ведущей идеей ее создания послужило замена отдельного бита в битовой карте на специальный более сложный элемент (элемент таблицы FAT), который кроме кодирования состояния свободного блока, позволяет задавать очередной элемент таблицы, описывающей дальнейшее размещение блоков для конкретного файла. Таким образом, таблица FAT объединяет в своих элементах как функции битовой карты, так и таблицы размещения отдельных файлов.
Таблицы FAT позволяют размещать файл в произвольной совокупности кластеров. Так, в приведенном примере последовательность кластеров размещения файла file1.txt временно прерывается для начала размещения файла file2.txt. В результате отдельный файл может размещаться в любой прерывистой последовательности кластеров, разбрасываясь своим содержимым по значительной части дискового пространства. Это хорошо тем, что позволяет просто использовать все дисковое пространство данных и всегда легко находить место для расширения файла. С точки же быстродействия, такая особенность системы FAT имеет отрицательный эффект: файл размещенный в несмежных кластерах будет читаться и перезаписываться значительно дольше, чем размещенный в смежных. Поэтому файловые системы FAT предназначены для операционных систем невысокой производительности: для однопрограммных и однопользовательских ОС.
Современные файловые системы типа FAT, начиная с самых первых в 80-х годах, используют записи оглавления размером 32 байта. Это решение сохранилось и для самых поздних модификаций (VFAT и FAT32).
52. Современные модификации файловой системы FAT.
Модификации файловой системы FAT возникли как не планировавшийся результат продолжения использования 32-битной модификации для операционной оболочки Windows 3.1 над MS DOS, которая при своем создании получила название Windows 95. В начале выпуска Windows 95 была снабжена промежуточной модификацией базовой файловой системы FAT16/FAT12, получившей название VFAT. Основная задача, решаемая VFAT, заключается в предоставлении пользователям длинных имен файлов. Исходная структура каталогов FAT позволяла использовать имена с собственной длиной не более 8 символов, что широким массам неквалифицированных пользователей казалось достаточно неудобным. Поэтому введенные изменения затронули структуры только каталогов этой системы. Причем из-за необходимости преемственности были сохранены и все старые структуры.
Изменения заключаются в том, что для именования файла стали использовать в общем случае не одну запись оглавления, а набор последовательно размещенных в каталоге записей прежнего 32-байтного формата, который был расширен по своим возможностям. Здесь кстати пришлось резервное поле, запасенное в исходной структуре записи FITEM.
Для того чтобы однозначно и невидимо для старых программ отделять новые по строению записи каталога от старых, использовали поле атрибутов attr. В этом поле для новых записей стали записывать код 0x0F, недопустимый в старом варианте (он задавал, что это файл скрытый, системный, доступный только для чтения и одновременно, что это не файл, а каталог). Программы, работающие со старыми форматами записей каталога, "не замечают" записи с таким кодом в поле атрибутов.
53. Принципы построения файловой системы NTFS.
Файловая система NTFS, во-первых, была создана с оглядкой на HPFS, во-вторых, явилась последовательной реализацией ряда категорических принципов. Связь с HPFS видна даже в том, что коды этих систем, рассматриваемые менеджерами загрузки и заносимые в ключевую структуру магнитного диска Master Boot Record, совпадают.
Файловая система NTFS является уникальным образцом безусловного следования некоторым базовым принципам. Хотя большинство технических систем, используя ряд принципов, как правило, сочетают их в некоторой практической взаимосвязи, NTFS строго следует выбранным принципам.
В качестве таких принципов NTFS использует три: атрибуты защиты для всевозможных частей операционной системы, реляционную модель данных и рассмотрение любой частично самостоятельной части дисковой информации как файл.
Преемственность к файловым системам FAT внутри NTFS заключается в использовании кластеров вместо секторов. Такое решение не обеспечивает очень эффективного использования дискового пространства, но сокращает объем служебной информации внутри файловой системы.
Согласно реляционному принципу, вся информация в NTFS организована в виде большой таблицы базы данных. Эта таблица называется Master File Table (MFT). Каждому файлу в этой таблице отвечает некоторая строка (запись файла в MFT). (Как собственно файлу с именем, видным пользователю, так и внутренним данным, искусственно оформленным как файл.) Номер строки (записи) в этой таблице служит для внутренней нумерации файлов в NTFS.
Файл на томе NTFS идентифицируется 64-битным значением, называемым файловой ссылкой (file reference). Она состоит из номера файла (48-битного номера единственной или базовой записи файла в MFT) и номера последовательности.
Каталог в NTFS это так называемый в терминологии баз данных индекс, т.е. набор имен файлов или каталогов с соответствующими файловыми ссылками. Причем этот набор организован как двоичное дерево для ускорения доступа.
54. Программный опрос файловой системы в Windows.
В операционных системах типа Windows разработчики включили проверку условия для имени файла в действия соответствующей системной функции. В этой ОС основных функций, работающих с содержимым каталогов, две: функция поиска первого файла по задаваемому условию и функция поиска следующего файла по тому же условию. Само условие задается как метанотация, т.е. записью совокупности файлов с помощью метасимволов * и ? в соответствующем аргументе имени файла. Использование этих метасимволов полностью совпадает с традиционным их применением в командах операционной системы, восходящим к правилам командного интерпретатора Unix.
В Windows для поиска первого файла в каталоге служит функция с прототипом
HANDLE FindFirstFile(char *metaname, WIN32_FIND_DATA *FindFileData),
Для поиска следующих файлов, удовлетворяющих той же метанотации, что была задана при выполнении функции FindFirstFile, в Windows служит функция с прототипом
BOOL FindNextFile(HANDLE hFindFile, WIN32_FIND_DATA *FindFileData),
Последняя функция возвращает значение TRUE, если находит очередной файл в текущем каталоге, удовлетворяющий метанотации, в противном случае она возвращает значение FALSE.
В завершение работы с каталогом должна вызываться функция с прототипом
BOOL FindClose(HANDLE hFindFile),
которая закрывает хэндл, ранее полученный от функции FindFirstFile.
Для вспомогательных действий по переустановке текущего каталога предназначена в Windows функция с прототипом
BOOL SetCurrentDirectory(char *PathName)),
а также функция с прототипом
DWORD GetCurrentDirectory(DWORD BufferSize, char *Buffer),
которая позволяет запомнить в символьном массиве полное имя текущего каталога.
55. Программный опрос файловой системы в Unix.
В ОС Unix операции с каталогом строятся подобно операциям с типизированными файлами, используемыми в Паскале, а именно, вводится указатель на структуру данных, описывающую каталог. Эта структура описана в заголовочном файле dirent.h и имеет имя DIR. Указатель на эту структуру данных используется для получения значения от функции opendir, имеющей прототип
DIR* opendir(char *dirname),
Дальнейшие действия выполняются системной функцией с прототипом
struct dirent *readdir(DIR* dirptr),
с аргументом, полученным от предыдущей функции. Каждое выполнение вызова readdir() возвращает указатель на содержимое структуры типа dirent, содержащей информацию об очередном элементе каталога. Эта структура данных описана также в заголовочном файле dirent.h. В последней структуре два основных поля, которые заданы в ней как
ino_t d_ino; /* Номер индексного дескриптора */
char d_name[ ]; /* Имя файла, заканчивающегося нулевым байтом*/
При использовании этих полей каталога следует иметь в виду, что нулевое значение поля d_ino вполне возможно у используемого каталога и обозначает неиспользуемую запись в каталоге (обычно по причине удаления информации о файле из данного каталога).
После окончания использования указателя на каталог, полученный от функции opendir(), следует выполнить закрытие доступа к каталогу и освобождение ресурсов вызовом функции с прототипом
int closedir(DIR* dirptr),
Вспомогательной функцией работы с каталогами служит функция, описываемая прототипом
void rewinddir(DIR* dirptr),
которая позволяет вернуться к началу каталога с целью чтения его опять с самого начала.
56. Получение информации об ошибках выполнения системной функции Windows.
Для получения же собственно кода ошибки в MS Windows программисту приходится принимать немалые дополнительные усилия. Если по возвращаемому значению системной функции определяется, что ошибка есть, следует немедленно вызывать специальную функцию GetLastError(), которая не имеет аргументов и возвращает значение типа DWORD. Функция GetLastError() возвращает последнюю ошибку, возникшую в ходе выполнения программы (точнее нити программы). Именно это 32-битное значение дает код ошибки. Собственно коды ошибок, общие для всех системных функций, содержатся в заголовочном файле WinError.h.
Числовые коды ошибок, возвращаемые функцией GetLastError(), достаточно сложно для разработчика соотнести с наименованием ошибки. Если требуется распознавание вида ошибки при автоматическом выполнении программы, то разработчики этой ОС предлагают для использования специальную функцию FormatMessage.
Прежде всего она имеет прототип
DWORD FormatMessage(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPTSTR lpBuffer, DWORD nSize, va_list *Arguments).
Здесь не будет рассматриваться все многообразие ее возможностей, которые в большинстве ситуаций излишни (от функции требуется лишь выдать текст, называющий причину ошибки).
Функция FormatMessage обладает возможностью возвращать порядка тысячи различных наименований ошибок на языке текущей локализации. Заметим кстати, что в более точных терминах программный интерфейс MS Windows называется Win32. Существенной особенностью рассматриваемой функции оказывается использование возвращаемых текстов сообщений об ошибках, представленных в кодировке для графического режима. Исторически сложилось так, что тексты, записанные не с помощью латинского алфавита, имеют различное представления в графическом и текстовом режиме.
57. Получение информации об ошибках выполнения системной функции Unix.
Наиболее традиционно эта проблема решается в Unix. Большинство системных функций Unix имеют вид
int имяфункции(список_аргументов),
возвращая в качестве основного значения целое число. Это число может нести значение, определяющее результат действия функции, но одно из значений (обычно -1), зарезервировано за условным кодом ошибки.
Другой формой контроля ошибок в Unix является специальная переменная errno. Для основных функций операционной системы Unix код ошибки неявно заносится в эту специальную глобальную переменную, описанную в заголовочном файле errno.h. Такой прием дает принципиальную возможность анализировать значение этой переменной после обращения к системной функции. Но делать это следует сразу же после системного вызова, так как следующий системный вызов, в свою очередь, изменяет эту переменную.
Практическое использование значений переменной ошибок errno достигается с помощью вспомогательных функций perror() и strerror(). Первая из них имеет прототип
void perror(const char *s),
а вторая имеет прототип
char *strerror(int errnum).
Вторая функция возвращает указатель на текст, описывающий ошибку, а первая непосредственно отображает в стандартном потоке ошибок этот текст, причем предваряемый любой пользовательской текстовой информацией, которая содержится в аргументе s.