Лекция 6 передачей физической памяти

Архитектура памяти

В Win32 виртуальное адресное пространство каждого процесса составляет 4 Гб, т.е. соответствующий 32-битный указатель может быть числом от $00000000 до $FFFFFFFF.

В DOS или в 16 разрядной Windows все процессы находились в едином адресном пространстве, поэтому сбой в процессе A мог привести к краху процесса B или всей ОС.

В Win32 каждому процессу отводится закрытое адресное пространство. Например, процесс А хранит массив информации по адресу $12345678. В то же время процесс B также хранит свою информацию по тому же адресу $12345678. Но процесс A имеет доступ к своему массиву, а процесс В к своей информации.

В Windows NT память ОС скрыта от других процессов, но в Windows 95 это не реализовано, поэтому любой процесс может случайным образом нарушить нормальную работу ОС.

Различные ОС разбивают адресное пространство на разделы по разному. На следующем рисунке показаны разделы адресного пространства процесса под управлением Windows 95


На следующем рисунке показаны разделы адресного пространства процесса под управлением Windows NT


Перед использованием памяти ее необходимо зарезервировать. ОС резервирует память по так называемым регионам ("порции" памяти). Границы региона памяти выбираются кратными некоторому числу, которое обеспечивает так называемую гранулярность выделения ресурсов. Это число зависит от типа процессора (для x86 - 64Кб). Гранулярность позволяет упростить хранение информации о регионах, а также снизить степень фрагментации регионов в адресном пространстве.

Резервируя память под регион ОС обеспечивает кратность размера региона размеру страницы (единица объема памяти). Размер страницы зависит от типа процессора (для x86 - 4Кб). Например, осуществляется попытка выделить 10 Кб памяти. ОС округлит это объем до ближайшего большего числа, кратного странице и следовательно выделит 12 Кб.

Чтобы практически использовать зарезервированный регион адресного пространства необходимо выделить физическую память и отобразить ее на этот регион. Эта операция называется передачей физической памяти.

Организация физической памяти в Win32 принципиально отличается от принятой в Windows 3.1 системы. Физическую память в Windows 95 и Windows NT следует рассматривать данные, хранимые в дисковых файлах со страничной структурой - так называемые страничные файлы. Поэтому размер страничного файла - главный фактор, определяющий количество физической памяти. Реальный объем оперативной памяти имеет гораздо меньшее значение. Рассмотрим процесс доступа к некоторому объему данных.


Чем меньше объем RAM, тем чаще приходится копировать блоки памяти из страничного файла в RAM и наоборот, тем больше нагрузка на жесткий диск. Поэтому, для увеличения общей производительности системы необходимо добавить оперативную память.

Функция

procedure GlobalMemoryStatus (var lpBuffer: TMemoryStatus)

позволяет отслеживать текущее состояние памяти. В качестве возвращаемого параметра используется структура:

TMemoryStatus = record

dwLength: DWORD;

dwMemoryLoad: DWORD;

dwTotalPhys: DWORD;

dwAvailPhys: DWORD;

dwTotalPageFile: DWORD;

dwAvailPageFile: DWORD;

dwTotalVirtual: DWORD;

dwAvailVirtual: DWORD;

end;

dwLength - размер структуры в байтах. Перед вызовом необходимо заполнить dwLength=SizeOf(TMemoryStatus )

dwMemoryLoad - показывает насколько занята система управления памятью (в процентах).

dwTotalPhys - отражает общий объем оперативной памяти в байтах.

dwAvailPhys - отражает общий объем свободной оперативной памяти в байтах.

dwTotalPageFile - сообщает максимальное количество байтов, которое может содержаться в страничных файлах.

dwAvailPageFile - сообщает количество свободной памяти в байтах, расположенной в страничном файле.

dwTotalVirtual - отражает общее количество байтов, отведенных под закрытое адресное пространство процесса.

dwAvailVirtual - кол-во байтов свободного адресного пространства.

В Win32 API существует функция, позволяющая запрашивать определенную информацию об участке памяти по заданному адресу: размер, тип памяти и атрибуты защиты.

function VirtualQuery (lpAddress: Pointer; var lpBuffer: TMemoryBasicInformation; dwLength: DWORD): DWORD;

(на самостоятельное изучение)

Механизмы управления памятью в приложениях

В Win32 три механизма управления памятью:

· виртуальная память - наиболее подходящая для операций с большими массивами объектов или структур;

· проецируемые в память файлы - наиболее подходящие для операций с интенсивными потоками данных;

· кучи - наиболее подходящие для работы с множеством малых объектов.

Использование виртуальной памяти

Для резервирования региона в адресном пространстве используется функция

function VirtualAlloc (lpvAddress: Pointer; dwSize, flAllocationType, flProtect: DWORD): Pointer;

Функция возвращает базовый адрес региона или nil, если зарезервировать регион не удалось

lpvAddress - указывает, где именно система должна зарезервировать регион. Если передается lpvAddress =Nil система выбирает место размещения региона автоматически.

dwSize - указывает размер резервируемого региона в байтах. Система округляет этот параметр до величины кратной размеру страницы.

flAllocationType - режим работы функции. В случае резервирования региона режим равен MEM_RESERVE. Если вы хотите выделить память и не освобождать ее в ближайшее время, то лучше всего выделить его в диапазоне старших адресов. В этом случае можно вместе с флагом MEM_RESERVE скомбинировать флаг MEM_TOP_DOWN.

flProtect - указывает атрибут защиты региона. Возможны следующие значения PAGE_NOACCESS, PAGE_READONLY, PAGE_READWRITE.

Прежде чем обращаться к содержимому зарезервированного региона, необходимо передать ему физическую память. Для этого используется та же функция с параметром flAllocationType равным MEM_COMMIT. Так как передавать физическую память всему региону сразу не обязательно, вы можете указать адрес и размер.

Можно использовать функцию VirtualAlloc для одновременного резервирования и передачи физической памяти. Для этого необходимо скомбинировать MEM_RESERVE и MEM_COMMIT.

Предположим вам необходимо в своей программе использовать электронную таблицу размером 200 строк на 256 колонок. Для описания ячейки данной таблицы вам необходимо структура CellData размер которой 126 байт. Проще всего описать двумерный массив. Для этого массива вам понадобится 200*256*128=6 553 600»6 Мб байтов физической памяти. Такое использование памяти можно назвать расточительным, так как пользователь, как правило, использует небольшое количество ячеек. Для экономии памяти используют связанные списки, но такой подход усложняет манипулирование данными.

Для решения поставленной задачи можно использовать виртуальную память, для чего необходимо:

1. зарезервировать регион виртуальной памяти для всего массива,

2. при вводе данных в ячейку вычислить адрес размещения данных и передать по этому адресу физическую память. Размер передаваемой памяти равен размеру структуры,

3. проинициализировать элементы структуры.

Существует проблема, связанная с необходимостью определения передана ли физическая память под конкретную ячейку или еще нет. Существует ряд решений:

1. Всегда передавать физическую память. Это возможно, так как функция VirtualAlloc проверяет, спроецирована ли физическая память по данному адресу.

2. Использовать структурную обработку исключительных ситуаций.

Для возврата физической памяти, отображенной на регион, или освобождения всего региона используется функция

function VirtualFree (lpAddress:Pointer;dwSize,dwFreeType:DWORD):BOOL

Для освобождения всего зарезервированного региона в lpAddress необходимо поместить базовый адрес региона, в параметре dwSize указать 0 (размер региона известен системе) при этом параметр dwFreeType должен быть равен MEM_RELEASE.

Для возврата физической памяти необходимо в lpAddress указать адрес первой освобождаемой страницы, в параметре dwSize - размер освобождаемой физической памяти, а параметр dwFreeType должен быть равен MEM_DECOMMIT. В случае если необходимо освободить все страницы зарезервированного региона dwSize должен быть равен 0.

В Win32 существуют функции необходимые для изменения атрибутов защиты страниц переданной физической памяти.

function VirtualProtect (lpAddress:Pointer;dwSize,flNewProtect:DWORD; lpflOldProtect:Pointer): BOOL

lpAddress - содержит адрес первой страницы региона памяти.

dwSize - размер региона.

flNewProtect - содержит новый атрибут защиты.

lpflOldProtect - содержит адрес выделенной памяти для размещения старого атрибута защиты.

ОС сама определяет, когда ей необходимо выгрузить или загрузить страницы из страничного файла в оперативную память. Тем не менее в Win32 API существую функции, позволяющие вмешаться в этот процесс.

function VirtualLock (lpAddress: Pointer; dwSize: DWORD): BOOL

Функция блокирует группу страниц в оперативной памяти, начиная с адреса lpAddress размером dwSize. Необходимо учесть, что заблокированные страницы остаются таковыми, если хотя бы один поток процесса выполняется. Если все потоки вытеснены, ОС может выгрузить заблокированные страницы из RAM. Если потоки вашего процесса вновь потребуют процессорного времени заблокированные страницы будут предварительно загружены в RAM.

function VirtualUnlock (lpAddress: Pointer; dwSize: DWORD): BOOL

Функция позволяет разблокировать группу страниц размером dwSize, начиная с адреса lpAddress.

Проецируемые в память файлы

Как и виртуальная память, проецируемые в память файлы позволяют зарезервировать регион адресного пространства и передать ему физическую память. Отличие состоит в том, что физическая память не выделяется из страничного файла, а берется из файла, который проецируется.

Проецируемые файлы применяются для

· загрузки и выполнения EXE и DLL модулей, что позволяет сэкономить на размере страничного файла и убыстрить подготовку к запуску,

· доступа к файлу с данными, что позволяет обойтись без операций файлового ввода/вывода,

· разделения данных между несколькими процессами.

С помощью проецируемых в память файлов несколько одновременно выполняемых экземпляров приложения могут совместно использовать один и тот же код, загруженный в оперативную память. Любая программа представляет собой цельное пространство, разделенное на разделы: раздел кода и раздел данных. При проецировании в память раздел данных следует за разделом кода.

 
 


Теперь допустим, что запущен второй экземпляр процесса. ОС проецирует код и данные на адресное пространство второго процесса.

 
 


Если один из экземпляров программы изменит глобальные переменные, это может привести к катастрофическим последствиям. В связи с этим ОС применяет прием, который называется "копирование при записи".

 
 


Всякий раз, когда программа записывает что-то в глобальные переменные, ОС перехватывает эту попытку, после чего создает новую страницу (страницы) в памяти и копирует туда страницу(станицы) данных, содержащую записываемую глобальную переменную.

Использование проецируемых в память файлов

Использование проекций файлов в программах чрезвычайно удобно при манипуляции с большими потоками данных. Для создания и использования проекций файлов необходимо:

1. Создать и открыть объект ядра "файл", который вы хотите спроецировать.

2. Создать объект ядра "проекция файла".

3. Указать системе, что вы собираетесь спроецировать объект ядра "проекция файла" на адресное пространство вашего процесса.

Закончив работу с проекцией Вам необходимо:

1. Сообщить системе об отмене проекции файла

2. Закрыть объект "проекция файла"

3. Закрыть объект "файл"

Для создания объекта ядра "файл" необходимо воспользоваться функцией

function CreateFile (lpFileName:PChar;

dwDesiredAccess,dwShareMode:Integer; lpSecurityAttributes:PSecurityAttributes; dwCreationDisposition,dwFlagsAndAttributes:DWORD; hTemplateFile:THandle): THandle;

Функция создает объект ядра "файл" и возвращает его описатель. Если попытка оказалась неудачной возвращает INVALID_HANDLE_VALUE=$FFFFFFFF.

lpFileName - имя файла

dwDesiredAccess - указывает способ доступа к содержимому файла. Может принимать следующие значения:

Значение Описание
  Содержимое файла нельзя считывать или записывать.
GENERIC_READ Чтение файла разрешено
GENERIC_WRITE Запись в файл разрешен
GENERIC_READ OR GENERIC_WRITE Разрешена и запись и чтение

dwShareMode - указывает тип совместного доступа к данному файлу. Может принимать следующие значения:

Значение Описание
  Файл не подлежит открытию со стороны
FILE_SHARE_READ Попытка постороннего процесса открыть файл с флагом GENERIC_WRITE не удастся
FILE_SHARE _WRITE Попытка постороннего процесса открыть файл с флагом GENERIC_READ не удастся
FILE_SHARE _READ OR FILE_SHARE_WRITE Посторонний процесс может открывать файл без ограничений

lpSecurityAttributes - указатель на структуру, содержащую дискриптор защиты (по умолчанию nil)

dwCreationDisposition - определяет режим работы функции CreateFile. Может принимать следующие значения:

Значение Описание
CREATE_NEW Создает новый файл. Если файл с таким именем существует, функция даст ошибку.
CREATE_ALWAYS Создает новый файл независимо от того, существует ли файл с таким же именем.
OPEN_EXISTING Открывает существующий файл. Если файла нет - ошибка.
OPEN_ ALWAYS Открывает существующий файл. Если файла нет - создает новый.
TRUNCATE_ EXISTING Открывает существующий файл и обрезает его длину до 0.

dwFlagsAndAttributes - атрибуты файлов и флаги устройств. Используйте значение FILE_ATTRIBUTE_NORMAL

hTemplateFile - Используйте значение nil.

Для создания объекта ядра "проекция файла" используется функция

function CreateFileMapping (hFile: THandle;

lpFileMappingAttributes: PSecurityAttributes;

flProtect, dwMaximumSizeHigh, dwMaximumSizeLow: DWORD;

lpName: PChar): THandle

Функция создает объекта ядра "проекция файла" и возвращает его описатель. Если это не удалось, возвращает nil.

hFile - описатель объекта ядра "файл"

lpFileMappingAttributes - указатель на дескриптор защиты

flProtect - атрибуты защиты, применяемые системой для страниц физической памяти, когда они отображаются на адресное пространство процесса. Значение этого параметра связано со способом доступа к содержимому файла dwDesiredAccess.

dwMaximumSizeHigh, dwMaximumSizeLow - размер доступной физической памяти, иначе говоря, максимальный размер файла, проецируемого на адресное пространство. Win32 позволяет работать с файлами, размеры которых выражается 64-х разрядным числом (до 18 экзабайтов). Поэтому dwMaximumSizeLow отражает младшие 32 бита, dwMaximumSizeHigh - старшие. Для отражения текущего состояния файла укажите в обоих параметрах 0. В этом случае Вы не сможете дописывать в файл.

lpName - имя объекта. Обычно nil.

Следующая функция резервирует регион адресного пространства под данные находящиеся в файле и передает ему физическую память

function MapViewOfFile (hFileMappingObject: THandle;

dwDesiredAccess: DWORD; dwFileOffsetHigh, dwFileOffsetLow, dwNumberOfBytesToMap: DWORD): Pointer;

hFileMappingObject - описатель объекта "проекция файла".

dwDesiredAccess - вид доступа к данным

Значение Описание
FILE_MAP_WRITE Данные можно записывать и считывать
FILE_MAP_READ Данные можно только считывать
FILE_MAP_ALL_ACCESS = FILE_MAP_READ
FILE_MAP_COPY Данные можно записывать и считывать. Запись приводит к закрытой копии записываемых страниц

dwFileOffsetHigh, dwFileOffsetLow - смещение от начала файла, начиная с которого Вы хотите проецировать файл. Данное смещение необходимо указывать с учетом гранулярности ресурсов.

dwNumberOfBytesToMap - размер представления файла в памяти. Если указать 0 ОС попытается спроецировать файл, начиная с указанного смещения до конца файла.

Когда необходимость в данных файла, спроецированного на адресное пространство, отпадает, необходимо освободить регион

function UnmapViewOfFile (lpBaseAddress: Pointer): BOOL;

lpBaseAddress - базовый адрес региона.

Так как повторный вызов MapViewOfFile приводит к резервированию нового региона, но не освобождению старого, Вам необходимо вызывать UnMapViewOfFile после каждого вызова MapViewOfFile.

Для повышения производительности ОС буферизует страницы данных и не сохраняет их немедленно в дисковом образе файла. При необходимости можно заставить ОС записать все измененные данные на диск

function FlushViewOfFile (const lpBaseAddress: Pointer; dwNumberOfBytesToFlush: DWORD): BOOL;

lpBaseAddress - базовый адрес региона.

dwNumberOfBytesToFlush - кол-во байтов, переписываемых на диск.

Пример:

Const

NCount = 2000000;

Type

TmArr= array [0..0] of extended;

procedure TForm1.Button1Click(Sender: TObject);

var hFile,hFileMapping:THandle;

Parr:^TmArr;

i:integer;

begin

hFile:=CreateFile(

PChar(ExtractFileDir(Application.ExeName)+'\matrix.map'),

GENERIC_READ OR GENERIC_WRITE,

0,nil,

OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,

0);

if hFile=INVALID_HANDLE_VALUE then

raise Exception.Create(SysErrorMessage(GetLastError));

hFileMapping:=CreateFileMapping(hFile,nil, PAGE_READWRITE, 0,

NCount*SizeOf(TmArr),

nil);

CloseHandle(hFile);

if hFileMapping=0 then

raise Exception.Create(SysErrorMessage(GetLastError));

Parr:=MapViewOfFile(hFileMapping,

FILE_MAP_WRITE,

0, 0, 0);

CloseHandle(hFileMapping);

if Parr=nil then

raise Exception.Create(SysErrorMessage(GetLastError));

for i:=0 to NCount-1 do PArr^[i]:=10.2;

UnMapViewOfFile(Parr);

end;

Синхронизация потоков

В среде, позволяющей исполнять несколько потоков одновременно, очень важно синхронизировать их деятельность. Общий принцип синхронизации заключается в следующем: синхронизируемый поток "засыпает" до тех пор, пока не наступит определенное событие. Это событие определяет сам поток и сообщает его ОС. Таким образом, поток синхронизирует свое выполнение с наступлением особого события.

В ОС предусмотрен ряд объектов для синхронизации выполнения потоков, которые будут рассмотрены ниже.

Критические секции

Критическая секция - это небольшой участок кода, требующий монопольного доступа к каким-либо общим данным. Другими словами это "узкое место" программы, через которое может "протиснуться" одновременно только один поток.

Критическая секция создается функцией

procedure InitializeCriticalSection (var lpCriticalSection: TRTLCriticalSection);

Она должна быть создана до начала выполнения синхронизируемых потоков. lpCriticalSection - структура типа TRTLCriticalSection. Переменная, которая передается в этом параметре, должна быть описана как глобальная.

Функция

procedure EnterCriticalSection (var lpCriticalSection: TRTLCriticalSection);

захватывает глобальную переменную lpCriticalSection. Данная функция вызывается в каком-либо потоке, после чего вызов аналогичной функции из других потоков заставляет из "засыпать" до тех пор, пока не будет вызвана функция

procedure LeaveCriticalSection (var lpCriticalSection: TRTLCriticalSection)

Данная функция "размораживает" глобальную переменную, делая ее доступной для захвата другими потоками.

При завершении программы Вам следует удалить критическую секцию вызовом функции

procedure DeleteCriticalSection (var lpCriticalSection: TRTLCriticalSection);

Пример:

Var

FLock: TRTLCriticalSection;

gIndex: integer;

gIndexSave: integer;

function StartThread1(Param:pointer):integer;

var i:integer;

begin

EnterCriticalSection(FLock);

for i:=0 to 100000000 do Inc(gIndex);

LeaveCriticalSection(FLock);

end;

function StartThread2(Param:pointer):integer;

begin

EnterCriticalSection(FLock);

gIndexSave:=gIndex;

LeaveCriticalSection(FLock);

end;

procedure TForm1.Button1Click(Sender: TObject);

var ThreadId: DWORD;

hObjThread: TWOHandleArray;

begin

gIndex:=0;

InitializeCriticalSection(FLock);

hObjThread[0]:=BeginThread(nil,0,@StartThread1,nil,0,ThreadId);

hObjThread[1]:=BeginThread(nil,0,@StartThread2,nil,0,ThreadId);

WaitForMultipleObjects(2,@hObjThread,TRUE,INFINITE);

ShowMessage(IntToStr(gIndexSave));

CloseHandle(hObjThread[0]);

CloseHandle(hObjThread[1]);

DeleteCriticalSection(FLock);

end;

Синхронизация потоков с объектами ядра

Критические секции не являются объектами ядра, а значит, используются при синхронизации потоков в одном процессе. Но что делать, если необходимо синхронизировать работу потока с событиями, возникающими в системе или другом процессе. Для этого в ОС предусмотрен ряд механизмов. Их можно применить при синхронизации со следующими объектами ядра:

· процессы

· потоки

· файлы

· мьютексы

· семафоры

· события

· ожидаемые таймеры

Последние четыре объекта предназначены собственно для синхронизации и не имеют другого назначения, в отличии от первых трех объектов.

Каждый объект ядра может находиться в двух состояниях: свободном или занятом. Идея синхронизации заключается в том, что поток "усыпляет" себя до того как объект ядра не освободиться.

function WaitForSingleObject (hHandle: THandle;

dwTimeOut: DWORD): DWORD;

С помощью данной функции поток сообщает системе, что будет ожидать освобождения объекта hHandle в течении dwTimeOut миллисекунд. Если через заданное время объект не освободится ОС продолжит выполнение потока.

Функция возвращает следующие значения:

Значение Описание
WAIT_OBJECT_0 Объект перешел в свободное состояние
WAIT_TIMEOUT Истекло время ожидания
WAIT_FAILED Ошибка

Если dwTimeOut равно 0 то функция просто вернет состояние объекта. Если dwTimeOut равно значению INFINITE, то время ожидания равно бесконечности.

function WaitForMultipleObjects (nCount: DWORD; lpHandles:

PWOHandleArray; bWaitAll: BOOL; dwTimeOut: DWORD): DWORD;

Функция ожидает освобождения всех или одного объекта из списка PWOHandleArray (указатель на массив переменных типа THandle).

nCount - количество объектов в массиве. Максимальное кол-во - 64.

bWaitAll - определяет режим ожидания. Если значение параметра равно True, то функция будет ждать освобождения всех объектов, иначе любого из передаваемого списка.

Мьютексы

С помощью мьютексов можно синхронизировать выполнение потоков нескольких процессов, так как мьютексы являются объектами ядра.

function CreateMutex (lpMutexAttributes: PSecurityAttributes;

bInitialOwner: BOOL; lpName: PChar): THandle;

Функция создает объект ядра мьютекс и возвращает его описатель или null.

lpMutexAttributes - атрибуты защиты.

bInitialOwner - определяет состояние объекта в момент создания. Если true значит вновь создаваемый объект будет занят, и другие потоки ждущие его освобождения заснут. Иначе объект будет "рожден свободным".

lpName - имя объекта. С помощью данного параметра можно иметь доступ к мьютексу из разных процессов.

Для открытия уже существующего мьютекса с определенным именем можно использовать предыдущую функцию. Для проверки того создан мьютекс или открыт существующий можно использовать функцию GetLastError. Если мьютекс уже существует функция вернет ERROR_ALREADY_EXISTS.

Функции WaitForSingleObject и WaitForMultipleObjects ждут освобождения мьютекса после чего переводят его в занятое состояние. Освободить мьютекс можно с помощью функции

function ReleaseMutex (hMutex: THandle): BOOL;

Освободить объект может только его хозяин. С этой целью функции WaitFor… не только захватывают мьютекс, но и передают текущему потоку права на владение им.

Пример:

Var

gIndex: integer;

gIndexSave: integer;

hMutex: THandle;

function StartThread1(Param:pointer):integer;

var i:integer;

begin

WaitForSingleObject(hMutex,INFINITE);

for i:=0 to 100000000 do Inc(gIndex);

ReleaseMutex(hMutex);

end;

function StartThread2(Param:pointer):integer;

begin

WaitForSingleObject(hMutex,INFINITE);

gIndexSave:=gIndex;

ReleaseMutex(hMutex);

end;

procedure TForm1.Button1Click(Sender: TObject);

var ThreadId: DWORD;

hObjThread: TWOHandleArray;

begin

gIndex:=0;

hMutex:=CreateMutex(nil,False,nil);

hObjThread[0]:=BeginThread(nil,0,@StartThread1,nil,0,ThreadId);

hObjThread[1]:=BeginThread(nil,0,@StartThread2,nil,0,ThreadId);

WaitForMultipleObjects(2,@hObjThread,TRUE,INFINITE);

CloseHandle(hObjThread[0]);

CloseHandle(hObjThread[1]);

CloseHandle(hMutex);

ShowMessage(IntToStr(gIndexSave));

end;

Семафоры

В отличие от мьютекса и критических секций семафор имеет так называемый счетчик ресурсов. Если этот счетчик больше нуля объект семафор является свободным, если же он равен 0, семафор занят.

С помощью данного объекта можно решать различные проблемы по учету свободных ресурсов, например:

 
 


Вы пишете программу, которая, прежде чем дать доступ к набору номера, проверяет, есть ли свободные линии. На момент проверки Вам необходимо приостановить выполнение других потоков, иначе может возникнуть коллизия (помните, что в момент выполнения любой команды Вашей программы поток может быть приостановлен и квант времени передан другому потоку).

Семафор создается с помощью функции

function CreateSemaphore (

lpSemaphoreAttributes: PSecurityAttributes;

lInitialCount, lMaximumCount: Longint; lpName: PChar): THandle;

Функция создает объект ядра семафор с максимальным значением счетчика lMaximumCount.

lpSemaphoreAttributes - атрибут защиты

lInitialCount - начальное значение счетчика ресурса

lpName - имя объекта

Функция WaitForSingleObject поступает следующим образом: если счетчик ресурсов семафора больше нуля функция уменьшают счетчик на единицу, и продолжает выполнять поток, если же счетчик равен 0, функция делает поток неактивным.

Освободить ресурс (увеличить счетчик ресурсов) можно с помощью функции

function ReleaseSemaphore (hSemaphore: THandle;

lReleaseCount: Longint; lpPreviousCount: Pointer): BOOL;

hSemaphore - описатель семафора

lReleaseCount - определяет, на сколько изменится счетчик ресурсов

lpPreviousCount - указатель на величину типа longint в которой возвращается предыдущее значение счетчика ресурсов. Можно передавать nil.

Приостановка выполнения потоков

procedure Sleep (dwMilliseconds: DWORD);

Приостанавливается выполнение потока на dwMilliseconds миллисекунд. Функция отдает это время операционной системе.

function MsgWaitForMultipleObjects (nCount: DWORD;

var pHandles; fWaitAll: BOOL; dwMilliseconds,

dwWakeMask: DWORD): DWORD;

Функция приостанавливает выполнение потока аналогично функции WaitForMultipleObjects. В отличие от нее поток может проснуться, если в очереди сообщений появятся сообщения из типов указанных в параметре dwWakeMask.

Пример:

PhObjThread:=nil;

MsgWaitForMultipleObjects(0,PhObjThread,TRUE,INFINITE,

QS_KEY or QS_MOUSE);

Приостанавливает выполнение потока, ожидая событие отладки

function WaitForDebugEvent (var lpDebugEvent: TDebugEvent;

dwMilliseconds: DWORD): BOOL;

Оконные сообщения и асинхронный ввод

Одна из главных целей ОС - предоставить всем приложениям отказоустойчивую среду. Для этого каждый поток должен исполняться в такой среде, где он может считать себя единственным. В силу того, что поток управляется событиями, последнее утверждение эквивалентно тому, что прочие потоки не должны влиять на очередь сообщений текущего.

С этой целью ОС создает на каждый поток собственную очередь синхронных сообщений(sent - message queue), очередь асинхронных сообщений (post - message queue), очередь ответных сообщений (reply - message queue) и др. Эти очереди реализованы в виде двусвязного списка.

Асинхронная посылка сообщений в очередь потока

С помощью функции

function PostMessage (hWnd: HWND; Msg: UINT; wParam: WPARAM;

lParam: LPARAM): BOOL;

можно поставить сообщение Msg с параметрами wParam и lParam в очередь асинхронных сообщений потока, которому принадлежит окно hWnd.

Внимание. Необходимо отметить, что окна - это объекты, принадлежащие потоку, а не процессу.

Возврат из функции происходит сразу же после постановки сообщения в очередь. Вызывающий поток "понятия не имеет" обработано сообщение или нет. Функция возвращает TRUE если сообщение удачно поставлено в очередь.

С помощью функции

function PostThreadMessage (idThread: DWORD; Msg: UINT;

wParam: WPARAM; lParam: LPARAM): BOOL;

можно поставить сообщение Msg с параметрами wParam и lParam в очередь асинхронных сообщений потока с идентификатором idThread. Данное сообщение не адресовано какому-либо конкретному окну, поэтому требует от принимающей стороны специальной обработки.

Синхронная посылка сообщений

Синхронная посылка сообщений осуществляется функцией

function SendMessage (hWnd: HWND; Msg: UINT; wParam: WPARAM;

lParam: LPARAM): LRESULT;

Отправляет сообщение Msg с параметрами wParam и lParam оконной процедуре окна hWnd. После обработки сообщения функция возвращает управление, т.е. в ней заложен механизм синхронизации. Функция возвращает результат выполнения в виде числа типа longint.

Поведение последней функции сильно зависит от того, какому потоку принадлежит окно hWnd. Если это тот же поток, что и поток-отправитель сообщения, то SendMessage просто вызывает оконную процедуру как подпрограмму. Все усложняется, если это другой поток (безразлично в этом или другом процессе). В этом случае ОС выполняет следующие действия:

1. переданное сообщение присоединяется к очереди синхронных сообщений потока-приемника. Поток-приемник извещается об этом при помощи флага состояния очереди сообщений QS_SENDMESSAGE.

2. если в данный момент поток-приемник не ждет никаких сообщений, он не прерывается и продолжает выполняться

3. после того как поток-приемник готов принять сообщение он проверяет (при условии, что статус очереди QS_SENDMESSAGE) очередь синхронных сообщений. Найдя отправленное сообщение поток обрабатывает его и сбрасывает флаг QS_SENDMESSAGE

4. обработав сообщение и получив его результат поток-приемник отправляет (асинхронным образом) этот результат в очередь ответных сообщений потока-отправителя

Все это время поток-отправитель простаивает, ожидая ответа в очереди ответных сообщений. После получения ответа он возобновляет работу.

Поскольку ОС обрабатывает синхронные сообщения подобным образом, то Ваш поток может "зависнуть", ожидая ответа от потока, который, например, вошел в бесконечный цикл. Но в Win32 API существуют функции, позволяющие защитить код Вашей программы.

function SendMessageTimeout (hWnd: HWND; Msg: UINT;

wParam: WPARAM; lParam: LPARAM; fuFlags, uTimeout: UINT;

var lpdwResult: DWORD): LRESULT;

Отправляет сообщение Msg с параметрами wParam и lParam оконной процедуре окна hWnd с указанием времени uTimeout, которое Ваш поток готов потратить, ожидая ответ. fuFlags - определяет режим ожидания и может принимать следующие значения

Значение Описание
SMTO_NORMAL или 0. Означает отсутствие каких-либо дополнительных условий ожидания
SMTO_ABORTIFHUNG проверка потока-приемника на "зависание". Поток считается зависшим, если он прекращает обработку событий более чем на 5 с. Если поток "завис" управление немедленно возвращается потоку-отправителю
SMTO_BLOCK предотвращает любую обработку синхронных сообщений до возврата из SendMessageTimeout

………..

………….

…………

Обработка сообщений

Для обработки сообщений процесс в цикле вызывает функции GetMessage или PeekMessage, а также ожидает появления сообщения в очереди с помощью WaitMessage. Когда сообщений в очереди нет, система приостанавливает выполнения потока, но как только потоку будет отправлено синхронное или асинхронное сообщение система пробуждает поток с помощью флагов пробуждения(wake flags).

Следующая функция возвращает состояние очередей

function GetQueueStatus (flags: UINT): DWORD;

flags - флаг или группа флагов, позволяющая проверить значение отдельных битов пробуждения.

Значение Описание
QS_KEY WM_KEYUP, WM_KEYDOWN, WM_SYSKEYUP, WM_SYSKEYDOWN
QS_MOUSEMOVE WM_MOUSEMOVE
QS_MOUSEBUTTON WM_(?)MOUSEBUTTON(*) (?) - L,R,M (*) - DOWN, UP, DBLCLK
QS_MOUSE QS_MOUSEMOVE or QS_MOUSEBUTTON
QS_PAINT WM_PAINT
QS_TIMER WM_TIMER
QS_INPUT QS_MOUSE or QS_KEY
QS_POSTMESSAGE Любое асинхронное сообщение
QS_SENDMESSAGE Синхронное сообщение, посланное другим потоком

Функция возвращает набор флагов в старшем слове результата. Возвращаемый результат является подмножеством передаваемого набора флагов. В младшем слове возвращается тип сообщения, помещенного в очередь.

Не все флаги обрабатываются одинаково. Например:

· Флаги QS_MOUSE и QS_KEY устанавливаются, если в очереди есть хотя бы одно соответствующее сообщение. Когда GetMessage или PeekMessage извлекает последнее сообщение из очереди, соответствующие флаги сбрасываются.

· Флаг QS_PAINT обрабатывается иначе. Если существует хотя бы один недействительный регион (требующий перерисовки), флаг не сбрасывается. Флаг сбрасывается в случае окончательной перерисовки.

До появления Win32 API в ОС использовался упорядоченный ввод (serialized input), т.е. система обрабатывала события от клавиатуры и мыши в том порядке, в каком они инициируются пользователем. Очередь ввода была одна, поэтому при зависании обработки нажатия клавиши могла зависнуть вся ОС. В Win32 API используется разупорядоченный ввод (deserialized input). В этом случае аппаратные события не обязательно обрабатываются в порядке их поступления. Поток принимает события в том порядке, в каком пользователь их формирует, но обрабатывает на уровне потока, а не на уровне ОС. При запуске ОС создает особый поток необработанного ввода (raw input thread, RIT).

 
 


Когда на клавиатуре нажимают или отпускают клавишу или передвигают мышь соответствующий драйвер устройства добавляет аппаратное событие в очередь RIT. В результате поток RIT пробуждается и транслирует его в событие WM_*. После этого RIT отправляет событие в виртуальную очередь ввода потока. Прежде чем отправить событие, RIT должен определить поток в чью виртуальную очередь ввода необходимо отправить событие. В случае сигнала от мыши RIT определяет, поверх какого окна находится курсор мыши, после чего отправляет событие потоку, создавшему это окно. Если событие поступило от клавиатуры, RIT определяет, какое окно активно(находится в фокусе ввода) и все повторяется.

Чтобы система могла переключаться между задачами, вне зависимости от того занят ли поток в данный момент обработкой ввода, RIT особым образом реагирует на специальные комбинации клавиш (например, Alt+Ctrl+Del или Alt+Tab).

В Win32 API существует ряд функций, которые позволяют объединять и разъединять виртуальные очереди ввода.

function AttachThreadInput (idAttach, idAttachTo: DWORD;

fAttach: BOOL): BOOL;

Функция объединяет или разъединят виртуальные очереди ввода.

idAttach - идентификатор потока, чья очередь уже не нужна.

idAttachTo - идентификатор потока, чья очередь будет разделяться.

fAttach - режим работы. Если TRUE то происходит объединение очередей, иначе разъединение.

Ввод с клавиатуры и фокус

При вводе с клавиатуры RIT отправляет событие в виртуальную очередь ввода потока, безотносительно конкретному окну. Когда поток получает событие функцией GetMessage, клавиатурное событие извлекается и связывается с окном, которое находится в фокусе.

 
 


Функция

function SetFocus (hWnd: HWND): HWND;

назначает фокус ввода окну hWnd. Функция возвращает описатель окна, которое находилось в фокусе. Если фокус не был передан, функция возвращает NULL. Изменение фокуса ввода приводит к тому, что окну теряющему фокус отправляется сообщение WM_KILLFOCUS, а окну, которое принимает фокус ввода - WM_SETFOCUS.

Данная функция не переключает виртуальные очереди ввода, поэтому если поток 1, будучи подключенным к RIT, вызовет SetFocus для окна E, то не произойдет никаких изменений. Кроме того, если поток 2 вызовет SetFocus для окна E, то при следующем подключении к RIT именно это окно будет в фокусе ввода.

В Win32 API появились две новые функции, которые управляют фокусом ввода.

function SetForegroundWindow (hWnd: HWND): BOOL;

Функция активизирует окно hWnd и переключает виртуальную очередь ввода на поток, который создал это окно.

function GetForegroundWindow: HWND;

Функция возвращает активное окно, вне зависимости от того, какой поток его создал.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: