
- •Операционные системы для программиста
- •Введение
- •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. Командные файлы и сценарии
- •Библиографический список
10.6. Динамически распределяемая память
При оперативной работе с динамическими структурами данных, не предназначенными для длительного хранения, возникает проблема эффективного использования памяти для хранения небольших структур данных. Системные функции распределения памяти в OS/2 и Windows предоставляют по запросу не менее страницы виртуальной памяти, что естественно расточительно для небольших объектов. Программист, конечно, может после запроса большого блока памяти взять на себя управление выделением из него небольших порций, но было бы удобней и надежней иметь для этого соответствующие системные средства. В основе этих средств лежит динамически распределяемая память, называемая также пулом памяти (pool).
В операционных системах Windows управление динамической памятью сделано весьма разнообразным. Во-первых, каждый процесс при его создании получает пул памяти по умолчанию, который обычно составляет 1 Мбайт. Это значение можно изменить при создании выполняемого EXE-файла с помощью соответствующей опции компоновщика. Если такого общего пула недостаточно при выполнении программы, то набор функций API Windows предоставляет возможности создания дополнительных пулов.
Основной из этих функций является функция с прототипом
HANDLE HeapCreate(DWORD options, DWORD size, DWORD maxsize),
где аргумент maxsize задает максимальный размер создаваемого пула для дальнейших его перераспределений, size – начальный размер пула, а параметр options задает режимы создания, которые в простейших случаях не используются. Функция эта возвращает хэндл созданного пула или NULL при неудаче. Созданный пул по исчезновению потребности в нем должен быть уничтожен функцией HeapDestroy с прототипом
BOOL HeapDestroy(HANDLE hHeap),
которая выводит из действия все страницы памяти, задействованные под пул, и освобождает диапазон виртуальных адресов, выделенных под него.
Для запроса порции памяти из пула служит функция с прототипом
void* HeapAlloc(HANDLE hHeap, DWORD flags, DWORD size),
где hHeap задает хэндл пула, size – размер запрашиваемой порции памяти, а flags задает символическими константами флаги выделения. Среди таких констант наиболее употребительная HEAP_ZERO_MEMORY, задает обнуление выделенного блока памяти. Функция возвращает виртуальный адрес начала запрошенной порции. Хэндл пула может быть получен от ранее выполненной функции HeapCreate или для пула по умолчанию – от функции GetProcessHeap, имеющей прототип
HANDLE GetProcessHeap(VOID).
Функция HeapAlloc аналогична по своему смыслу функции malloc из Unix, но, как видим, более сложна в использовании и привязана к Windows. Обратной ей по действиям (аналогичной функции free из Unix) служит функция HeapFree с прототипом
BOOL HeapFree(HANDLE hHeap, DWORD flags, void* pMem),
где hHeap задает хэндл пула, pMem – адрес начала порции, полученный ранее от функции HeapAlloc, а flags – флаги, которые чаще всего не используются, так что аргумент flags полагается равным нулю.
Дополнительные возможности изменения размеров блоков, полученных из пула, предоставляет функция HeapReAlloc, описываемая прототипом
void* HeapReAlloc(HANDLE hHeap, DWORD flags, void* pMem,DWORD size).
Она позволяет изменить размеры уже имеющегося блока (порции памяти) с адресом pMem из пула с хэндлом hHeap до нового размера size. Флаги, задаваемые в аргументе flags, указываются обычно константами HEAP_REALLOC_IN_PLACE_ONLY и HEAP_ZERO_MEMORY. Первая из них выдвигает требование изменить размер блока, оставляя его на собственном месте (не меняя его виртуального адреса), что выполнимо в большинстве случаев только для уменьшения размера блока. Вторая из этих констант требует заполнить нулями добавляемую часть блока при увеличении его размера.
Дополнительные возможности предоставляет функция с прототипом
DWORD HeapSize(HANDLE hHeap, DWORD *flags, void* pMem),
которая позволяет получить в качестве своего значения размер блока, выделенного по адресу pMem из пула с хэндлом hHeap (одновременно можно получить значение флагов этого блока).
Применение рассмотренных функций для работы с пулом памяти в Windows демонстрирует программа, приведенная в листинге 10.6.1.
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main()
{char *pblock1, *pblock2, *pblock3;
HANDLE hheap;
hheap=HeapCreate(0,15000, 60000);
if (hheap= =0)
{printf(“Error HeapCreate with RC=%ld\n”, GetLastError());
getchar(); exit(0);}
pblock1=HeapAlloc(hheap, HEAP_ZERO_MEMORY, 1); //min= =12 !!
strcpy((char*)pblock1, “piece1”);
printf(“Block1 Adrress=%08X, its size=%ld\n”, pblock1,
HeapSize(hheap,0,pblock1));
printf(“Pause after writing into block1.\n”);getchar();
pblock2=HeapAlloc(hheap, HEAP_ZERO_MEMORY, 24);
strcpy((char*)pblock2, “second piece”);
printf(“Block2 Adrress=%08X, its size=%ld\n”, pblock2,
HeapSize(hheap,0,pblock2));
printf(“Pause after writing into block2.\n”);getchar();
pblock3=HeapAlloc(hheap, HEAP_ZERO_MEMORY, 27);
pblock2=HeapReAlloc(hheap, 0, pblock2,47);
printf(“After realesing block2.\n”);getchar();
printf(“Block2 Adrress=%08X, its size=%ld\n”, pblock2,
HeapSize(hheap,0,pblock2));
printf(“Contens of block1 = %s, its size=%ld\n”, pblock1,
HeapSize(hheap,0,pblock1));
HeapFree(hheap, 0, pblock1);
printf(“Pause after realesing block1.\n”);getchar();
printf(“Contens of block2 = %s, its size=%ld\n”, pblock2,
HeapSize(hheap,0,pblock2));
HeapDestroy(hheap);
}
Листинг 10.6.1. Управление пулом памяти в Windows
Вначале программа создает новый пул памяти с возможным максимальным размером в 60 000 байтов и текущим размером в 16 000 байтов. После этого из нового пула функцией HeapAlloc запрашивается 1 байт памяти (а выделяется 12 – меньше не выделяется). Затем той же функцией запрашиваются порции по 24 и 27 байтов. Во все полученные таким образом блоки памяти записывается текстовая информация, затем второй блок изменяется в размере до 47 байтов, первый блок освобождается. В завершение созданный ранее пул уничтожается.
Для сравнения в листинге 10.6.2 приведена аналогичная программа работы с динамической памятью в Unix.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main()
{char *pm, *pblock1, *pblock2, *pblock3, *pblock2a;
pblock1=malloc(7);
strspy((char*)pblock1, “piece1”); 0
printf(“Bloc{1 adrress=%08X\n”, pblock1);
0 printf(“Pause after writing i~to block1.\n”);getchar();
0 pblock2=malloc(24);
strcpy((char*)pblock2, “second piece”); 0
printf(“Block2 adrress=%08X\n”, pblock2);
printf(“Pause after writing into block2.\n”);getchar();
pblock3=malloc(27);
strcpy((char*)pblock3, “third piece”);
printf(“Block3 adrress=%08X\n”, pblock3);
printf(“Pause after writing into block3.\n”);getchar();
pblock2a=realloc(pblock2,47);
printf(“After realloc pblock2a=%08X\n”, pblock2a);
if (pblock2a!=NULL) pblock2=pblock2a;
printf(“Block2 adrress=%08X\n”, pblock2);
printf(“Contens of block1 = %s\n”, pblock1);
free(pblock1);
printf(“Contens of block2 = %s\n”, pblock2);
free(pblock2);
printf(“Contens of block3 = %s\n”, pblock3);
free(pblock3);
}
Листинг 10.6.2. Управление пулом памяти в Unix
Непосредственное сравнение последних программ показывает, что API Unix дает самые компактные и простые для использования программистом средства управления памятью.
Упражнение
Разработать многопоточную программу, отображающую на экране взаимодействие трех нитей "читателей" из общей области данных и двух "писателей", записывающих в этот буфер данные. Буфер предназначен для хранения 10 символов. Первая нить-писатель выводит в буфер данные в латинском алфавите, вторая нить-писатель выводит в буфер данные в русском алфавите. Такой вывод эти две нити осуществляют в два приема, первый из которых записывает половину своего текста без завершающего этот промежуточный текст нуля. Между такими половинами вывода нити производят задержку на случайную величину миллисекунд, но не более 2 с. После вывода своего текста в буфер каждая нить-писатель переходит в ожидание порядка 2-3 с до следующей попытки записи в буфер. Нити-читатели через случайный интервал порядка 300 мс. читают данные из буфера, если это позволяют средства синхронизации доступа между нитями, и выводят прочитанный текст на экран, каждая в свой столбец. Каждый вывод нити-читателя осуществляется в новую строку своего столбца, поэтому суммарные действия вывода в таких нитях предусмотреть только для 20 – 24 строки. Синхронизацию осуществить с помощью семафоров (возможны два вариант задания – для Windows и для Linux).