Исторически классическая Mac OS использовала форму управления памятью , которая вышла из моды в современных системах. Критика этого подхода была одной из ключевых областей, затронутых изменением в Mac OS X.
Первоначальная проблема для инженеров Macintosh заключалась в том, как оптимально использовать 128 КБ ОЗУ , которыми была оснащена машина, на компьютерном оборудовании на базе Motorola 68000 , которое не поддерживало виртуальную память . [1] Поскольку в то время машина могла запускать только одну прикладную программу одновременно, а фиксированного вторичного хранилища не было , инженеры реализовали простую схему, которая хорошо работала с этими конкретными ограничениями. Этот выбор дизайна не масштабировался хорошо с разработкой машины, создавая различные трудности как для программистов, так и для пользователей.
Похоже, что основной заботой первоначальных инженеров была фрагментация , то есть повторное выделение и освобождение памяти через указатели , приводящее к множеству небольших изолированных областей памяти, которые не могут быть использованы, поскольку они слишком малы, даже если общая свободная память может быть достаточной для удовлетворения конкретного запроса на память. Чтобы решить эту проблему, инженеры Apple использовали концепцию перемещаемого дескриптора , ссылки на память, которая позволяла перемещать фактические данные, на которые ссылаются, не делая дескриптор недействительным. Схема Apple была простой — дескриптор был просто указателем на (неперемещаемую) таблицу дополнительных указателей, которые, в свою очередь, указывали на данные. [2] Если запрос памяти требовал уплотнения памяти, это делалось, и таблица, называемая главным блоком указателей, обновлялась. Сама машина реализовала две области в памяти, доступные для этой схемы — системную кучу (используемую для ОС) и кучу приложения. [3] Пока одновременно запускалось только одно приложение, система работала хорошо. Поскольку вся куча приложения растворялась при завершении работы приложения, фрагментация была минимизирована.
Система управления памятью имела слабые стороны; системная куча не была защищена от ошибочных приложений, как это было бы возможно, если бы архитектура системы поддерживала защиту памяти , и это часто было причиной проблем и сбоев системы. [4] Кроме того, подход на основе дескрипторов также открыл источник ошибок программирования, когда указатели на данные внутри таких перемещаемых блоков не могли гарантированно оставаться действительными при вызовах, которые могли бы привести к перемещению памяти. Это была реальная проблема почти для каждого существующего системного API . Из-за прозрачности структур данных, принадлежащих системе, в то время API мало что могли сделать для решения этой проблемы. Таким образом, бремя было на программисте, чтобы не создавать такие указатели или, по крайней мере, управлять ими очень осторожно, разыменовывая все дескрипторы после каждого такого вызова API. Поскольку многие программисты, как правило, не были знакомы с этим подходом, ранние программы Mac часто страдали от ошибок, возникающих из-за этого. [5]
Palm OS и 16-разрядная Windows используют похожую схему управления памятью, но версии Palm и Windows усложняют программисту ошибку. Например, в Mac OS, чтобы преобразовать дескриптор в указатель, программа просто напрямую разыменовывает дескриптор, но если дескриптор не заблокирован, указатель может быстро стать недействительным. Вызовы блокировки и разблокировки дескрипторов не сбалансированы; десять вызовов HLock
отменяются одним вызовом HUnlock
. [6] В Palm OS и Windows дескрипторы являются непрозрачным типом и должны быть разыменованы с помощью MemHandleLock
в Palm OS или Global/LocalLock
в Windows. Когда приложение Palm или Windows завершает работу с дескриптором, оно вызывает MemHandleUnlock
или Global/LocalUnlock
. Palm OS и Windows ведут счетчик блокировок для блоков; после трех вызовов MemHandleLock
блок станет разблокированным только после трех вызовов MemHandleUnlock
.
Решить проблему вложенных блокировок и разблокировок можно просто (хотя и утомительно) с помощью различных методов, но они ухудшают читаемость соответствующего блока кода и требуют осведомленности и дисциплины со стороны кодера.
Осведомленность и дисциплина также необходимы для предотвращения «утечек» памяти (неудачного освобождения памяти в пределах выделенной области) и для избежания ссылок на устаревшие дескрипторы после освобождения (что обычно приводит к жесткому сбою — раздражающему в однозадачной системе и потенциально катастрофическому, если запущены другие программы).
Ситуация ухудшилась с появлением Switcher , который был способом для Mac с 512 КБ или более памяти запускать несколько приложений одновременно. [7] Это был необходимый шаг вперед для пользователей, которые считали подход «одно приложение за раз» очень ограничивающим. Поскольку Apple теперь была привержена своей модели управления памятью, а также совместимости с существующими приложениями, она была вынуждена принять схему, в которой каждому приложению выделялась собственная куча из доступной оперативной памяти. Объем фактической оперативной памяти, выделенной для каждой кучи, устанавливался значением, закодированным в метаданных каждого приложения, установленным программистом. Иногда этого значения было недостаточно для определенных видов работы, поэтому настройка значения должна была быть предоставлена пользователю, чтобы он мог настроить размер кучи в соответствии со своими собственными требованиями. Хотя это было популярно среди « опытных пользователей », это раскрытие технических деталей реализации противоречило философии пользователя Mac. Помимо того, что он открывал пользователям эзотерические технические подробности, он был неэффективен, поскольку приложение было вынуждено захватить всю выделенную ему оперативную память, даже если впоследствии оно оставляло большую ее часть неиспользованной. Другое приложение могло испытывать нехватку памяти, но не могло использовать свободную память, «принадлежащую» другому приложению. [3]
Хотя приложение не может с пользой использовать кучу другого приложения, оно, безусловно, может ее разрушить, как правило, непреднамеренно записывая по бессмысленному адресу. Приложение, случайно обрабатывающее фрагмент текста или изображения или неназначенное местоположение как указатель, может легко перезаписать код или данные других приложений или даже ОС, оставляя "скрытых" даже после выхода из программы. Такие проблемы может быть чрезвычайно сложно анализировать и исправлять.
Switcher превратился в MultiFinder в System 4.2, который стал Process Manager в System 7 , и к тому времени эта схема уже давно укоренилась. Apple предприняла несколько попыток обойти очевидные ограничения — временная память была одной из них, где приложение могло «заимствовать» свободную оперативную память, которая лежала за пределами его кучи на короткие периоды, но это было непопулярно среди программистов, поэтому в значительной степени не решило проблемы. Дополнение Apple System 7 Tune-up добавило «минимальный» размер памяти и «предпочтительный» размер — если предпочтительный объем памяти был недоступен, программа могла запускаться в минимальном пространстве, возможно, с ограниченной функциональностью. Это было включено в стандартную ОС, начиная с System 7.1, но все еще не решило основную проблему. [8]
Схемы виртуальной памяти , которые делали больше памяти доступной путем подкачки неиспользуемых участков памяти на диск, были реализованы сторонними утилитами, такими как Connectix Virtual, а затем и Apple в System 7. Это увеличило емкость памяти Macintosh за счет производительности, но не добавило защищенной памяти и не предотвратило сжатие кучи менеджером памяти, которое сделало бы некоторые указатели недействительными.
Первоначально Macintosh имел 128 КБ ОЗУ с реальным пределом в 4 МБ, несмотря на то, что он был распаян. Этот предел был впервые достигнут с Macintosh Plus и его обновляемой пользователем памятью. Эти первые несколько компьютеров Macintosh использовали 68000 CPU, 32-битный процессор, который имел всего 24 физические адресные линии. 24 линии позволяют процессору адресовать до 16 МБ памяти (224 байта ), что считалось достаточным объемом в то время. Предел ОЗУ в конструкции Macintosh составлял 4 МБ ОЗУ и 4 МБ ПЗУ , а оставшиеся 8 МБ адресов были разделены между чипами SCC, IWM и VIA из-за структуры карты памяти. [9] [10] Это было исправлено путем изменения карты памяти в Macintosh II , что позволило использовать до 8 МБ ОЗУ, путем сокращения адресов ПЗУ и ввода-вывода до 1 МБ каждый и выделения оставшихся 6 МБ адресов слотам NuBus . Продукты Connectix MAXIMA, RAM Doubler и Virtual позволяли получать доступ и перераспределять 6 МБ адресов, выделенных картам NuBus, в общей сложности до 14 МБ, минус 1 МБ на каждый занятый слот. [11] [12]
Поскольку память была дефицитным ресурсом, авторы Classic Mac OS решили воспользоваться неиспользуемым байтом в каждом адресе. Первоначальный диспетчер памяти (вплоть до появления System 7) помещал флаги в верхние 8 бит каждого 32-битного указателя и дескриптора . Каждый адрес содержал флаги, такие как «заблокирован», «очищаемый» или «ресурс», которые хранились в главной таблице указателей. При использовании в качестве фактического адреса эти флаги маскировались и игнорировались ЦП. [4]
Хотя это было хорошее использование очень ограниченного пространства ОЗУ, эта конструкция вызвала проблемы, когда Apple представила Macintosh II, в котором использовался 32-битный процессор Motorola 68020. У 68020 было 32 физических адресных линии, которые могли адресовать до 4 ГБ памяти. Флаги, которые диспетчер памяти хранил в старшем байте каждого указателя и дескриптора, теперь были значимыми и могли приводить к ошибкам адресации.
На Macintosh IIci и более поздних машинах HLock()
и других API были переписаны для реализации блокировки дескрипторов способом, отличным от пометки старших битов дескрипторов. Но многие программисты приложений Macintosh и большая часть самого кода системного программного обеспечения Macintosh обращались к флагам напрямую, а не с помощью API, таких как HLock()
, которые были предоставлены для управления ими. Делая это, они сделали свои приложения несовместимыми с настоящей 32-битной адресацией, и это стало известно как не являющееся «чистым 32-битным».
Чтобы остановить постоянные сбои системы, вызванные этой проблемой, System 6 и более ранние версии, работающие на 68020 или 68030, принудительно переводили машину в 24-битный режим и распознавали и обращались только к первым 8 мегабайтам ОЗУ, что является очевидным недостатком в машинах, чье оборудование было рассчитано на прием до 128 МБ ОЗУ, и чья литература по продукту рекламировала эту возможность. С System 7 системное программное обеспечение Mac наконец-то стало 32-битным, но проблема грязных ПЗУ все еще оставалась. Проблема заключалась в том, что решение об использовании 24-битной или 32-битной адресации приходилось принимать на самом раннем этапе процесса загрузки, когда процедуры ПЗУ инициализировали диспетчер памяти для настройки базовой среды Mac, в которой загружаются и выполняются ПЗУ NuBus и драйверы дисков. Старые ПЗУ не поддерживали 32-битный диспетчер памяти, поэтому загрузка в 32-битном режиме была невозможна. Удивительно, но первое решение этой уязвимости было опубликовано компанией-разработчиком программного обеспечения Connectix , чье расширение System 6, OPTIMA, повторно инициализировало диспетчер памяти и повторило ранние части процесса загрузки Mac, позволяя системе загружаться в 32-битном режиме и позволяя использовать всю оперативную память машины. Позже OPTIMA превратилась в более знакомый продукт 1991 года MODE32 для System 7. Apple лицензировала программное обеспечение у Connectix позднее в 1991 году и распространяла его бесплатно. Macintosh IIci и более поздние компьютеры Macintosh на базе Motorola имели 32-битные чистые ПЗУ.
Прошло довольно много времени, прежде чем приложения были обновлены для удаления всех 24-битных зависимостей, и System 7 предоставила способ переключения обратно в 24-битный режим, если были обнаружены несовместимости приложений. [3] К моменту перехода на PowerPC и System 7.1.2 32-битная чистота стала обязательной для создания собственных приложений, и даже более поздние компьютеры Mac на базе Motorola 68040 не могли поддерживать 24-битный режим. [6] [13]
Рост объектно-ориентированных языков для программирования Mac — сначала Object Pascal , затем C++ — также вызвал проблемы для принятой модели памяти. Сначала казалось естественным, что объекты будут реализованы с помощью дескрипторов, чтобы получить преимущество перемещаемости. Эти языки, как они изначально были разработаны, использовали указатели для объектов, что приводило к проблемам фрагментации. Решение, реализованное компиляторами THINK ( позже Symantec ) , состояло в том, чтобы использовать дескрипторы внутри для объектов, но использовать синтаксис указателей для доступа к ним. Сначала это казалось хорошей идеей, но вскоре возникли серьезные проблемы, поскольку программисты не могли определить, имеют ли они дело с перемещаемым или фиксированным блоком, и поэтому не имели возможности узнать, брать ли на себя задачу блокировки объектов или нет. Излишне говорить, что это привело к огромному количеству ошибок и проблем с этими ранними реализациями объектов. Более поздние компиляторы не пытались этого делать, а использовали настоящие указатели, часто реализуя собственные схемы распределения памяти для обхода модели памяти Mac OS.
В то время как модель памяти Mac OS со всеми присущими ей проблемами оставалась такой вплоть до Mac OS 9 , из-за серьезных ограничений совместимости приложений растущая доступность дешевой оперативной памяти означала, что в целом большинство пользователей могли обновиться, чтобы выйти из затруднительного положения. Память использовалась неэффективно, но ее было достаточно, чтобы проблема никогда не становилась критической. Это иронично, учитывая, что целью первоначального дизайна было максимально использовать очень ограниченные объемы памяти. Mac OS X наконец-то отказалась от всей схемы, реализовав современную схему страничной виртуальной памяти . Подмножество API старой модели памяти все еще существует для совместимости как часть Carbon , но отображается на современный менеджер памяти (потокобезопасная malloc
реализация) под ним. [6] Apple рекомендует, чтобы код Mac OS X использовал malloc
и free
«почти исключительно». [14]