Это перевод Why is the page size on ia64 8K? Автор: Реймонд Чен.
На машинах x86 Windows выбирает размер страницы равным 4 Кб, потому что это единственный размер, который поддерживался архитектурой в момент проектирования операционной системы (поддержка страниц в 4 Мб была добавлена позже - в Pentium-е, как мне помнится, но этот размер, очевидно, слишком велик для повсеместного использования).
Для ia64 Windows же выбирает размер стрницы в 8 Кб. Почему 8 Кб?
Это баланс между двумя соревнующимися целями. Большие размеры страниц улучшают производительность ввода-вывода, поскольку вы передаёте больше данных за один заход. Однако большие размеры страниц также увеличивают вероятность, что дополнительный выполняемый вами ввод-вывод пропадёт зря из-за плохой скученности данных.
На ia64 провели эксперименты с различными размерами страниц (даже с 64 Кб страницами, которые серьёзно рассматривались когда-то), и 8 Кб показали наилучший баланс.
Заметьте, что изменение размера страниц приводит ко всем видам проблем совместимости. Есть большое количество программ, которые предполагают, что размер страницы равен 4 Кб. Наверное это сюрприз и для них самих (прим.пер.: посмотрите на наш недавний код и заметьте, как легко написать его с жёстко зашитой константой).
...when altering one's mind becomes as easy as programming a computer, what does it mean to be human?..
вторник, 30 июня 2009 г.
понедельник, 29 июня 2009 г.
Shift отменяет NumLock
Это перевод The shift key overrides NumLock. Автор: Реймонд Чен.
Возможно, в отличие от деньков, когда кнопки управления курсором разделяли пространство с NumPad-ом, сегодня уже не так хорошо известно, что Shift отменяет NumLock.
Если NumLock включен (а обычно он включен), тогда нажатие на кнопку на NumPad-е при зажатом Shift-е отключает NumLock и вместо этого генерирует кнопку управления курсором, значок которой рисуется ниже большой цифры на кнопке.
(Shift также отменяет CapsLock. Если вы включили CapsLock, то удерживание Shift при печати приведёт к печати в нижнем регистре).
Возможно, вы решите, что этот трюк с Shift-ом совершенно незначителен, по крайней мере, до тех пор, пока вы не сделаете что-то типа назначения Shift + Numpad0 как hot-key и удивляться, почему она не работает. Теперь вы знаете.
Возможно, в отличие от деньков, когда кнопки управления курсором разделяли пространство с NumPad-ом, сегодня уже не так хорошо известно, что Shift отменяет NumLock.
Если NumLock включен (а обычно он включен), тогда нажатие на кнопку на NumPad-е при зажатом Shift-е отключает NumLock и вместо этого генерирует кнопку управления курсором, значок которой рисуется ниже большой цифры на кнопке.
(Shift также отменяет CapsLock. Если вы включили CapsLock, то удерживание Shift при печати приведёт к печати в нижнем регистре).
Возможно, вы решите, что этот трюк с Shift-ом совершенно незначителен, по крайней мере, до тех пор, пока вы не сделаете что-то типа назначения Shift + Numpad0 как hot-key и удивляться, почему она не работает. Теперь вы знаете.
воскресенье, 28 июня 2009 г.
Одновременность относительна даже в компьютерах
Это перевод Even in computing, simultaneity is relative. Автор: Реймонд Чен.
Эйнштейн обнаружил, что одновременность относительна. Это также применимо и к компьютерам.
Люди часто спрашивают: "Нормально ли делать X в одном потоке и Y в другом одновременно?". Вот несколько примеров:
X = "закрыть дескриптор" и Y = "использовать дескриптор".
X = "вызвать UnregisterWaitForSingleObject для дескриптора", Y = "вызвать UnregisterWaitForSingleObject на него же".
Вы можете ответить сами на этот вопрос, даже ничего не зная о внутреннем поведении указанных операций. Всё, что вам нужно знать - это немного физики, и ответить на гораздо более простые вопросы о допустимости последовательного кода.
Эйнштейн обнаружил, что одновременность относительна. Это также применимо и к компьютерам.
Люди часто спрашивают: "Нормально ли делать X в одном потоке и Y в другом одновременно?". Вот несколько примеров:
X = "закрыть дескриптор" и Y = "использовать дескриптор".
X = "вызвать UnregisterWaitForSingleObject для дескриптора", Y = "вызвать UnregisterWaitForSingleObject на него же".
Вы можете ответить сами на этот вопрос, даже ничего не зная о внутреннем поведении указанных операций. Всё, что вам нужно знать - это немного физики, и ответить на гораздо более простые вопросы о допустимости последовательного кода.
суббота, 27 июня 2009 г.
Почему Windows хранит время в BIOS в локальном формате?
Это перевод Why does Windows keep your BIOS clock on local time? Автор: Реймонд Чен.
Хотя Windows NT внутри себя использует UTC время, часы BIOS остаются на местном (локальном) времени. Почему так делается?
На это есть несколько причин. Одна из них - цепочка из обратных совместимостей.
Хотя Windows NT внутри себя использует UTC время, часы BIOS остаются на местном (локальном) времени. Почему так делается?
На это есть несколько причин. Одна из них - цепочка из обратных совместимостей.
пятница, 26 июня 2009 г.
Как найти исполняемый файл Internet Explorer
Это перевод How to find the Internet Explorer binary. Автор: Реймонд Чен.
По какой-то причине, некоторые люди забираются в неимоверные дебри, чтобы найти исполняемый файл Internet Explorer для запуска его с некоторыми опциями.
Чтобы сделать это - вам вовсе не нужно это делать.
По какой-то причине, некоторые люди забираются в неимоверные дебри, чтобы найти исполняемый файл Internet Explorer для запуска его с некоторыми опциями.
Чтобы сделать это - вам вовсе не нужно это делать.
четверг, 25 июня 2009 г.
Важность выравнивания даже на x86, часть 2
Это перевод Importance of alignment even on x86 machines, part 2. Автор: Реймонд Чен.
Различные функции атомарного доступа семейства Interlocked (InterlockedIncrement и т.д.) требуют, чтобы обновляемая переменная имела корректное выравнивание, даже на x86 - платформе, где центральный процессор молча исправляет все ошибки доступа к памяти без выравнивания.
Различные функции атомарного доступа семейства Interlocked (InterlockedIncrement и т.д.) требуют, чтобы обновляемая переменная имела корректное выравнивание, даже на x86 - платформе, где центральный процессор молча исправляет все ошибки доступа к памяти без выравнивания.
среда, 24 июня 2009 г.
Важность выравнивания даже на x86
Это перевод Importance of alignment even on x86 machines. Автор: Реймонд Чен.
Иногда доступ к памяти без выравнивания (unaligned memory) может подвесить машину.
Некоторые видео-карты не позволяют вам получать доступ ко всей памяти за раз. Вместо этого вам даётся окно, в которое вы можете выбрать для проецирования одно из нескольких подмножеств видео-памяти ("банк памяти"). Например, видео-карта EGA имела 256 Кб памяти, разделённой в 4 банка по 64 Кб. Если вы хотели получить доступ к памяти в первых 64 Кб, вы должны были выбрать нулевой банк для проецирования в окно, но если вам нужна была память выше 64 Кб, но меньше 128 Кб, то вы должны были выбрать первый банк.
Переключение банков (bank-switching) делает доступ к памяти более сложным. Например, если вы хотите скопировать блок памяти в область памяти с переключением банков, мы должны проверить, не пересекает ли ваш блок границы банков, и если да - разбить копирование всего блока на копирование нескольких кусков. Если вы делаете что-то, что требует не последовательного доступа (скажем, рисуете диагональную линию), то вы должны выяснять, в какой момент ваша линия пересечёт границы банков.
Для упрощения работы, Windows 95 имела драйвер, называемый VFLATD, который делал банк-память "как-бы плоской" для остальной системы. "Уплощение" (flattening) банк-памяти было также жизненно необходимым для поддержки DirectDraw; в частности, метод IDirectDrawSurface.Lock давал вам прямой доступ к (казалось бы) плоскому виду видео-памяти. Например, если приложение хотело увидеть все 256 Кб видео-памяти и писало что-то в первые 64 Кб, то драйвер VFLATD выбирал нулевой банк и проецировал 64 Кб физической памяти окна видео-памяти в первые 64 Кб виртуального пространства в 256 Кб.
Это прекрасно работало, пока все использовали только память с корректным выравниванием. Но если вы пытались обратиться к памяти без выравнивания, вы могли послать VFLATD в бесконечный цикл и подвесить машину.
Предположим, вы делаете обращение к памяти без выравнивания, затрагивая два банка. Этот доступ к памяти никогда не может быть выполнен. При доступе к нижней части возбуждается ошибка страницы (page fault), и VFLATD проецирует в память банк для нижней части, что делает банк для верхней части недоступным. Теперь ошибка страницы возбуждается для верхней части и VFLATD теперь должен спроецировать верхний банк; это отвязывает (unmaps) нижний банк, т.к. в один момент времени у видео-карты с банк-памятью только один банк может быть доступен. Теперь ошибка сраницы генерируется для нижней части, и цикл повторяется снова и снова.
Мораль истории: не нарушайте выравнивание памяти, даже на x86, на котором большинство людей считают "безопасным" нарушать правила выравнивания.
В следующий раз мы рассмотрим другой пример как данные с невеным выравниванием могут создавать баги на x86.
Иногда доступ к памяти без выравнивания (unaligned memory) может подвесить машину.
Некоторые видео-карты не позволяют вам получать доступ ко всей памяти за раз. Вместо этого вам даётся окно, в которое вы можете выбрать для проецирования одно из нескольких подмножеств видео-памяти ("банк памяти"). Например, видео-карта EGA имела 256 Кб памяти, разделённой в 4 банка по 64 Кб. Если вы хотели получить доступ к памяти в первых 64 Кб, вы должны были выбрать нулевой банк для проецирования в окно, но если вам нужна была память выше 64 Кб, но меньше 128 Кб, то вы должны были выбрать первый банк.
Переключение банков (bank-switching) делает доступ к памяти более сложным. Например, если вы хотите скопировать блок памяти в область памяти с переключением банков, мы должны проверить, не пересекает ли ваш блок границы банков, и если да - разбить копирование всего блока на копирование нескольких кусков. Если вы делаете что-то, что требует не последовательного доступа (скажем, рисуете диагональную линию), то вы должны выяснять, в какой момент ваша линия пересечёт границы банков.
Для упрощения работы, Windows 95 имела драйвер, называемый VFLATD, который делал банк-память "как-бы плоской" для остальной системы. "Уплощение" (flattening) банк-памяти было также жизненно необходимым для поддержки DirectDraw; в частности, метод IDirectDrawSurface.Lock давал вам прямой доступ к (казалось бы) плоскому виду видео-памяти. Например, если приложение хотело увидеть все 256 Кб видео-памяти и писало что-то в первые 64 Кб, то драйвер VFLATD выбирал нулевой банк и проецировал 64 Кб физической памяти окна видео-памяти в первые 64 Кб виртуального пространства в 256 Кб.
Это прекрасно работало, пока все использовали только память с корректным выравниванием. Но если вы пытались обратиться к памяти без выравнивания, вы могли послать VFLATD в бесконечный цикл и подвесить машину.
Предположим, вы делаете обращение к памяти без выравнивания, затрагивая два банка. Этот доступ к памяти никогда не может быть выполнен. При доступе к нижней части возбуждается ошибка страницы (page fault), и VFLATD проецирует в память банк для нижней части, что делает банк для верхней части недоступным. Теперь ошибка страницы возбуждается для верхней части и VFLATD теперь должен спроецировать верхний банк; это отвязывает (unmaps) нижний банк, т.к. в один момент времени у видео-карты с банк-памятью только один банк может быть доступен. Теперь ошибка сраницы генерируется для нижней части, и цикл повторяется снова и снова.
Мораль истории: не нарушайте выравнивание памяти, даже на x86, на котором большинство людей считают "безопасным" нарушать правила выравнивания.
В следующий раз мы рассмотрим другой пример как данные с невеным выравниванием могут создавать баги на x86.
вторник, 23 июня 2009 г.
Почему некоторые записи оканчиваются массивом размером 1?
Это перевод Why do some structures end with an array of size 1? Автор: Реймонд Чен.
Некоторые записи (структуры) в Windows имеют переменный размер, они начинаются с фиксированного заголовка, а далее следует переменная часть в виде массива. Когда объявляют такие структуры, они часто объявляются с массивом размера 1 на месте части переменного размера. Например:
Если вы посмотрите объявление ANYSIZE_ARRAY, то увидите, что эта константа равна 1, поэтому этот код объявляет запись с ведомым массивом из единственного элемента.
При таком объявлении, вам нужно выделять память для одной такой записи примерно так:
и вы должны инициализировать запись примерно так:
Многие люди считают, что запись должна быть объявлена так:
Тогда код выделения был бы таким:
Эта альтернатива имеет два недостатка. Один косметического плана, а другой - фатальный.
Во-первых, косметический недостаток: становится тяжелее получать доступ к элементам массива в переменной части. Например, инициализация теперь должна выглядеть примерно так:
Но настоящий недостаток является фатальным. Вышеприведённый код вылетает на 64-х битных Windows. Запись TSidAndAttributes выглядит вот так:
Заметьте, что первое поле этой записи является указателем, PSID. Поэтому запись TSidAndAttributes требует выравнивания на границу указателя, который на 64-х битной Windows равен 8-ми байтам. С другой стороны, предлагаемая новая запись TTokenGroups состоит просто из одного DWORD-а, требуя поэтому только 4-х байтового выравнивания. SizeOf(TTokenGroups) равен четырём.
Я надеюсь, вы видите, к чему я клоню.
При предлагаемом объявлении записи, массив из записей TSidAndAttributes не будет размещён на границу по 8-и байтам, а только по 4-м. Необходимый пробел (padding) между полем GroupCount и первым элементом TSidAndAttributes отсутствует. Попытка доступа к элементу массива приведёт к вылету с исключением STATUS_DATATYPE_MISALIGNMENT.
Окей, вы можете спросить: почему бы тогда не использовать массивы нулевой длины, вместо массива с одним элементом?
Потому что путешествия во времени ещё не совершенны.
Массивы нулевой длины не были частью стандарта C до 1999-го года. Поскольку Windows существовала задолго до этого времени, то она не могла воспользоваться этой возможностью языка C (прим. пер.: ну а заголовочники Delphi являются просто буквальным переводом заголовочников Windows - чтобы C-ный код, переведённый на Delphi, работал бы без изменений).
Некоторые записи (структуры) в Windows имеют переменный размер, они начинаются с фиксированного заголовка, а далее следует переменная часть в виде массива. Когда объявляют такие структуры, они часто объявляются с массивом размера 1 на месте части переменного размера. Например:
type
PTOKEN_GROUPS = ^TOKEN_GROUPS;
_TOKEN_GROUPS = record
GroupCount: DWORD;
Groups: array [0..ANYSIZE_ARRAY - 1] of SID_AND_ATTRIBUTES;
end;
TOKEN_GROUPS = _TOKEN_GROUPS;
TTokenGroups = TOKEN_GROUPS;
PTokenGroups = PTOKEN_GROUPS;
Если вы посмотрите объявление ANYSIZE_ARRAY, то увидите, что эта константа равна 1, поэтому этот код объявляет запись с ведомым массивом из единственного элемента.
При таком объявлении, вам нужно выделять память для одной такой записи примерно так:
var
TokenGroups: PTokenGroups;
begin
GetMem(TokenGroups, SizeOf(TTokenGroups) + (NumberOfGroups - 1) * SizeOf(TSidAndAttributes));
и вы должны инициализировать запись примерно так:
TokenGroups.GroupCount := NumberOfGroups;
for Index := 0 to NumberOfGroups - 1 do
TokenGroups.Groups[Index] := ...;
Многие люди считают, что запись должна быть объявлена так:
type
PTOKEN_GROUPS = ^TOKEN_GROUPS;
_TOKEN_GROUPS = record
GroupCount: DWORD;
end;
TOKEN_GROUPS = _TOKEN_GROUPS;
TTokenGroups = TOKEN_GROUPS;
PTokenGroups = PTOKEN_GROUPS;
Тогда код выделения был бы таким:
var
TokenGroups: PTokenGroups;
begin
GetMem(TokenGroups, SizeOf(TTokenGroups) + NumberOfGroups * SizeOf(TSidAndAttributes));
Эта альтернатива имеет два недостатка. Один косметического плана, а другой - фатальный.
Во-первых, косметический недостаток: становится тяжелее получать доступ к элементам массива в переменной части. Например, инициализация теперь должна выглядеть примерно так:
TokenGroups.GroupCount := NumberOfGroups;
for Index := 0 to NumberOfGroups - 1 do
PSidAndAttributes(Cardinal(TokenGroups) + SizeOf(TTokenGroups) + Index * SizeOf(TSidAndAttributes))^ := ...;
Но настоящий недостаток является фатальным. Вышеприведённый код вылетает на 64-х битных Windows. Запись TSidAndAttributes выглядит вот так:
PSID_AND_ATTRIBUTES = ^SID_AND_ATTRIBUTES;
_SID_AND_ATTRIBUTES = record
Sid: PSID;
Attributes: DWORD;
end;
SID_AND_ATTRIBUTES = _SID_AND_ATTRIBUTES;
TSidAndAttributes = SID_AND_ATTRIBUTES;
PSidAndAttributes = PSID_AND_ATTRIBUTES;
Заметьте, что первое поле этой записи является указателем, PSID. Поэтому запись TSidAndAttributes требует выравнивания на границу указателя, который на 64-х битной Windows равен 8-ми байтам. С другой стороны, предлагаемая новая запись TTokenGroups состоит просто из одного DWORD-а, требуя поэтому только 4-х байтового выравнивания. SizeOf(TTokenGroups) равен четырём.
Я надеюсь, вы видите, к чему я клоню.
При предлагаемом объявлении записи, массив из записей TSidAndAttributes не будет размещён на границу по 8-и байтам, а только по 4-м. Необходимый пробел (padding) между полем GroupCount и первым элементом TSidAndAttributes отсутствует. Попытка доступа к элементу массива приведёт к вылету с исключением STATUS_DATATYPE_MISALIGNMENT.
Окей, вы можете спросить: почему бы тогда не использовать массивы нулевой длины, вместо массива с одним элементом?
Потому что путешествия во времени ещё не совершенны.
Массивы нулевой длины не были частью стандарта C до 1999-го года. Поскольку Windows существовала задолго до этого времени, то она не могла воспользоваться этой возможностью языка C (прим. пер.: ну а заголовочники Delphi являются просто буквальным переводом заголовочников Windows - чтобы C-ный код, переведённый на Delphi, работал бы без изменений).
понедельник, 22 июня 2009 г.
Почему структуру TFileTime нельзя рассматривать как Int64?
Это перевод Why can't you treat a FILETIME as an __int64? Автор: Реймонд Чен.
Запись TFileTime представляет 64-х битное значение в двух частях:
Вы можете поддаться соблазну взять запись TFileTime и обращаться с ней, как если бы она была типа Int64. В конце концов, её раскладка по памяти в точности совпадает с отпечатком памяти для 64-х битного (little-endian) Integer. Некоторые люди написали пример кода, который делает именно это.
Но почему это неверно?
Запись TFileTime представляет 64-х битное значение в двух частях:
type
PFileTime = ^TFileTime;
_FILETIME = record
dwLowDateTime: DWORD;
dwHighDateTime: DWORD;
end;
TFileTime = _FILETIME;
FILETIME = _FILETIME;
Вы можете поддаться соблазну взять запись TFileTime и обращаться с ней, как если бы она была типа Int64. В конце концов, её раскладка по памяти в точности совпадает с отпечатком памяти для 64-х битного (little-endian) Integer. Некоторые люди написали пример кода, который делает именно это.
Но почему это неверно?
воскресенье, 21 июня 2009 г.
Опасайтесь не нуль-терминированных строк в реестре
Это перевод Beware of non-null-terminated registry strings. Автор: Реймонд Чен.
Хотя значение хранится в реестре как REG_SZ, это не означает, что данные действительно заканчиваются корректным терминатором. На самом нижнем уровне реестр - просто иерархически организованная база данных имя/значение.
И вы можете соврать, и это может сойти вам с рук.
Хотя значение хранится в реестре как REG_SZ, это не означает, что данные действительно заканчиваются корректным терминатором. На самом нижнем уровне реестр - просто иерархически организованная база данных имя/значение.
И вы можете соврать, и это может сойти вам с рук.
суббота, 20 июня 2009 г.
Страшная структура STRRET
Это перевод The kooky STRRET structure. Автор: Реймонд Чен.
Если вы игрались с пространством имён оболочки (shell namespace), то вы, без сомнения, встречались со структурой STRRET, которая используется IShellFolder.GetDisplayNameOf для возвращения имён элементов оболочки. Как вы можете видеть по документации, STRRET иногда представляет собой строковый буфер ANSI, иногда указатель на Unicode строку, а иногда (и это страннее всего) смещение в pidl. Что же здесь происходит?
Структура STRRET появляется на сцене в эру Windows 95. В это время компьютеры были относительно медленными и имели ограниченный набор памяти (минимальными требованиями для Windows 95 были 4 Мб памяти и процессор 386DX - который работал на "невероятных" 25 МГц). Было намного быстрее выделить память на стеке (одна простая инструкция "sub"), чем выделять её из кучи (что могло занять тысячи инструкций!), поэтому структура STRRET была спроектирована так, чтобы типичные (для Windows 95) сценарии могли бы быть выполнены без выделения памяти из кучи.
Флаг STRRET_OFFSET поднимает эту планку до ещё больших высот. Часто, вы храните имя внутри самого pidl, и копирование его в структуру STRRET займёт, боже, целых 200 тиков (!). Чтобы избежать этого бессмысленного копирования памяти, STRRET_OFFSET позволял вам просто вернуть смещение в pidl, которое вызывающий потом мог использовать для прямой работы со строкой.
Woo-hoo, вы только что сэкономили копирование строки.
Конечно же, когда с течением времени компьютеры стали быстрее и память уже не стала таким сильным ограничением, эти микро-оптимизации стали скорее раздражающими помехами. Экономия 200 тиков на копировании строки едва ли что-то принесёт вам сегодня. На процессоре 1 ГГц, простая программная ошибка страницы (soft page fault) стоит вам миллион циклов; а аппаратная (hard page fault) стоит десятки миллионов.
Вы можете скопировать ОЧЕНЬ много строк за двадцать миллионов циклов.
И даже более того: сценарии, которые были типичными в Windows 95, уже не являются типичными сегодня, так что исходный сценарий, для которого и создавалась эта оптимизация, сегодня уже практически не встречается. Эта оптимизация пережила свой век полезности.
К счастью, вам не нужно думать о структуре STRRET. Есть несколько вспомогательных функций, которые принимают структуру STRRET и переводят её в что-то более простое для управления.
Помешанность структуры STRRET теперь инкапсулирована в этом коде. Ну и слава богу.
Если вы игрались с пространством имён оболочки (shell namespace), то вы, без сомнения, встречались со структурой STRRET, которая используется IShellFolder.GetDisplayNameOf для возвращения имён элементов оболочки. Как вы можете видеть по документации, STRRET иногда представляет собой строковый буфер ANSI, иногда указатель на Unicode строку, а иногда (и это страннее всего) смещение в pidl. Что же здесь происходит?
Структура STRRET появляется на сцене в эру Windows 95. В это время компьютеры были относительно медленными и имели ограниченный набор памяти (минимальными требованиями для Windows 95 были 4 Мб памяти и процессор 386DX - который работал на "невероятных" 25 МГц). Было намного быстрее выделить память на стеке (одна простая инструкция "sub"), чем выделять её из кучи (что могло занять тысячи инструкций!), поэтому структура STRRET была спроектирована так, чтобы типичные (для Windows 95) сценарии могли бы быть выполнены без выделения памяти из кучи.
Флаг STRRET_OFFSET поднимает эту планку до ещё больших высот. Часто, вы храните имя внутри самого pidl, и копирование его в структуру STRRET займёт, боже, целых 200 тиков (!). Чтобы избежать этого бессмысленного копирования памяти, STRRET_OFFSET позволял вам просто вернуть смещение в pidl, которое вызывающий потом мог использовать для прямой работы со строкой.
Woo-hoo, вы только что сэкономили копирование строки.
Конечно же, когда с течением времени компьютеры стали быстрее и память уже не стала таким сильным ограничением, эти микро-оптимизации стали скорее раздражающими помехами. Экономия 200 тиков на копировании строки едва ли что-то принесёт вам сегодня. На процессоре 1 ГГц, простая программная ошибка страницы (soft page fault) стоит вам миллион циклов; а аппаратная (hard page fault) стоит десятки миллионов.
Вы можете скопировать ОЧЕНЬ много строк за двадцать миллионов циклов.
И даже более того: сценарии, которые были типичными в Windows 95, уже не являются типичными сегодня, так что исходный сценарий, для которого и создавалась эта оптимизация, сегодня уже практически не встречается. Эта оптимизация пережила свой век полезности.
К счастью, вам не нужно думать о структуре STRRET. Есть несколько вспомогательных функций, которые принимают структуру STRRET и переводят её в что-то более простое для управления.
Помешанность структуры STRRET теперь инкапсулирована в этом коде. Ну и слава богу.
пятница, 19 июня 2009 г.
Резюме последних статей о /3GB
Это перевод Summary of the recent spate of /3GB articles. Автор: Реймонд Чен.
Теперь, когда вся серия закончена (я надеюсь), вот её содержание.
Теперь, когда вся серия закончена (я надеюсь), вот её содержание.
Execution protection (NX) и PAE
Это перевод Execution protection (NX) and PAE. Автор: Carmen Crincoli.
Комментарий к предыдущей записи об управлении памятью поставил хороший вопрос. Как PAE взаимодействует с новым механизмом No Execute (NX), включаемым на Opteron, Athlon64 и новых Xeon на основе Prescott?
В Windows XP SP2 и Server 2003 SP1 оба они неразрывно связаны. Двухуровневая схема трансляции адресов, используемая не-PAE ядром, не имеет достаточно места для размещения какой-либо описательной информацию об отдельных страницах памяти. Трёх-уровневая схема, которая необходима PAE, позволяет использовать новый атрибут NX (это просто бит в Page Table Entry (PTE), который указывает, что на память в этом месте не разрешается ссылаться по instruction pointer (EIP). “Не Выполнять Под Угрозой Смерти”).
Когда вы используете ключ /NoExecute в этих ОС-ах, то ntldr теперь загружает PAE-ядро, но в специальном режиме. Вы не получите доступа к памяти выше 4 Гб, и, что более важно, ваши драйвера тоже не получат физических адресов выше 4 Гб. Это важно, потому что мы обнаружили, что многие устройства и драйвера устройств, особенно на потребительском рынке, счастливо полагают, что они никогда не получат адрес памяти в пространстве выше границы 4 Гб.
Хотя вы можете использовать максимум только 4 Гб памяти в XP, это не означает, что часть её не может иметь физических адресов выше границы в 4 Гб. BIOS или устройства могут перепроецировать память оттуда в предположении, что её нельзя увидеть или использовать. И когда PAE-ядро начинает использовать адреса на эти страницы памяти, могут происходить плохие вещи. Простейшим путём убедиться, что всё будет работать, как и в прошлом - гарантировать, что мы не будем исользовать никаких адресов выше границы в 4 Гб.
Если вы добавите ключ /PAE, то вы получите обычное поведение PAE. Конечно же, это в точности то, что вы хотели получить в серверном пространстве; в конце концов, у вас есть причина, чтобы устанавливать эту дополнительную память, верно? Также заметьте, что эти свойства не транзитивны. Хотя и /PAE и /NoExecute используют один и тот же файл ядра (ntkrnlpa.exe или ntkrpamp.exe) и механизм трансляции адресов, но вам нужно включать оба ключа, чтобы задействовать обе возможности.
Заметьте, что вся информация выше применима только к NX на процессорах в режиме x86. Платформы IA-64 и x64 имеют родную поддержку NX, которая доступна начиная с первого релиза Server 2003 и XP для 64bit. Обе эти платформы уже используют трёхуровневую схему трансляции адресов, но она никак не связана с PAE. Они адресуют 64 бита напрямую (natively), а структуры памяти имеют достаточно места для информации о NX. Нам надо было просто добавить эту поддержку в ОС.
Комментарий к предыдущей записи об управлении памятью поставил хороший вопрос. Как PAE взаимодействует с новым механизмом No Execute (NX), включаемым на Opteron, Athlon64 и новых Xeon на основе Prescott?
В Windows XP SP2 и Server 2003 SP1 оба они неразрывно связаны. Двухуровневая схема трансляции адресов, используемая не-PAE ядром, не имеет достаточно места для размещения какой-либо описательной информацию об отдельных страницах памяти. Трёх-уровневая схема, которая необходима PAE, позволяет использовать новый атрибут NX (это просто бит в Page Table Entry (PTE), который указывает, что на память в этом месте не разрешается ссылаться по instruction pointer (EIP). “Не Выполнять Под Угрозой Смерти”).
Когда вы используете ключ /NoExecute в этих ОС-ах, то ntldr теперь загружает PAE-ядро, но в специальном режиме. Вы не получите доступа к памяти выше 4 Гб, и, что более важно, ваши драйвера тоже не получат физических адресов выше 4 Гб. Это важно, потому что мы обнаружили, что многие устройства и драйвера устройств, особенно на потребительском рынке, счастливо полагают, что они никогда не получат адрес памяти в пространстве выше границы 4 Гб.
Хотя вы можете использовать максимум только 4 Гб памяти в XP, это не означает, что часть её не может иметь физических адресов выше границы в 4 Гб. BIOS или устройства могут перепроецировать память оттуда в предположении, что её нельзя увидеть или использовать. И когда PAE-ядро начинает использовать адреса на эти страницы памяти, могут происходить плохие вещи. Простейшим путём убедиться, что всё будет работать, как и в прошлом - гарантировать, что мы не будем исользовать никаких адресов выше границы в 4 Гб.
Если вы добавите ключ /PAE, то вы получите обычное поведение PAE. Конечно же, это в точности то, что вы хотели получить в серверном пространстве; в конце концов, у вас есть причина, чтобы устанавливать эту дополнительную память, верно? Также заметьте, что эти свойства не транзитивны. Хотя и /PAE и /NoExecute используют один и тот же файл ядра (ntkrnlpa.exe или ntkrpamp.exe) и механизм трансляции адресов, но вам нужно включать оба ключа, чтобы задействовать обе возможности.
Заметьте, что вся информация выше применима только к NX на процессорах в режиме x86. Платформы IA-64 и x64 имеют родную поддержку NX, которая доступна начиная с первого релиза Server 2003 и XP для 64bit. Обе эти платформы уже используют трёхуровневую схему трансляции адресов, но она никак не связана с PAE. Они адресуют 64 бита напрямую (natively), а структуры памяти имеют достаточно места для информации о NX. Нам надо было просто добавить эту поддержку в ОС.
четверг, 18 июня 2009 г.
Миф: чтобы использовать AWE - вам нужно включить PAE
Это перевод Myth: In order to use AWE, you must enable PAE. Автор: Реймонд Чен.
Address Windowing Extensions (AWE) не требует PAE. Я не знаю, почему люди продолжают утверждать это, поскольку это легко продемонстрировать.
Возьмите любую программу, которая использует AWE. Если у вас нет готовой под рукой - вы можете взять её из примеров к MSDN, где она демонстрирует, как использовать AWE. Дайте себе привилегию "Закрепление страниц в памяти" ("Lock Pages in Memory") и запустите программу. Заметьте, что она работает.
Теперь уберите ключ /PAE из своего файла boot.ini, перезагрузитесь, и снова запустите программу. Заметьте, что она всё ещё работает.
Миф развеян прямым экспериментом и наблюдением.
Address Windowing Extensions (AWE) не требует PAE. Я не знаю, почему люди продолжают утверждать это, поскольку это легко продемонстрировать.
Возьмите любую программу, которая использует AWE. Если у вас нет готовой под рукой - вы можете взять её из примеров к MSDN, где она демонстрирует, как использовать AWE. Дайте себе привилегию "Закрепление страниц в памяти" ("Lock Pages in Memory") и запустите программу. Заметьте, что она работает.
Теперь уберите ключ /PAE из своего файла boot.ini, перезагрузитесь, и снова запустите программу. Заметьте, что она всё ещё работает.
Миф развеян прямым экспериментом и наблюдением.
среда, 17 июня 2009 г.
Миф: PAE увеличивает виртуальное адресное пространство за 4 Гб
Это перевод Myth: PAE increases the virtual address space beyond 4GB. Автор: Реймонд Чен.
PAE увеличивает количество физической памяти, которую может адресовать процессор, но это никак не связяно в виртуальным адресным пространством (вспомните, что PAE расшифровывается как Physical Address Extensions - Расширения Физических Адресов).
PAE увеличивает физическое адресное пространство (адресное пространство, которое процессор может использовать для доступа к чипам памяти в вашей машине) с 32-х бит до 36-ти бит на Pentium 2, что даёт теоретический максимум в 64 Гб. Однако, размер указателя не изменятеся - он по-прежнему равен 32-м битам (для 32-х битного процессора), что означает, что виртуальное адресное пространство всё ещё равно 4 Гб.
С включенным PAE, записи таблицы и каталога страниц (page table and page directory) удваиваются в размере (чтобы учесть дополнительные биты в фрейме страницы), что значительно увеличивает количество памяти, требуемой для таблиц и каталогов страниц (поскольку каждая таблица страниц описывает теперь в два раза меньше памяти, чем раньше).
Заметьте, что из-за этого получается, что PAE и /3GB в некоторой степени конфликтуют друг с другом. Если вы включите и PAE и /3GB, то ядро будет вынуждено ограничить себя только 16 Гб физической памяти. Это происходит потому, что в адресном пространстве ядра просто не хватает места для размещения данных по отслеживанию увеличенной памяти.
На процессорах AMD, физическое адресное пространство расширяется до 40 бит, что даёт теоретический максимум в 1 Тб. Однако, менеджер памяти использует только 37 бит из них, что даёт реальный максимум в 128 Гб. Почему? По той же самой причине, почему ядро ограничивает память до 16 Гб в режиме /3GB: не хватает адресного пространства. Нам пора переселяться на 64-х битные процессоры...
PAE увеличивает количество физической памяти, которую может адресовать процессор, но это никак не связяно в виртуальным адресным пространством (вспомните, что PAE расшифровывается как Physical Address Extensions - Расширения Физических Адресов).
PAE увеличивает физическое адресное пространство (адресное пространство, которое процессор может использовать для доступа к чипам памяти в вашей машине) с 32-х бит до 36-ти бит на Pentium 2, что даёт теоретический максимум в 64 Гб. Однако, размер указателя не изменятеся - он по-прежнему равен 32-м битам (для 32-х битного процессора), что означает, что виртуальное адресное пространство всё ещё равно 4 Гб.
С включенным PAE, записи таблицы и каталога страниц (page table and page directory) удваиваются в размере (чтобы учесть дополнительные биты в фрейме страницы), что значительно увеличивает количество памяти, требуемой для таблиц и каталогов страниц (поскольку каждая таблица страниц описывает теперь в два раза меньше памяти, чем раньше).
Заметьте, что из-за этого получается, что PAE и /3GB в некоторой степени конфликтуют друг с другом. Если вы включите и PAE и /3GB, то ядро будет вынуждено ограничить себя только 16 Гб физической памяти. Это происходит потому, что в адресном пространстве ядра просто не хватает места для размещения данных по отслеживанию увеличенной памяти.
На процессорах AMD, физическое адресное пространство расширяется до 40 бит, что даёт теоретический максимум в 1 Тб. Однако, менеджер памяти использует только 37 бит из них, что даёт реальный максимум в 128 Гб. Почему? По той же самой причине, почему ядро ограничивает память до 16 Гб в режиме /3GB: не хватает адресного пространства. Нам пора переселяться на 64-х битные процессоры...
вторник, 16 июня 2009 г.
А почему это размер виртуального адресного пространства равен 4 Гб?
Это перевод Why is the virtual address space 4GB anyway? Автор: Реймонд Чен. Альтернативный перевод.
Размер адресного пространства ограничен числом уникальных значений указателей. Для 32-х битного процессора 32-х битное целое может представлять 232 различных значений. Если вы позволите каждому такому значению адресовать свой байт в памяти, то вы получите 232 байт или 4 Гб.
Если вы хотели отбросить плоскую модель памяти и работать с селекторами, тогда вы могли бы комбинировать 16-ти битный селектор с 32-х битным смещением, чтобы получить комбинированный 48-ми битный указатель. Это созаёт теоретический максимум в 248 различных значений указателя, что даёт вам 256 Тб памяти.
Однако, этот теоретический максимум не может быть достигнут на процессорах класса Pentium. Одной из причин является то, что младшие биты в значении сегмента кодируют информацию о типе селектора. В результате, из 65536 возможных значений у вас остаётся только 8191 из них, которые доступны в режиме пользователя. Это отбрасывает вас на 32 Тб.
Но действительное ограничение на адресное пространство при использовании модели селектор:смещение - это то, что каждый селектор просто описывает подмножество плоского 32-х битного адресного пространства. Так что даже если бы вы использовали все 8191 селекторов, они все были бы просто представлениями одного и того же 32-х битного адресного пространства.
(Кроме того, я серьёзно сомневаюсь, что люди захотят вернуться к былым дням использования сегментной модели памяти).
Ограничение в 2 Гб больше не существует в 64-х битных Windows; пользовательское виртуальное адресное пространство - теперь неимоверные 8 Тб. Даже если бы вы выделяли мегабайт памяти в секунду, у вас бы ушло 3 месяца, чтобы исчерпать такое адресное пространство. Заметьте, однако, вы можете установить флаг /LARGEADDRESSAWARE:NO для вашей 64-х битной программы, чтобы сообщить операционной системе, что программа будет жить ниже границы в 2 Гб. Не очень ясно, зачем вообще вы захотите сделать это, поскольку вы теряете бонусы 64-х битного адресного пространства, но при этом используете 64-х битные указатели. Это как если вы платите за кабельное телевидение, но не смотрите его.
Вооружённые тем, что вы уже узнали к этому моменту, может быть, вы сможете ответить на следующий запрос от одного из наших клиентов:
Размер адресного пространства ограничен числом уникальных значений указателей. Для 32-х битного процессора 32-х битное целое может представлять 232 различных значений. Если вы позволите каждому такому значению адресовать свой байт в памяти, то вы получите 232 байт или 4 Гб.
Если вы хотели отбросить плоскую модель памяти и работать с селекторами, тогда вы могли бы комбинировать 16-ти битный селектор с 32-х битным смещением, чтобы получить комбинированный 48-ми битный указатель. Это созаёт теоретический максимум в 248 различных значений указателя, что даёт вам 256 Тб памяти.
Однако, этот теоретический максимум не может быть достигнут на процессорах класса Pentium. Одной из причин является то, что младшие биты в значении сегмента кодируют информацию о типе селектора. В результате, из 65536 возможных значений у вас остаётся только 8191 из них, которые доступны в режиме пользователя. Это отбрасывает вас на 32 Тб.
Но действительное ограничение на адресное пространство при использовании модели селектор:смещение - это то, что каждый селектор просто описывает подмножество плоского 32-х битного адресного пространства. Так что даже если бы вы использовали все 8191 селекторов, они все были бы просто представлениями одного и того же 32-х битного адресного пространства.
(Кроме того, я серьёзно сомневаюсь, что люди захотят вернуться к былым дням использования сегментной модели памяти).
Ограничение в 2 Гб больше не существует в 64-х битных Windows; пользовательское виртуальное адресное пространство - теперь неимоверные 8 Тб. Даже если бы вы выделяли мегабайт памяти в секунду, у вас бы ушло 3 месяца, чтобы исчерпать такое адресное пространство. Заметьте, однако, вы можете установить флаг /LARGEADDRESSAWARE:NO для вашей 64-х битной программы, чтобы сообщить операционной системе, что программа будет жить ниже границы в 2 Гб. Не очень ясно, зачем вообще вы захотите сделать это, поскольку вы теряете бонусы 64-х битного адресного пространства, но при этом используете 64-х битные указатели. Это как если вы платите за кабельное телевидение, но не смотрите его.
Вооружённые тем, что вы уже узнали к этому моменту, может быть, вы сможете ответить на следующий запрос от одного из наших клиентов:
В одном из наших файлов boot.ini есть ключ /7GB. Наш консультант сказал нам, что мы должны установить его на 1 Гб меньше, чем у нас есть системной памяти. Поскольку у нас установлено 8 Гб, 8 Гб - 1 Гб = 7 Гб. Консультант сказал, что эта настройка позволит приложениям выделать больше чем 2 Гб памяти. Мы бы хотели, чтобы Microsoft прокомментировала этот анализ.
понедельник, 15 июня 2009 г.
Миф: ключ /3GB позволит мне выделить 1 гигантский блок памяти в 3 Гб
Это перевод Myth: The /3GB switch lets me map one giant 3GB block of memory. Автор: Реймонд Чен. Альтернативный перевод.
Просто то, что у вас есть аж 3 Гб виртуального адресного пространства, ещё не означает, что вы можете выделить один гигантский блок памяти размером 3 Гб. Стандартные дыры в виртуальном адресном пространстве не изменились: это 64 Кб внизу и 64 Кб около границы в 2 Гб.
Просто то, что у вас есть аж 3 Гб виртуального адресного пространства, ещё не означает, что вы можете выделить один гигантский блок памяти размером 3 Гб. Стандартные дыры в виртуальном адресном пространстве не изменились: это 64 Кб внизу и 64 Кб около границы в 2 Гб.
воскресенье, 14 июня 2009 г.
Почему Exchange рекомендует включать /3GB, если у вас больше 1 Гб памяти?
Это перевод Why does Exchange recommend /3GB if you have more than 1GB of physical memory? Автор: Реймонд Чен.
Если вы пошарите по Knowledge Base, то вы можете увидеть статью, в которой сказано, что Exchange 2000 требует ключ /3GB, если у вас есть больше 1 Гб физической памяти. Хотя я всё это время писал о том, что /3GB не имеет никакого отношения к физической памяти. Так в чём же дело?
Заголовок статьи мог бы быть чуть более понятным. На самом деле там должно быть написано что-то вроде "Exchange 2000 требует ключ /3GB, чтобы воспользоваться преимуществами более чем 1 Гб физической памяти".
Похоже, что Exchange 2000 не использует технику переключения банков памяти, что я описал ранее (ну, я и не виню их - это ведь чрезвычайно запутанно). Соответственно, для Exchange 2000, виртуальное адресное пространство = виртуальная память.
"Вместимость" программы обычно является комбинацией нескольких факторов, наименьший из которых устанавливает предел. По аналогии: предположим, вам нужно два кусочка хлеба, два кусочка болоньи и ломоть сыра, чтобы сделать сырный сэндвич. Как только у вас закончится любой ингридиент - вы не сможете больше делать сэндвичи. Если у вас закончится сыр, добавление болоньи вам не поможет.
Окей, так какое же отношение сыр и болонья имеют к Exchange 2000?
Из описания в статья становится ясно, что, похоже, программа store.exe большую часть времени ограничена физической памятью (сначала у вас заканчивается сыр). Но как только память машины переваливает за гигабайт, у вас появляется избыток памяти, а адресное пространство становится новым сдерживающим фактором (вы добавили много сыра, и теперь вам не хватает болоньи). Вот тут и вылезает ключ /3GB. Он увеличивает размер пользовательского адресного пространства, снижая тем самым давление на адресное пространство.
Если вы пошарите по Knowledge Base, то вы можете увидеть статью, в которой сказано, что Exchange 2000 требует ключ /3GB, если у вас есть больше 1 Гб физической памяти. Хотя я всё это время писал о том, что /3GB не имеет никакого отношения к физической памяти. Так в чём же дело?
Заголовок статьи мог бы быть чуть более понятным. На самом деле там должно быть написано что-то вроде "Exchange 2000 требует ключ /3GB, чтобы воспользоваться преимуществами более чем 1 Гб физической памяти".
Похоже, что Exchange 2000 не использует технику переключения банков памяти, что я описал ранее (ну, я и не виню их - это ведь чрезвычайно запутанно). Соответственно, для Exchange 2000, виртуальное адресное пространство = виртуальная память.
"Вместимость" программы обычно является комбинацией нескольких факторов, наименьший из которых устанавливает предел. По аналогии: предположим, вам нужно два кусочка хлеба, два кусочка болоньи и ломоть сыра, чтобы сделать сырный сэндвич. Как только у вас закончится любой ингридиент - вы не сможете больше делать сэндвичи. Если у вас закончится сыр, добавление болоньи вам не поможет.
Окей, так какое же отношение сыр и болонья имеют к Exchange 2000?
Из описания в статья становится ясно, что, похоже, программа store.exe большую часть времени ограничена физической памятью (сначала у вас заканчивается сыр). Но как только память машины переваливает за гигабайт, у вас появляется избыток памяти, а адресное пространство становится новым сдерживающим фактором (вы добавили много сыра, и теперь вам не хватает болоньи). Вот тут и вылезает ключ /3GB. Он увеличивает размер пользовательского адресного пространства, снижая тем самым давление на адресное пространство.
суббота, 13 июня 2009 г.
Миф: ключ /3GB расширяет пользовательское адресное пространство для всех программ
Это перевод Myth: The /3GB switch expands the user-mode address space of all programs. Автор: Реймонд Чен. Альтернативный перевод (C стиль).
На самом деле, он влияет только на программы с ключом /LARGEADDRESSAWARE.
По соображениям совместимости, только программы, которые явно пометили себя, что они умеют обрабатывать виртуальное адресное пространство больше 2 Гб, получат большее адресное пространство. Не помеченные программы получат свои обычные 2 Гб, а адресное пространство между 2 Гб и 3 Гб не будет использоваться вовсе.
Почему?
Потому что слишком много программ предполагают, что старший бит адреса в пользовательском режиме всегда очищен (т.е. равен 0), часто делая это невольно. В MSDN есть страничка, на которой перечисленны несколько способов использования такого предположения. Например, вы можете захотеть найти средний адрес между двумя другими - используя для этого формулу (a + b) / 2. Но если a и b будут больше 2 Гб, то их сумма не влезет в 4-х байтное целое - следовательно, вы получите неверный результат (для верного вычисления надо использовать выражение a + (b - a) / 2). Соответственно, вы не можете просто взять программу, которую вы не писали, пометить её флагом /LARGEADDRESSAWARE и объявить, что дело сделано. Вам вместе с авторами программы надо проверить, что код не делает никаких предположений насчёт этих 2 Гб (а тот факт, что программа не была помечена, как совместимая с 3 Гб, означает, что никаких проверок не было сделано. В самом деле - в противном случае она была бы уже помечена флагом /LARGEADDRESSAWARE!).
Пометка вашей программы флагом /LARGEADDRESSAWARE указывает операционной системе: "давай, дай мне доступ к этому дополнительному гигабайту памяти пользовательского адресного пространства", в результате адреса в третьем гигабайте становятся возможными возвращаемыми значениями в функциях выделения памяти. Если вы установите флаг "Top down" в предпочтениях менеджера памяти, вы можете указать менеджеру памяти выделять память сначала по старшим адресам, таким образом, вы заставите свою программу работать на третьем гигабайте сразу же, а не когда заполнится остальное место. Это очень удобный режим для проверки вашей программы в конфигурации /3GB, посольку он заставляет скорее, чем в обычном режиме, использовать проблемные адреса.
На самом деле, он влияет только на программы с ключом /LARGEADDRESSAWARE.
По соображениям совместимости, только программы, которые явно пометили себя, что они умеют обрабатывать виртуальное адресное пространство больше 2 Гб, получат большее адресное пространство. Не помеченные программы получат свои обычные 2 Гб, а адресное пространство между 2 Гб и 3 Гб не будет использоваться вовсе.
Почему?
Потому что слишком много программ предполагают, что старший бит адреса в пользовательском режиме всегда очищен (т.е. равен 0), часто делая это невольно. В MSDN есть страничка, на которой перечисленны несколько способов использования такого предположения. Например, вы можете захотеть найти средний адрес между двумя другими - используя для этого формулу (a + b) / 2. Но если a и b будут больше 2 Гб, то их сумма не влезет в 4-х байтное целое - следовательно, вы получите неверный результат (для верного вычисления надо использовать выражение a + (b - a) / 2). Соответственно, вы не можете просто взять программу, которую вы не писали, пометить её флагом /LARGEADDRESSAWARE и объявить, что дело сделано. Вам вместе с авторами программы надо проверить, что код не делает никаких предположений насчёт этих 2 Гб (а тот факт, что программа не была помечена, как совместимая с 3 Гб, означает, что никаких проверок не было сделано. В самом деле - в противном случае она была бы уже помечена флагом /LARGEADDRESSAWARE!).
Пометка вашей программы флагом /LARGEADDRESSAWARE указывает операционной системе: "давай, дай мне доступ к этому дополнительному гигабайту памяти пользовательского адресного пространства", в результате адреса в третьем гигабайте становятся возможными возвращаемыми значениями в функциях выделения памяти. Если вы установите флаг "Top down" в предпочтениях менеджера памяти, вы можете указать менеджеру памяти выделять память сначала по старшим адресам, таким образом, вы заставите свою программу работать на третьем гигабайте сразу же, а не когда заполнится остальное место. Это очень удобный режим для проверки вашей программы в конфигурации /3GB, посольку он заставляет скорее, чем в обычном режиме, использовать проблемные адреса.
пятница, 12 июня 2009 г.
Миф: вам нужно включать /3GB, если у вас есть больше 2 Гб памяти
Это перевод Myth: You need /3GB if you have more than 2GB of physical memory. Автор: Реймонд Чен. Альтернативный перевод (C стиль).
Физическая память - это не виртуальная память.
Я не уверен, какой логический процесс привёл к рождению этого мифа. Это не может быть из-за неверной интерпретации соответствия один-к-одному виртуальной и физической памяти, поскольку отображение явно не один-к-одному. Обычно у вас намного больше виртуальной памяти, чем физической. Свободная физическая память не имеет соответствия ни в одном виртуальном адресном пространстве. А разделяемая память обозначена в нескольких виртуальных адресных пространствах, хотя соответствует одним и тем же страницам физической памяти.
Хотя это напомнило мне одну историю.
В Windows/386 так получилось, что ядро могло просто спроецировать всю физическую память на виртуальное адресное пространство режима ядра. Там была такая функция: _MapPhysToLinear. Вы передавали ей диапазон адресов физической памяти, а она возвращала диапазон линейных адресов, по которым можно было получить доступ к запрошенной физической памяти. Некоторые разработчики драйверов обнаружили, что ядро проецирует всю физическую память, а _MapPhysToLinear просто возвращает указатели из этой области. В результате, они просто вызывали _MapPhysToLinear(0, 0x1000), а потом, когда в будущем им нужно было обратиться к физической памяти, они просто добавляли смещение к результату этого вызова. Другими словами, они считали, что:
Менеджер памяти был полностью переписан в Windows 95, и указанное совпадение больше не выполнялось. Чтобы экономить виртуальное адресное пространство ядра, физическая память теперь проецировалась только по необходимости.
Конечно же, драйвера, рассчитывающие на старое поведение, теперь не работали, потому что недокументированное поведение, на которое они опирались, больше не существовало.
В результате, когда она запускалась, Windows 95 проверяла, не загружен ли драйвер, который хочет старое поведение (Windows 3.1 не поддерживала динамическую загрузку драйверов, так что проверки только во время загрузки было достаточно). Если такой драйвер присутствовал, то она проецировала всю физическую память в виртуальное адресное пространство ядра, чтобы драйвер был счастлив. Это сжирало кучу виртуального адресного пространства, но, по крайней мере, ваша машина работала.
Я прямо уже слышу, как люди говорят: "Microsoft не следовало делать что либо для поддержки этих бажных драйверов. Лучше бы машина вылетала - это был бы пинок разработчикам драйвера, чтобы они выпустили нормальную версию". Да, конечно же, но это предполагает, что по вылету вы смогли бы определить драйвер, который его вызвал. Самое частое проявление блуждающего указателя в режиме ядра - это повреждение памяти, что означает, что вылетающий компонент - это не тот, в котором была проблема.
Например, практически все синие экраны смерти в Windows 95 при вылете по VMM(01) были вызваны повреждениями памяти. VMM(01) - это не подкачиваемая часть ядра Windows 95, где живёт менеджер памяти. Если драйвер повреждает кучу режима ядра, то синий экран смерти в менеджере памяти - это то, как баг этого драйвера себя проявит.
Физическая память - это не виртуальная память.
Я не уверен, какой логический процесс привёл к рождению этого мифа. Это не может быть из-за неверной интерпретации соответствия один-к-одному виртуальной и физической памяти, поскольку отображение явно не один-к-одному. Обычно у вас намного больше виртуальной памяти, чем физической. Свободная физическая память не имеет соответствия ни в одном виртуальном адресном пространстве. А разделяемая память обозначена в нескольких виртуальных адресных пространствах, хотя соответствует одним и тем же страницам физической памяти.
Хотя это напомнило мне одну историю.
В Windows/386 так получилось, что ядро могло просто спроецировать всю физическую память на виртуальное адресное пространство режима ядра. Там была такая функция: _MapPhysToLinear. Вы передавали ей диапазон адресов физической памяти, а она возвращала диапазон линейных адресов, по которым можно было получить доступ к запрошенной физической памяти. Некоторые разработчики драйверов обнаружили, что ядро проецирует всю физическую память, а _MapPhysToLinear просто возвращает указатели из этой области. В результате, они просто вызывали _MapPhysToLinear(0, 0x1000), а потом, когда в будущем им нужно было обратиться к физической памяти, они просто добавляли смещение к результату этого вызова. Другими словами, они считали, что:
_MapPhysToLinear(p, x) = _MapPhysToLinear(0, x) + pМенеджер памяти был полностью переписан в Windows 95, и указанное совпадение больше не выполнялось. Чтобы экономить виртуальное адресное пространство ядра, физическая память теперь проецировалась только по необходимости.
Конечно же, драйвера, рассчитывающие на старое поведение, теперь не работали, потому что недокументированное поведение, на которое они опирались, больше не существовало.
В результате, когда она запускалась, Windows 95 проверяла, не загружен ли драйвер, который хочет старое поведение (Windows 3.1 не поддерживала динамическую загрузку драйверов, так что проверки только во время загрузки было достаточно). Если такой драйвер присутствовал, то она проецировала всю физическую память в виртуальное адресное пространство ядра, чтобы драйвер был счастлив. Это сжирало кучу виртуального адресного пространства, но, по крайней мере, ваша машина работала.
Я прямо уже слышу, как люди говорят: "Microsoft не следовало делать что либо для поддержки этих бажных драйверов. Лучше бы машина вылетала - это был бы пинок разработчикам драйвера, чтобы они выпустили нормальную версию". Да, конечно же, но это предполагает, что по вылету вы смогли бы определить драйвер, который его вызвал. Самое частое проявление блуждающего указателя в режиме ядра - это повреждение памяти, что означает, что вылетающий компонент - это не тот, в котором была проблема.
Например, практически все синие экраны смерти в Windows 95 при вылете по VMM(01) были вызваны повреждениями памяти. VMM(01) - это не подкачиваемая часть ядра Windows 95, где живёт менеджер памяти. Если драйвер повреждает кучу режима ядра, то синий экран смерти в менеджере памяти - это то, как баг этого драйвера себя проявит.
четверг, 11 июня 2009 г.
Миф: без ключа /3GB программа не может выделить больше 2 Гб виртуальной памяти
Это перевод Myth: Without /3GB a single program can't allocate more than 2GB of virtual memory. Автор: Реймонд Чен. Альтернативный перевод (C код).
Виртуальная памяти - это не виртуальное адресное пространство (часть 2).
Этот миф всё ещё держится, даже когда я пишу эту серию статей.
Виртуальная памяти - это не виртуальное адресное пространство (часть 2).
Этот миф всё ещё держится, даже когда я пишу эту серию статей.
среда, 10 июня 2009 г.
Миф: без ключа /3GB суммарный размер памяти для всех программ не может превышать 2 Гб
Это перевод Myth: Without /3GB the total amount of memory that can be allocated across all programs is 2GB. Автор: Реймонд Чен. Альтернативный перевод.
Виртуальная память - это не виртуальное адресное пространство (часть 1).
Я не знаю, откуда пошёл этот миф.
Виртуальное адресное пространство описывает, как разрешаются (resolve) адреса, но поскольку каждый процесс имеет своё собственно виртуальное адресное пространство, то количество, потребляемое одной программой, не оказывает никакого влияния на другую программу.
Например, пусть у вас есть программа, которая выделила 1 Гб памяти. Запустите три её копии. Теперь у вас есть 3 Гб выделенной памяти. И ни одна из этих программ даже не приблизилась к исчерпанию своего предела в 2 Гб на размер виртуального адресного пространства режима пользователя.
Завтра мы посмотрим другую вариацию этого мифа.
Виртуальная память - это не виртуальное адресное пространство (часть 1).
Я не знаю, откуда пошёл этот миф.
Виртуальное адресное пространство описывает, как разрешаются (resolve) адреса, но поскольку каждый процесс имеет своё собственно виртуальное адресное пространство, то количество, потребляемое одной программой, не оказывает никакого влияния на другую программу.
Например, пусть у вас есть программа, которая выделила 1 Гб памяти. Запустите три её копии. Теперь у вас есть 3 Гб выделенной памяти. И ни одна из этих программ даже не приблизилась к исчерпанию своего предела в 2 Гб на размер виртуального адресного пространства режима пользователя.
Завтра мы посмотрим другую вариацию этого мифа.
вторник, 9 июня 2009 г.
Последствия ключа /3GB для режима ядра
Это перевод Kernel address space consequences of the /3GB switch. Автор: Реймонд Чен. Альтернативный перевод.
Одно из негативных последствий ключа /3GB в том, что он заставляет ядро работать внутри гораздо меньшего пространства.
Одно из негативных последствий ключа /3GB в том, что он заставляет ядро работать внутри гораздо меньшего пространства.
понедельник, 8 июня 2009 г.
Часто непонимаемый ключ /3GB
Это перевод The oft-misunderstood /3GB switch. Автор: Реймонд Чен.
Очень просто объяснить, что он делает. Но люди всё равно часто не понимают его.
Очень просто объяснить, что он делает. Но люди всё равно часто не понимают его.
воскресенье, 7 июня 2009 г.
Никогда не устанавливайте фокус на отключенный контрол
Это перевод Never leave focus on a disabled control. Автор: Реймонд Чен.
Одним из самых главных НЕТ-НЕТ в управлении диалоговыми окнами - это отключение (disable) контрола, который владеет фокусом, без перемещения этого фокуса в другое место.
Одним из самых главных НЕТ-НЕТ в управлении диалоговыми окнами - это отключение (disable) контрола, который владеет фокусом, без перемещения этого фокуса в другое место.
суббота, 6 июня 2009 г.
Как установить фокус в диалоговом окне
Это перевод How to set focus in a dialog box. Автор: Реймонд Чен.
Установка фокуса в диалоговом окне - это нечто большее, чем просто вызов SetFocus.
Диалоговое окно поддерживает концепцию "кнопки по-умолчанию" (default button). Кнопка по-умолчанию обычно рисуется в визульно отличном стиле (толстая рамка или другой цвет) и указывает на выполняемое диалогом действие при нажатии кнопки Enter. Заметьте, что это не то же самое, что контрол с фокусом.
Например, откройте диалог "Выполнить..." из меню Пуск. Заметьте, что кнопка OK является кнопкой по-умолчанию; её вид отличается от других кнопок. Но фокус находится в строке ввода. Ваш ввод с клавиатуры идёт в строку ввода, пока вы не нажмёте Enter. Нажатие Enter активирует кнопку по-умолчанию, которой является кнопка OK.
Попробуйте понажимать Tab в диалоге и посмотреть, что происходит с кнопкой по-умолчанию. Когда диалоговое окно перемещает фокус на кнопку, эта кнопка становится новой кнопкой по-умолчанию. Но когда диалоговое окно убирает фокус на что-то другое (не являющееся кнопкой), то кнопка OK снова становится кнопкой по-умолчанию.
Менеджер диалоговых окон (dialog manager) запоминает, какая кнопка была умалчиваемой при создании диалога и, когда фокус перемещается к чему-то, что не является кнопкой, то он восстанавливает эту кнопку кнопкой по-умолчанию.
Вы можете узнать, какая кнопка является кнопкой по-умолчанию, послав диалоговому окну сообщение DM_GETDEFID; аналогично, вы можете изменить её с помощью сообщения DM_SETDEFID.
(Заметьте, что возвращаемое сообщением DM_GETDEFID значение содержит упакованные ID контрола (в младшем слове) и флаги (в старшем слове). Это ещё одно место, где расширение ID контролов на 32 бита ничего вам не даст).
Как видно из примечаний к описанию DM_SETDEFID, беспечное изменение ID контрола по-умолчанию может привести к загадочному поведению диалогового окна, когда оно имеет две кнопки по-умолчанию. К счастью, вам очень редко может понадобиться возможность менять контрол по-умолчанию.
Более серьёзная проблема заключается в использовании SetFocus для перемещения фокуса по диалогу. Если вы делаете это, то вы общаетесь напрямую с оконным менеджером (window manager), минуя менеджер диалоговых окон (dialog manager). Это означает, что вы можете создать "невозможные" ситуации, например: фокус может стоять на кнопке, но эта кнопка не является кнопкой по-умолчанию!
Для избежания этой проблемы, вам не следует использовать SetFocus для изменения фокуса в диалоге. Вместо этого - используйте сообщение WM_NEXTDLGCTL. Как следует из примечаний к описанию сообщения WM_NEXTDLGCTL, функция DefDlgProc обрабатывает сообщение WM_NEXTDLGCTL, обновляя внутренние состояния менеджера диалоговых окон, решая, какая кнопка должна стать умалчиваемой и т.п.
Теперь вы можете обновлять диалоговые окна, как настоящий профессионал, избегая таких неприятных ситуаций, как отсутствие кнопки по-умолчанию или, что хуже, двух кнопок по-умолчанию!
Прим. пер.: VCL Delphi не использует диалоговых окон в смысле системы.
Установка фокуса в диалоговом окне - это нечто большее, чем просто вызов SetFocus.
Диалоговое окно поддерживает концепцию "кнопки по-умолчанию" (default button). Кнопка по-умолчанию обычно рисуется в визульно отличном стиле (толстая рамка или другой цвет) и указывает на выполняемое диалогом действие при нажатии кнопки Enter. Заметьте, что это не то же самое, что контрол с фокусом.
Например, откройте диалог "Выполнить..." из меню Пуск. Заметьте, что кнопка OK является кнопкой по-умолчанию; её вид отличается от других кнопок. Но фокус находится в строке ввода. Ваш ввод с клавиатуры идёт в строку ввода, пока вы не нажмёте Enter. Нажатие Enter активирует кнопку по-умолчанию, которой является кнопка OK.
Попробуйте понажимать Tab в диалоге и посмотреть, что происходит с кнопкой по-умолчанию. Когда диалоговое окно перемещает фокус на кнопку, эта кнопка становится новой кнопкой по-умолчанию. Но когда диалоговое окно убирает фокус на что-то другое (не являющееся кнопкой), то кнопка OK снова становится кнопкой по-умолчанию.
Менеджер диалоговых окон (dialog manager) запоминает, какая кнопка была умалчиваемой при создании диалога и, когда фокус перемещается к чему-то, что не является кнопкой, то он восстанавливает эту кнопку кнопкой по-умолчанию.
Вы можете узнать, какая кнопка является кнопкой по-умолчанию, послав диалоговому окну сообщение DM_GETDEFID; аналогично, вы можете изменить её с помощью сообщения DM_SETDEFID.
(Заметьте, что возвращаемое сообщением DM_GETDEFID значение содержит упакованные ID контрола (в младшем слове) и флаги (в старшем слове). Это ещё одно место, где расширение ID контролов на 32 бита ничего вам не даст).
Как видно из примечаний к описанию DM_SETDEFID, беспечное изменение ID контрола по-умолчанию может привести к загадочному поведению диалогового окна, когда оно имеет две кнопки по-умолчанию. К счастью, вам очень редко может понадобиться возможность менять контрол по-умолчанию.
Более серьёзная проблема заключается в использовании SetFocus для перемещения фокуса по диалогу. Если вы делаете это, то вы общаетесь напрямую с оконным менеджером (window manager), минуя менеджер диалоговых окон (dialog manager). Это означает, что вы можете создать "невозможные" ситуации, например: фокус может стоять на кнопке, но эта кнопка не является кнопкой по-умолчанию!
Для избежания этой проблемы, вам не следует использовать SetFocus для изменения фокуса в диалоге. Вместо этого - используйте сообщение WM_NEXTDLGCTL. Как следует из примечаний к описанию сообщения WM_NEXTDLGCTL, функция DefDlgProc обрабатывает сообщение WM_NEXTDLGCTL, обновляя внутренние состояния менеджера диалоговых окон, решая, какая кнопка должна стать умалчиваемой и т.п.
Теперь вы можете обновлять диалоговые окна, как настоящий профессионал, избегая таких неприятных ситуаций, как отсутствие кнопки по-умолчанию или, что хуже, двух кнопок по-умолчанию!
Прим. пер.: VCL Delphi не использует диалоговых окон в смысле системы.
пятница, 5 июня 2009 г.
Для чего нужен стиль DS_CONTROL?
Это перевод What is the DS_CONTROL style for? Автор: Реймонд Чен.
Стиль диалоговых окон DS_CONTROL указывает, что создаваемое диалоговое окно будет использоваться как дочернее в другом диалоге, нежели будет диалоговым окном верхнего уровня.
Вложенные диалоговые окна едва ли самое редкое зрелище. Например, вы можете видеть их в диалоге "Свойства" (property sheets). Каждая страничка в окне свойств является отдельным диалогом; все они живут внутри внешнего диалога. Вложенные диалоги иногда используются в общих файловых диалогах (common file dialogs): вы можете видеть одного из них в действии, когда вы вызываете "Сохранить как" в Блокноте. Дополнительные опции сохранения внизу окна диалога и являются вложенным диалогом.
Когда вы устанавливаете стиль DS_CONTROL в шаблоне (template) диалога (или устанавливаете расширенный стиль WS_EX_CONTROLPARENT для обычного окна), то в игру вступает куча новых правил.
Во-первых, стили WS_CAPTION и WS_SYSMENU в вашем диалоге игнорируются. Потому что вы теперь являетесь дочерним окном, а не окном верхнего уровня (top-level window), так что у вас не может быть заголовка или системного меню (заголовок и меню получаются от внешнего окна).
Во-вторых, функции навигации по диалогу (такие как GetNextDlgTabItem) будут рекурсивно перебирать окна, помеченные WS_EX_CONTROLPARENT, когда они проверяют элементы управления в диалоге (в случае GetNextDlgTabItem - потому что она ищет контрол для передачи ему фокуса). Без этого расширенного стиля, поиск контролов рассматривает вложенный диалог как один большой контрол, а не как контейнер для других контролов.
Когда вы создаёте диалог со стилем DS_CONTROL, вы непременно используете одну из функций типа CreateDialogParam, нежели одну из диалоговых функций типа DialogBoxParam, потому что модальный цикл контролируется внешним диалогом, а не внутренним.
Это рекурсивное поведение важно для избежания бесконечного цикла в менеджере диалогов (dialog manager). Когда вы просите функцию GetNextDlgTabItem найти предыдущий элемент, то она берёт исходный контрол, затем проходит по контролам в диалоге, пока не вернётся в исходную же точку, в этот момент она возвращает последний из увиденных перед исходным. Если вы забудете пометить свой диалог как DS_CONTROL, и фокус будет в под-диалоге, то перебор элементов не войдёт во вложенный диалог и не сможет вернуться к исходному элементу. Диалоговый менеджер будет бесконечно перебирать контролы во внешнем диалоге, ища исходный контрол и не находя его, т.к. он лежит во вложенном диалоге без стиля DS_CONTROL.
Эта проблема существует и без стиля DS_CONTROL. Если вы запустите поиск от запрещённого или невидимого контрола, то перебор контролов никогда не вернётся к исходному контролу, т.к. невидимые и отключенный контролы пропускаются при "tab-бинге" по диалогу.
Стиль диалоговых окон DS_CONTROL указывает, что создаваемое диалоговое окно будет использоваться как дочернее в другом диалоге, нежели будет диалоговым окном верхнего уровня.
Вложенные диалоговые окна едва ли самое редкое зрелище. Например, вы можете видеть их в диалоге "Свойства" (property sheets). Каждая страничка в окне свойств является отдельным диалогом; все они живут внутри внешнего диалога. Вложенные диалоги иногда используются в общих файловых диалогах (common file dialogs): вы можете видеть одного из них в действии, когда вы вызываете "Сохранить как" в Блокноте. Дополнительные опции сохранения внизу окна диалога и являются вложенным диалогом.
Когда вы устанавливаете стиль DS_CONTROL в шаблоне (template) диалога (или устанавливаете расширенный стиль WS_EX_CONTROLPARENT для обычного окна), то в игру вступает куча новых правил.
Во-первых, стили WS_CAPTION и WS_SYSMENU в вашем диалоге игнорируются. Потому что вы теперь являетесь дочерним окном, а не окном верхнего уровня (top-level window), так что у вас не может быть заголовка или системного меню (заголовок и меню получаются от внешнего окна).
Во-вторых, функции навигации по диалогу (такие как GetNextDlgTabItem) будут рекурсивно перебирать окна, помеченные WS_EX_CONTROLPARENT, когда они проверяют элементы управления в диалоге (в случае GetNextDlgTabItem - потому что она ищет контрол для передачи ему фокуса). Без этого расширенного стиля, поиск контролов рассматривает вложенный диалог как один большой контрол, а не как контейнер для других контролов.
Когда вы создаёте диалог со стилем DS_CONTROL, вы непременно используете одну из функций типа CreateDialogParam, нежели одну из диалоговых функций типа DialogBoxParam, потому что модальный цикл контролируется внешним диалогом, а не внутренним.
Это рекурсивное поведение важно для избежания бесконечного цикла в менеджере диалогов (dialog manager). Когда вы просите функцию GetNextDlgTabItem найти предыдущий элемент, то она берёт исходный контрол, затем проходит по контролам в диалоге, пока не вернётся в исходную же точку, в этот момент она возвращает последний из увиденных перед исходным. Если вы забудете пометить свой диалог как DS_CONTROL, и фокус будет в под-диалоге, то перебор элементов не войдёт во вложенный диалог и не сможет вернуться к исходному элементу. Диалоговый менеджер будет бесконечно перебирать контролы во внешнем диалоге, ища исходный контрол и не находя его, т.к. он лежит во вложенном диалоге без стиля DS_CONTROL.
Эта проблема существует и без стиля DS_CONTROL. Если вы запустите поиск от запрещённого или невидимого контрола, то перебор контролов никогда не вернётся к исходному контролу, т.к. невидимые и отключенный контролы пропускаются при "tab-бинге" по диалогу.
четверг, 4 июня 2009 г.
Когда нужно использовать вдавленную клиентскую область?
Это перевод When should you use a sunken client area? Автор: Реймонд Чен.
Расширенный стиль окна WS_EX_CLIENTEDGE позволяет вам создать окно, чья клиентская область будет "вдавлена" (sunken). Когда вам нужно использовать этот стиль?
В секции Border Style сказано, что вдавленная рамка должна "обозначать рабочую область внутри окна" (раньше эта информация была частью мануала Guidelines for User Interface Developers and Designers).
В частности, это означает, что окно является "контейнером". Так, например, панель содержимого Проводника имеет вдавленную рамку, потому что папка "содержит" свои элементы. Пользователи ожидают, что они могут манипулировать элементами внутри контейнера. И наоборот: диалоговое окно - это не контейнер, поэтому оно не имеет вдавленной рамки.
По крайней мере такие правила были в 1995-м. Возможно, с тех пор правила изменились (в самом деле, я не удивлюсь, если оно так и есть).
Расширенный стиль окна WS_EX_CLIENTEDGE позволяет вам создать окно, чья клиентская область будет "вдавлена" (sunken). Когда вам нужно использовать этот стиль?
В секции Border Style сказано, что вдавленная рамка должна "обозначать рабочую область внутри окна" (раньше эта информация была частью мануала Guidelines for User Interface Developers and Designers).
В частности, это означает, что окно является "контейнером". Так, например, панель содержимого Проводника имеет вдавленную рамку, потому что папка "содержит" свои элементы. Пользователи ожидают, что они могут манипулировать элементами внутри контейнера. И наоборот: диалоговое окно - это не контейнер, поэтому оно не имеет вдавленной рамки.
По крайней мере такие правила были в 1995-м. Возможно, с тех пор правила изменились (в самом деле, я не удивлюсь, если оно так и есть).
среда, 3 июня 2009 г.
Эволюция косметики в Windows UI
Это перевод The evolution of mascara in Windows UI. Автор: Реймонд Чен. Входит в книгу The Old New Thing.
"Вид" интерфейса пользователя Windows прошёл через цикл мод.
"Вид" интерфейса пользователя Windows прошёл через цикл мод.
вторник, 2 июня 2009 г.
Запрашиваем информацию из окна Проводника
Это перевод Querying information from an Explorer window. Автор: Реймонд Чен.
Иногда разработчики программ начинают изобретать велосипед. Но часто достаточно просто сложить вместе несколько кусков головоломки. Сегодняшний пост - это один из последних случаев.
Иногда разработчики программ начинают изобретать велосипед. Но часто достаточно просто сложить вместе несколько кусков головоломки. Сегодняшний пост - это один из последних случаев.
понедельник, 1 июня 2009 г.
Как показать строку без этих уродливых квадратиков
Это перевод How to display a string without those ugly boxes. Автор: Реймонд Чен.
Вы все видели эти квадратики. Когда вы пытаетесь отобразить строку и используемый вами шрифт не поддерживает всех символов в ней, вы увидите прямоугольники вместо символов, которые недоступны в выбранном шрифте (прим. пер.: с некоторыми шрифтами вы видите символы "Ђ").
Вы все видели эти квадратики. Когда вы пытаетесь отобразить строку и используемый вами шрифт не поддерживает всех символов в ней, вы увидите прямоугольники вместо символов, которые недоступны в выбранном шрифте (прим. пер.: с некоторыми шрифтами вы видите символы "Ђ").
Подписаться на:
Сообщения (Atom)