Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6266
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

Глава 16. Стек потока.docx 535

//произошло переполнение стека.

SetDlgItemText(hWnd, IDC_ANSWER, TEXT("Error"));

chMB("The number is too big, please enter a smaller number");

}else {

//сумма вычислена успешно

SetDlgItemInt(hWnd, IDC_ANSWER, uSum, FALSE);

}

break;

}

}

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

INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

switch (uMsg) {

 

chHANDLE_DLGMSG(hWnd, WM_INITDIALOG,

Dlg_OnInitDialog);

 

 

chHANDLE_DLGMSG(hWnd, WM_COMMAND,

Dlg_OnCommand);

 

}

 

 

 

 

return(FALSE);

 

 

 

}

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

int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR, int) {

DialogBox(hinstExe, MAKEINTRESOURCE(IDD_SUMMATION), NULL, Dlg_Proc); return(0);

}

//////////////////////////////// End of File //////////////////////////////////

Оглавление

 

Г Л А В А 1 7 Проецируемые в память файлы....................................................................

536

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

537

Статические данные не разделяются несколькими экземплярами EXE или

 

DLL .........................................................................................................................................

538

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

550

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

551

Обработка больших файлов .................................................................................................

570

Проецируемые файлы и когерентность..............................................................................

572

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

573

Особенности проецирования файлов.................................................................................

575

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

576

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

577

Частичная передача физической памяти проецируемым файлам ................................

583

Г Л А В А 1 7

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

Операции с файлами — это то, что рано или поздно приходится делать практически во всех программах, и всегда это вызывает массу проблем. Должно ли приложение просто открыть файл, считать и закрыть его, или открыть, считать фрагмент в буфер и перезаписать его в другую часть файла? В Windows многие из этих проблем решаются очень изящно — с помощью проецируемых в память файлов (memory-mapped files).

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

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

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

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

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

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

Глава 17. Проецируемые в память файлы.docx 537

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

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

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

2.Создает новый объект ядра «процесс».

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

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

/BASE.

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

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

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

2.Если зарезервировать регион по желательному для DLL базовому адресу не удается (из-за того, что он слишком мал либо занят каким-то еще EXE-или DLL-файлом), система пытается найти другой регион. Но по двум причинам такая ситуация весьма неприятна. Во-первых, если в DLL нет информации о возможной переадресации (relocation information), загрузка может вообще не получиться. (Такую информацию можно удалить из DLL при компоновке с параметром /FIXED. Это уменьшит размер DLL-файла, но тогда модуль должен грузиться только по указанному базовому адресу.) Во-вторых, системе приходится выполнять модификацию адресов (relocations) внутри DLL. В Windows на это уходит дополнительная физическая память, выделяемая из страничного файла, да и загрузка такой DLL займет больше времени.

538 Часть III. Управление памятью

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

связана со страничным файлом.

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

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

Статические данные не разделяются несколькими экземплярами EXE или DLL

Когда вы создаете новый процесс для уже выполняемого приложения, система просто открывает другое проецируемое в память представление (view) объекта «проекция файла» (file-mapping object), идентифицирующего образ исполняемого файла, и создает новые объекты «процесс» и «поток» (для первичного потока). Этим объектам присваиваются идентификаторы процесса и потока. С помощью проецируемых в память файлов несколько одновременно выполняемых экземпляров приложения может совместно использовать один и тот же код, загруженный в оперативную память.

Здесь возникает небольшая проблема. Процессы используют линейное (flat) адресное пространство. При компиляции и компоновке программы весь ее код и данные объединяются в нечто, так сказать, большое и цельное. Данные, конечно, отделены от кода, но только в том смысле, что они расположены вслед за кодом в ЕХЕ-файле1. Вот упрощенная иллюстрация того, как код и данные приложения загружаются в виртуальную память, а затем отображаются на адресное пространство процесса:

1 Надо найти

Глава 17. Проецируемые в память файлы.docx 539

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

Примечание. В действительности содержимое файла разбивается на два раздела. В одном разделе находится код, а в другом — глобальные переменные. Эти секции выравниваются по размеру страниц. Приложение может определить размер страницы, вызвав GetSystemInfo. В .exeили . dllфайле раздела кода обычно предшествует разделу данных.

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

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

540 Часть III. Управление памятью

когда первый экземпляр программы попытается изменить какую-нибудь глобальную переменную на второй странице данных:

Система выделяет новую страницу и копирует на нее содержимое страницы данных 2. Адресное пространство первого экземпляра изменяется так, чтобы отобразить новую страницу данных на тот же участок, что и исходную. Теперь процесс может изменить глобальную переменную, не затрагивая данные другого экземпляра.

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

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

Глава 17. Проецируемые в память файлы.docx 541

Статические данные разделяются несколькими экземплярами EXE или DLL

По умолчанию для большей безопасности глобальные и статические данные нс разделяются несколькими проекциями одного и того же EXE или DLL. Но иногда удобнее, чтобы несколько проекций EXE разделяли единственный экземпляр переменной. Например, в Windows не так-то просто определить, запущено ли несколько экземпляров приложения. Если бы у вас была переменная, доступная всем экземплярам приложения, она могла бы отражать число этих экземпляров. Тогда при запуске нового экземпляра приложения его поток просто проверил бы значение глобальной переменной (обновленное другим экземпляром приложения) и, будь оно больше 1, сообщил бы пользователю, что запустить можно лишь один экземпляр; после чего эта копия приложения была бы завершена.

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

Любой образ EXEили DLL-файла состоит из группы разделов. По соглашению имя каждого стандартного раздела начинается с точки. Например, при компиляции программы весь код помещается в раздел .text, неинициализированные данные — в раздел .bss, а инициализированные — в раздел .data.

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

Табл. 17-1. Атрибуты разделов

Атрибут

Описание

READ

Разрешает чтение из раздела

WRITE

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

EXECUTE

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

SHARED

Раздел доступен нескольким экземплярам приложения (этот атрибут

 

отключает механизм копирования при записи)

Запустив утилиту DumpBin из Microsoft Visual Studio (с ключом /Headers), вы увидите список разделов в файле образа EXE или DLL Пример такого списка, показанный ниже, относится к ЕХЕ-файлу.

SECTION HEADER #1

.text name

11A70 virtual size

1000 virtual address

12000 size of raw data

1000 file pointer to raw data

0 file pointer to relocation table

0 file pointer to line numbers

0 number of relocations

542 Часть III. Управление памятью

0 number of line numbers

60000020 flags Code

Execute Read

SECTION HEADER #2

.rdata name

1F6 virtual size

13000 virtual address

1000 size of raw data

13000 file pointer to raw data

0 file pointer to relocation table

0 file pointer to line numbers

0 number of relocations

0 number of line numbers

40000040 flags Initialized Data Read 0nly

SECTION HEADER #3

.data name

560 virtual size

14000 virtual address

1000 size of raw data

14000 file pointer to raw data

0 file pointer to relocation table

0 file pointer to line numbers

0 number of relocations

0 number of line numbers

C0000040 flags Initialized Data Read Write

SECTION HEADER #4

.idata name

58D virtual size

15000 virtual address

1000 size of raw data

15000 file pointer to raw data

0 file pointer to relocation table

0 file pointer to line numbers

0 number of relocations

0 number of line numbers

C0000040 flags Initialized Data Read Write

Глава 17. Проецируемые в память файлы.docx 543

SECTION HEADER 05

.didat name

7A2 virtual size

16000 virtual address

1000 size of raw data

16000 file pointer to raw data

0 file pointer to relocation table

0 file pointer to line numbers

0 number of relocations

0 number of line numbers

C0000040 flags Initialized Data Read Write

SECTION HEADER #6

.reloc name

26D virtual size

17000 virtual address

1000 size of raw data

17000 file pointer to raw data

0 file pointer to relocation table

0 file pointer to line numbers

0 number of relocations

0 number of line numbers

42000040 flags Initialized Data Discardable Read Only

Summary

1000 .data

1000 .didat

1000 .idata

1000 .rdata

1000 .reloc

12000 .text

Некоторые из часто встречающихся разделов перечислены в таблице ниже.

Табл. 17-2. Общие разделы исполняемых файлов

Имя раздела

Описание

.bss

Неинициализированные данные

.CRT

Неизменяемые данные библиотеки С

.data

Инициализированные данные

Соседние файлы в предмете Программирование на C++