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

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

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

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

//мы вызываем ForceClose, потому что попытка обнуления

//части файла при его проецировании приводит к провалу

//вызова DeviceIoControl с ошибкой ERROR_USER_MAPPED_FILE

//("запрошенная операция с файлом невозможна, пока открыт

//раздел, проецируемый пользователем")

g_mmf.DecommitPortionOfStream(0, STREAMSIZE);

// для сброса разреженного состояние закроем описатель файла

//и снова откроем его.

CloseHandle(g_hStream);

g_hStream = CreateFile(g_szPathname, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

if (g_hStream == INVALID_HANDLE_VALUE) { chFAIL("Failed to create file."); return;

}

//Reset the MMF wrapper for the new file handle.

g_mmf.Initialize(g_hStream, STREAMSIZE);

// Update the UI. Dlg_ShowAllocatedRanges(hWnd); 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 pszCmdLine, int) {

DialogBox(hInstExe, MAKEINTRESOURCE(IDD_MMFSPARSE), NULL, Dlg_Proc); return(0);

}

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

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

SparseStream.h

/****************************************************************************** Module: SparseStream.h

Notices: Copyright (c) 2007 Jeffrey Richter & Christophe Nasarre

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

#include "..\CommonFiles\CmnHdr.h" /* см. приложение А */ #include <WinIoCtl.h>

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

#pragma once

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

class CSparseStream { public:

static BOOL DoesFileSystemSupportSparseStreams(PCTSTR pszVolume); static BOOL DoesFileContainAnySparseStreams(PCTSTR pszPathname);

public:

CSparseStream(HANDLE hStream = INVALID_HANDLE_VALUE) { Initialize(hStream);

}

virtual ~CSparseStream() { }

void Initialize(HANDLE hStream = INVALID_HANDLE_VALUE) { m_hStream = hStream;

}

public:

operator HANDLE() const { return(m_hStream); }

public:

BOOL IsStreamSparse() const; BOOL MakeSparse();

BOOL DecommitPortionOfStream(

__int64 qwFileOffsetStart, __int64 qwFileOffsetEnd);

FILE_ALLOCATED_RANGE_BUFFER* QueryAllocatedRanges(PDWORD pdwNumEntries); BOOL FreeAllocatedRanges(FILE_ALLOCATED_RANGE_BUFFER* pfarb);

private:

HANDLE m_hStream;

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

private:

static BOOL AreFlagsSet(DWORD fdwFlagBits, DWORD fFlagsToCheck) { return((fdwFlagBits & fFlagsToCheck) == fFlagsToCheck);

}

};

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

inline BOOL CSparseStream::DoesFileSystemSupportSparseStreams( PCTSTR pszVolume) {

DWORD dwFileSystemFlags = 0;

BOOL bOk = GetVolumeInformation(pszVolume, NULL, 0, NULL, NULL, &dwFileSystemFlags, NULL, 0);

bOk = bOk && AreFlagsSet(dwFileSystemFlags, FILE_SUPPORTS_SPARSE_FILES); return(bOk);

}

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

inline BOOL CSparseStream::IsStreamSparse() const {

BY_HANDLE_FILE_INFORMATION bhfi; GetFileInformationByHandle(m_hStream, &bhfi);

return(AreFlagsSet(bhfi.dwFileAttributes, FILE_ATTRIBUTE_SPARSE_FILE));

}

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

inline BOOL CSparseStream::MakeSparse() {

DWORD dw;

return(DeviceIoControl(m_hStream, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &dw, NULL));

}

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

inline BOOL CSparseStream::DecommitPortionOfStream( __int64 qwOffsetStart, __int64 qwOffsetEnd) {

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

DWORD dw; FILE_ZERO_DATA_INFORMATION fzdi;

fzdi.FileOffset.QuadPart = qwOffsetStart; fzdi.BeyondFinalZero.QuadPart = qwOffsetEnd + 1;

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

return(DeviceIoControl(m_hStream, FSCTL_SET_ZERO_DATA, (PVOID) &fzdi, sizeof(fzdi), NULL, 0, &dw, NULL));

}

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

inline BOOL CSparseStream::DoesFileContainAnySparseStreams( PCTSTR pszPathname) {

DWORD dw = GetFileAttributes(pszPathname); return((dw == 0xfffffff)

? FALSE : AreFlagsSet(dw, FILE_ATTRIBUTE_SPARSE_FILE));

}

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

inline FILE_ALLOCATED_RANGE_BUFFER* CSparseStream::QueryAllocatedRanges( PDWORD pdwNumEntries) {

FILE_ALLOCATED_RANGE_BUFFER farb; farb.FileOffset.QuadPart = 0; farb.Length.LowPart =

GetFileSize(m_hStream, (PDWORD) &farb.Length.HighPart);

//правильно определить размер блока памяти до попыток сбора этих

//данных нельзя, и я просто беру 100 * sizeof(*pfarb)

DWORD cb = 100 * sizeof(farb);

FILE_ALLOCATED_RANGE_BUFFER* pfarb = (FILE_ALLOCATED_RANGE_BUFFER*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cb);

DeviceIoControl(m_hStream, FSCTL_QUERY_ALLOCATED_RANGES, &farb, sizeof(farb), pfarb, cb, &cb, NULL);

*pdwNumEntries = cb / sizeof(*pfarb); return(pfarb);

}

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

inline BOOL CSparseStream::FreeAllocatedRanges( FILE_ALLOCATED_RANGE_BUFFER* pfarb) {

// освобождаем выделенную память return(HeapFree(GetProcessHeap(), 0, pfarb));

}

///////////////////////////////// End Of File /////////////////////////////////

Оглавление

 

Г Л А В А 1 8 Динамически распределяемая память.......................................................

598

Стандартная куча процесса ...................................................................................................

599

Дополнительные кучи в процессе........................................................................................

600

Защита компонентов ........................................................................................................

600

Более эффективное управление памятью ...............................................................

601

Локальный доступ ............................................................................................................

602

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

602

Быстрое освобождение всей памяти в куче .............................................................

602

Создание дополнительной кучи ...........................................................................................

603

Выделение блока памяти из кучи ................................................................................

605

Изменение размера блока...............................................................................................

607

Определение размера блока..........................................................................................

608

Освобождение блока........................................................................................................

608

Уничтожение кучи..............................................................................................................

608

Использование куч в программах на C++..................................................................

608

Другие функции управления кучами....................................................................................

612

Г Л А В А 1 8

Динамически распределяемая память

Третий, и последний, механизм управления памятью — динамически распределяемые области памяти, или кучи (heaps). Они весьма удобны при создании множества небольших блоков данных. Например, связанными списками и деревьями проще манипулировать, используя именно кучи, а не виртуальную память (глава 15) или файлы, проецируемые в память (глава 17). Преимущество динамически распределяемой памяти в том, что она позволяет вам игнорировать гранулярность выделения памяти и размер страниц и сосредоточиться непосредственно на своей задаче. А недостаток — выделение и освобождение блоков памяти проходит медленнее, чем при использовании других механизмов, и, кроме того, вы теряете прямой контроль над передачей физической памяти и ее возвратом системе.

Куча — это регион зарезервированного адресного пространства. Первоначально большей его части физическая память не передается. По мере того, как программа занимает эту область под данные, специальный диспетчер, управляющий кучами (heap manager), постранично передает ей физическую память (из страничного файла). А при освобождении блоков в куче диспетчер возвращает системе соответствующие страницы физической памяти.

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

Глава 18. Динамически распределяемая память.docx 599

Стандартная куча процесса

При инициализации процесса система создает n сто адресном пространстве стандартную кучу (process's default heap). Не размер по умолчанию — 1 Мб. Но система позволяет увеличивать этот размер, для чего надо указать компоновщику при сборке программы ключ /HEAP. (Однако при сборке DLL этим ключом пользоваться нельзя, так как для DLL куча нс создастся.)

/HEAP:reserve[, commit]

Стандартная куча процесса необходима многим Windows-функциям. Например, функции ядра Windows выполняют все операции с использованием Unicodeсимволов и строк. Если вызвать ANSI-версию какой-нибудь Windows-функции, ей придется, преобразовав строки из ANSI в Unicode, вызывать свою Unicodeверсию. Для преобразования строк ANSI-функции нужно выделить блок памяти, в котором она размещает Unicode-версию строки. Этот блок памяти заимствуется из стандартной кучи вызывающего процесса. Есть и другие функции, использующие временные блоки памяти, которые тоже выделяются из стандартной кучи процесса. Из нее же черпают себе память и функции 16-разрядной Windows,

управляющие кучами (LocalAlloc и GlobalAlloc).

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

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

Описатель стандартной кучи процесса возвращает функция GetProcessHeap:

HANDLE GetProcessHeap();

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

Дополнительные кучи в процессе

В адресном пространстве процесса допускается создание дополнительных куч. Для чего они нужны? Тому может быть несколько причин:

защита компонентов;

более эффективное управление памятью;

локальный доступ;

исключение издержек, связанных с синхронизацией потоков;

быстрое освобождение всей памяти в куче. Рассмотрим эти причины подробнее.

Защита компонентов

Допустим, программа должна обрабатывать два компонента: связанный список структур NODE и двоичное дерево структур BRANCH. Представим также, что у вас есть два файла исходного кода: LnkLst.cpp, содержащий функции для обработки связанного списка, и BinTree.cpp с функциями для обработки двоичного дерева.

Если структуры NODE и BRANCH хранятся в одной куче, то она может выглядеть примерно так, как показано на рис. 18-1.

Рис. 18-1. Единая куча, в которой размещены структуры NODE и BRANCH

Теперь предположим, что в коде, обрабатывающем связанный список, «сидит жучок», который приводит к случайной перезаписи 8 байтов после NODE 1. А это в свою очередь влечет порчу данных в BRANCH 3. Впоследствии, когда код из файла BinTree.cpp пытается «пройти» по двоичному дереву, происходит сбой из-за того, что часть данных в памяти испор-

Глава 18. Динамически распределяемая память.docx 601

чена. Можно подумать, что ошибка возникает из-за «жучка» в коде двоичного дерева, тогда как на самом деле он — в коде связанного списка. А поскольку разные типы объектов смешаны в одну кучу (в прямом и переносном смысле), то отловить «жучков» в коде становится гораздо труднее.

Создав же две отдельные кучи — одну для NODE, другую для BRANCH, — вы локализуете место возникновения ошибки. И тогда «жучок» в коде связанного списка нс испортит целостности двоичного дерева, и наоборот. Конечно, всегда остается вероятность такой фатальной ошибки в коде, которая приведет к записи данных в постороннюю кучу, но это случается значительно реже.

Более эффективное управление памятью

Кучами можно управлять гораздо эффективнее, создавая в них объекты одинакового размера. Допустим, каждая структура NODE занимает 24 байта, а каждая структура BRANCH — 32. Память для всех этих объектов выделяется из одной кучи. На рис. 18-2 показано, как выглядит полностью занятая куча с несколькими объектами NODE и BRANCH. Если объекты NODE 2 и NODE 4 удаляются, память в куче становится фрагментированной. И если после этого попытаться выделить в ней память для структуры BRANCH, ничего не выйдет — даже несмотря на то что в куче свободно 48 байтов, а структура BRANCH требует всего 32.

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

Рис. 18-2. Фрагментированная куча, содержащая несколько объектов NODE и BRANCH

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

Локальный доступ

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

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

Если же «свалить» оба типа структур в одну кучу, объекты NODE необязательно будут размещены строго друг за другом. При самом неблагоприятном стечении обстоятельств на странице окажется всего одна структура NODE, а остальное место займут структуры BRANCH. В этом случае просмотр связанного списка будет приводить к ошибке страницы (page fault) при обращении к каждой структуре NODE, что в результате может чрезвычайно замедлить скорость выполнения вашего процесса.

Исключение издержек, связанных с синхронизацией потоков

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

Быстрое освобождение всей памяти в куче

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

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