концепция-максимум:
стандартный 128К (48K?)
чтобы на систему ложился си, то есть рекурсия (и куча, аллоки макс 16К! или любые через массивы на сегментах)
бесконечный стек (делается ручками на hl', там номера точек, а не адреса), настоящий стек только для вызова ОС
бесконечный объём кода для каждого процесса (своп на лету при каждом джампе)
бесконечное количество процессов
свопинг всего юзерпрожного
никакой самомодификации в юзерпрогах

Своп внутри нижней памяти даже при абсолютной релоцируемости всех пользовательских
программ имеет вопросы:
1. Когда релоцировать (при десвопе?)
2. Что свопить
3. Куда размещать вновь загружаемые программы
4. Как свопить, не нарушая работу шедулера (желательно своп в контексте процесса (для этого нужно иметь стек процесса в памяти), вне критической секции)
5. Как не свопить, например, драйвер памяти (несвопляемые растут сверху, свопляемые снизу? а как нарастить несвопляемые, если место уже занято свопляемым нерелоцируемым?) (драйвер памяти в ядре?)

Чтобы эффективнее и быстрее (т.е. не перемещать модули целиком) распределять нижнюю память в СДНП, нужно разбивать программы и статические данные на сегменты по 256 байт.
Это можно поручить компилятору (DS n переносить на сегмент вперёд при необходимости, а в коде
при достижении адреса >=#xxfa ставить jp #(xx+1)00. если с xxfa не перепрыгивать, то может
встретиться 4-байтная команда, и jp не поместится.
в программах нельзя использовать jr вперёд.
jr назад можно автоматически заменять на jp при необходимости.
djnz только если не жалко флага Z, т.к. замена - dec b:jp nz).
Составление списка адресов релокации тоже можно поручить компилятору.
при работе такого модуля всё равно требуется, чтобы все его данные были в нижней памяти (в том числе заказанные).
большие буфера в нижней памяти (для zxrar) нужно заказывать кусочками по 256 байт (или статически
скомпилировать много таких кусочков),
и потребуется таблица старших байтов адресов полученных кусочков, которую система тоже должна уметь
релоцировать на лету.

Итого надо релоцировать (только старший байт в каждом случае):
внутренние переходы
обращения к статическим переменным
переменные типа "указатель на внутренние данные"
таблица старших байтов адресов кусочков
регистры типа "указатель на внутренние данные" (тип регистра можно менять системным макросом)

всё перечисленное, кроме регистров, релоцируется одним и тем же механизмом - по списку адресов и списку "старый адрес сегмента - новый адрес сегмента"
можно предусмотреть ячейки для хранения регистров, когда процесс неактивен, и релоцировать их так же
можно использовать тип "указатель" как тип "указатель или NULL", т.к. 0 не будет релоцироваться, ибо его нет в списке
как ускорить релокацию?
exd в случае, если тип de!=тип hl, нужно делать через системный макрос
ld h,(hl) в случае, если ст.тип hl!=нов.тип hl, нужно делать через системный макрос
ld l,(hl) в случае, если ст.тип hl!=нов.тип hl, нужно делать через системный макрос (оно вообще надо?)
ld hl,(hl) - тоже системный макрос, чтобы не париться с регистром A
нельзя класть в стек неуказатели.
лучше даже вообще не использовать push:pop, как и jr, чтобы не было надобности проверять.
при этом невозможна рекурсия.

системные вызовы (например, setpage) не могут сохранять регистры каким бы то ни было образом
(нельзя в локальные переменные: 1. неизвестны типы этих регистров (можно узнать, но долго);
2. эта же п/п может параллельно быть вызвана другим процессом и затрёт эти переменные)

или каждая запись в стек - это 4 байта, из которых 2 указатель (релоцируется), а 2 число (нет)
вызов: push af:call [z,]...:pop af
запоминание числа bc: push bc,bc:...:pop bc(релоцируется),bc
запоминание указателя hl: push af,hl:...:pop hl(релоцируется),af


если регистры не типизируются, а указатели лежат только в IX, IY, то как прочитать
указатель из переменной и записать его обратно?
читать:
ld (pointer1),ix
pointer1=$+2 ;H релоцируется, если надо
ld iy,(0)
;40t

писать:
ld (pointer1),ix
pointer1=$+2 ;H релоцируется, если надо
ld (0),iy
;40t

Если надо прочитать из (ix+...), то придётся использовать математику :(
push ix ;указатель, так что стек срелоцируется правильно
ld de,...
add ix,de
ld (pointer1),ix
pointer1=$+2 ;H релоцируется, если надо
ld iy,(0)
pop ix
;94t

писать:
push ix ;указатель, так что стек срелоцируется правильно
ld de,...
add ix,de
ld (pointer1),ix
pointer1=$+2 ;H релоцируется, если надо
ld (0),iy
pop ix
;94t

А так не получится:
ld a,ly
ld (ix+...),a
???ld (pointer1),ix ;?????????
ld (ix+(...+1)),0
pointer1=$-1-1 ;релоцируется, если надо (только H)

Если только IX,IY, то нельзя использовать LDIR и быстрые файл-буферы (256 байт (DE),(HL)).
Что делать с файл-буфером, открытым на запись, когда процесс прервали?
Профлашить автоматически? Для этого надо ещё один тип регистра - указатель на
файл-буфер на запись, или система сама определит по адресу?


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





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

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

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

переход на модуль:
==================
для каждого сегмента модуля
{
проверяем, есть ли он в НДП
если есть, то переносим слот, где он находится, в хвост очереди доступных слотов
}
для каждого сегмента модуля
{
проверяем, есть ли он в НДП
если нет, то
{
берём сегмент из начала очереди доступных слотов
переносим его в хвост очереди доступных слотов (если очередь кольцевая, то автоматом)
десвопим текущий сегмент модуля в этот слот
добавляем в список изменённых сегментов
}
смотрим, изменился ли адрес сегмента
если да, то релоцируем код, ссылающийся на этот сегмент
}
по списку изменённых сегментов составляем таблицу <новый ст. байт адреса сегмента>(<старый ст. байт адреса сегмента>)
релоцируем данные и регистры, пользуясь для каждого релокабельного адреса этой таблицей
очищаем список изменённых сегментов
вычисляем адрес перехода на модуль
переходим туда

Процессу нужно:
1. Входы ОС (в том числе входы менеджера памяти)
2. Нижняя память под код (единица выделения - 256 байт? или 16?)
3. Нижняя память под данные (можно в одном сегменте с кодом, отделение сегмента кода для шарения между копиями программы не окупится при ПСО, но может окупиться при СНДП)
4. Нижняя память под стек (достаточно 256 байт)
5. Страничная память (можно не разрешать класть туда код и стек, хотя для пт плеера неудобно занимать место внизу)
6. Динамическая память? (в НДП можно выделять так же, как под код/данные/стек, по 256 байт)
7. Экран? (достаточно экрана в страницах)
8. Память под сообщения (можно в НДП; выделение страницы может вылиться в большое количество занятых пустых страниц чисто для сообщений) (единица выделения - 256 байт под все сообщения задачи? или по 16 для каждого сообщения?)
9. Память под описание процесса (в НДП?)

Защищённая от свопа НДП (гуй, пт плеер...) растёт с начала нижней памяти, а незащищённая - с конца.
При исчерпании нижней памяти незащищённые начинают свопиться и грузиться/заказывать память опять с конца нижней памяти.
После свопа процессы должны всегда попадать на те же места!
Если нижняя память закончится при заказе защищённой НДП, то проблема:
свопить незащищённую нельзя - её нельзя будет свопнуть обратно.
Поэтому придётся в этом случае убрать защиту.
Если кусок памяти лежит в свопе, то на его месте стопудово стоит другой незащищённый. Если этот другой освобождается, то он не освобождает сам кусок памяти! Иначе этот кусок может захватить защищённая НДП, и незащищённый не сможет вернуться.
Если на кусок памяти больше не претендует ни один незащищённый, он освобождается и доступен для заказа защищёнными. То есть для каждого блока НДП надо знать число использующих его процессов.

Если размер блока 16 байт, то требуется 49152/16=3072 байта для таблицы занятости (при макс. 256 процессах в каждом куске). Положить в страничку?

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

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


стек на hl' (можно иметь два стека - один релоцируемый (SP), другой нерелоцируемый (hl') или (IY)):
можно вместо номера точки адрес её адреса в таблице #c000...#ffff в странице процесса
тогда максимум 8192 точек на процесс
можно адрес + виртуальный номер страницы

call:
exx
ld (hl),nnH
dec l
ld (hl),nnL
call z,os_growstack
dec l ;указывает не на голову, а на байт ниже (может быть пустой сегмент)
ld (hl),d?? ;pgH
dec l
ld (hl),e?? ;pgL
call z,os_growstack
dec l ;указывает не на голову, а на байт ниже (может быть пустой сегмент)
exx

ret:
jp os_ret
os_ret:
exx
inc l
call z,os_reducestack
ld e,(hl)
inc l
ld d,(hl)
call os_setpage
inc l
call z,os_reducestack
ld e,(hl)
inc l
ld d,(hl)
exd
ld a,(hl)
inc l
ld h,(hl)
ld l,a ;или лучше там jp?
exd
push de
exx
ret

тут не учитывается, что процедура может быть засвоплена!

своп на лету:
(если окажется медленнее чтения через резидент, то единственная выгода - на обработке файлов)
(компилятор должен стараться пересортировать процедуры так, чтобы каждый цикл умещался в сегмент или даже каждая процедура, если это возможно)
надо определить, замаплен ли сегмент адресата джампа (все джампы между сегментами - длинные!)
а) таблица лог.сегмент->физ.сегмент (размер может быть неограничен!!!), возможно, та самая, по которой работает релокатор ОС
медленно! даже самые частые джампы требуют сложных операций!
б1) при релокации исправлять переходы
б2) при релокации исправлять таблицы переходов для сегментов (каждая точка - один джамп (однократно - инлайн), вся таблица в том же сегменте)
б3) при релокации исправлять керналь переходов для задачи (размер может быть неограничен!!!) - в верхней памяти
в) таблица номер перехода->физ.сегмент (размер может быть неограничен!!!) - в верхней памяти
