Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
3 семестр, WinAPI, MFC.pdf
Скачиваний:
370
Добавлен:
15.06.2014
Размер:
6.17 Mб
Скачать

3.Какими методами исполняемый файл можно спроецировать на адресное пространство вызывающего процесса.

4.Что происходит после того, как только DLL спроецирована на адресное пространство вызывающего процесса?

5.Как обрабатываются глобальные и статические переменные DLL-библиотеки? Обрабатываются ли глобальные и статические данные DLL-библиотек в 16-разрядной

Windows и Win32 по-разному?

6.Объясните методы проецирования исполняемого файла на адресное пространство вызывающего процесса.

7.В каких ситуациях операционная система вызывает функцию входа/выхода DllMain?

8.О чем сообщает параметр fdwReason функции DllMain? Какие он принимает значения?

9.Какая функция вызывается в процессе инициализации после загрузки библиотеки в память? Где она должна быть определенна? Какие ее задачи?

10.Перечислите параметры, передаваемые функции LibEntry при загрузке DLL-библиотеки в память.

11.Какая функция получает управление при загрузке DLL-библиотеки в память?

12.Когда вызывается функция WEP?

13.Возможен ли из DLL экспорт и импорт функций? Если да, то в чем их суть?

14.Что содержит заголовочный файл DLL? Куда нужно его включить?

15.Из каких разделов состоит DLL-файл? Можно ли создать свои разделы?

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

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

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

Применение:

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

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

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

Проецирование в память EXE- и DLL-файлов

Действия системы при вызове из потока функции CreateProcess :

Отыскивает ЕХЕ-файл, указанный при вызове CreateProcess. Если файл не найден, новый процесс не создастся, а функция возвращает FALSE.

Создает новый объект ядра "процесс"

Создает адресное пространство нового процесса

Резервирует регион адресного пространства — такой, чтобы в него поместился данный ЕХЕ-файл. Желательное расположение этого региона указывается внутри самого ЕХЕ-файла. По умолчанию базовый адрес ЕХЕ-файла — 0x00400000 (в 64-разрядном приложении под управлением 64-разрядпой Windows 2000 этот адрес может быть другим). При создании исполняемого файла приложения базовый адрес может быть изменен через параметр компоновщика /BASE.

Отмечает, что физическая память, связанная с зарезервированным регионом, — ЕХЕ-файл на диске, а не страничный файл.

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

Резервирует регион адресного пространства - такой, чтобы в него мог поместиться заданный DLL-файл. Желательное расположение этого региона указывается внутри самого DLL-файла. По умолчанию Microsoft Visual C++ присваивает DLL-модулям базовый адрес 0x10000000. При компоновке DLL это значение можно изменить с помощью параметра /BASE. У всех стандартных системных DLL, поставляемых с Windows, разные базовые адреca, чтобы не допустить их перекрытия при загрузке в одно адресное пространство.

Если зарезервировать регион по желательному для DLL базовому адресу не удается, система пытается найти другой регион. Но если в DLL нет информации о возможной переадресации (relocation information), загрузка может вообще не получиться. Системе приходится выполнять модификацию адресов (relocations) внутри DLL. В Windowы 98 эта операция осуществляется по мере подкачки страниц в оперативную память. Но в Windows 2000 на это уходит дополнительная физическая память, выделяемая из страничного файла, да и загрузка такой DLL займет больше времени.

Физическая память, связанная с зарезервированным регионом, — DLL-файл на диске, а не страничный файл. Если Windows 2000 пришлось выполнять модификацию адресов из-за того, что DLL не удалось загрузить по желательному базовому адресу, она запоминает, что часть физической памяти для DLL связана со страничным файлом.

!Если система почему-либо не свяжет ЕХЕ-файл с необходимыми ему DLL, появится соответствующее сообщение, а адресное пространство процесса и объект "процесс" будут освобождены. При этом CreateProcess вернет FALSE.

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

Статические данные могут:

Не разделяться несколькими экземплярами EXE или DLL

При создании нового процесса для уже выполняемого приложения, система просто открывает другое проецируемое в память представление (view) объекта "проекция файла" (file-mapping object), идентифицирующего образ исполняемого файла, и

создает новые объекты "процесс" и "поток". Этим объектам присваиваются идентификаторы процесса и потока. С помощью проецируемых в память файлов несколько одновременно выполняемых экземпляров приложения может совместно использовать один и тот же код, загруженный в оперативную память. Здесь возникает небольшая проблема. Процессы используют линейное (flat) адресное пространство. При компиляции и компоновке программы весь ее код и данные объединяются в нечто, так сказать, большое и цельное. Данные отделены от кода – они расположены вслед за кодом в ЕХЕ-файле< snoska. Но содержимое файла разбито на отдельные разделы (sections). Код находится в одном разделе, а глобальные переменные — в другом разделы выравниваются по границам страниц. Приложение определяет размер страницы через функцию GetSystemInfo. В EXEили DLL-флйле раздел кода обычно предшествует разделу данных.

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

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

Разделяться несколькими экземплярами EXE или DLL

Возможно совместное использование переменных всеми экземплярами EXE или DLL. Любой образ EXEили DLL-файла состоит из группы разделов. Имя каждого стандартного раздела начинается с точки:

при компиляции программы весь код помещается в раздел .text,

неинициализированные данные - в раздел .bss

инициализированные данные — в раздел .data.

Атрибуты разделов:

READ - разрешает чтение из раздела

WRITE - разрешает запись в раздел

EXECUTE - содержимое раздела можно исполнять

SHARED - раздел доступен нескольким экземплярам приложения (отключает механизм копирования при записи)

Создание раздела в EXEили DLL-файле:

#pragma data_seg("имя_раздела")

Создание раздел Shared, в котором содержится единственная переменная типа LONG:

#pragma data_seg("Shared") LONG g_lInstanceCount = 0; #pragma data_seg()

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

_declspec(allocate("Shared")) int d

Чаще всего переменные помещают в собственные разделы, намереваясь сделать их разделяемыми между несколькими проекциями EXE или DLL. Можно сгруппировать в отдельном разделе переменные, которые должны быть доступны всем проекциям EXE

или DLL.

Для этого надо:

указать компилятору о выделение переменных в раздел;

сообщить компоновщику, что переменные в этом разделе должны быть общими; SECTION:имя,атрибуты

Соответствующие директивы для компоновщика можно вставлять прямо в исходный код

#pragma comment(linker, /SECTION Shared,RWS )

Создавать общие разделы не рекомендуется:

разделение памяти таким способом может нарушить защиту

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

Файлы данных, проецируемые в память.

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

Методы проецирования:

один файл, один буфер

Выделение блока памяти, достаточного для размещения всего файла. Открытие файла, считывание его содержимого в блок памяти, закрытие. Располагая в памяти содержимым файла, можно поменять первый байт с последним, второй — с предпоследним и т. д. Этот процесс будет продолжаться, пока не поменяются местами два смежных байта, находящихся в середине файла. После этого – открытие файла и перезаписывание его содержимого.

Недостатки:

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

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

два файла, один буфер

Открытие существующего файла и создание на диске нового — нулевой длины. Затем выделяется небольшой внутренний буфер, например 8 Кб. Устанавливается указатель файла в позицию 8 Кб от конца, считываются в буфер последние 8 Кб содержимого файла, меняются в нем порядок следования байтов на обратный и переписывается буфер в только что созданный файл. Повтор операции до начала исходного файла. После обработки - закрытие обоих файлов и удаление исходного.

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

Недостатки:

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

может понадобиться огромное пространство на жестком диске.

один файл, два буфера

Программа инициализирует два раздельных буфера, допустим, по 8 Кб и считывает первые 8 Кб файла в один буфер, а последние 8 Кб — в другой. Далее содержимое обоих буферов обменивается в обратном порядке, и первый буфер записывается в конец, а второй — в начало того же файла. На каждой итерации программа перемещает восьмикилобайтовые блоки из одной половины файла в другую.

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

Недостатки:

очень сложный метод в реализации.

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

один файл и никаких буферов

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

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

Недостаток: может испортить файл данных, если процесс вдруг прервется.

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

Для этого нужно выполнить три операции:

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

Функция CreateFile - вызвав CreateFile, операционной системе указывается, где находится физическая память для проекции файла на жестком диске в сети, на CD-ROM или в другом месте.

HANDLE CreateFile( PCSTR pszFileName, DWORD dwDesiredAccess, DWORD dwShareMode, PSECURITY_AIIRIBUTES psa, DWORD dwCreationDisposition, DWORD dwFlagsAndAttribules, HANDLE hTemplateFile);

pszFileName - идентифицирует имя создаваемого или открываемого файла

dwDesiredAccess - указывает способ доступа к содержимому файла:

1.0 – содержимое файла нельзя считывать или записывать

2.GENERIC _READ – чтение файла разрешено

3.GENERIC_WRITE – запись в файл разрешена

4.GENERIC_READ | GENERIC_WRITE – разрешено чтение и запись

dwSbareMode – указывает тип совместного доступа к данному файлу:

1. 0 – другие попытки открыть файл закончатся неудачно

2. FILE_SHARE_READ – попытка постороннего процесса открыть файл с флагом GENERIC _READ не удается

3. FILE_SHARE_WRITE – попытка постороннего процесса открыть файл с флагом GENERIC_WRITE не удается

4. FILE_SHARE_READ | FILE_SHARE_WRITE – посторонний процесс может открывать файл без ограничений

CreateFile возвращает его описатель файла или идентификатор

INVALID_HANDLE_VALUE

2. Создать объект ядра "проекция файла", чтобы сообщить системе размер файла и способ доступа к нему.

Функция CreateFileMapping – вызвав CreateFileMapping, системе сообщается, какой объем физической памяти нужен проекции файла.

HANDLE CreateFileMapping( HANDLE hFile, PSECURITY_ATTRIBUTES psa, DWORD fdwProtect, DWOPD dwMaximumSizeHigh, DWORD dwMaximumSizcLow, PCSTR pszName);

hFile идентифицирует описатель файла, проецируемый на адресное пространство процесса этот описатель.

psa — указатель на структуру SECURITY_ATTRIBUTES, которая относится к обьекту ядра "проекция файла", для установки защиты по умолчанию ему присваивается NULL.

fdwProteсt – желательные атрибуты защиты:

1.PAGE_READONLY – можно считывать данные из файла. Передача в

CreateFile флага GENERIC_READ

2.PAGE_READWRITE – можно считывать данные из файла и записывать их. Передача в CreateFile комбинацию флагов GENERIC_READ | GENERIC_WRITE.

3.PAGE_WRITECOPY – можно считывать данные из файла и записывать их. Запись приведет к созданию закрытой копии страницы. Передача в

CreateFile либо GENERIC_READ, либо GENERIC_READ | GENERIC_WRITE

dwMaximumSizeHigh и dwMaximum SizeLow сообщают системе максимальный размер файла в байтах.

pszName – строка с нулевым байтом в конце; в ней указывается имя объекта "проекция файла", которое используется для доступа к данному объекту из другого процесса

3.Указать системе, как спроецировать в адресное пространство процесса объект

"проекция файла" — целиком или частично.

Функция MapViewOfFile - вызвав MapViewOfFile, система резервирует регион адресного пространства под данные файла и передаёт их как физическую память, отображенную на регион

PVOID MapViewOfFile( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap);

hFileMappingObject - идентифицирует описатель объекта "проекция файла", возвращаемый предшествующим вызовом либо CreateFtleMapping,

либо OpenFileMapping

dwDesiredAccess - идентифицирует вид доступа к данным:

1.FILE_MAP_WRITE - файловые данные можно считывать и записывать.

Передача функции CreateFileMapping атрибут PAGE_READWRITE

2.FILE MAP_READ - файловые данные можно только считывать. Вызов CreateFileMapping с любым из следующих атрибутов

PAGE_READONLY, PAGE_READWRITE или PAGE_WRITECOPY

3.FILE_MAP_ALL_ACCESS - файловые данные можно считывать и записывать. Передача функции CreateFileMapping атрибут

PAGE_READWRITE

4.FILE_MAP_COPY - файловые данные можно считывать и записывать, но запись приводит к созданию закрытой копии страницы. Вызов CrealeFileMapping с любым из следующих атрибутов

PAGE_READONIY, PAGE_READWRITE или РАСЕ_WRITECOPY

dwFileOffsetHigh и dwFileOffsetLow – сообщение системе, какой байт файла данных считать в представлении первым.

dwNumberOfBytesToMap - указывается, сколько байтов файла данных должно быть спроецировано на адресное пространство.

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

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

Функция UnmapViewOfFile - освобождение региона.

BOOL UnmapViewOfFile(PVOID pvBaseAddress);

pvBaseAddress - указывает базовый адрес возвращаемо го системе региона. Он должен совпадать со значением, полученным после вызова MapViewOfFile.

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

Функция FlushViewOfFile – система записывает измененные данные (все или частично) в дисковый образ файла

BOOL FlushViewOfFile( PVOID pvAddress, SIZE_T dwNuuiberOfBytesToFlush);

pvAddress - адрес байта, который содержится в границах представления файла, проецируемого в память.

SIZE_T dwNuuiberOfBytesToFlush - определяет количество байтов, которые надо записать в дисковый образ файла.

!В случае проецируемых файлов, физическая память которых расположена на сетевом диске, FlushViewOfFile гарантирует, что файловые данные будут перекачаны с рабочей станции. Но она не гарантирует, что сервер, обеспечивающий доступ к этому файлу, запишет данные на удаленный диск, так как он может просто кэшировать их. Для подстраховки при создании объекта "проекция файла" и последующем проецировании его представления используется флаг FILE_FLAG_WRITE_THROUGH. При открытии файла с этим флагом функция FlushViewOfFile вернет управление только после сохранения на диске сервера всех файловых данных.

Если первоначально представление было спроецировано с флагом FILE_MAP_COPY, любые изменения, внесенные в файловые данные, на самом деле производятся над копией этих данных, хранящихся в страничном файле. Вызванной в этом случае функции UnmapViewOfFile нечего обновлять в дисковом файле, и она просто инициирует возврат системе страниц физической памяти, выделенных из страничного файла. Все изменения в данных на этих страницах теряются.

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

2. Закрыть этот объект.

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

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

Функция CloseHandle – закрытие объектов "проекция файла" и "файл"(двойной вызов)

HANDLE hFile = CreateFile(...);

HANDLE hFileMapping = CreateFileMapping(hFile,...);

PVOID pvFilfi = MapViewOfFile(hFileMapping, );

[работа с файлом, спроецированным в память] UnmapViewOfFile(pvFile);

CloseHandle(hFileMapping);

CloseHandle(hFile);

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

Совместный доступ процессов к данным через механизм проецирования

Самый низкоуровневый механизм совместного использования данных на одной машине

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

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

При открытии ЕХЕ-файла на диске система вызывает CreateFile, с помощью CreateFileMapping создает объект "проекция файла" и, наконец, вызывает MapVtewQfFileEx для отображения ЕХЕ-файла на адресное пространство только что созданного процесса. MapViewOfFileEx вызывается вместо MapViewOfFile, чтобы представление файла было спроецировано по базовому адресу, значение которого хранится в самом ЕХЕ-файле. Потом создается первичный поток процесса, адрес первого байта исполняемого кода в спроецированном представлении заносится в регистр указателя команд (IP), и процессор приступает к исполнению кода.

При запуске второго экземпляра того же приложения, система увидит, что объект "проекция файла" для нужного ЕХЕ-файла уже существует и не станет создавать новый объект. Она просто спроецирует еще одно представление файла — на этот раз в контексте адресного пространства только что созданного второго процесса. Это позволяет эффективнее использовать память.

Файлы, проецируемые на физическую память из страничного файла