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


Понятие элемента работы (Work Item). Назначение элементов работы. Операции с элементами работы. Очереди элементов работы. Обслуживание элементов работы



страница18/21
Дата28.11.2016
Размер2.21 Mb.
Просмотров5876
Скачиваний1
1   ...   13   14   15   16   17   18   19   20   21

39. Понятие элемента работы (Work Item). Назначение элементов работы. Операции с элементами работы. Очереди элементов работы. Обслуживание элементов работы.

WorkItem выполняет обработку для элемента работы, который был в очереди IoQueueWorkItem.

IO_WORKITEM_ROUTINE WorkItem;

VOID WorkItem(

_In_ PDEVICE_OBJECT DeviceObject,

_In_opt_ PVOID Context

)

{ ... }


Параметры

DeviceObject - Указатель на один из объектов устройств вызывающего абонента. Это указатель, который был принят в качестве DeviceObject параметра IoAllocateWorkItem, когда рабочий элемент был выделен, или как IoObject параметра IoInitializeWorkItem, когда рабочий элемент инициализирован.

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

Возвращаемое значение - None

Замечания

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

Пример

Чтобы определить возвращаемое значение рабочего элемента, сначало нам надо определить декларацию функции(которая и определяет тип возвращаемого значения). Windows предоставляет набор функциональных типов обратного вызова для драйверов. Указание типа помогает различным верификаторам распознать ошибки.



Например, ниже определение своего рабочего элемента MyWorkItem:

IO_WORKITEM_ROUTINE MyWorkItem;

И реализация:

_Use_decl_annotations_

ПУСТОТА

MyWorkItem (



PDEVICE_OBJECT DeviceObject,

PVOID Контекст

)

{

// Функция тела



}

(Инфа из книги Рихтера)

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

BOOL QueueUserWorkItem(

PTHREAD_START_ROUTINE pfnCallback,

PVOID pvContext,

ULONG dwFlags);

Эта функция помещает «рабочий элемент» (work item) в очередь потока в пуле и тут же возвращает управление. Рабочий элемент — это просто вызов функции (на которую ссылается параметр pfnCallback), принимающей единственный параметр, pvContext. В конечном счете какой-то поток из пула займется обработкой этого элемента, в результате чего будет вызвана Ваша функция. У этой функции обратного вызова, за реализацию которой отвечаете Вы, должен быть следующий прототип:

DWORD WINAPI WorkItemFunc(PVOID pvContext);

Несмотря на то что тип возвращаемого значения определен как DWORD, на самом деле оно игнорируется. Обратите внимание, что Вы сами никогда не вызываете CreateThread. Она вызывается из пула потоков, автоматически создаваемого для Вашего процесса, а к функции WorkItemFunc обращается один из потоков этого пула.

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

Многое в пуле потоков происходит скрытно от разработчика: QueueUserWorkItem проверяет число потоков, включенных в сферу ответственности компонента поддержки других операций (не относящихся к вводу-выводу), и в зависимости от текущей нагрузки (количества рабочих элементов в очереди) может передать ему другие потоки. После этого QueueUserWorkItem выполняет операции, эквивалентные вызову PostQueuedCompletionStatus, пересылая информацию о рабочем элементе в порт завершения ввода-вывода. В конечном счете поток, ждущий на этом объекте, извлекает Ваше сообщение (вызовом GetQueuedCompletionStatus) и обращается к Вашей функции. После того как она возвращает управление, поток вновь вызывает GetQueuedCompletionStatus, ожидая появления следующего рабочего элемента.

Очереди элементов работы

IoQueueWorkItem

IoQueueWorkItem ассоциирует WorkItem с рабочим элементом и вставляет его в очередь для дальнейшей обрабоки системным процессом.

VOID IoQueueWorkItem(

_In_ PIO_WORKITEM IoWorkItem,

_In_ PIO_WORKITEM_ROUTINE WorkerRoutine,

_In_ WORK_QUEUE_TYPE QueueType,

_In_opt_ PVOID Context

);

Параметры



IoWorkItem [in] - Указатель на структуру IO_WORKITEM, которая резервируется с помощью IoAllocateWorkItem или инициализируется с помощью IoInitializeWorkItem.

WorkerRoutine [in] - указатель на WorkItem.

QueueType [in] - определяет значение WORK_QUEUE_TYPE, которое обусловливает тип системного потока для управления рабочим элементом. Драйвера должны определять DelayedWorkQueue.

Context [in, optional] - определяет драйверную информацию для рабочего элемента.




40. Управление памятью в ОС Windows. Менеджер памяти. Виртуальная память процесса. Управление памятью в пользовательском режиме. Страничная виртуальная память. Куча (свалка, heap). Проецирование файлов в память.

Менеджер памяти.

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

Аппаратно Memory Manager реализован на Memory Management Unit (MMU), являющимся частью процессора. Принцип работы современных MMU основан на разделении виртуального адресного пространства (одномерного массива адресов, используемых центральным процессором) на участки одинакового, как правило несколько килобайт. Младшие n бит адреса (смещение внутри страницы) остаются неизменными. Старшие биты адреса представляют собой номер (виртуальной) страницы. MMU обычно преобразует номера виртуальных страниц в номера физических страниц используя буфер ассоциативной трансляции.

Виртуальная память процесса.

У каждого процесса есть своя собственная виртуальная память, именуемая адресным пространством, в которой исполняется код этот процесса и его данные, на которые этот код ссылается и которыми управляет. 32-битные процессы используют 32-битные указатели на адреса в виртуальной памяти, которые создают абсолютный верхний предел в 4 ГБ на объем виртуальной памяти, которую 32-битный процесс может адресовать. Однако, чтобы операционная система могла обратиться к своему собственному коду и данным и к коду и данным, выполняющегося в настоящее время процесса, без необходимости изменять адресное пространство, она делает свою виртуальную память видимой из адресных пространств всех процессов. По умолчанию 32-битная версия Windows разделяет адресное пространство процесса поровну между системой и активным процессом, создавая границу в 2 Гб для каждого.

Некоторые приложения управляют большими структурами данных, объем которых намного превышает доступное для них адресное пространство. Windows поддерживает параметры загрузки (спецификатор increaseuserva в базе данных конфигурации загрузки — Boot Configuration Database.), которые дают процессам, выполняющим специально помеченные программы (в заголовке исполняемого образа должен быть установлен флаг признака большого адресного пространства), возможность использования до 3 Гбайт закрытого адресного пространства (оставляя 1 Гбайт для операционной системы).

Часть виртуальной памяти процесса, которая находится резидентно в физической памяти, называется рабочим набором – Working Set. Диапазон рабочего набора устанавливается функцией SetProcessWorkingSetSize(). Стандартный минимальный рабочий набор – 50 страниц по 4 КБ (200 КБ), стандартный максимальный рабочий набор – 345 страниц по 4 КБ (1380 КБ).

Управление памятью в пользовательском режиме.

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

Страничная виртуальная память:

Выделение: VirtualAlloc(), VirtualAllocEx(), VirtualAllocExNuma(), VirtualFree(), VirtualFreeEx(). Гранулярность в user mode – 64 КБ.

Защита страниц: VirtualProtect(), VirtualProtectEx().

Фиксация страниц в физической памяти: VirtualLock(), VirtualUnlock().

Информация: VirtualQuery(), VirtualQueryEx().

Куча (свалка) – Heap:

Создание: HeapCreate(), HeapDestroy().

Выделение: HeapAlloc(), HeapReAlloc(), HeapSize(), HeapFree(). Гранулярность – 8 байтов на x86, 16 байтов на x64.

Информация: HeapValidate(), HeapWalk(), HeapQueryInformation(), HeapSetInformation().

Кучи процесса: GetProcessHeap() – стандартная куча равная 1 MB, GetProcessHeaps() – все кучи процесса.

Проецирование файлов в память – File Mapping:

Объект ядра, описывающий отображение фрагмента файла в диапазон виртуальных адресов, называется разделом (Section Object).

Страничная виртуальная память.

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

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

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

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

Любой процесс может выполняться только в том случае, если используемые им страницы памяти размещаются в оперативной памяти. При отсутствии запрашиваемой страницы в оперативной памяти возникает исключительная ситуация — страничное нарушение (page fault). Тогда затребованная страница подкачивается из внешней памяти (swap-файла) в свободный страничный кадр физической памяти, а при отсутствии свободных страничных кадров в оперативной памяти первоначально в swap-файл выгружается мало используемая страница памяти.

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

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

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

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

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

Другой общеупотребимый случай использования отображений — создание разделяемых несколькими процессами фрагментов памяти. Процесс в защищенном режиме, вообще говоря, не позволяет другим процессам обращаться к «своей» памяти. Программы, которые пытаются обратиться не к своей памяти генерируют исключительные ситуации invalid page faults или segmentation violation. Есть несколько способов безопасно (без возникновения исключительных ситуаций) сделать память доступной нескольким процессам, и использование файлов, отображенных на память — один из наиболее популярных способов сделать это. Два или более приложений могут одновременно отобразить один и тот же физический файл на свою память и обратиться к этой памяти.


41. Управление памятью в пользовательском режиме ОС Windows. Оптимизация работы кучи с помощью списков предыстории (Look-aside Lists) и низко-фрагментированной кучи (Low Fragmentation Heap).




У каждого процесса имеется как минимум одна куча — куча процесса, предлагаемая по умолчанию. Эта куча создается при запуске процесса и не удаляется, пока существует процесс. Ее размер по умолчанию составляет 1 Мбайт, но ее можно сделать больше, указав начальный размер в файле образа с помощью флага /HEAP компоновщика. Однако этот размер является всего лишь начальным, и по мере необходимости он автоматически увеличивается. (В файле образа можно также указать изначально подтвержденный размер.)




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




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




Списки предысторий (look-aside lists)

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

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

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

Структура PAGED_LOOKASIDE_LIST описывает списки предысторий, содержащие страничные буферы. Cледующие системные подпрограммы поддерживают работу со списками предысторий, которые описываются с помощью структуры PAGED_LOOKASIDE_LIST:

ExAllocateFromPagedLookasideList

ExDeletePagedLookasideList

ExFreeToPagedLookasideList

ExInitializePagedLookasideList

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

ExAllocateFromNPagedLookasideList

ExDeleteNPagedLookasideList

ExFreeToNPagedLookasideList

ExInitializeNPagedLookasideList

Списки представлены в виде 128 связных списков свободных блоков. Каждый список содержит элементы строго определенного размера – от 8 байтов до 1 КБ на x86 и от 16 байтов до 2 КБ на x64.

Когда блок памяти освобождается, он помещается в список предыстории, соответствующий его размеру. Затем, если запрашивается блок памяти такого же размера, он берется из списка предыстории методом LIFO. Для организации списка предыстории используются функции InterlockedPushEntrySList() и InterlockedPopEntrySList().

Раз в секунду ОС уменьшает глубину списков предыстории с помощью функции ядра KiAdjustLookasideDepth().




Слабо фрагментированная куча

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




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




Технология слабо фрагментированной кучи (LFH) позволяет избегать фрагментации путем управления выделенными блоками в заранее заданных диапазонах размеров блоков, которые называются корзинами (buckets). Когда процесс выделяет память из кучи, LFH предлагает корзину, отображаемую на наименьший блок, подходящий под требуемый размер. (Наименьший блок имеет размер 8 байт.) Первая корзина служит для выделения в диапазоне от 1 до 8 байт, вторая — в диапазоне от 9 до 16 байт и т. д., вплоть до тридцать второй корзины, предназначенной для выделения в диапазоне от 249 до 256 байт, за которой следует тридцать третья корзина, служащая для выделения в диапазоне от 257 до 272 байт и т. д. И наконец, очередь доходит до сто двадцать восьмой корзины, которая в итоге используется для выделения в диапазоне от 15 873 до 16 384 байт. Все это известно как система двоичного дружелюбия (binary buddy).




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




В целях обеспечения масштабируемости LFH расширяет часто используемые внутренние структуры на ряд слотов, количество которых вдвое превышает текущее количество процессоров, имеющихся на машине. Назначение потоков этим слотам осуществляется LFH-компонентом, который называется диспетчером родственности (affinity manager). Изначально LFH задействует для выделения кучи первый слот, но если при обращении к каким-либо внутренним данным обнаруживается конфликтная ситуация, LFH переключает текущий программный поток на другой слот. Последующие конфликтные ситуации ведут к «расползанию» потоков на дополнительные слоты. Для лучшей локальности и минимизации общего потребления памяти управление этими слотами осуществляется для корзин каждого размера.




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


42. Структура виртуальной памяти в ОС Windows. Виды страниц. Состояния страниц. Структура виртуального адреса. Трансляция виртуального адреса в физический. Кэширование виртуальных адресов.




Виды и состояния страниц

Виртуальная память состоит из двух типов страниц:

Малые страницы – 4 КБ. Большие страницы – 4 МБ на x86 или 2 МБ на x64. Большие страницы используются для Ntoskrnl.exe, Hal.dll, данных ядра, описывающих резидентную память ядра и состояние физических страниц памяти.

При вызове VirtualAlloc() можно указать флаг MEM_LARGE_PAGE. Для всей страницы применяется единый режим защиты и блокировки памяти.




Страницы в виртуальном адресном пространстве процесса могут быть свободными (free), зарезервированными (reserved), подтвержденными (committed) или общими (shareable).




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




Закрытые страницы выделяются с помощью Windows-функций VirtualAlloc, VirtualAllocEx и VirtualAllocExNuma. Эти функции позволяют программному потоку резервировать адресное пространство, а затем подтверждать части зарезервированного пространства. Промежуточное «зарезервированное» состояние позволяет потоку отложить непрерывный диапазон виртуальных адресов для возможного использования в будущем (например, в качестве массива), потребляя при этом незначительные системные ресурсы, а затем выделить подтвержденные части зарезервированного пространства, как только это потребуется для выполнения приложения. Или же, если требуемые объемы известны заранее, поток может выполнить резервирование и подтверждение в одном вызове функции. В любом случае, подтвержденные в итоге страницы затем становятся доступны потоку. Попытки обращения к свободной или зарезервированной памяти приводят к исключению, поскольку страница не отображена ни на одно из хранилищ, способных разрешить ссылку.




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




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




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





Поделитесь с Вашими друзьями:
1   ...   13   14   15   16   17   18   19   20   21


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

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


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