Mikle писал(а):Возможно где-то удобнее иметь > 4гБ одним куском?
Это реже, чем много мелких буферов.
У flat-модели один большой недостаток. Допустим, нам надо выделить буфер размером 1 Гб. А суммарный объём свободного места в АП процесса — 1,99 Гб. Но выделить 1 Гб мы не сможем, потому что АП фрагментировано микроскопическими занятыми участками. И в оставшиеся свободные области этот гигабайтный кусок не вписать — никуда не влазит. И микроскопические области не передвинуть, потому что никто не знает, в скольки местах есть указатели на них.
Вот ещё раньше была крутая вещь (сейчас она признана устаревшей) — это Global-функции. Пока кто-то не вызвал GlobalLock — не важно, где лежит блок в памяти. Дефрагментация виртуального АП возможна.
Ну или другой, но всё тот же по сути, сценарий: всё-таки нам удалось выделить 1 Гб. Но оказалось, что 1 Гб мало, и надо сделать ReAlloc, и выделить 1,05 Гб. И тут облом: хоть у нас 99 % виртуального АП процесса свободно, процесс падает с «Out of memroy». Не потому, что нет нужного количества свободных страниц, а потому что нет диапазона (нужной длины) свободных страниц.
Горе в том, x64 эту проблему не решает. Да, теперь у нас просто гигантское адресное пространство, но теперь и разработчики (которые постоянно деградируют, с ходом времени делая всё больший по размеру плевок на то, как устроен компьютер, разрабатывая так, как будто работают на идеальном сферическом компьютере с бесконечными характеристиками) будут тратить память гораздо более неэкономно. Если нужно было работать с 3 Гб файлом, то горе-разработчик в принципе не мог прочитать его целиком в АП (он туда не влезет), и даже ту часть, что влезет — не мог (потому что съестся весь файл подкачки, скорее всего), он был вынужден применять технически идеальный вариант — маппить файл в АП кусками и работать с ним. В этом случае на диски не создавалось две копии одни и тех же данных (одна в самом файле, другая в файлах подкачки). Теперь разработчик спокоен — он может не то, что маппнуть, а даже загрузить весь этот файл целиком в виртуальное АП.
Кстати, я ещё об этом напишу какую-нибудь статью с рассуждениями, но по моему мнению, ОСи, исповедующие flat-модель для себя и своих процессов, практически полностью загубили идею защищённого режима, основанного на задачах.
Что такое процессорная задача в современных ОС? Это поток в составе процесса. У процессов изолированное АП, но для всех потоков оно общее. Защита, по сути, только одного процесса от чужих процессов. Потоки же в рамках одного процесса могут свободно мочить друг друга, что даёт повод устранивать Core Wars основанный на особенностях архитектуры ОС.
А что такое изначально задача? Это субъект действия защищённого режима. Это выполняющийся код со своими привилегиями и доступам к сегментам. И нужно было воспринимать задачу как асинхронную процедуру.
Вот например веб-сервер. Получили мы запрос от сервера, и нужно его обработать. Но наш код может содержать уязвимости, а пришедший запрос может содержать специально сформированные данные, которые нарушат безопасность.
Так вот эта ситуация должна разрешать так: для обработки пришедшего запроса мы создаём новую задачу. Причём «создаём задачу» надо читать не как «создаём новый поток», а как «инициируем вызов асинхронной процедуры». Причём создавающая задача может заснуть на время выполнения созданной, но может и не засыпать (а обрабатывать, например, следующий запрос).
Фишка в том, что когда мы создали задачу для обработки пришедшего от клиента запроса, мы можем указать, чем будет владеть данная задача. И мы наделяем задачу доступом к трём крохотным сегментами:
1) Сегмент с кодом этой задачи.
2) Сегмент, содержащий данные пришедшего запроса.
3) Сегмент, куда нужно записать результаты обработки запроса.
Причём первому сегменту присвоены атрибуты R-E, второму R--, третьего RW-.
Всё.
Какая бы страшная уязвимость не присутствовала в нашем коде, каким бы гениальным не был взломщик, что бы чудовищное не произошло внутри задачи, всё это дело не проникнет за границу задачи. Никакого переполнения буфера не возможно в принципе — потому что защита границ буфера аппаратная. Нет даже теоретической возможности обратиться к данным, за концом буфера. Буфер становится настоящим буфером (контейнером данных), а не просто областью в огромном АП, обозванной буфером, с чисто формальными, легко нарушаемыми границами.
В текущей реализации есть замечательные фишки от подобных уязвимостей, вроде DEP. Они замечательны сами по себе, но мегаущербны по сравнению с той концепцией обеспечения безопасности, которая могла бы быть реализована. Которую x86 позволяет реализовать (причём уже очень давно).
Ну допустим, я, злобный хакер, записал код в буфер, предназначенный для данных. И перезаписал стек так, чтобы адрес возврата указывал на мой внедряемый хакерский код. Сработал DEP. И что? А ничего — DEP, это исключение, а механизм обработки исключений документирован и полностью доступен прикладному ПО. А значит он не может использоваться для защиты. И конечно его обходят. И кроме того, можно адресом возврата сделать VirtualProtect, которая сделает внедрённый хакерский код легальным (с точки зрения DEP), и дальше всё как по маслу.
Вот сейчас у процесса есть возможность обработки исключений. Если их не обрабатывать, запись или чтение туда/оттуда, куда/откуда нельзя вызывает печально известное сообщение с предложением «Отправить отчёт». Ну, хорошо, а если обрабатывать исключения самому? Хорошо, мы знаем, что наш поток стал писать туда, куда нельзя. Но мы ведь не писал ли перед этим наш вышедший из под контроля поток, туда, куда на деле нельзя, но технически (из-за атрибутов доступа страницы ВАП) вполне себе возможно? Такой гарантии нет, поэтому обычно если такое происходит, и исключение обрабатывается своим обработчиком (а не самым крайним в цепочке SEH-хендлеров, системным), процесс в конечном счёте всё равно завершается, разве что сообщение об ошибке выводится не системное, а кастомное.
Кроме того, вышедший из под контроля поток, записывающий что-то абы-куда может испортить сам код обработки исключения. Может испортить цепочку SEH-хендлеров. Это часто случается: видели, когда процесс, который должен крах-нуться обычным образом (с окном «Отправить отчёт») или вообще завершается безшумно (просто завершается), или не завершается вообще?
* * *
Сейчас модны идеи виртуализации, идеи песочницы во имя обеспечения безопасности. Это выглядит странно, потому что архитектура x86 уже давно несёт эту идею: задача — вот вам песочница. Но современные ОС используют процессорный объект «задача» самым невыгодным с точки зрения использования её потенциальных возможностей образом.