Автор:
mOleg Дата публикации: 14.11.2009
публикуется впервые
Работа с памятью Форт-систем Введение Традиционно в Форт-системах для работы с памятью системы используется механизм HERE ALLOT в различных вариациях. В простейшем случае в системе имеется всего одно адресное пространство, а память распределяется с помощью очень простого механизма. Создается указатель на конец используемой памяти, который обычно называется DP (data pointer):
VARIABLE DP ( --> addr ) \ указатель может быть обычной глобальной переменной
с самим указателем не работают напрямую, а используют следующие слова:
\ вернуть адрес первого неиспользованного байта(ячейки) в памяти системы
: HERE ( --> addr ) DP @ ;
\ зарезервировать (освободить) указанное количество адресуемых единиц (обычно байт) в памяти системы
: ALLOT ( # --> ) DP +! ;
\ резервировать место на HERE , сохранить значение n в выделенной области
: , ( n --> ) HERE CELL ALLOT ! ;
Все остальные слова для работы с памятью системы строятся по похожему сценарию. То есть, в случае добавления еще одного адресного пространства, к примеру, пользовательской области, будут использоваться: USER-DP USER-HERE USER-ALLOT и т.д. В случае необходимости сохранять не только ячейки, а к примеру, символьные значения, добавляется: C, ; для строк, добавляется S, и т.д.
Выравнивание данных в памяти системы производится с помощью слова ALIGNED и определенного через него:
\ выровнять указатель DP на значение,
: ALIGN ( --> ) HERE DUP ALIGNED SWAP – ALLOT ;
причем, величина, на которую производится выравнивание, в системе обычно задается либо константой, либо VALUE переменной.
Указатель DP (или аналог) не обязательно хранит правильный адрес, он может хранить, к примеру, смещение от чего-либо. Это означает, что обращаться к DP напрямую нельзя: резервировать место можно только с помощью ALLOT , а получать действительный адрес с помощью HERE. При этом, наиболее правильной стратегией при работе с памятью является предварительное выделение места на HERE с последующим сохранением данных, то есть последовательность:
... HERE !
…
CELL ALLOT
…
не правильна по нескольким причинам:
1. в случае многопоточной системы несколько потоков могут попытаться сохранить данные в одно и то же место (впрочем, многопоточная компиляция – это отдельный разговор)
2. возможна ситуация, когда за HERE память вообще не адресуется, к примеру, если эта память выделяется в ХИПе.
3. память за HERE может использоваться для временного хранения данных (у некоторых фортеров есть ужасная привычка использовать указатель HERE для временного хранения данных, к примеру, слово WORD во многих системах копирует выкусываемое из входящего потока слово (лексему) в область за HERE (иногда с небольшим отступом)).
Предлагаемые изменения и дополнения В добавление к существующим словам очень напрашивается добавить несколько дополнительных определений:
\ зарезервировать место в памяти, длиной в # минимально адресуемых единиц,
\ вернуть адрес начала выделенной области
: PLACE ( # --> addr ) HERE SWAP ALLOT ;
\ выровнять число base на указанное значение n
\ граница выравнивания произвольная.
\ выравнивание производится в большую сторону
: ROUND ( addr base --> addr ) TUCK 1 - + OVER / * ;
После чего, переопределить ALIGNED следующим образом:
\ округлить адрес к ближайшему большему значению, кратному ALIGN#
: ALIGNED ( addr --> a-addr ) ALIGN# ROUND ;
Впрочем, с выравниванием данных в памяти связано достаточно много моментов:
- для каждого отдельного адресного пространства величина выравнивания может сильно отличаться;
- для каждого вида данных выравнивание может требоваться на разные величины;
- в ряде случаев необходимо выравнивание с заполнением, к примеру, NOP-ами в случае выравнивания кода;
- в разных потоках для одного адресного пространства может требоваться разное выравнивание.
В связи с тем, что указатель данных DP может иметь различную природу (быть простой переменной, VALUE переменной, вектором, определением), было бы замечательно, чтобы слово HERE (и подобные ему слова) всегда возвращало действительные адреса!
К сожалению, в данный момент это не всегда так (в том же СПФ USER-HERE возвращает не действительный адрес, а смещение относительно начала пользовательской области).
Принадлежность кода и данных Код и данные в Форт системе не существуют сами по себе. Каждый кусок кода и данных (обычно) связан с конкретным именем, и может быть вызван по имени. Исключением являются :NONAME определения, но не во всех Форт-системах они существуют, и далеко не всегда совсем не имеют поля имени. Вне зависимости от того, в каком адресном пространстве находится код, ссылка на него может быть получена только через поиск ассоциируемого с ним имени в одном из словарей системы. Создание нового определение начинается с создания заголовка с помощью слова HEADER (или подобного ему). HEADER создает заголовок слова и включает его в текущий словарь, сохраняет в поле ссылки адрес на текущее HERE , после чего можно создавать поле кода, если оно необходимо.
Многопоточная компиляция Достаточно легко заметить, что работа с памятью обычно разбита на некие «сеансы» , которые начинаются созданием очередного определения и заканчиваются к моменту создания следующего за ним. А это значит, что в случае многопоточной компиляции можно выбрать две возможные стратегии:
A) собирать определение в отдельном участке памяти, уникальном для данного потока, и после завершения сборки переносить код в общее пространство;
Б) блокировать доступ к общему для потоков адресному пространству кода и данных на время сборки очередного определения (так сделано в
форке).
Оба варианта предполагают наличие как минимум мьютекса, для синхронизации обращений к памяти кода и данных.
Преимуществом варианта А является возможность одновременной компиляции в нескольких потоках, с редкими блокировками доступа к общему пространству памяти на время переноса кода, а так же возможность «легкого отката» в случае возникновения ошибки, так как собираемое слово еще не включено в общий словарь. Кроме того, в момент переноса кода определения на место напрашивается преобразование (оптимизация) кода. Однако реализация варианта А достаточно сложна.
Преимущество варианта Б в том, что для сборки нового определения не нужно выделять место где-то еще (определение собирается на месте), а так же нет нужды в переносе кода (правке адресных ссылок). Недостатком является невозможность одновременной компиляции: то есть пока один поток работает с общим хранилищем кода и данных другой должен ожидать освобождения ресурса. Зато вариант Б проще в реализации.
В любом случае необходимо использовать слово
;CREATE ( --> ) которое будет в зависимости от выбранного варианта либо переносить уже собранный код в общее хранилище, добавляя имя в текущий словарь (для варианта А), либо просто освобождать мьютекс, позволяя другому потоку начать сборку нового определения (если этого не сделать, то компилировать всегда будет только один поток…)
Соответственно, желательно определить ‘;’ следующим образом:
\ завершить текущее определение
: ; ( --> )
тут обычные действия
;CREATE
;
А в случаях, когда нельзя по умолчанию выполнять ;CREATE проставлять его в тексте программы принудительно:
CREATE ZZZ 128 ALLOT ;CREATE
Так же ссылка на незавершенное слово будет невозможна до встречи ;CREATE.
Компиляция в нелинейно распределяемую память Используя стандартный механизм HERE ALLOT можно распределять не только в линейных непрерывных областях памяти (например в ХИП). Для этого достаточно, чтобы слово HEADER при работе с такой памятью выделяло в ней достаточного размера буфер, а слово ;CREATE урезало этот буфер до необходимого размера, либо, необходимо в процессе работы научить слово ALLOT увеличивать размер используемого блока. Причем, наиболее логично делать и то и другое:
1. HEADER создает блок фиксированного размера и направляет на него HERE
2. производится сохранение данных в пространство блока памяти
3. ALLOT при превышении размера блока самостоятельно увеличивает его
4. по завершении вызывается ;CREATE , после которого фиксируется действительный адрес блока, после чего в памяти блок перемещать уже нельзя.
Литература:
1. mOleg Что такое словарь
2. Джеф Фокс Суть Форта
3. mOleg SPF Fork