7. Вывод информации в окно. Механизм перерисовки окна. Понятие области обновления окна. Операции с областью обновления окна



страница7/16
Дата14.06.2018
Размер5,7 Mb.
1   2   3   4   5   6   7   8   9   10   ...   16

12. Системы координат. Трансформации. Матрица трансформаций. Виды трансформаций и их представление в матрице трансформаций. Преобразования в страничной системе координат. Режимы масштабирования.
Системы координат

Cуществует несколько видов систем координат:

Мировая – world coordinate space (2^32). Обеспечивает параллельный перенос, масштабирование, отражение, поворот, наклон.
Устройства – device coordinate space (2^27). Обеспечивает параллельный перенос (к началу координат на устройстве).
Физическая – physical device coordinate space. Например, клиентская область окна на экране. Для дисплея физическая система координат характеризуется двумя осями Х и У. Х – горизонтально направлена вправо. У – вертикально вниз. Координаты – целые числа.
Логическая (страничная) – page coordinate space (2^32). Устаревшая система координат, основанная на режимах масштабирования (mapping modes). Обеспечивает параллельный перенос, масштабирование, отражение.

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

При выводе, Windows осуществляет перерасчет. В логической системе координат направления осей Х и У можно задать, и единицами измерения могут быть не только пиксели устройства, но и десятые, сотые доли миллиметра и дюйма.

При пересчете Windows осуществляет пересчет логической точки (LP) из логического пространства координат, в физическую точку из физической системы координат (DP). Это делается за 3 шага:

1. Параллельный перенос изображения на логической плоскости путем вычитания из координат каждой точки изображения заданных константных значений.

2. Масштабирование полученного изображения путем масштабирования заданной точки (умножением на заданный коэффициент). Изображение переносится на физическую плоскость.

3. Параллельный перенос изображения на физической плоскости за счет добавления заданных константных значений.

DX=(LX-XWO)*XVE/XWE+XVO

DY=(LY-YWO)*YVE/YWE+YVO

Где:


LX – координата Х в логической системе

XWO – смещение по оси Х в логической системе

XVO – смещение по оси Х в физической системе координат

XVE/XWE – масштабный интерфейс по оси Х



В ОС существуют функции, которые выполняют заданные преобразования для массива точек: LPtoDP() и DPtoLP().

Матрицы трансформаций

Сама матрица трансформации имеет размер 3х3 и в общем виде записывается так:



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



Коэффициент

Описание

a

Изменение масштаба по горизонтали. Значение больше 1 расширяет элемент, меньше 1, наоборот, сжимает.

b

Наклон по горизонтали. Положительное значение наклоняет влево, отрицательное вправо.

c

Наклон по вертикали. Положительное значение наклоняет вверх, отрицательное вниз.

d

Изменение масштаба по вертикали. Значение больше 1 расширяет элемент, меньше 1 — сжимает.

tx

Смещение по горизонтали в пикселах. Положительное значение сдвигает элемент вправо на заданное число пикселов, отрицательное значение сдвигает влево.

ty

Смещение по вертикали в пикселах. При положительном значении элемент опускается на заданное число пикселов вниз или вверх при отрицательном значении.

Для работы с трансформацией необходимо включить расширенный графический режим:

int SetGraphicsMode(HDC hdc, int iMode); GM_ADVANCED

Матрица трансформаций представлена структурой:

struct XFORM {

FLOAT eM11;

FLOAT eM12;

FLOAT eM21;

FLOAT eM22;

FLOAT eDx;

FLOAT eDy;

};

Переменные соответствуют следующим элементам матрицы:





Виды трансформаций

Параллельный перенос:



Масштабирование:



Отражение:



Поворот:


Наклон:




Режимы масштабирования

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

Для установки типа масштабирования используется метод контекста устройства int SetMapMode(HDC hdc, int fnMapMode), а для получения типа масштабирования - метод GetMapMode().

Для указания режима масштабирования в файле windows.h определены символьные константы с префиксом MM_ (от Mapping Mode - режим масштабирования).

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

Режим масштабирования

Логических единиц

Физических единиц

Направление осей

Х

У

MM_TEXT

1

1 pixel





MM_LOMETRIC

10

1 mm





MM_HIMETRIC

100

1 mm





MM_LOENGLISH

100

1 inch





MM_HIENGLISH

1000

1 inch





MM_TWIPS

1440

1 inch





MM_ISOTROPIC

Задается

Задается





MM_ANISOTROPIC

Задается

Задается





По умолчанию действует режим ММ_ТЕХТ, в котором ось Y имеет направление сверху вниз.

Отличие MM_ISOTROPIC от MM_ANISOTROPIC заключается в том, что, при масштабировании в первом из этих двух режимов, оси координат будут масштабироваться равномерно, то есть изображение будет масштабироваться без искажений. Для определения ориентации осей координат и единиц измерения необходимо использовать функции SetWindowExtEx и SetViewportExtEx.




13. Понятие ресурсов программ Windows. Виды ресурсов. Операции с ресурсами.

Ресурсы – двоичные данные, записываемые в исполняемый модуль приложения.

Стандартные виды ресурсов:

· Курсор – Cursor

· Картинка – Bitmap

· Значок – Icon

· Меню – Menu

· Окно диалога – Dialog Box

· Таблица строк – String Table

· Таблица сообщений (об ошибках) – Message Table

· Шрифт – Font

· Таблица горячих клавиш – Accelerator Table

· Информация о версии – Version Information

· Ресурс Plug and Play

· Ресурс VXD

· Ресурс HTML

· Манифест приложения – Side-by-Side Assembly Manifest

· Двоичные данные – RCData

Окна выполняемые в монопольном режиме – окна диалога. В ОС окна диалога загружаются из ресурса и выполняются с помощью одной функции.

Программист может создавать свои собственные типы ресурсов. Ресурсы – отдельны от кода и данных. Ресурсы обладают свойством разделяемости. Несколько программ могут использовать копию одного ресурса.

В .ехе файле ресурс может идентифицироваться числом (0..65536) или строкой. Ресурсы создаются на языке описания ресурсов и располагается в фалах *.rc. При сборке ехе файла rc файлы дописываются в ехе файл.

Добавление и удаление ресурсов исполняемого модуля:

· HANDLE BeginUpdateResource(LPCTSTR pFileName, bool bDeleteExistingResources);

· bool UpdateResource(HANDLE hUpdate, LPCTSTR lpType, LPCTSTR lpName, WORD wLanguage, void* lpData, DWORD cbData);

· bool EndUpdateResource(HANDLE hUpdate, bool fDiscard);
Функция BeginUpdateResource возвращает дескриптор, который может быть использован функцией UpdateResource для добавления, удаления или замены ресурсов в исполняемом файле.Процесс записи ресурсов в файл начинается с вызова BeginUpdateResource. При этом флаг bDeleteExistingResources задает режим записи: с удалением существующих ресурсов или без.Заканчивается процесс записи вызовом EndUpdateResource. Если флаг bDiscard установлен в TRUE, то запись ресурсов отменяется, в противном случае ресурсы записываются в файл.

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


Загрузка ресурсов из исполняемого модуля:

· HRSRC FindResourceEx(HMODULE hModule, LPCTSTR lpType, LPCTSTR lpName, WORD wLanguage); FindResource, EnumResourceXxx.

· HGLOBAL LoadResource(HMODULE hModule, HRSRC hResInfo); LoadImage, LoadMenu, LoadXxx.

· DWORD SizeofResource( HMODULE hModule, HRSRC hResInfo);

Функция FindResource выясняет место ресурса с заданным типом и именем в указанном модуле.

Функция LoadResource загружает указанный ресурс в глобальную память.



14. Понятие динамически-загружаемой библиотеки. Создание DLL-библиотеки. Использование DLL-библиотеки в программе методом статического импорта процедур. Соглашения о вызовах процедур DLL-библиотеки. Точка входа-выхода DLL-библиотеки.



  • Динамически-загружаемая библиотека (DLL) – двоичный модуль операционной системы. Это программа с множеством точек входа. Включает код, данные и ресурсы.

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

· Создавая DLL, Вы создаете набор функций, которые могут быть вызваны из EXE-модуля (или другой DLL). DLL может экспортировать переменные, функции или C++ классы в другие модули. На самом деле я бы не советовал экспортировать переменные, потому что это снижает уровень абстрагирования Вашего кода и усложняет его поддержку. Кроме того, C++ классы можно экспортировать, только если импортирующие их модули транслируются тем же компилятором. Так что избегайте экспорта C++ классов, если Вы не уверены, что разработчики EXE модулей будут пользоваться тем же компилятором.

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

Модуль: MyLib.h

************************************************************/

#ifdef MYLIBAPI

// MYLIBAPI должен быть определен во всех модулях исходного кода DLL

// до включения этого файла

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

#else

// этот заголовочный файл включается в исходный код EXE-файла; // указываем, что все функции и переменные импортируются #define MYLIBAPI extern "C" __declspec(dllimport)

#endif

////////////////////////////////////////////////////////////////////////////

// здесь определяются все структуры данных и идентификаторы (символы)

////////////////////////////////////////////////////////////////////////////

// Здесь определяются экспортируемые переменные. // Примечание: избегайте экспорта переменных. MYLIBAPI int g_nResult;

////////////////////////////////////////////////////////////////////////////

// здесь определяются прототипы экспортируемых функций MYLIBAPI int Add(int nLeft, int nRight);

Этот заголовочный файл надо включать в самое начало исходных файлов Вашей DLL следующим образом.

/***************************************************************************

Модуль: MyLibFile1.cpp

***************************************************************************/

// сюда включаются стандартные заголовочные файлы Windows и библиотеки C #include

// этот файл исходного кода DLL экспортирует функции и переменные

#define MYLIBAPI extern "C" __declspec(dllexport)

// включаем экспортируемые структуры данных, идентификаторы, функции и переменные

#include "MyLib.h" ////////////////////////////////////////////////////////////////////////////

// здесь размещается исходный код этой DLL int g_nResult;

int Add(int nLeft, int nRight) { g_nResult = nLeft + nRight; return(g_nResult);

}

При компиляции исходного файла DLL, показанного на предыдущем листинге, MYLIBAPI определяется как __declspec(dllexport) до включения заголовочного файла MyLib.h. Такой модификатор означает, что данная переменная, функция или C++ класс экспортируется из DLL. Заметьте, что идентификатор MYLIBAPI помещен в заголовочный файл до определения экспортируемой переменной или функции.

Также обратите внимание, что в файле MyLibFile1.cpp перед экспортируемой переменной или функцией не ставится идентификатор MYLIBAPI. Он здесь не нужен: проанализировав заголовочный файл, компилятор запоминает, какие переменные и функции являются экспортируемыми.

Идентификатор MYLIBAPI включает extern. Пользуйтесь этим модификатором только в коде на C++, но ни в коем случае не в коде на стандартном C. Обычно компиляторы C++ искажают (mangle) имена функций и переменных, что может приводить к серьезным ошибкам при компоновке. Представьте, что DLL написана на C++, а исполняемый код — на стандартном C. При сборке DLL имя функции будет искажено, но при сборке исполняемого модуля — нет. Пытаясь скомпоновать исполняемый модуль, компоновщик сообщит об ошибке: исполняемый модуль обращается к несуществующему идентификатору. Модификатор extern не дает компилятору искажать имена переменных или функций, и они становятся доступными исполняемым модулям, написанным на C, C++ или любом другом языке программирования.

Теперь Вы знаете, как используется заголовочный файл в исходных файлах DLL. А как насчет исходных файлов EXE-модуля? В них MYLIBAPI определять не надо: включая заголовочный файл, Вы определяете этот идентификатор как __declspec(dllimport), и при компиляции исходного кода EXE-модуля компилятор поймет, что переменные и функции импортируются из DLL.

Просмотрев стандартные заголовочные файлы Windows (например, WinBase.h), Вы обнаружите, что практически тот же подход исповедует и Microsoft.

Статический импорт DLL-библиотеки

  • Экспорт функции при создании DLL:

n __declspec(dllexport) int Min(int X, int Y);

  • Импорт функции из DLL:

- Добавить библиотеку DLL в проект Visual Studio.

- __declspec(dllimport) int Min(int X, int Y);

- При сборке проекта будет создана статическая библиотека импорта с расширением LIB. Эта статическая библиотека включается в EXE-файл и содержит код вызова функции Min из DLL-библиотеки.

  • Соглашения о вызовах подпрограмм:

- __declspec(dllimport) int __stdcall Min(int X, int Y);

- __cdecl – Параметры передаются на стек в обратном порядке. За освобождение стека после вызова подпрограммы отвечает вызывающая программа.

- __pascal – Передача параметров на стек в прямом порядке. Освобождение стека осуществляет сама вызванная подпрограмма.

- __stdcall – Соглашение для стандартных DLL ОС Windows. Передача параметров на стек происходит в обратном порядке. Освобождение стека выполняет вызванная подпрограмма.

__register – Передача параметров преимущественно через регистры процессора. Не используется при создании DLL, поскольку не стандартизировано.

  • Соглашения о вызовах подпрограмм:

n- Разные способы передачи параметров создают трудности. Главная трудность связана с применением соглашения __stdcall. В VisualStudio использование соглашение о вызовах __stdcall вводит определенные правила именования функций в DLL. Функция получает имя: _Имя@КоличествоБайтПараметров.



_Min@8

Библиотека импорта может создаваться вручную на основе существующей DLL библиотеки. Для этого создается текстовый DEF-файл описания DLL библиотеки и включается в проект.

EXPORTS

Min,Max

При наличии DEF-файла компилятор выбирает из него имена для функций.

Функция входа/выхода

В DLL может быть лишь одна функция входа/выхода. Система вызывает ее в некоторых ситуациях (о чем речь еще впереди) сугубо в информационных целях, и обычно она используется DLL для инициализации и очистки ресурсов в конкретных процессах или потоках. Если Вашей DLL подобные уведомления не нужны, Вы не обязаны реализовывать эту функцию. Пример — DLL, содержащая только ресурсы. Но если же уведомления необходимы, функция должна выглядеть так:

BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, PVOID fImpLoad) {

switch (fdwReason) {

case DLL_PROCESS_ATTACH:

// DLL проецируется на адресное пространство процесса

break;

case DLL_THREAD_ATTACH: // создается поток break;

case DLL_THREAD_DETACH:

// поток корректно завершается

break;

case DLL_PROCESS_DETACH:
// DLL отключается от адресного пространства процесса break;

}

return(TRUE); // используется только для DLL_PROCESS_ATTACH }



Не забывайте, что DLL инициализируют себя, используя функции DllMain. К моменту выполнения Вашей DllMain другие DLL в том же адресном пространстве могут не успеть выполнить свои функции DllMain, т. е. они окажутся неинициализированными. Поэтому Вы должны избегать обращений из DllMain к функциям, импортируемым из других DLL. Кроме того, не вызывайте из DllMain функции LoadLibrary(Ex) и FreeLibrary, так как это может привести к взаимной блокировке.

В документации Platform SDK утверждается, что DllMain должна выполнять лишь простые виды инициализации — настройку локальной памяти потока, создание объектов ядра, открытие файлов и т. д. Избегайте обращений к функциям, связанным с User, Shell, ODBC, COM, RPC и сокетами (а также к функциям, которые их вызывают), потому что соответствующие DLL могут быть еще не инициализированы. Кроме того, подобные функции могут вызывать LoadLibrary(Ex) и тем самым приводить к взаимной блокировке.

Аналогичные проблемы возможны и при создании глобальных или статических C++ объектов, поскольку их конструктор или деструктор вызывается в то же время, что и Ваша DllMain.

Уведомление DLL_PROCESS_ATTACH

Система вызывает DllMain с этим значением параметра fdwReason сразу после того, как DLL спроецирована на адресное пространство процесса. А это происходит, только когда образ DLL-файла проецируется в первый раз. Если затем поток вызовет LoadLibrary(Ex) для уже спроецированной DLL, система просто увеличит счетчик числа пользователей этой DLL; так что DllMain вызывается со значением DLL_PROCESS_ATTACH лишь раз.

Обрабатывая DLL_PROCESS_ATTACH, библиотека должна выполнить в процессе инициализацию, необходимую ее функциям. Например, в DLL могут быть функции, которым нужна своя куча (создаваемая в адресном пространстве процесса). В этом случае DllMain могла бы создать такую кучу, вызвав HeapCreate при обработке уведомления DLL_PROCESS_ATTACH, а описатель созданной кучи сохранить в глобальной переменной, доступной функциям DLL.

При обработке уведомления DLL_PROCESS_ATTACH значение, возвращаемое функцией DllMain, указывает, корректно ли прошла инициализация DLL. Например, если вызов HeapCreate закончился благополучно, следует вернуть TRUE. А если кучу создать не удалось — FALSE. Для любых других значений fdwReason — DLL_PROCESS_DETACH, DLL_THREAD_ATTACH или DLL_THREAD_DETACH — значение, возвращаемое DllMain, системой игнорируется.

Уведомление DLL_PROCESS_DETACH

При отключении DLL от адресного пространства процесса вызывается ее функция DllMain со значением DLL_PROCESS_DETACH в параметре fdwReason. Обрабатывая это значение, DLL должна провести очистку в данном процессе. Например, вызвать HeapDestroy, чтобы разрушить кучу, созданную ею при обработке уведомления DLL_PROCESS_ATTACH. Обратите внимание: если функция DllMain вернула FALSE, получив уведомление DLL_PROCESS_ATTACH, то ее нельзя вызывать с уведомлением DLL_PROCESS_DETACH. Если DLL отключается из-за завершения процесса, то за выполнение кода DllMain отвечает поток, вызвавший ExitProcess (обычно это первичный поток приложения). Когда Ваша входная функция возвращает управление стартовому коду из библиотеки C/C++, тот явно вызывает ExitProcess и завершает процесс.

Если DLL отключается в результате вызова FreeLibrary или FreeLibraryAndExitThread, код DllMain выполняется потоком, вызвавшим одну из этих функций. В случае обращения к FreeLibrary управление не возвращается, пока DllMain не закончит обработку уведомления DLL_PROCESS_DETACH. Учтите также, что DLL может помешать завершению процесса, если, например, ее DllMain входит в бесконечный цикл, получив уведомление DLL_PROCESS_DETACH. Операционная система уничтожает процесс только после того, как все DLL-модули обработают уведомление DLL_PROCESS_DETACH.

Если процесс завершается в результате вызова TerminateProcess, система не вызывает DllMain со значением DLL_PROCESS_DETACH. А значит, ни одна DLL, спроецированная на адресное пространство процесса, не получит шанса на очистку до завершения процесса. Последствия могут быть плачевны — вплоть до потери данных. Вызывайте TerminateProcess только в самом крайнем случае!

Уведомление DLL_THREAD_ATTACH

Когда в процессе создается новый поток, система просматривает все DLL, спроецированные в данный момент на адресное пространство этого процесса, и в каждой из таких DLL вызывает DllMain со значением DLL_THREAD_ATTACH. Тем самым она уведомляет DLL модули о необходимости инициализации, связанной с данным потоком. Только что созданный поток отвечает за выполнение кода в функциях DllMain всех DLL. Работа его собственной (стартовой) функции начинается лишь после того, как все DLL модули обработают уведомление DLL_THREAD_ATTACH.

Если в момент проецирования DLL на адресное пространство процесса в нем выполняется несколько потоков, система не вызывает DllMain со значением DLL_ THREAD_ATTACH ни для одного из существующих потоков. Вызов DllMain с этим значением осуществляется, только если DLL проецируется на адресное пространство процесса в момент создания потока.

Обратите также внимание, что система не вызывает функции DllMain со значением DLL_THREAD_ATTACH и для первичного потока процесса. Любая DLL, проецируемая на адресное пространство процесса в момент его создания, получает уведомление DLL_PROCESS_ATTACH, а не DLL_THREAD_ATTACH.

Уведомление DLL_THREAD_DETACH

Лучший способ завершить поток — дождаться возврата из его стартовой функции, после чего система вызовет ExitThread и закроет поток. Эта функция лишь сообщает системе о том, что поток хочет завершиться, но система не уничтожает его немедленно. Сначала она просматривает все проекции DLL, находящиеся в данный момент в адресном пространстве процесса, и заставляет завершаемый поток вызвать DllMain в каждой из этих DLL со значением DLL_THREAD_DETACH. Тем самым она уведомляет DLL модули о необходимости очистки, связанной с данным потоком. Например, DLL версия библиотеки C/C++ освобождает блок данных, используемый для управления многопоточными приложениями.

Заметьте, что DLL может не дать потоку завершиться. Например, такое возможно, когда функция DllMain, получив уведомление DLL_THREAD_DETACH, входит в бесконечный цикл. А операционная система закрывает поток только после того, как все DLL заканчивают обработку этого уведомления.

Если поток завершается из-за того, что другой поток вызвал для него TerminateThread, система не вызывает DllMain со значением DLL_THREAD_DETACH. Следовательно, ни одна DLL, спроецированная на адресное пространство процесса, не получит шанса на выполнение очистки до завершения потока, что может привести к потере данных. Поэтому TerminateThread, как и TerminateProcess, можно использовать лишь в самом крайнем случае!

Если при отключении DLL еще выполняются какие-то потоки, то для них DllMain не вызывается со значением DLL_THREAD_DETACH. Вы можете проверить это при обработке DLL_PROCESS_DETACH и провести необходимую очистку.

Ввиду упомянутых выше правил не исключена такая ситуация: поток вызывает LoadLibrary для загрузки DLL, в результате чего система вызывает из этой библиотеки DllMain со значением DLL_PROCESS_ATTACH. (В этом случае уведомление DLL_ THREAD_ATTACH не посылается.) Затем поток, загрузивший DLL, завершается, что приводит к новому вызову DllMain — на этот раз со значением DLL_THREAD_DETACH. Библиотека уведомляется о завершении потока, хотя она не получала DLL_ THREAD_ATTACH, уведомляющего о его подключении. Поэтому будьте крайне осторожны при выполнении любой очистки, связанной с конкретным потоком. К счастью, большинство программ пишется так, что LoadLibrary и FreeLibrary вызываются одним потоком.


15. Понятие динамически-загружаемой библиотеки. Создание DLL-библиотеки. Использование DLL-библиотеки в программе методом динамический импорта процедур.

1 и 2 часть вопроса смотри в вопросе номер 14

Загрузка DLL-библиотеки в память:

HMODULE LoadLibrary(LPCTSTR lpFileName);

HMODULE LoadLibraryEx(LPCTSTR lpFileName, _Reserved_ HANDLE hFile, DWORD dwFlags);

HMODULE GetModuleHandle(LPCTSTR lpModuleName); GetModuleHandleEx.

DWORD GetModuleFileName(HMODULE hModule, LPTSTR lpFilename, DWORD nSize);

Освобождение DLL-библиотеки:

bool FreeLibrary(HMODULE hModule); FreeLibraryAndExitThread.

Получение адреса функции в DLL-библиотеке:

void* GetProcAddress(HMODULE hModule, LPCSTR lpProcName);

Применение:

typedef int TMin(int x, int y); // добавить __stdcall

TMin* pMin;

pMin = (TMin*)GetProcAddress(hModule, "_Min@8");

int a = pMin(10, 20);


16. Понятие динамически-загружаемой библиотеки. Создание в DLL-библиотеке разделяемых между приложениями глобальных данных. Разделы импорта и экспорта DLL-библиотеки. Переадресация вызовов процедур DLL-библиотек к другим DLL-библиотекам. Исключение конфликта версий DLL.

1 часть в вопросе номер 14

Разделяемые данные – shared data:

#pragma section("mysection", read, write, shared)

__declspec(allocate("mysection")) int Number = 0;

Экспорт

Если модификатор __declspec(dllexport) указан перед переменной, прототипом функции или C++классом, компилятор Microsoft С/С++ встраивает в конечный OBJфайл дополнительную информацию. Она понадобится компоновщику при сборке DLL из OBJфайлов.

Обнаружив такую информацию, компоновщик создает LIB-файл со списком идентификаторов, экспортируемых из DLL. Этот LIB-файл нужен при сборке любого EXE-модуля, ссылающегося на такие идентификаторы. Компоновщик также вставляет в конечный DLL-файл таблицу экспортируемых идентификаторов — раздел экспорта, в котором содержится список (в алфавитном порядке) идентификаторов экспортируемых функций, переменных и классов. Туда же помещается относительный виртуальный адрес (relative virtual address, RVA) каждого идентификатора внутри DLL-модуля.

Воспользовавшись утилитой DumpBin.exe (с ключом -exports) из состава Microsoft Visual Studio, мы можем увидеть содержимое раздела экспорта в DLL-модуле.



Импорт

Разрешая ссылки на импортируемые идентификаторы, компоновщик создает в конечном EXE-модуле раздел импорта (imports section). В нем перечисляются DLL, необходимые этому модулю, и идентификаторы, на которые есть ссылки из всех используемых DLL.

Воспользовавшись утилитой DumpBin.exe (с ключом -imports), мы можем увидеть содержимое раздела импорта. Ниже показан фрагмент полученной с ее помощью таблицы импорта Calc.exe.

C:\WINNT\SYSTEM32>DUMPBIN >imports Calc.EXE

Microsoft (R) COFF Binary File Dumper Version 6.00.8168

Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

Dump of file calc.exe

File Type: EXECUTABLE IMAGE

Section contains the following imports:

SHELL32.dll

10010F4 Import Address Table

1012820 Import Name Table

FFFFFFFF time date stamp

FFFFFFFF Index of first forwarder reference

77C42983 7A ShellAboutW

MSVCRT.dll

1001094 Import Address Table

10127C0 Import Name Table

FFFFFFFF time date stamp

FFFFFFFF Index of first forwarder reference

78010040 295 memmove

78018124 42 _EH_prolog

78014C34 2D1 toupper

78010F6E 2DD wcschr

78010668 2E3 wcslen
ADVAPI32.dll

1001000 Import Address Table

101272C Import Name Table

FFFFFFFF time date stamp

FFFFFFFF Index of first forwarder reference

779858F4 19A RegQueryValueExA

77985196 190 RegOpenKeyExA

77984BA1 178 RegCloseKey

KERNEL32.dll

100101C Import Address Table

1012748 Import Name Table

FFFFFFFF time date stamp

FFFFFFFF Index of first forwarder reference

77ED4134 336 lstrcpyW

77ED33E8 1E5 LocalAlloc

77EDEF36 DB GetCommandLineW

77ED1610 15E GetProfileIntW

77ED4BA4 1EC LocalReAlloc

Header contains the following bound import information:

Bound to SHELL32.dll [36E449E0] Mon Mar 08 14:06:24 1999

Bound to MSVCRT.dll [36BB8379] Fri Feb 05 15:49:13 1999

Bound to ADVAPI32.dll [36E449E1] Mon Mar 08 14:06:25 1999

Bound to KERNEL32.dll [36DDAD55] Wed Mar 03 13:44:53 1999

Bound to GDI32.dll [36E449E0] Mon Mar 08 14:06:24 1999

Bound to USER32.dll [36E449E0] Mon Mar 08 14:06:24 1999

Summary


2000 .data

3000 .rsrc

13000 .text

Как видите, в разделе есть записи по каждой DLL, необходимой Calc.exe: Shell32.dll, MSVCRt.dll, AdvAPI32.dll, Kernel32.dll, GDI32.dll и User32.dll. Под именем DLL-модуля выводится список идентификаторов, импортируемых программой Calc.exe. Например, Calc.exe обращается к следующим функциям из Kernel32.dll: lstrcpyW, LocalAlloc, GetCommandLineW, GetProfileIntW и др.

Число слева от импортируемого идентификатора называется «подсказкой» (hint) и для нас несущественно. Крайнее левое число в строке для идентификатора сообщает адрес, по которому он размещен в адресном пространстве процесса. Такой адрес показывается, только если было проведено связывание (binding) исполняемого модуля.

Переадресация вызовов функций

Запись о переадресации вызова функции (function forwarder) — это строка в разделе экспорта DLL, которая перенаправляет вызов к другой функции, находящейся в другой DLL. Например, запустив утилиту DumpBin из Visual C++ для Kernel32.dll в Windows 2000, Вы среди прочей информации увидите и следующее.

C:\winnt\system32>DumpBin 􏰀Exports Kernel32.dll

(часть вывода опущена)

. 360 167 HeapAlloc (forwarded to NTDLL.RtlAllocateHeap) 


. 361 168 HeapCompact (000128D9) 


. 362 169 HeapCreate (000126EF) 


. 363 16A HeapCreateTagsW (0001279E) 


. 364 16B HeapDestroy (00012750) 


. 365 16C HeapExtend (00012773) 


. 366 16D HeapFree (forwarded to NTDLL.RtlFreeHeap) 


. 367 16E HeapLock (000128ED) 


. 368 16F HeapQueryTagW (000127B8) 


. 369 170 HeapReAlloc (forwarded to NTDLL.RtlReAllocateHeap) 


. 370 171 HeapSize (forwarded to NTDLL.RtlSizeHeap) 
 (остальное тоже опущено)


Здесь есть четыре переадресованные функции. Всякий раз, когда Ваше приложение вызывает HeapAlloc, HeapFree, HeapReAlloc или HeapSize, его EXE модуль динамически связывается с Kernel32.dll. При запуске EXE модуля загрузчик загружает Kernel32.dll и, обнаружив, что переадресуемые функции на самом деле находятся в NTDLL.dll, загружает и эту DLL. Обращаясь к HeapAlloc, программа фактически вызывает функцию RtlAllocateHeap из NTDLL.dll. А функции HeapAlloc вообще нет!

При вызове HeapAlloc (см. ниже) функция GetProcAddress просмотрит раздел эк порта Kernel32.dll и, выяснив, что HeapAlloc — переадресуемая функция, рекурсивно вызовет сама себя для поиска RtlAllocateHeap в разделе экспорта NTDLL.dll. GetProcAddress(GetModuleHandle("Kernel32"), "HeapAlloc");

Вы тоже можете применять переадресацию вызовов функций в своих DLL. Самый

простой способ — воспользоваться директивой pragma: // переадресация к функции из DllWork

#pragma comment(linker, "/export:SomeFunc=DllWork.SomeOtherFunc")

Эта директива сообщает компоновщику, что DLL должна экспортировать функцию SomeFunc, которая на самом деле реализована как функция SomeOtherFunc в модуле DllWork.dll. Такая запись нужна для каждой переадресуемой функции.

1. Исключение конфликта версий DLL:

a. c:\myapp\myapp.exe загружает старую версию

b. c:\program files\common files\system\mydll.dll,

c. а должен загружать mydll.dll из своего же каталога.

d. n Создать пустой файл c:\myapp\myapp.exe.local.

e. Будет грузиться библиотека c:\myapp\mydll.dll.

f. n Создать каталог c:\myapp\myapp.exe.local.

g. Будет грузиться c:\myapp\myapp.exe.local\mydll.dll.

h. n Создать файл манифеста для приложения. В этом случае .local файлы будут игнорироваться.

Когда разрабатывались первые версии Windows, оперативная память и дисковое пространство были крайне дефицитным ресурсом, так что Windows была рассчитана на предельно экономное их использование — с максимальным разделением между потребителями. В связи с этим Microsoft рекомендовала размещать все модули, используемые многими приложениями (например, библиотеку C/C++ и DLL, относящиеся к MFC) в системном каталоге Windows, где их можно было легко найти.

Однако со временем это вылилось в серьезную проблему: программы установки приложений то и дело перезаписывали новые системные файлы старыми или не полностью совместимыми. Из-за этого уже установленные приложения переставали работать. Но сегодня жесткие диски стали очень емкими и недорогими, оперативная память тоже значительно подешевела. Поэтому Microsoft сменила свою позицию на прямо противоположную: теперь она настоятельно рекомендует размещать все файлы приложения в своем каталоге и ничего не трогать в системном каталоге Windows. Тогда Ваше приложение не нарушит работу других программ, и наоборот.

С той же целью Microsoft ввела в Windows 2000 поддержку перенаправления DLL (DLL redirection). Она заставляет загрузчик операционной системы загружать модули сначала из каталога Вашего приложения и, только если их там нет, искать в других каталогах.

Чтобы загрузчик всегда проверял сначала каталог приложения, нужно всего лишь поместить туда специальный файл. Его содержимое не имеет значения и игнорируется — важно только его имя: оно должно быть в виде AppName.local. Так, если исполняемый файл Вашего приложения — SuperApp.exe, присвойте перенаправляющему файлу имя SuperApp.exe.local.

Функция LoadLibrary(Ex) проверяет наличие этого файла и, если он есть, загружает модуль из каталога приложения; в ином случае LoadLibrary(Ex) работает так же, как и раньше.

Перенаправление DLL исключительно полезно для работы с зарегистрированными COM объектами. Оно позволяет приложению размещать DLL с COM объектами в своем каталоге, и другие программы, регистрирующие те же объекты, не будут мешать его нормальной работе.

17. Понятие объекта ядра ОС Windows. Виды объектов ядра. Атрибуты защиты объекта ядра. Дескриптор защиты объекта ядра. Создание и удаление объектов ядра.

Что такое объект ядра

Создание, открытие и прочие операции с объектами ядра станут для Вас, как разработчика Windows-приложений, повседневной рутиной. Система позволяет создавать и оперировать с несколькими типами таких объектов, в том числе: маркерами доступа (access token objects), файлами (file objects), проекциями файлов (file-mapping objects), портами завершения ввода-вывода (I/O completion port objects), заданиями (job objects), почтовыми ящиками (mailslot objects), мьютексами (mutex objects), каналами (pipe objects), процессами (process objects), семафорами (semaphore objects), потоками (thread objects) и ожидаемыми таймерами (waitable timer objects). Эти объекты создаются Windows-функциями. Каждый объект ядра — на самом деле просто блок памяти, выделенный ядром и доступный только ему. Этот блок представляет собой структуру данных, в элементах которой содержится информация об объекте. Некоторые элементы (дескриптор защиты, счетчик числа пользователей и др.) присутствуют во всех объектах, но бо҆льшая их часть специфична для объектов конкретного типа. Например, у объекта «процесс» есть идентификатор, базовый приоритет и код завершения, а у объекта «файл» — смещение в байтах, режим разделения и режим открытия. Поскольку структуры объектов ядра доступны только ядру, приложение не может самостоятельно найти эти структуры в памяти и напрямую модифицировать их содержимое. Такое ограничение Microsoft ввела намеренно, чтобы ни одна программа не нарушила целостность структур объектов ядра. Это же ограничение позволяет Microsoft вводить, убирать или изменять элементы структур, не нарушая работы каких-либо приложений. Но вот вопрос: если мы не можем напрямую модифицировать эти структуры, то как же наши приложения оперируют с объектами ядра? Ответ в том, что в Windows предусмотрен набор функций, обрабатывающих структуры объектов ядра по строго определенным правилам. Мы получаем доступ к объектам ядра только через эти функции. Когда Вы вызываете функцию, создающую объект ядра, она возвращает описатель, идентифицирующий созданный объект. Описатель следует рассматривать как «непрозрачное» значение, которое может быть использовано любым потоком Вашего процесса. Этот описатель Вы передаете Windows-функциям, сообщая системе, какой объект ядра Вас интересует. Но об описателях мы поговорим позже (в этой главе). Для большей надежности операционной системы Microsoft сделала так, чтобы значения описателей зависели от конкретного процесса. Поэтому, если Вы передадите такое значение (с помощью какого-либо механизма межпроцессной связи) потоку другого процесса, любой вызов из того процесса со значением описателя, полученного в Вашем процессе, даст ошибку.



Учет пользователей объектов ядра

Объекты ядра принадлежат ядру, а не процессу. Иначе говоря, если Ваш процесс вызывает функцию, создающую объект ядра, а затем завершается, объект ядра может быть не разрушен. В большинстве случаев такой объект все же разрушается; но если созданный Вами объект ядра используется другим процессом, ядро запретит разрушение объекта до тех пор, пока от него не откажется и тот процесс. Ядру известно, сколько процессов использует конкретный объект ядра, поскольку в каждом объекте есть счетчик числа его пользователей. Этот счетчик — один из элементов данных, общих для всех типов объектов ядра. В момент создания объекта счетчику присваивается 1. Когда к существующему объекту ядра обращается другой процесс, счетчик увеличивается на 1. А когда какой-то процесс завершается, счетчики всех используемых им объектов ядра автоматически уменьшаются на 1. Как только счетчик какого-либо объекта обнуляется, ядро уничтожает этот объект.



Защита

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

Почти все функции, создающие объекты ядра, принимают указатель на структуру SECURITY_ATTRIBUTES как аргумент, например:

HANDLE CreateFileMapping( HANDLE hFile, PSECURITY_ATTRIBUTES psa, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, PCTSTR pszName);

Большинство приложений вместо этого аргумента передает NULL и создает объект с защитой по умолчанию. Такая защита подразумевает, что создатель объекта и любой член группы администраторов получают к нему полный доступ, а все прочие к объекту не допускаются. Однако Вы можете создать и инициализировать структуру SECURITY_ATTRIBUTES, а затем передать ее адрес. Она выглядит так:

typedef struct _SECURITY_ATTRIBUTES {

DWORD nLength;

LPVOID lpSecurityDescriptor;

BOOL bInheritHandle;

} SECURITY_ATTRIBUTES;

Хотя структура называется SECURITY_ATTRIBUTES, лишь один ее элемент имеет отношение к защите — lpSecurityDescriptor. Если надо ограничить доступ к созданному Вами объекту ядра, создайте дескриптор защиты и инициализируйте структуру SECURITY_ATTRIBUTES следующим образом:

SECURITY_ATTRIBUTES sa;

sa.nLength = sizeof(sa); // используется для выяснения версий

sa.lpSecurityDescriptor = pSD; // адрес инициализированной SD

sa.bInheritHandle = FALSE; // об этом позже

HANDLE hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, 0, 1024, "MyFileMapping");

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

Кроме объектов ядра Ваша программа может использовать объекты других типов — меню, окна, курсоры мыши, кисти и шрифты. Они относятся к объектам User или GDI. Новичок в программировании для Windows может запутаться, пытаясь отличить объекты User или GDI от объектов ядра. Как узнать, например, чьим объектом — User или ядра — является данный значок? Выяснить, не принадлежит ли объект ядру, проще всего так: проанализировать функцию, создающую объект. Практически у всех функций, создающих объекты ядра, есть параметр, позволяющий указать атрибуты защиты, — как у CreateFileMapping. В то же время у функций, создающих объекты User или GDI, нет параметра типа PSECURITY_ATTRIBUTES.



Создание объекта ядра

Когда процесс инициализируется в первый раз, таблица описателей еще пуста. Но стоит одному из его потоков вызвать функцию, создающую объект ядра (например, CreateFileMapping), как ядро выделяет для этого объекта блок памяти и инициализирует его; далее ядро просматривает таблицу описателей, принадлежащую данному процессу, и отыскивает свободную запись. Поскольку таблица еще пуста, ядро обнаруживает структуру с индексом 1 и инициализирует ее. Указатель устанавливается на внутренний адрес структуры данных объекта, маска доступа — на доступ без ограничений и, наконец, определяется последний компонент — флаги. Функции, создающие объекты ядра обычно имеют вид HANDLE CreateXxx(…).

Все функции, создающие объекты ядра, возвращают описатели, которые привязаны к конкретному процессу и могут быть использованы в любом потоке данного процесса. Значение описателя представляет собой индекс в таблице описателей, принадлежащей процессу, и таким образом идентифицирует место, где хранится информация, связанная с объектом ядра. Вот поэтому при отладке своего приложения и просмотре фактического значения описателя объекта ядра Вы и видите такие малые величины: 1, 2 и т. д. Но помните, что физическое содержимое описателей не задокументировано и может быть изменено. Всякий раз, когда Вы вызываете функцию, принимающую описатель объекта ядра как аргумент, Вы передаете ей значение, возвращенное одной из Create-функций. При этом функция смотрит в таблицу описателей, принадлежащую Вашему процессу, и считывает адрес нужного объекта ядра. Если Вы передаете неверный индекс (описатель), функция завершается с ошибкой и GetLastError возвращает 6 (ERROR_INVALID_HANDLE). Это связано с тем, что на самом деле описатели представляют собой индексы в таблице, их значения привязаны к конкретному процессу и недействительны в других процессах. Если вызов функции, создающей объект ядра, оказывается неудачен, то обычно возвращается 0 (NULL). Такая ситуация возможна только при острой нехватке памяти или при наличии проблем с защитой. К сожалению, отдельные функции возвращают в таких случаях не 0, а –1 (INVALID_HANDLE_VALUE). Например, если CreateFile не сможет открыть указанный файл, она вернет именно INVALID_HANDLE_VALUE. Будьте очень осторожны при проверке значения, возвращаемого функцией, которая создает объект ядра. Так, для CreateMutex проверка на INVALID_HANDLE_VALUE бессмысленна:

HANDLE hMutex = CreateMutex(...);

if (hMutex == INVALID_HANDLE_VALUE) {

// этот код никогда не будет выполнен, так как при ошибке CreateMutex возвращает NULL

}

Точно так же бессмыслен и следующий код:



HANDLE hFile = CreateFile(...);

if (hFile == NULL) {

// и этот код никогда не будет выполнен, так как при ошибке CreateFile возвращает INVALID_HANDLE_VALUE (-1)

}

Закрытие объекта ядра

Независимо от того, как именно Вы создали объект ядра, по окончании работы с ним его нужно закрыть вызовом CloseHandle:

BOOL CloseHandle(HANDLE hobj);

Эта функция сначала проверяет таблицу описателей, принадлежащую вызывающему процессу, чтобы убедиться, идентифицирует ли переданный ей индекс (описатель) объект, к которому этот процесс действительно имеет доступ. Если переданный индекс правилен, система получает адрес структуры данных объекта и уменьшает в этой структуре счетчик числа пользователей; как только счетчик обнулится, ядро удалит объект из памяти. Если же описатель неверен, происходит одно из двух. В нормальном режиме выполнения процесса CloseHandle возвращает FALSE, а GetLastError — код ERROR_INVALID_HANDLE. Но при выполнении процесса в режиме отладки система просто уведомляет отладчик об ошибке. Перед самым возвратом управления CloseHandle удаляет соответствующую запись из таблицы описателей: данный описатель теперь недействителен в Вашем процессе и использовать его нельзя. При этом запись удаляется независимо от того, разрушен объект ядра или нет! После вызова CloseHandle Вы больше не получите доступ к этому объекту ядра; но, если его счетчик не обнулен, объект остается в памяти. Тут все нормально, это означает лишь то, что объект используется другим процессом (или процессами). Когда и остальные процессы завершат свою работу с этим объектом (тоже вызвав CloseHandle), он будет разрушен. А вдруг Вы забыли вызвать CloseHandle — будет ли утечка памяти? И да, и нет. Утечка ресурсов (тех же объектов ядра) вполне вероятна, пока процесс еще исполняется. Однако по завершении процесса операционная система гарантированно освобождает все ресурсы, принадлежавшие этому процессу, и в случае объектов ядра действует так: в момент завершения процесса просматривает его таблицу описателей и закрывает любые открытые описатели.
18. Проецирование файлов в память. Отличие в механизме проецирования файлов в память в ОС Windows и UNIX/Linux. Действия по проецированию файла в память.

На платформах Win/Unix существуют средства для работы с файлами как с оперативной памятью.



Идея в том, чтобы закрепить за началом файла какой-либо адрес памяти, а дальше выполнять чтение и запись файла методом чтения/записи байтов оперативной памяти. Т.к. файл не может поместится в оперативной памяти целиком, он делится на страницы и в оперативную память подгружаются лишь те страницы, с которыми происходит работа. Адресное пространство файла является виртуальным, оно может значительно превосходить по размерам оперативную память. Для прозрачной поддержки проецирования файлов в память необходимо иметь поддержку виртуальной памяти на уровне процессора и архитектуры компьютера.

В ОС Win процессы работают в виртуальном адресном пространстве, для которого создается на диске файл подкачки (swap). При проецировании файлов в память, файл подкачки не затрагивается, хотя проецирование происходит в виртуальное адресное пространство процесса. Такое возможно за счет аппаратной поддержки сложных таблиц страниц. В WIN – File Mapping; Unix – Memory Mapping.

В ОС Windows отображение файлов в память является двухуровневым:



В Unix схема отображения файлов одноуровневая:



Открытие или создание файла, объекта ядра файл, происходит с помощью функции:



HANDLE CreateFile(

LPCTSTR lpFileName, // путь к файлу

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

DWORD dwShareMode, // будет ли файл доступным для совместного использования со стороны других процессов. 0 – запретить сторонним процессам открывать этот файл

LPSECURITY_ATTRIBUTES lpSecurityAttributes, // атрибуты защиты

DWORD dwCreationDisposition, // режим открытия/создания файла

DWORD dwFlagsAndAttributes, // флаги и атрибуты файла

HANDLE hTemplateFile // дескриптор файла с дополнительными атрибутами

);

Хотя в Win существует OpenFile, но использовать ее не рекомендуется. Открытие/создание рекомендуется производить CreateFile.

Шаги:

1. Открытие файла CreateFile.



2. Создание объекта ядра под названием «проекция файла».

HANDLE CreateFileMapping(

HANDLE hFile, // Дескриптор файла полученный из CreateFile

LPSECURITY_ATTRIBUTES lpAttributes, // атрибуты защиты

DWORD flProtect, // Флаги.

Существуют флаги для отображения в память DLL и выполняемых файлов в формате PE (Portable Executable). Эти флаги обеспечивают автоматическое назначение областям кода и данных соответствующих атрибутов защиты. Коду устанавливается атрибут защиты READONLY, данным – WRITECOPY. Существуют дополнительные флаги, обеспечивающие разделяемость проецируемой памяти между процессами.

DWORD dwMaximumSizeHigh, // high-order DWORD of size

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

DWORD dwMaximumSizeLow, // low-order DWORD of size

LPCTSTR lpName // Имя объекта ядра.

);

Проецирование файла на физическую память:

LPVOID MapViewOfFile(

HANDLE hFileMappingObject, // Дескриптор на проекцию файла.

DWORD dwDesiredAccess, // Набор флагов, определяющих, какой все же будет доступ к памяти.

DWORD dwFileOffsetHigh, // high-order DWORD of offset

DWORD dwFileOffsetLow, // low-order DWORD of offset

SIZE_T dwNumberOfBytesToMap // Размер окна проекции.

);

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



BOOL UnmapViewOfFile(

LPCVOID lpBaseAddress // starting address

); //Закрывает окно проекции

BOOL FlushViewOfFile(

LPCVOID lpBaseAddress, // starting address

SIZE_T dwNumberOfBytesToFlush // number of bytes in range

); //Записывает Все изменения в файл

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


19. Современные многопроцессорные архитектуры SMP и NUMA. Многоуровневое кэширование памяти в современных процессорах. Проблема перестановки операций чтения и записи в архитектурах с ослабленной моделью памяти. Способы решения проблемы перестановки операций чтения и записи.

Симметричная многопроцессорная архитектура – SMP (Symmetric Multi-Processor Architecture):





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

Память служит, в частности, для передачи сообщений между процессорами, при этом все вычислительные устройства при обращении к ней имеют равные права и одну и ту же адресацию для всех ячеек памяти. Поэтому SMP-архитектура называется симметричной. Последнее обстоятельство позволяет очень эффективно обмениваться данными с другими вычислительными устройствами. SMP-система строится на основе высокоскоростной системной шины, к слотам которой подключаются функциональные блоки типов: процессоры, подсистема ввода/вывода и т.п. Наиболее известными SMP-системами являются SMP-cерверы и рабочие станции на базе процессоров Intel (IBM, HP, Compaq, Dell, ALR, Unisys, DG, Fujitsu и др.). Вся система работает под управлением единой ОС (обычно UNIX-подобной, но для Intel-платформ поддерживается Windows NT). ОС автоматически (в процессе работы) распределяет процессы по процессорам, но иногда возможна и явная привязка.

Основные преимущества SMP-систем:

· простота и универсальность для программирования. Архитектура SMP не накладывает ограничений на модель программирования, используемую при создании приложения: обычно используется модель параллельных ветвей, когда все процессоры работают независимо друг от друга. Однако можно реализовать и модели, использующие межпроцессорный обмен. Использование общей памяти увеличивает скорость такого обмена, пользователь также имеет доступ сразу ко всему объему памяти. Для SMP-систем существуют довольно эффективные средства автоматического распараллеливания;

· простота эксплуатации. Как правило, SMP-системы используют систему кондиционирования, основанную на воздушном охлаждении, что облегчает их техническое обслуживание;

· относительно невысокая цена.

Основной недостаток: системы с общей памятью плохо масштабируются.

Этот существенный недостаток SMP-систем не позволяет считать их по-настоящему перспективными. Причиной плохой масштабируемости является то, что в данный момент шина способна обрабатывать только одну транзакцию, вследствие чего возникают проблемы разрешения конфликтов при одновременном обращении нескольких процессоров к одним и тем же областям общей физической памяти. Вычислительные элементы начинают друг другу мешать. Когда произойдет такой конфликт, зависит от скорости связи и от количества вычислительных элементов. В настоящее время конфликты могут происходить при наличии 8-24 процессоров. Кроме того, системная шина имеет ограниченную (хоть и высокую) пропускную способность (ПС) и ограниченное число слотов. Все это очевидно препятствует увеличению производительности при увеличении числа процессоров и числа подключаемых пользователей. В реальных системах можно задействовать не более 32 процессоров. Для построения масштабируемых систем на базе SMP используются кластерные или NUMA-архитектуры. При работе с SMP-системами используют так называемую парадигму программирования с разделяемой памятью (shared memory paradigm).



Гибридная архитектура с неоднородным доступом к памяти – NUMA (Non-Uniform Memory Access Architecture):




Поделитесь с Вашими друзьями:
1   2   3   4   5   6   7   8   9   10   ...   16


База данных защищена авторским правом ©grazit.ru 2019
обратиться к администрации

войти | регистрация
    Главная страница


загрузить материал